You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Problem — On the observation path, writes never route (the read plane is GET-only) — a prompt / archive / delete to a peer-owned session either falls to the local engine (which never held it) or is dropped. There is no gated path to execute a remote write. See #1540. Approach — Add an ObservationWritePlane mirroring the read plane: for a non-GET request to a peer-owned session, consult evaluateRemoteWriteGate (active control grant + reachable transport) → proxy to the owner with the control token; otherwise a named, honest deny (never local). Wire it into server dispatch as an observeWrite seam, attached only in the observation branch. Scope — in: the write-plane resolver + peer proxy; dispatch wiring; honest deny; self-owned enable→prompt/archive/delete end-to-end. · out: grant issuance (#1541); UI affordances (slice 4); live streaming (slice 3). Assumptions — #1541 provides an active control grant + presentable token for a self-owned peer.
Warning
Deliberate-review corrections (2026-09-24) — these BIND over any stale text below.
Grant-key defect (the signature bug).evaluateRemoteWriteGate reads grantReader(ownerMachineId), but the grant store keys on requesterMachineId. In the self-owned case the controlling machine is the requester and the driven session's owner is the target, so a naïve lookup returns undefined and every write denies. Resolve grants by targetMachineId === ownerMachineId over the controlling machine's own control grants — NOT by the store's requesterMachineId key. (Fleet Studio B2b · slice 1 — Grant foundation: Enroll seeds lifecycle-admin + self-owned control issuance #1541 delivers this read.)
Token + credential path. Present the control token (from that resolved grant) via the plane's own proxyToPeer, mirroring observation_read_plane.ts — NOTControlGatedResolver/controlGatedMultiplexAdapter, whose adapter drops the peer credential ("a future integration slice will thread it through the proxy"). Name where the token is read (the requester-side grant view from Fleet Studio B2b · slice 1 — Grant foundation: Enroll seeds lifecycle-admin + self-owned control issuance #1541).
AC3 vocabulary. The write gate emits grant-revoked / transport-down / insufficient-scope (it collapsesrevocation-pending→grant-revoked); it never emits read-only/unavailable (those are ControlGatedTarget kinds). Restate AC3 in the gate's real reasons.
Byte-identity: extend amicode_service_observe_read_routing.test.ts's "falls through byte-identical" family; the write router is a mirror with the GET/non-GET decision inverted (route non-GET, ignore GET).
Full record: ADR 0034 decision record (D2/D4).
Acceptance Criteria
With no control grant, a remote write (prompt/archive/delete to a peer-owned session) is denied with reason no-control-grant and is never executed locally.
With an active control grant + reachable transport, a prompt/archive/delete to a peer-owned session routes to the owner with the control token and succeeds.
A revoked / revocation-pending / transport-down grant yields the named read-only / unavailable state — mutation suspended, session still readable.
Local-owned and unowned sessions: writes fall through byte-identical to today (the plane is inert).
Testing Decisions
Extend the observation read-routing suite family (mirror its fixtures for the write cases). Reuse the remote_write_gate unit suite (the pure authorize decision is already covered) and add the wiring test: dispatch consults the write gate before the engine proxy. Extends #1541's grant surface — the modify-vs-create call on that shared fixture is the implementer's.
Key Decisions
Reuse the battle-tested HubProxy peer hop with the control token exactly as the read plane reuses it with the reader token — a synthetic per-peer credential produces the peer-token auth the owner expects.
The write-gate result { allowed, requiresConfirmation, confirmationContext } | { allowed:false, reason } is the contract between the plane and its caller; requiresConfirmation is satisfiable by existing UI (slice 4), not necessarily a new modal.
Grant-free reads preserved — the write path's grant requirement must not regress Observe.
Byte-identity for local-owned / unowned / flag-off.
Prior Art
Read first (modules): the observation read plane + its router (the #1537 read seam this mirrors), remote_write_gate (the pure authorize gate), control_gated_routing (the four-state resolver + its noted credential-threading gap), hub_proxy (the streaming peer hop), and the server dispatch seam that consults observeRead.
Mechanical routing gated by an already-pure primitive, pinned by byte-identity tests → AFK. Its foundation (#1541) is HITL-reviewed, so the trust model is human-checked upstream.
Important
Problem — On the observation path, writes never route (the read plane is GET-only) — a prompt / archive / delete to a peer-owned session either falls to the local engine (which never held it) or is dropped. There is no gated path to execute a remote write. See #1540.
Approach — Add an
ObservationWritePlanemirroring the read plane: for a non-GET request to a peer-owned session, consultevaluateRemoteWriteGate(activecontrolgrant + reachable transport) → proxy to the owner with the control token; otherwise a named, honest deny (never local). Wire it into server dispatch as anobserveWriteseam, attached only in the observation branch.Scope — in: the write-plane resolver + peer proxy; dispatch wiring; honest deny; self-owned enable→prompt/archive/delete end-to-end. · out: grant issuance (#1541); UI affordances (slice 4); live streaming (slice 3).
Assumptions — #1541 provides an active
controlgrant + presentable token for a self-owned peer.Warning
Deliberate-review corrections (2026-09-24) — these BIND over any stale text below.
evaluateRemoteWriteGatereadsgrantReader(ownerMachineId), but the grant store keys onrequesterMachineId. In the self-owned case the controlling machine is the requester and the driven session's owner is the target, so a naïve lookup returnsundefinedand every write denies. Resolve grants bytargetMachineId === ownerMachineIdover the controlling machine's owncontrolgrants — NOT by the store'srequesterMachineIdkey. (Fleet Studio B2b · slice 1 — Grant foundation: Enroll seeds lifecycle-admin + self-owned control issuance #1541 delivers this read.)proxyToPeer, mirroringobservation_read_plane.ts— NOTControlGatedResolver/controlGatedMultiplexAdapter, whose adapter drops the peer credential ("a future integration slice will thread it through the proxy"). Name where the token is read (the requester-side grant view from Fleet Studio B2b · slice 1 — Grant foundation: Enroll seeds lifecycle-admin + self-owned control issuance #1541).requiresConfirmation:truefor every remote write (no action branch). This slice authorizes + routes; confirmation policy is a consumer concern keyed onaction(Fleet Studio B2b · slice 4 — Control UI: enable, driving banner, fail-closed affordances #1544). Downgrade "enable→delete/archive end-to-end" to "gated route reaches the owner"; the delete-confirm UI is Fleet Studio B2b · slice 4 — Control UI: enable, driving banner, fail-closed affordances #1544.grant-revoked/transport-down/insufficient-scope(it collapsesrevocation-pending→grant-revoked); it never emitsread-only/unavailable(those areControlGatedTargetkinds). Restate AC3 in the gate's real reasons.amicode_service_observe_read_routing.test.ts's "falls through byte-identical" family; the write router is a mirror with the GET/non-GET decision inverted (route non-GET, ignore GET).Full record: ADR 0034 decision record (D2/D4).
Acceptance Criteria
controlgrant, a remote write (prompt/archive/delete to a peer-owned session) is denied with reasonno-control-grantand is never executed locally.controlgrant + reachable transport, a prompt/archive/delete to a peer-owned session routes to the owner with the control token and succeeds.revoked/revocation-pending/ transport-down grant yields the namedread-only/unavailablestate — mutation suspended, session still readable.Testing Decisions
Extend the observation read-routing suite family (mirror its fixtures for the write cases). Reuse the
remote_write_gateunit suite (the pure authorize decision is already covered) and add the wiring test: dispatch consults the write gate before the engine proxy. Extends #1541's grant surface — the modify-vs-create call on that shared fixture is the implementer's.Key Decisions
HubProxypeer hop with the control token exactly as the read plane reuses it with the reader token — a synthetic per-peer credential produces the peer-token auth the owner expects.Data Contracts
The write-gate result
{ allowed, requiresConfirmation, confirmationContext } | { allowed:false, reason }is the contract between the plane and its caller;requiresConfirmationis satisfiable by existing UI (slice 4), not necessarily a new modal.Constraints & Invariants
Prior Art
Read first (modules): the observation read plane + its router (the #1537 read seam this mirrors),
remote_write_gate(the pure authorize gate),control_gated_routing(the four-state resolver + its noted credential-threading gap),hub_proxy(the streaming peer hop), and the server dispatch seam that consultsobserveRead.Source
docs/adr/0034-base-peer-studio-control.md), decisions D2/D4/D5.Notes
Mechanical routing gated by an already-pure primitive, pinned by byte-identity tests → AFK. Its foundation (#1541) is HITL-reviewed, so the trust model is human-checked upstream.