# title
Helix Internet Monitor <-> Helix NMS inbound event contract

# $defs.HelixSyncRole.$comment
What staff sync may produce. `owner` is absent on purpose: it carries billing, org deletion and the power to appoint further owners, and must not be reachable from another system.

# $defs.KnownNmsRole.$comment
Reference only. `StaffUpsert.nms_role` is an unconstrained string on the wire: an unrecognised value is REFUSED with reason_code `role_unrecognised`, which is a different and more useful outcome than a schema rejection, because it reaches the sender's retry logic with a terminal code attached.

# $defs.InboundEvent.properties.delivery_id.$comment
Unique per delivery attempt-SET, not per attempt: a retry of the same event reuses it. Bound to its payload — reusing one with refreshed data is a contract violation, because the receiver recognises the id, treats it as handled and discards the newer version with neither side able to tell.

# $defs.InboundEvent.properties.occurred_at.$comment
RFC 3339 UTC, millisecond precision. When the thing happened, which is not when it was delivered.

# $defs.StaffUpsert.properties.nms_role.$comment
See #/$defs/KnownNmsRole for the values that map.

# $defs.StaffRevoke.$comment
One of email or external_id is required; a revoke naming neither is refused as invalid_payload.

# $defs.MaintenanceEvent.properties.scope.$comment
`network` is core work affecting the whole ASN; `service` requires service_id.

# $defs.MaintenanceEvent.properties.expected_end_at.$comment
Nullable on purpose: 'we have started and do not yet know when we will finish' is an honest answer the contract can carry.

# $defs.BindingUpsert.properties.service_label.$comment
Optional, non-personal, human-readable; shown to the SUBSCRIBER when choosing which connection this ISP serves. MUST NOT contain an installation or billing address, a personal or company name, a phone number, an email address or an account number. Deliberately carries no maxLength: Helix truncates to 64 characters on receipt rather than refusing, and a schema that refused would misdescribe the server.

# $defs.ServiceId.$comment
The ISP's own account number and the join key, byte-exact after trim. Must be OPAQUE. Nothing in Helix renders it to a subscriber as an account number, so a synthetic per-subscription identifier is preferred: the natural candidate on an ISP's side is often a username, and a username is often derived from a phone number. This channel is documented as carrying no personal data, and a phone-derived join key would quietly make that false. Values over 128 characters are refused as invalid_payload.

# $defs.ProvisionedKbps.$comment
Helix STORES a value outside this range as absent rather than refusing the binding, so a client-side validator built from this schema will be stricter than the server. That is the safe direction and it is stated rather than hidden.

# $defs.Refusal.$comment
The response shape when an event is understood and not applied. Branch on reason_code, never on `reason`, which is prose for a human reading a log.

# $defs.Refusal.properties.retryable.$comment
Derived from reason_code, never set independently — two sources for one fact is how they disagree.