0034. Durable Channex ARI publication and automatic repair
Questi contenuti non sono ancora disponibili nella tua lingua.
- Status: Accepted, revised after R1 R2 and R3
- Date: 2026-10-27
- Replaces the initial connection-wide closure, quiet-period recovery latch and permanent unknown-send blockade.
- Extended by: ADR 0050: publication reaches the workspace’s Channex environment, staging for a sandbox.
Observable acceptance criteria
Section titled “Observable acceptance criteria”An administrator maps compatible base/derived plans, leaves direct-only plans unpublished, activates publication after booking reconciliation, and inspects local health even while Channex discovery fails. Saved mappings survive reload. Concurrent mapping writes require strong ETag/If-Match and stale writes return 412. Other roles inspect; only administrators configure, realign or acknowledge stuck publication. Every command is tenant-isolated and audited. Errors retain drafts and date errors link to fields in both English and Italian.
ADR 0036 preserves that built-in matrix while
allowing custom delegation: health, mappings and upstream plan discovery require
channex.view_health; mapping, resync and repair require channex.configure. Channex’s price
limits in health also require rates.read for the property (ADR 0036, 30 October 2026).
Selected-property members cannot use these workspace-wide Channex capabilities.
The still-unapplied role migration follows this ARI chain as 0016.
Ledger changes and received/applied OTA bookings send current absolute availability for every affected date, including when it equals the last sent value. Forty changing prices edited sequentially across three ranges in 30 seconds coalesce into one restrictions call; two real imported bookings produce two priority availability calls. A received booking signal holds positive availability for its property while revisions are applied. Zero closures always pass. During a hold verified identical zeros are suppressed while the original changes remain pending for positive dates after settlement. A hold lasting 15 minutes enters explicit degraded closure, with a health alert naming the workspace administrator as the responsible operator. Unrelated properties continue publishing. Quiet periods never close a property or require manual takeover.
A lost response or expired sender cannot permanently stop publication. Identifiable tasks are checked; unknown packets settle after a bounded quarantine, then their dates are recomputed with the cache bypassed. Sold-out/stop-sell closures can pass during that wait. Administrators can see and request audited repair of stuck sends/rejected changes from Channels. All state survives restart. Each horizon date opens when its mapped plans have verified prices or verified stop sell; unrelated restriction tasks do not block it.
The assigned briefs substitute for a separate feature issue. Main’s e-mail migration 0012 is followed by unpublished ARI migrations 0013 (publisher), 0014 (repair) and 0015 (reopening). Renumbering preserves their SQL bytes. The generated manifest and real PostgreSQL catalog/isolation checks use the integrated contiguous chain 0001–0015.
Publication protocol
Section titled “Publication protocol”Core owns the protocol and injected ports, clock and identifiers. The existing property writer gate, reservation ledger, effective rates and transactional outbox remain the source of truth. Database adapters implement exact-ID claims, forced RLS, composite tenant references, leases, pacing, retries, mapping versions and verified commercial state.
A numbered lease is acquired atomically. Every HTTP attempt is recorded before the call. Lease/heartbeat, pacing, retry deadlines, unknown settling and daily full-sync quotas use PostgreSQL time. An expired holder’s in-flight packets are adopted without its cooperation; a late task ID can settle its original record, but cannot finish another generation’s claims. A slow discovery response must revalidate the lease before changing metadata.
Availability never uses the last-sent delta cache: Channex automatically decrements OTA bookings, independently of GuestCraft. Prices/restrictions compare against verified daily values, merging partial fields. Explicit full-resync intent survives supersession and splitting. Closing sends zero availability and stop sell on every mapped plan. A safety state transition seeds closure/reopening; settled closure rows do not spin forever.
Availability gathers for six seconds. Commercial edits share a property window anchored on the oldest eligible pending commercial change (45 seconds); deferred beyond-horizon and capped rows cannot anchor the window. When it opens, newer pending edits join it. Priority booking and stop-sell changes use the fast path. Adjacent equal dates merge. Requests fit a 512 KiB budget, with durable date-window splitting. ARI POST deadlines scale from 7 seconds with serialized size, capped at 16 seconds; reads retain their bounded deadlines. The ARI job has a 90-second budget for discovery and both writes.
Each endpoint permits at most one call every six seconds per property, using database state without accumulated bursts. 429/5xx pause publication for at least a minute, with exponential backoff and Retry-After/RateLimit support. Item rejections back off from one minute to one hour; after six rejected attempts, automatic retries stop and health retains an operator issue. Administrator repair clears the retry cap and queues current values. A consumed wake-up during a pause or live lease clears its marker and schedules a durable continuation at the later database deadline, even if the holder has died. An earlier priority wake can replace a later scheduled continuation.
Booking barriers and catch-up
Section titled “Booking barriers and catch-up”Verified webhook bodies route only the canonical property identifier; no upstream body or personal data enters signal storage, logs or action state. Signals advance a durable connection sequence and that property’s barrier generation. A signal without a routable property conservatively holds the connection’s properties. Requests and safety-drain starts do not raise barriers. An older drain cannot clear a newer signal.
The importer isolates each filtered feed infrastructure failure, visits healthy sibling
properties in the same bounded drain, then reports the failure for retry. Failure-triggered
recovery holds only properties with unresolved revisions; healthy siblings remain open.
It drains signalled properties individually and settles each after its filtered
feed is applied/quarantined, before unrelated backlog completes. Settlement transactionally
wakes the ARI publisher. Quarantined/unknown room types and incompatible managed plans
produce explicit health warnings and prudent closures. Local application/quarantine settles
a property even if acknowledgement fails and the revision remains in the feed. Ack retries
have a separate half-budget, cannot starve other properties, and do not initiate outage
recovery. Failed acknowledgements still request a retry through the job result after local
settlement commits; initial activation and full resync inspect intake failure separately. A fully locally processed feed clears feed-stage recovery despite failed acks.
A normal barrier holds positive values only. Its first unresolved signal timestamp survives
additional signals until a drain settles that generation. A partial settlement advances
the remaining hold age to the successful drain, so continuous healthy intake does not
escalate an old timestamp; after 15 minutes of database time an unresolved hold becomes degraded
and seeds prudent zero/stop-sell closure. Health shows booking_feed_unavailable in both
languages and assigns repair responsibility to the workspace administrator. The durable
continuation/safety scheduler revisits the deadline. Local reconciliation clears the alert
and seeds reopening. Alerts on the property and the webhook catalog are ADR 0048’s;
individual incident assignment remains planned. Drain age alone is a monitoring signal.
Silence is not evidence of downtime.
A resident restart with an old checkpoint initiates automatic recovery. Hosted deployments
use the stable VERCEL_DEPLOYMENT_ID, persisted per connection, so a new deployment can
initiate catch-up after an outage without treating every Function invocation as a restart.
Sustained failed drains also initiate recovery. The durable history → future bookings →
feed stages resume across workers. Historical revisions are scanned newest first to the
last complete scan/last drain minus ten minutes; known IDs are skipped without ending the
scan. Current/future bookings are reconciled through GET /bookings and their revision IDs.
Pages contain at most five entries to keep transport calls inside the booking job budget.
The final catch-up drain reopens automatically. It never consumes the manual full-sync
quota or requires another takeover acknowledgement.
Initial takeover remains explicit: operators reconcile pre-existing bookings and revoke other writers. A successful empty feed alone does not prove initial historical completeness.
Unknown outcomes
Section titled “Unknown outcomes”Known tasks are polled. Unidentified sends remain durable and visible for 120 seconds,
measured from the persisted send timestamp using database time. After that bounded wait,
the worker records bounded_repair, returns the original exact changes to pending, and
recomputes current absolute values with commercial cache bypass. It never replays stored
payload bytes. While waiting, positive values are held; zero/stop-sell closures are allowed.
An administrator can request repair from health; the app records the acknowledgement and
flag, and the worker performs the repair with worker-only send permissions.
This follows the review-authorized absolute-value repair alternative. It does not prove a remote packet cannot arrive after the quarantine. Channex has no provider fencing token; late remote application and the snapshot-to-unreceived-signal interval remain distributed ordering risks. Provider request lookup (ADR 0048) now settles before this wait ends the unknown sends whose values Channex holds; it never resends earlier, because nothing Channex lists is specific to one request. These limitations are distinct from a permanent publication blockade.
Horizon, mapping and health
Section titled “Horizon, mapping and health”Queued and verified horizon watermarks are separate. Every missing horizon day is seeded,
including after downtime, capped at min(500, state_length). Initial, reopening and newly entering
horizon rows use per-plan/date price readiness: a verified rate or verified stop sell on
each mapped plan allows that date’s room availability to publish. Already published prices
satisfy readiness immediately; prices sent during this run qualify when their task is
verified. No whole-row restrictions_done gate remains.
Accepted items populate the daily cache even when another item in the packet is rejected. A rejected price invalidates only its plan/date cache cells and enqueues a separate priority stop-sell-only write for those cells. Such safety writes never complete the price retry. Inventory-only restriction writes cannot reopen an unpriced plan. Availability completion and retries are independent of the commercial rejection cap. Other dates and plans reopen within the fast window, and administrator repair retries current prices and reseeds the horizon even without another local edit. A horizon seed cannot supersede an ordinary availability change. Safety transitions run before the quiet return, including quarantine resolution that changes no inventory.
Changes beyond the current horizon remain durable for later entry. Health pending counts cover eligible work within the queued horizon; capped failures remain visible as issues. Neither capped rows nor deferred rows cause repeated quiet discovery calls.
Only mapped local plans publish. Unmapped direct/corporate plans remain usable locally and appear as a health notice; adding one cannot block resync or recovery. Currency, sell mode, room type and inheritance are checked at mapping time and before publication. Live mappings are additive; a live target is retired or replaced only through the verified workflow of “Live target retirement” below. Discovery reads are cached for a minute and reserve a database slot to coalesce concurrent member loads. Channex settings are shown with translated names/values and warnings, never automatically rewritten. Live discovery failure affects the mapping section only.
Health refreshes every 15 seconds and includes pending counts, sends, warnings, rejection attempts/deadlines, drain staleness, unpublished plans, lease, pause, barriers and horizon. API revision 1.12.0 adds versioned mapping reads and audited repair, updates health and requires strong If-Match on PUT /mappings. OpenAPI, SDK and query factories are generated.
Request triggers and runtime ownership
Section titled “Request triggers and runtime ownership”On Vercel production, committed outbox writes and verified signals wake the same worker through Next.js after()/waitUntil. Async-local request state and after-commit notifications prevent rollback/read wake-ups. The route permits 300 seconds; the pump uses at most 240 seconds minus request time and reserves full job/cleanup headroom. Fluid compute is required. Docker retains its resident worker. Development, previews and restored/unowned instances cannot publish (ADR 0022).
A publisher schedules continuation only for pending changes, accepted task polling, bounded unknown repair or a real pause deadline. Quiet publishers do not self-schedule. A request pump waits only when the queue contains real delayed work within the remaining budget and coalescing window; it ends when work is exhausted. Horizon/safety schedules remain durable. Daily Hobby-compatible cron is a demo recovery safety net. An undrained booking signal or stalled deployment recovery closes the affected property on all mapped channels after 15 minutes; a daily cron cannot prevent this after a killed pump or redeploy. A commercial hosted installation requires a 1–5 minute Pro cron or external HTTPS scheduler: backoffs, unknown repair, missed webhooks and killed functions can exceed the request pump’s wait window. Longer pauses and missed requests still need another invocation. Scheduler 202 acknowledges scheduling, not completion.
Verification and remaining scope
Section titled “Verification and remaining scope”R1 and R2 responses map every finding to changes and regressions. All seven reviewer probes were converted to assertions and failed against the reviewed publisher before passing after repair. Database tests move persisted timestamps where production reads database time. Real PostgreSQL/PgBouncer, the independent simulator, webhook/pg-boss integration, production browser workflows and accessibility checks cover the supported workflow; test counts do not measure product completeness.
Repeated unknown outcomes still use the bounded 120-second repair cadence without consecutive-timeout incident escalation (R2 L1, deferred). Their unknown/bounded-repair send history remains visible. Exceptionally late packets remain the accepted R2 L2 risk. Webhook routing verifies the secret before lazily reading up to 256 KiB, retaining only the validated identifier; larger or invalid deliveries conservatively remain unrouted.
Nightly remote drift repair, provider request lookup and alerts are implemented by ADR 0048. Live target retirement and sales caps are implemented (see the sections below and ADR 0027), as is occupancy pricing; real Channex certification remains planned. Hosted execution cannot guarantee work while no Function/scheduler runs. See the workflow contract and the provider booking collections.
Third review integration
Section titled “Third review integration”R3 response maps F1, N6 and L1–L4 to regressions and verification. ARI/import clocks and simulator inputs are fixed; stay dates derive from those clocks. PostgreSQL deadline tests shift relative persisted instants, and elapsed worker/transport timing uses a monotonic clock. Built-browser fixtures and the test-only server preload share one injected instant without freezing worker/authentication timers. Existing acknowledgement failures remain visible per revision; acknowledgement failures are aggregated per property in ARI health and raise a sync failure alert (ADR 0048). Repeated-unknown escalation remains planned.
The HTTP runtime passes its clock into request-pump and cron workers; resident worker composition accepts the same explicit clock. Integration tests no longer fall back to the host date for worker horizons. Database deadlines remain separate from business dates: ARI acquisition/preparation enforce pauses, and the merged mail delivery scan uses database time just like the claim. Pre-arrival dates retain the injected clock.
Shared accounts
Section titled “Shared accounts”ADR 0028 defines account modes, booking consumption, property linking, webhooks, recovery and takeover limitations.
ARI call-site audit: core/channex/ari.ts obtains a workspace property mapping before
plan/settings discovery, setup, health or publication. Payloads are computed from mapped
local room types and explicitly mapped compatible rate plans; availability/restriction
writes contain that mapping’s remote property ID. Property/channel rules and plan lists
use that same property filter. Settings are inspected, never rewritten. Task IDs come
only from the mapped property’s persisted sends. Core, adapter and simulator tests prove
that an unrelated property is neither discovered by ARI nor changed. Main has no Channex
message operations. Exclusive booking consumption does not broaden ARI publication.
Reversible links
Section titled “Reversible links”ADR 0028 defines audited unlink and retryable webhook cleanup. Unlink disables publication, revokes the numbered lease, retires queued rows without claiming upstream delivery, removes rate/room mappings and caches, and retains send diagnostics. The publisher checks its lease immediately before a send and before accepting its result. A later confirmed link requires fresh mappings and explicit takeover. Already transmitted writes cannot be recalled; unlink sends no compensating availability or price updates.
Stopped publication (2026-10-30)
Section titled “Stopped publication (2026-10-30)”The pump classifies a failed run (ADR 0008). A retryable failure pauses the property with
backoff and Retry-After, as before; a failure whose outcome is unknown keeps the schedule,
because the job runner retries it. Any other failure stops the property: a non-retryable
Channex failure (refused key or property, unusable answer) as upstream_rejected, a bug or
another permanent failure as internal_failure. An unsupported Channex property currency is
an expected outcome of the settings read and stops it as currency_unsupported; a mapping
write answers channex.currency_unsupported (409). The error still reaches the job runner,
which dead-letters a bug or permanent failure; the lease is always released.
Rate-plan discovery and a mapping replacement read Channex for the administrator. A
non-retryable Channex failure of those reads answers channex.mapping_read_refused (409): the
key or the property is the administrator’s to fix. A retryable failure still answers
server.unavailable (503, with Retry-After when Channex gave one) and a bug server.internal.
Migration 0026 (expand) adds the nullable ari_properties.stop_code and resumed_generation.
recovery_required was not reused: it means a lost ownership that needs a takeover full
resync, which has a daily quota. pause_until lapses by itself, and degraded closes sales. A stopped property is not
leased, woken or scheduled: acquire, request (the gate of every enqueue: rate changes,
booking signals, the minute’s safety pass, the pump’s continuation) and nextDelay refuse it.
Its pending changes, accepted and unknown sends stay durable and are settled or repaired after
the resume. Health reports the stop as a property-level issue with retry_at null; Channels
shows it as Publication has stopped with the recovery. A requested repair, an enabled
publication (resync or takeover) or a saved Channex connection clears the stop and records the
lease generation it happened in as resumed_generation.
A worker stops the property only while its lease holds it (same generation and
lease_owner) and only if no administrator resumed the property during that lease: its read
may have failed before a repair or a new key, and a stale stop would silently undo the resume.
The lease alone does not show a resume, because recovery does not take the lease from a
running worker: doing so, as unlink does, would let a second worker publish while the first
one’s send is still in flight. A worker refused this way releases as usual; its
continuation runs the property again, and the next lease stops it if the cause persists. API
revision 3.8.0 documents the codes.
Sales caps (2026-11-01)
Section titled “Sales caps (2026-11-01)”ADR 0027 adds a plan’s sales cap (maxSell). The publisher sends its remaining count,
max(0, cap − sold), as max_sell (OpenAPI Restrictions.RestrictionUpdate.max_sell: “Maximum
rooms that may be sold on the date. null removes the limit”):
- Channex does not document whether
max_sellcounts the bookings Channex itself receives. The remaining count never oversells under either reading: exact if it does not, an under-sale of the OTA rooms counted twice if it does. Sending the cap itself would oversell by the direct sales Channex never sees. This is listed for the certification session. - Every full restriction write of a mapped plan carries
max_sell,nullwithout a cap, so a stale limit cannot survive; the published cache and the drift read (GET /restrictionswithmax_sellinfilter[restrictions]) keepnullas a value, and a difference is repaired like any field. A plan with nothing left is also sentstop_sell: true, for channels that would not applymax_sell, and counts as closed when deciding whether its room type’s availability closes. A withheld plan is still sent only its stop sell. - Saving a reservation and importing a revision queue a
rateschange with the only fieldsalesper plan whose sales change, so a plan switch on unchanged room-type inventory republishes both plans. Such a change sends only the plan’s stop sell and, for a capped plan, its remainingmax_sell, never its prices or other restrictions, so a booking keeps certification scenario 9’s single closure call; like a cap edit (maxSell), it takes the fast path and also refreshes availability (the database’s fast-path fields list both withstopSellandrestrictions). An inventory change of the room type republishes its plans’ remainingmax_sellwith their stop sell. - Channel availability rules (
availability_offset,max_availability,close_out) stay read-only and are never folded into the sold count.
Live target retirement (2026-11-01)
Section titled “Live target retirement (2026-11-01)”A live mapping cannot simply be removed: an OTA keeps selling whatever Channex last held for a
target GuestCraft no longer publishes. Removing or changing a mapping while publication is
enabled therefore answers channex.ari_mapping_live, and an administrator (channex.configure,
workspace-wide, connection ownership, the bounded property lock) retires one live target at a
time instead: a mapped rate plan, or a mapped room type with every plan on it. The
action is unmap, or replace by another Channex rate plan (of the Channex room type the local
room type is mapped to) or another Channex room type with a new plan list for the local plans
of that type (plans may be dropped). A replacement must exist in Channex, not be mapped, and be
publishable exactly as a mapping save requires (mappingIssue): a local setting it cannot
publish names its field (rate_plan.not_distributable), anything else answers
channex.ari_replacement_invalid with not_found, mapped or incompatible. The last mapped
room type cannot be unmapped (channex.ari_retirement_last_room_type): that is an unlink.
Preview and start. validate_only answers, without writing, what the stop covers: the
target, the local names, the nights [property-local today, horizon end), the Channex rate plans
sent a stop sell and the room type sent 0, the open sends, the imported channel bookings still to
depart that the target sold, and whether the target is live; one that is not is changed with the
ordinary mapping edit. A start takes the mapping’s strong version (If-Match), refuses a second
active retirement (channex.ari_retirement_active, a partial unique index), records the
retirement and queues a priority change of the target’s room type over the horizon, marked for a
full resend so that a stale published cache cannot skip the closure. Mapping edits are refused
while a retirement is active.
States. stopping (queued) → draining → verifying → switching (replacement only) →
completed, plus failed with a stable code and cancelled. Every state but completed and
cancelled is active. The publication run moves the retirement on after its sends, under its
lease and locks, like the drift check:
- While active, the target joins the withholding: its mappings are stop-sold, a stop sell
without prices or other restrictions. A room type whose every plan is withheld closes its
availability, so a retired room type is sent availability 0 and each of its plans an
explicit
stop_sell: true; a retired plan whose room type keeps healthy plans is stop-sold alone. A stop sell now wins over a plan issue’s room-type closure (an inherited plan is sent one too); a mapping a plan issue sends nothing (its Channex plan does not exist) stays unsent, and that plan counts as closed (see step 3). GuestCraft never sendsstop_sell: falseto reopen a zero-availability plan and never writes Channex’s channel availability rules; the closure is the explicit stop sell, which closes a plan whatever itsmax_sell, so nomax_sellis needed to prove it. A retirement in progress keeps the property’s runs coming (isQuietis false); a failed one keeps its target withheld until an administrator acts. drainingwaits until the closure is settled: no booking hold, no open send (isOpenSend, so an unknown send is first looked up or repaired) and no change of the target’s room type left to publish before the queued horizon, other than one Channex rejected past the retry limit. A task acknowledgement or a local queue state is never taken as proof.verifyingreads Channex back over[today, queued horizon)in 92-night windows (GET /availability,GET /restrictions): the stop-sold plans’ explicit closure (stop_sell_manual, which the adapter exposes asstop_sell) on every night, and for a room type availability 0 on every night. An absent value is not a closure. A booking signal, an open send or a mapping change since the reads discards them, back todrainingwithout using an attempt. A target Channex no longer lists among the property’s plans or room types sells nothing there and counts as closed; one it still lists but returns nothing for fails the retirement astarget_missing; differing nights are forgotten from the published cache, queued again and waited for (back todraining, one attempt used); the third such verification fails it asreadback_mismatch. A temporary Channex failure is read again by the next run; a refusal fails it asupstream_rejected.- A closure held on every night switches the mapping in one transaction under the gate and
lease: the property’s plan mappings without the target (or with its replacement), the room
mapping moved or removed, a new mapping version, the retired Channex targets (a retired room
type fences its plans too) recorded in
channex_retired_targets, their published cache rows deleted so that the drift check never compares them again, and, for a room mapping change, the property’s quarantinedunknown_room_typeandretired_targetreceipts re-armed for one more automatic replay, as a mapping save does through a new connection version (the worker cannot lock the workspace row that a connection update locks). An unmap completes there. - A replacement is then
switching: the same local mappings, now naming the replacement, stay withheld, so the replacement is first sent a stop sell and a replacement room type availability 0; never both targets open, and the interval in which neither sells is visible in health. Once Channex has applied those values (acknowledged tasks, not a read-back) on every night and nothing of the room type is left to publish, the retirement completes, the room type is queued again and the replacement publishes its rates and restrictions; its availability opens only through the existing price-readiness rule. If nothing is left to publish while those values are still missing, as when Channex rejects them past the retry limit or the replacement was deleted in Channex, the retirement fails asreplacement_unpublished, the replacement still closed.
Cancel and resume. Cancel is allowed before the switch (stopping, draining, verifying,
failed): the target leaves the withholding and its room type is queued again over the horizon;
the closure already sent stays until that reseed is published. Cancel is also allowed for a failed
switch: it keeps the switched mapping and the replacement publishes normally. Otherwise, once the
mapping switched, it cannot be cancelled, and a completed retirement cannot be undone. Resume after
replacement_unpublished returns to switching and sends the replacement’s values again; any
other failure resumes from stopping with its attempts reset and its closure resent. Starts, cancels and resumes
are audited with the acting principal (channex.ari_retirement_started|cancelled|resumed); the
run’s transitions are system-audited (…_mismatch, …_failed, …_switched, …_completed),
each with its domain event. A failed retirement opens the sync_failure alert with the cause
retirement_failed (ADR 0048). Unlinking the property cancels its active retirement. Health
shows the active retirement, or the latest one.
Bookings of a retired target. A revision that names a retired Channex room type is not
projected as a normal amendment, and never into the replacement: it is quarantined as
retired_target, acknowledged and replayed automatically once per connection version exactly
like unknown_room_type (ADR 0028), its encrypted archive kept. The administrator resolves it
with an audited retry that applies it to the retired target’s recorded local room type, or maps
the Channex room type again, which removes its fence, and retries. The fence is keyed by the
workspace and the Channex identifier, like migration 0024’s retired properties, so relinking the
local property elsewhere does not erase it. A retired Channex rate plan keeps resolving to the
local plan it was sold under (plan identity retention), never to its replacement, so a booking
sold on it still imports with its plan and sold counts.
Migration 0073 (expand) adds ari_retirements and channex_retired_targets, both with composite
foreign keys cascading from the property, forced workspace and property RLS and the
workspace_active archival policy; the worker gains the grants the switch needs. API revision
6.9.0 adds POST /properties/{property_id}/channex/ari/retirements (preview with
validate_only, or create), GET …/retirements/{retirement_id}, POST …/cancel and
POST …/resume, and health’s retirement. The simulator proves the protocol; a live check of
the closure with Channex staff is part of certification.