Skip to content

Plan acknowledgements

This page defines the PETRA 1.0 acknowledgement returned by a subject that received a published plan version. It is a durable record-plane value, separate from the live CommandAck response to a pending DeviceCommand.

Contract target, not current implementation

Android currently encodes plan acknowledgements as a CommandAck whose command_id is plan/<scope>/<plan>/<seq>, and Web does not populate its plan_acks projection. That form is not this contract: it crosses the closed acknowledgement key grammar, exposes semantic identifiers, has no plan-specific type or classification binding, and cannot provide retained DDIL delivery. The Common, Directory, Server, Android, and Web changes tracked from the PETRA 1.0 roadmap must land as one coordinated contract migration. There is no dual-read or compatibility alias.

Wire type and identity

Common owns this protobuf and its generated Rust and TypeScript bindings:

message PlanAcknowledgementV1 {
  string scope_unit_id = 1;
  string plan_id = 2;
  uint64 plan_seq = 3;
  uint64 acked_at_ms = 4;
}

The structural contract is:

  • scope_unit_id and plan_id are non-empty UTF-8 and each is at most 128 encoded bytes;
  • plan_seq is a positive uint64;
  • acked_at_ms is authenticated event metadata only. It is not freshness, monotonicity, merge precedence, capability validity, or receipt ordering.

The value is deterministically encoded, sealed under the current deployment group-key epoch, and carried by a signed AuthEnvelope with purpose DURABLE_CONTENT. The verified envelope principal and signing key are the acknowledger. The payload contains no principal field and no caller-supplied identity may override the verified subject.

CommandAck remains an unchanged LIVE message. Its consumer accepts it only after correlation with a pending DeviceCommand, and neither its publisher nor its consumer recognizes, encodes, or decodes PlanAcknowledgementV1. Conversely, a plan-ack consumer never interprets CommandAck or a command_id prefix as a plan acknowledgement.

Exact plan-version binding

The signed tuple (scope_unit_id, plan_id, plan_seq) names one real published plan version. A receiver accepts the acknowledgement only when all three fields exactly match a verified stored PlanRecord version. The unit id is a globally unique ORBAT unit key; Web resolves operation_id from that unit and the exact plan row, never from caller input, the acknowledger's current operation, a selected UI operation, or receipt routing. No operation id is carried in the acknowledgement.

A previously published version remains real after a higher sequence is published. An acknowledgement created under then-valid cached authority may arrive later within the offline horizon and is projected against that historical version. Supersession alone is not rejection. Receivers reject:

  • a missing plan tuple, plan_seq == 0, a sequence that was never published, or a tuple that resolves across operation boundaries;
  • a version the verified subject was never entitled to receive;
  • absent, malformed, expired at signed creation time, wrong-subject, wrong-key, wrong-purpose, or otherwise invalid publication authority;
  • a subject or signing key that is revoked when the publication is accepted; and
  • a classification mismatch or CMBAC denial.

A higher sequence never inherits a lower sequence's acknowledgement. Sender timestamps, receipt time, database ids, and Zenoh HLC never translate an acknowledgement between versions.

Connected plan-publication registration

Web may publish a plan version only after a versioned connected registration succeeds. This is a bounded trusted assertion from the authorized Web application, not browser delegation and not authority minting by Web:

  • Web computes the exact acknowledging recipients and authorized acknowledgement readers from its committed plan-workflow projection;
  • the publishing human signs the complete canonical registration;
  • the authorized Web machine co-signs those same bytes and submits them to Directory; and
  • Directory commits the registration and alone mints the resulting plan publication, plan-read, plan-ack publication, acknowledgement-reader, and recorder grants.

The canonical registration binds the actor and Web-machine IdentityTokens/signing keys, Directory challenge nonce, timestamp, exact scope_unit_id, plan_id, positive plan_seq, exact classification, bytewise ordered unique acknowledging-recipient set, bytewise ordered unique acknowledgement-reader set, and registered_content_sha256. The digest is the domain-separated registered-content commitment over the exact canonical plan publication key, exact sealed payload bytes, canonical classification, required-empty owner, DURABLE_GRANT, and storage-key digest. It is not a hash of plaintext PlanRecord and not a hash of the completed AuthEnvelope.

