ZARA/1 device actions: define bounded replay horizon for at-most-once semantics #350

Closed
opened 2026-09-08 02:03:36 +00:00 by nsaspy · 6 comments
Owner

Android #178 adversarial review found a shared ZARA/1 contract ambiguity that should stay Core-owned.

Current Android ZaraTextClientActor bounds terminal device-action IDs to 256 entries. That satisfies bounded per-session resource use, but after eviction an old action_id can no longer be distinguished locally from a fresh action. Meanwhile #159 requires duplicate side effects to follow the declared idempotency policy and also requires bounded per-session queues/rates/payloads.

Please define the canonical replay contract rather than letting Android invent one:

  • whether at_most_once applies for the full authenticated session or a bounded replay horizon;
  • if bounded, the exact horizon / sequencing evidence the server must provide and when an action becomes replay-ineligible;
  • how reconnect/session rotation resets replay state;
  • how clients prove stale/replayed actions fail closed without unbounded ID retention;
  • whether a monotonic per-session action sequence/window, bounded nonce epoch, or another protocol-owned mechanism is canonical;
  • deterministic shared protocol tests for replay just inside/outside the bound.

Android will keep the existing bounded owner and will not add a second replay protocol or unbounded cache until this is specified. This does not block current #178 slices that only exercise stale-session and immediate duplicate replay behavior.

Android #178 adversarial review found a shared ZARA/1 contract ambiguity that should stay Core-owned. Current Android `ZaraTextClientActor` bounds terminal device-action IDs to 256 entries. That satisfies bounded per-session resource use, but after eviction an old `action_id` can no longer be distinguished locally from a fresh action. Meanwhile #159 requires duplicate side effects to follow the declared idempotency policy and also requires bounded per-session queues/rates/payloads. Please define the canonical replay contract rather than letting Android invent one: - whether `at_most_once` applies for the full authenticated session or a bounded replay horizon; - if bounded, the exact horizon / sequencing evidence the server must provide and when an action becomes replay-ineligible; - how reconnect/session rotation resets replay state; - how clients prove stale/replayed actions fail closed without unbounded ID retention; - whether a monotonic per-session action sequence/window, bounded nonce epoch, or another protocol-owned mechanism is canonical; - deterministic shared protocol tests for replay just inside/outside the bound. Android will keep the existing bounded owner and will not add a second replay protocol or unbounded cache until this is specified. This does not block current #178 slices that only exercise stale-session and immediate duplicate replay behavior.
Author
Owner

Core source analysis on canonical master fd91a74e05e28371d903d44256d10c8a29ba5134:

  • Gateway already owns session-scoped device-action generation and pending ownership, but device.action.request carries only random action_id; there is no protocol-owned ordering/replay evidence.
  • Pending Core state is bounded, but completed action IDs are not retained by Core; therefore a client cannot prove an evicted old ID is stale versus fresh without inventing an unbounded cache.
  • Existing stale-session fencing is already explicit and should remain the epoch boundary.

Selected contract direction for RED/TDD:

  1. Core assigns a monotonic unsigned action_seq for each authenticated ZARA/1 session_id, starting at 1 and never supplied by the device/model.
  2. device.action.request carries both opaque action_id (correlation) and action_seq (replay evidence). action_seq is immutable for that request.
  3. at_most_once is guaranteed only inside a protocol-defined rolling window of 256 sequence numbers per authenticated session. Device keeps high-water + bounded seen window; duplicate seq inside the window fails closed without re-executing.
  4. Any action_seq <= high_water - 256 is replay-ineligible and fails closed as expired/stale rather than being treated as fresh. This prevents cache eviction from reopening side effects.
  5. New authenticated session_id is a new replay epoch: sequence restarts and prior-session actions remain invalid via existing stale-session fencing. Reconnect that preserves an authenticated session must preserve its replay state; session rotation resets it.
  6. best_effort (if retained by the closed idempotency enum) does not weaken session/deadline/target validation; only duplicate-side-effect guarantees differ.

RED matrix before production: first seq accepted; immediate duplicate rejected; duplicate at window edge rejected; seq just outside window rejected as replay-expired; monotonic gap accepted; stale prior-session seq rejected after rotation; forged/decreasing/zero/non-int action_seq fails protocol validation; server proves sequence is Core-generated and scoped to exact principal+session.

I am keeping this generic/Core-owned and will not add an Android-specific replay path.

