Skip to content

Format compatibility

The Payload discriminants and the canonical encoding are frozen. Changing them invalidates every already-sealed log. A new payload type takes the next free integer; nothing is ever renumbered.

The rule has already been exercised several times. Actor (tag 7) was added after the fact for the actor chain, in preference to adding a field to Delegation; ConfigReload (tag 8), SessionCert (tag 9) and McpAccess (tag 10) followed the same way. The record_format_is_frozen test carries reference hashes for the existing payloads; none of them moved. The day that test fails, the question is not “how do I update the constants” but “which sealed logs have just been invalidated”.

A bundle’s format string is part of the signed bytes, so a revision is a new string, and every revision an artifact was published under keeps verifying with the signing bytes of its day.

Exercised twice:

  • obsign-identity/2 extended the signed bytes with the machine markers;
  • obsign-policy/2 extended them with the argument declarations.

Both follow the same two rules:

  1. a /1 bundle keeps its hash and signature;
  2. a /1 file carrying fields only /2 signs is refused rather than trusted, since otherwise those fields would be unsigned authority.

The control plane emits /2 only when a deployment actually uses the new feature, so a fleet that never does never forces a gateway upgrade.

JSON allows too much freedom (key order, whitespace, numbers): two serializers can produce two hashes for the same data. In the canonical encoding every field is length-prefixed and concatenation is injective. JSON stays the transport and reading format, and never the computation one. obsign-audit-core is the only place a hash is computed, shared by the gateway, the ledger, the control plane and the verifier alike.