Directory verifies both signatures and the actor's current plan-publication authority; requires the Web machine's explicit Directory-owned plan-registrar relationship; and rejects unknown, inactive, revoked, duplicate, or classification-ineligible principals. Recipient count, reader count, identifier size, and total canonical request bytes are bounded by the Common contract. Directory derives the operation from its authoritative unit ledger and never accepts caller-declared operation identity. It derives Web recorder inclusion from the existing Directory-owned Web-core recorder relationship; the request cannot add, nominate, or widen recorder authority.

Directory atomically commits the plan-version registration, recipient/read sets, content commitment, and affected entitlement-generation changes. Identical retries are idempotent on (scope_unit_id, plan_id, plan_seq). Reuse of that tuple with a different commitment, classification, recipient set, or reader set is an idempotency conflict. The record proves authorized registration, not Zenoh delivery. An orphaned registration is harmless because projection still requires the exact stored plan version.

The resulting grants are exact:

  • the Web machine receives one concrete PUBLISH + REGISTERED_CONTENT + DURABLE_GRANT capability carrying that registration's registered_content_sha256;
  • each exact recipient receives plan-read authority plus plan-ack publication authority for that subject's exact derived acknowledgement key;
  • each exact authorized reader receives query authority for only that exact opaque plan-version acknowledgement prefix; and
  • the explicitly related Web recorder receives that same exact-version query grant.

There is no unit-, operation-, or deployment-wide expansion. Query authority remains a separate exact REUSABLE_CONTENT capability and never inherits REGISTERED_CONTENT.

Web seals the exact plan payload and computes the registration digest before either signature. After Directory commits and returns the exact capability, Web puts it in AuthEnvelope.publication_capability, signs the completed envelope normally, and only then publishes. Relay + Storage verifies the Directory capability, subject/key, exact key, purpose, classification, final envelope signature and retained capability binding, and recomputes registered_content_sha256 without decrypting. Separately, StorageUseProofV1.value_sha256 equals SHA-256 of the exact final canonical AuthEnvelope bytes. Retries reuse that exact stored envelope and generate only a fresh use proof; they never rebuild the capability or envelope on each drain.

A disconnected endpoint may acknowledge only when it already holds the exact valid plan-version grant. If Relay supplies a newly registered plan before that endpoint has refreshed its new authority, the UI shows acknowledgement pending authority and defers publication until Directory reconnects. PETRA 1.0 does not add incremental Relay-delivered capability delegation to remove this limitation.

Opaque key expression

The only key is:

waypoint/global/record/plan-ack/<opaque-plan-version>/<opaque-acknowledger>

Both variable segments are exactly 32 lowercase hexadecimal characters produced by the opaque-addressing primitive. The registered domains are plan-ack-version and plan-acknowledger.

First construct these unambiguous bytes, where str32(x) is u32be(len(utf8(x))) || utf8(x):

plan_version_binding =
  ascii("petra-plan-ack-version-v1\0")
  || str32(scope_unit_id)
  || str32(plan_id)
  || u64be(plan_seq)

opaque_plan_version =
  opaque("plan-ack-version", lowercase_hex(plan_version_binding))

acknowledger_binding =
  ascii("petra-plan-acknowledger-v1\0")
  || str32(opaque_plan_version)
  || str32(verified_principal_id)

opaque_acknowledger =
  opaque("plan-acknowledger", lowercase_hex(acknowledger_binding))

Length framing, fixed-width sequence encoding, separate domains, and the type-specific prefixes make the binding independent of delimiter choices. Including opaque_plan_version in the acknowledger derivation prevents Relay + Storage from correlating one principal's acknowledgement segment across unrelated plan versions.

The decoded payload must reproduce the exact delivered key using the deployment opaque salt and the verified envelope principal. The AuthEnvelope.storage_key_digest, retained publication capability, and fresh use proof independently bind the same concrete key and exact envelope bytes. A changed unit, plan, sequence, principal, key, envelope, or proof fails before projection.

No clear unit, plan, operation, principal, delimiter-encoded tuple, or reverse-addressing material appears in the key or in Relay + Storage logs. A plan acknowledgement never uses the command ack namespace, command-id domain, or ack-source domain.

Publication and read authority