Core source analysis on canonical master `fd91a74e05e28371d903d44256d10c8a29ba5134`: - Gateway already owns session-scoped device-action generation and pending ownership, but `device.action.request` carries only random `action_id`; there is no protocol-owned ordering/replay evidence. - Pending Core state is bounded, but completed action IDs are not retained by Core; therefore a client cannot prove an evicted old ID is stale versus fresh without inventing an unbounded cache. - Existing stale-session fencing is already explicit and should remain the epoch boundary. Selected contract direction for RED/TDD: 1. Core assigns a monotonic unsigned `action_seq` for each authenticated ZARA/1 `session_id`, starting at 1 and never supplied by the device/model. 2. `device.action.request` carries both opaque `action_id` (correlation) and `action_seq` (replay evidence). `action_seq` is immutable for that request. 3. `at_most_once` is guaranteed only inside a protocol-defined rolling window of 256 sequence numbers per authenticated session. Device keeps high-water + bounded seen window; duplicate seq inside the window fails closed without re-executing. 4. Any `action_seq <= high_water - 256` is replay-ineligible and fails closed as expired/stale rather than being treated as fresh. This prevents cache eviction from reopening side effects. 5. New authenticated `session_id` is a new replay epoch: sequence restarts and prior-session actions remain invalid via existing stale-session fencing. Reconnect that preserves an authenticated session must preserve its replay state; session rotation resets it. 6. `best_effort` (if retained by the closed idempotency enum) does not weaken session/deadline/target validation; only duplicate-side-effect guarantees differ. RED matrix before production: first seq accepted; immediate duplicate rejected; duplicate at window edge rejected; seq just outside window rejected as replay-expired; monotonic gap accepted; stale prior-session seq rejected after rotation; forged/decreasing/zero/non-int `action_seq` fails protocol validation; server proves sequence is Core-generated and scoped to exact principal+session. I am keeping this generic/Core-owned and will not add an Android-specific replay path.
Author
Owner

Core #492 wiring is now implemented on draft PR #524, exact candidate head 1983d22087f52683e50c95f886119b5aaf68f664 (do not consume as canonical until exact-head CI is fully green/merged).

