class PilbaraCycloneNoticeLeoBatch < BaseAgent contract "doil.operationsContract/v1" do file "pilbara-cyclone-notice-leo-batch.doil" title "Pilbara cyclone: notify at first cut, LEO per approved batch" exposure :public_blueprint, visibility: :public, sensitive: false, recipients: [:noc_automation_reviewer, :noc_duty_operator] execution :event, trigger: :cyclone_track_update, max_cycles: 120, expires_at: "2027-04-30T15:59:59.000Z", concurrency: 1, failure_policy: :stop_and_escalate, approval: :human_required_before_entrypoint portability :runbook, :noc_agent test_clock "2026-10-31T16:00:00.000Z" objective "OBJ-CYCLONE-001", criticality: :required, derived_from: ["desk://decision/apr_3ldnonbpm_auto-notice-batch-approval", "desk://run/run_XrocrdewYnI0pMJ4Zfh0f", "desk://scene/scn_cDacgeTcAeUQMh0jl5QZC", "desk://scene/scn_QVPkwUohK4NMC6f0Xubxt", "desk://evidence/sha256:7709a0a75a5e99f37d4027d9cd2725f1e5b3b9c696270059ca646f307ef5f4bd"] end intent "When a severe tropical cyclone's forecast track crosses the Pilbara corridor, then at the first circuit-impact read showing a cut: notify every affected customer by id within 30 minutes without waiting for approval and keep the receipts; switch each mine-site batch of cut First-class circuits to its LEO fallback only after the duty operator approves that batch, paging the operator again at minute 15 for any batch still unapproved; and at minute 30 report by name every First circuit still down." context do require geography: "Port Hedland corridor, Pilbara, Western Australia: from the Port Hedland POP to the Hedland highway junction (reader-drawn, about 319 km2); synthetic au-pilbara network" require technology: "Synthetic fibre transport sold as 100G, 10G and 1G lines; First class adds a LEO satellite fallback path" require link: "Every fibre span ending at the four corridor nodes, every circuit whose work or protect path rides such a span, and each First circuit's LEO fallback path" require system: "The operator's circuit-impact read, LEO gateway, customer-notice channel and duty-operator paging, resolved by the receiver; the operator is not named" require event: "A severe tropical cyclone (10-minute mean wind 64 kt or more) whose forecast track crosses the corridor; exercised on Register's Cyclone Zelia drill au-pilbara-cyclone-zelia" require user: :noc_duty_operator require nodes: ["pop-port-hedland", "ln-hedland", "on-hedland-port", "ex-hedland-jn"] require time: { starts_at: "2026-11-01T00:00:00+08:00", ends_at: "2027-04-30T23:59:59+08:00" } require customer: "Affected customers by customer id only; synthetic network, no customer or operator named" require leo_fallback: { classes: ["first"], batch: "one batch per mine site", max_circuits_per_batch: 20, approval: "the duty operator approves each batch before it switches" } require outage_notice: { window_minutes: 30, measured_from: "the circuit-impact read first showing the circuit cut", approval: "none per notice; sent at the first cut read", carries: ["customer id", "circuit ids", "impact status", "expected restore class"], receipts: "kept per notice" } require escalation: { at_minute: 15, page: "noc_duty_operator", when: "any LEO batch is still unapproved" } require minute_30_report: { at_minute: 30, to: "noc_duty_operator", lists: "every First circuit still down, by circuit id and customer id" } end triggers do on_event :cyclone_track_update do handler :respond_to_cyclone end end tools do read_cut_circuits description: "Read which circuits riding a span at the corridor nodes have a cut fibre path, and which First circuits need LEO, grouped by mine site", effect: "read", vendor_op: "receiver_owned", parameters: ["nodes"] send_outage_notice description: "Send the outage notice to one affected customer by id with its circuits, impact and expected restore class, and keep the receipt", effect: "external_change", vendor_op: "receiver_owned", parameters: ["customer", "outage_notice"] send_correction_notice description: "Send a correction notice to a customer this contract notified on a false cut read", effect: "external_change", vendor_op: "receiver_owned", parameters: ["customer", "outage_notice"] switch_to_leo_fallback description: "Switch one operator-approved mine-site batch of cut First circuits onto their LEO fallback path", effect: "external_change", vendor_op: "receiver_owned", parameters: ["leo_fallback"] revert_leo_fallback description: "Return a circuit from its LEO fallback path when the LEO read shows the switch did not carry", effect: "external_change", vendor_op: "receiver_owned", parameters: ["leo_fallback"] read_clock_state description: "Read notice receipts and LEO site state against the 30-minute window", effect: "read", vendor_op: "receiver_owned", parameters: ["outage_notice", "leo_fallback"] page_duty_operator description: "Page the duty operator at minute 15 for any LEO batch still waiting for approval", effect: "propose", vendor_op: "receiver_owned", parameters: ["escalation"] report_first_circuits_down description: "Report at minute 30, by circuit id and customer id, every First circuit still down", effect: "propose", vendor_op: "receiver_owned", parameters: ["minute_30_report"] end guardrails do pre_execution do require :approval end during_execution do max_changes 200 end end output do stage :customer_notice do generate :notice_receipts present :customers_notified_and_exceptions require :approval end stage :leo_batches do generate :leo_switch_receipts present :batch_for_operator_approval require :approval end stage :response_clock do generate :minute_30_report present :first_circuits_still_down_by_name end end def respond_to_cyclone preconditions = tools.read_cut_circuits.apply(:nodes) if preconditions.ready result = tools.send_outage_notice.apply(:nodes) result_2 = tools.switch_to_leo_fallback.apply(:nodes) verification = tools.read_clock_state.apply(:nodes) if verification.failed tools.revert_leo_fallback.apply(:nodes, result) end end end end