Directory is the sole authority issuer:

  • PUBLISH — Directory issues one exact REUSABLE_CONTENT grant for the complete concrete plan-ack key only to the subject/signing-key pair entitled to receive that exact plan version. The grant permits only DURABLE_CONTENT, carries the plan's exact classification ceiling, and expires no later than the offline horizon.
  • QUERY / live read — Directory issues waypoint/global/record/plan-ack/<exact-opaque-plan-version>/* only to an authorized commander/author reader for that exact version and to an explicitly authorized Web recorder. The final * is the one typed opaque-acknowledger slot, not generic Zenoh containment. **, a wildcard plan version, an operation-wide selector, and a deployment-wide selector are rejected.

Directory derives both opaque segments and the relationship. A client never supplies an opaque expression as proof of entitlement. Reader authority is independently bound to its principal/signing key, exact plan version, action, purpose, classification ceiling, validity interval, complete-set generation, and current revocation state. Publication authority proves that the acknowledger was entitled to receive the referenced version; possession of plan ciphertext or the deployment opaque salt does not.

The envelope classification must be byte-equivalent to the referenced PlanRecord classification. Common's normative CMBAC decision must permit the subject at publication and the reader at query/live ingestion. A missing label, different policy, changed classification/category set, or denial fails closed before opening or persistence.

Relay + Storage validates and retains the original opaque envelope for no longer than the offline horizon. It never decodes plan or acknowledgement semantics. Web is an authorized application archive: after independently verifying the original envelope, capability, proof, key binding, plan binding, classification, entitlement, and revocation gates, it stores the projection and may retain it under the operation's audit/replay policy. Web's archive is not a mesh value and cannot be republished as the endpoint's original traffic.

DDIL delivery and replay

Plan acknowledgements are durable outbox-eligible values. The publisher constructs and signs one canonical envelope when the operator acknowledges the plan, persists those exact envelope bytes, and retries them unchanged to configured Relay + Storage destinations until normal outbox completion or the offline horizon. A drain does not refresh acked_at_ms, nonce, envelope issue time, classification, payload, or signature and does not manufacture a new acknowledgement. A fresh per-attempt StorageUseProofV1 may bind the same envelope digest and concrete key; it never changes the retained value.

The durable-purpose verifier evaluates identity and publication capability at the signed creation time, while the outbox and retained-store horizons bound delayed acceptance. Known current subject/signing-key revocation still rejects a late arrival. Expiry after a valid within-horizon publication does not erase an acknowledgement already verified and archived by Web.

Retrying the byte-identical value is an idempotent duplicate, not a new event. Within the supported horizon, a surviving Storage copy answers an authorized exact-version query with the original bytes. Plan acknowledgements are not command execution, delivery receipts, transport high-water marks, or proof that every intended recipient received a plan.

Web projection and coverage

The idempotency key is:

(operation_id, scope_unit_id, plan_id, plan_seq, verified_subject_principal_id)

An identical duplicate succeeds without inserting another row or changing attribution. Equal identity with different tuple fields is a different exact-version acknowledgement; arrival order never collapses versions. Web preserves the original authenticated event time and its own receipt time separately.

Current coverage counts only rows for the displayed (scope_unit_id, plan_id, plan_seq). Historical acknowledgements remain attached to their exact version and visible in Replay. Zero means no verified acknowledgement for that exact version; unavailable means the recorder/read path is not authoritative or ready. The UI never converts unavailable into zero and never rolls an older version's count forward.

Required hostile cases

The Common implementation publishes one Rust/TypeScript corpus for canonical payload, opaque derivation, key construction, envelope/key binding, exact publish/query grants, and use proof. Dependent suites cover at least:

  • empty or over-128-byte identifiers, invalid UTF-8, zero/overflow sequence, and malformed protobuf;
  • delimiter collisions, changed tuple framing, wrong opaque domain, malformed opaque segments, changed plan-version or acknowledger segment, **, wrong-slot wildcard, missing/extra segment, family crossing, and a command-ack key;
  • absent, malformed, expired-at-use, wrong-action, wrong-purpose, wrong-mode, wrong-subject/key, stolen, replayed, or widened capability/proof;
  • moved envelope, changed envelope digest, classification mismatch, CMBAC denial, revoked subject/key, nonexistent/future plan version, cross-operation tuple, and a subject that lacked entitlement to the exact version;
  • byte-identical retry, late within-horizon delivery for a real superseded version, duplicate SQL delivery, restart/catch-up, and current-versus-historical coverage; and
  • proof that ordinary CommandAck consumers ignore this family and plan-ack consumers reject CommandAck bytes.

Implementation sequence

The dependency order is Common #208 → Directory #158 → Server #148 → Android #283 → Web #606. Android and Web may prepare consumer code against the merged Common version, but no component enables the family before Directory and Server can authorize and retain it. The old plan/<scope>/<plan>/<seq> CommandAck form is removed when the coordinated family becomes active; it is never accepted as a compatibility path.