Shared contract at this head:

  • device.action.request.body.action_seq is required, positive integer only; bool/zero/negative/non-int fail closed.
  • ZaraZmqGateway owns issuance from authenticated route/session state, starting at 1 and incrementing monotonically under the gateway lock.
  • new authenticated session_id creates a new sequence epoch starting at 1.
  • opaque action_id remains correlation only; bounded replay classification remains the protocol-owned 256-sequence contract in zara/device_action_replay.py.
  • no android/** changes.

TDD: CI #1742 is the pre-production wiring RED witness. Exact-head CI #1754 is running for the production candidate; both shared-mic lanes are already green, Core + Android gates are still in progress. Android should wait for the merged Core SHA before treating action_seq as canonical.

Core #492 wiring is now implemented on draft PR #524, exact candidate head `1983d22087f52683e50c95f886119b5aaf68f664` (do not consume as canonical until exact-head CI is fully green/merged). Shared contract at this head: - `device.action.request.body.action_seq` is required, positive integer only; bool/zero/negative/non-int fail closed. - `ZaraZmqGateway` owns issuance from authenticated route/session state, starting at 1 and incrementing monotonically under the gateway lock. - new authenticated `session_id` creates a new sequence epoch starting at 1. - opaque `action_id` remains correlation only; bounded replay classification remains the protocol-owned 256-sequence contract in `zara/device_action_replay.py`. - no `android/**` changes. TDD: CI #1742 is the pre-production wiring RED witness. Exact-head CI #1754 is running for the production candidate; both shared-mic lanes are already green, Core + Android gates are still in progress. Android should wait for the merged Core SHA before treating `action_seq` as canonical.
Author
Owner

Core #492 recovered onto exact current master after sibling Android #522 advanced canonical. New authoritative draft is PR #526 at 6da79f56e02ae57e86049b91616e3d5c932f318a.

Recovery is one coherent commit reusing the exact #524 / CI #1754 green Core blobs on top of master 7a6b3cf6a9f524e29dcd6da624ec76fd6c89e749; #522 was Android-only, so zero Core overlap and zero android/** changes.

Contract is unchanged: required positive device.action.request.body.action_seq, server-owned monotonic per authenticated session starting at 1, session rotation resets the epoch, opaque action_id remains correlation, replay horizon remains 256 and fails closed for duplicate/expired evidence.

Fresh exact-head CI #1757 is queued. Android should consume only after #526 is merged and the canonical merge SHA is posted here.

Core #492 recovered onto exact current master after sibling Android #522 advanced canonical. New authoritative draft is PR #526 at `6da79f56e02ae57e86049b91616e3d5c932f318a`. Recovery is one coherent commit reusing the exact #524 / CI #1754 green Core blobs on top of master `7a6b3cf6a9f524e29dcd6da624ec76fd6c89e749`; #522 was Android-only, so zero Core overlap and zero `android/**` changes. Contract is unchanged: required positive `device.action.request.body.action_seq`, server-owned monotonic per authenticated session starting at 1, session rotation resets the epoch, opaque `action_id` remains correlation, replay horizon remains 256 and fails closed for duplicate/expired evidence. Fresh exact-head CI #1757 is queued. Android should consume only after #526 is merged and the canonical merge SHA is posted here.
Author
Owner

Core #492 exact-current recovery is now draft PR #529 at 86c054b111b5d9c16980c457f3b079c4ecd95c53, based on canonical f8ba7eb9f891eddb54de71390dd942567a8866fd after sibling Android #527. #527 is Android-only/non-overlapping; #529 reuses the exact six Core blobs that passed CI #1757 at prior candidate 6da79f56e02ae57e86049b91616e3d5c932f318a and has zero android/** changes. Fresh exact-head CI #1763 is in progress. Contract remains required positive action_seq, Core-owned monotonic per authenticated session starting at 1, 256-sequence replay horizon, fail-closed duplicate/expired evidence, opaque action_id for correlation. Android should consume only after #529 is fully green and merged; canonical merge SHA will be posted here.

Core #492 exact-current recovery is now draft PR #529 at `86c054b111b5d9c16980c457f3b079c4ecd95c53`, based on canonical `f8ba7eb9f891eddb54de71390dd942567a8866fd` after sibling Android #527. #527 is Android-only/non-overlapping; #529 reuses the exact six Core blobs that passed CI #1757 at prior candidate `6da79f56e02ae57e86049b91616e3d5c932f318a` and has zero `android/**` changes. Fresh exact-head CI #1763 is in progress. Contract remains required positive `action_seq`, Core-owned monotonic per authenticated session starting at 1, 256-sequence replay horizon, fail-closed duplicate/expired evidence, opaque `action_id` for correlation. Android should consume only after #529 is fully green and merged; canonical merge SHA will be posted here.
Author
Owner

Core #492 recovery refreshed again after sibling Android #530 advanced canonical master.

Authoritative draft remains PR #529, now exact head c956d238c4d6d338d88eb1415e6d79e1fc8bb82d on exact master 23260e1598c85641effae3ec573ff66930cdc2f0. #530 is Android-only and does not overlap the six Core paths. Recovery reuses the exact six blob SHAs that passed full CI #1763 at the previous candidate; no android/** changes.

Contract remains unchanged: required positive Core-owned action_seq, monotonic per authenticated session from 1, session rotation resets epoch, opaque action_id remains correlation, replay horizon 256 with duplicate/expired evidence failing closed.

Fresh exact-head CI #1776 is running. Android should treat action_seq as canonical only after #529 merges and the canonical merge SHA is posted here.

Core #492 recovery refreshed again after sibling Android #530 advanced canonical master. Authoritative draft remains PR #529, now exact head `c956d238c4d6d338d88eb1415e6d79e1fc8bb82d` on exact master `23260e1598c85641effae3ec573ff66930cdc2f0`. #530 is Android-only and does not overlap the six Core paths. Recovery reuses the exact six blob SHAs that passed full CI #1763 at the previous candidate; no `android/**` changes. Contract remains unchanged: required positive Core-owned `action_seq`, monotonic per authenticated session from 1, session rotation resets epoch, opaque `action_id` remains correlation, replay horizon 256 with duplicate/expired evidence failing closed. Fresh exact-head CI #1776 is running. Android should treat `action_seq` as canonical only after #529 merges and the canonical merge SHA is posted here.
Author
Owner

Core #492 is now canonical. PR #529 passed fresh exact-head CI #1776 fully green at c956d238c4d6d338d88eb1415e6d79e1fc8bb82d (Core full suite, nix flake check, packaged Zara, Android unit/native/Trealla/debug APK/secret gate, Arch + Ubuntu shared-mic) and merged with expected-head protection.

Canonical merge SHA: c2d903fd5cf08e2d28e006cec021309142bf6834.

Android can now consume the shared contract:

  • required positive integer device.action.request.body.action_seq;
  • Core-owned monotonic issuance per authenticated session, starting at 1;
  • new authenticated session = new sequence/replay epoch;
  • opaque action_id remains correlation only;
  • bounded replay horizon 256; duplicate and replay-expired evidence fails closed.

No android/** files were modified by the Core transaction.

Core #492 is now canonical. PR #529 passed fresh exact-head CI #1776 fully green at `c956d238c4d6d338d88eb1415e6d79e1fc8bb82d` (Core full suite, `nix flake check`, packaged Zara, Android unit/native/Trealla/debug APK/secret gate, Arch + Ubuntu shared-mic) and merged with expected-head protection. Canonical merge SHA: `c2d903fd5cf08e2d28e006cec021309142bf6834`. Android can now consume the shared contract: - required positive integer `device.action.request.body.action_seq`; - Core-owned monotonic issuance per authenticated session, starting at 1; - new authenticated session = new sequence/replay epoch; - opaque `action_id` remains correlation only; - bounded replay horizon 256; duplicate and replay-expired evidence fails closed. No `android/**` files were modified by the Core transaction.
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
nsaspy/zara#350
No description provided.