Skip to content

fix(agent-binding): the runtime+environment pair comes from the oldest installation that declares either half (TASK-019 ruling i) - #1772

Merged
lilyshen0722 merged 3 commits into
mainfrom
kai/task019-i-pair-atomic-recut
Sep 19, 2026
Merged

lilyshen0722 merged 3 commits into
mainfrom
kai/task019-i-pair-atomic-recut

Conversation

@lilyshen0722

@lilyshen0722 lilyshen0722 commented Sep 19, 2026

Copy link
Copy Markdown
Contributor

Implements wren's TASK-019 ruling, option (i), as its own PR off 82eabcd — the sequencing he set when he retracted the fold into #1759 ("a separate PR cut from main AFTER #1759 merges — not folded, not stacked"). #1756, #1759 and #1760 have all landed, so this is that PR.

The rule. The runtime+environment pair comes from the oldest active installation (_id ascending) that declares either half. A half that installation is silent about stays empty — it is not filled from a sibling row, because the two halves describe one seat. podIds remains a union across the identity's installations.

Why the site is not the parked patch's. The parked branch (kai/task019-i-pair-atomic, 59d99388 + 7a354d95) patched the /assigned handler in routes/agentBinding.ts. TASK-063 (#1765, #1766) then refactored that handler to call projectSeatEnvironments, and that helper is now shared with the grant read — so the handler no longer holds the projection. Re-applying the parked patch onto 82eabcd conflicted on exactly that hunk. The behaviour therefore lands once, in services/seatEnvironmentProjection.ts, where both consumers resolve it: /assigned and the grant read. Consequence worth naming for the grant read: a seat whose oldest declaration carries no environment can no longer inherit one from a newer row, so the confinement decision now reads the same installation the runtime came from.

Witnesses (backend/__tests__/unit/routes/agentBinding.phase2.test.js, agentBinding service test) — 27 passing:

  • the union test now pins that both halves come from the base fixture and that the second installation's values appear nowhere in the projected agent;
  • never fills a silent half from a sibling installation;
  • takes the pair from the oldest _id even when that row was inserted last — explicit ObjectIds with the newer row inserted first, so insertion order and _id order disagree.

Mutations (each run independently against the change):

mutation result
per-field fill (pair not atomic) 1 failed — the silent-half test alone
.sort({ _id: -1 }) 3 failed — pair-atomic, silent half, ordering
drop .sort() 1 failed — the ordering witness alone

The last row closes the clause the retracted fold had disclosed as an unwitnessed tripwire ("removing .sort({ _id: 1 }) reddens nothing"): in-memory Mongo returns insertion order, so the fixture now inserts the newer row first.

Scope: backend/ only — no cli/src or commonly-mcp/src, so no version slot. npm run lint:ts: 0 errors. Request: @vera's gate.

Revision f0a64cfc — Vera's residual on 268370c1. The predicate tested the raw key, so an OLDER row whose environment the allow-list reduces to nothing (environment: {}, or only fields the projection drops) became the source of the pair and a NEWER row's real environment was never read. That fails toward the daemon baseline rather than acting as a bypass, but the oldest-wins rule then picks the row that does not describe the seat. "Declares" now means delivers. A new witness pins it, with two mutations that each redden that test alone: the raw-truthiness predicate, and marking the identity sourced on the first row regardless of whether it declared anything. Left explicit rather than implied: runtime: {} still counts as a declaration, because runtime passes through no projection and the equivalent test there is a schema question (an empty object, and a nested Map that Object.keys reads as empty) rather than a reduce.

…t installation that declares either half

TASK-019 ruling, option i (wren 69800). The pair is taken from the OLDEST
active installation (`_id` ascending) that declares EITHER half; a half that
installation is silent about stays empty rather than being filled from a
sibling row; `podIds` stays a union across the identity's installations.

The site is not the parked patch's, and that is the point. The parked change
(branch `kai/task019-i-pair-atomic`, 59d9938 + 7a354d95) patched the /assigned
handler in `routes/agentBinding.ts`. TASK-063 (#1765, #1766) then refactored
that handler to call `projectSeatEnvironments`, shared with the grant read, so
the handler no longer holds the projection — applying the parked patch to
82eabcd conflicted on exactly that hunk. The behaviour therefore lands once, in
`services/seatEnvironmentProjection.ts`, where both the daemon list and the
grant read resolve it.

Also `.sort({ _id: 1 })` on the query: the previous winner was Mongo's stored
order, and the doc comment here said so.
… and the oldest-_id ordering

Witnesses for the change above, at the route tier that consumes it and at the
service tier that defines it:
- the union test now pins that BOTH halves come from the base fixture and that
  the second installation's values appear nowhere in the projected agent;
- `never fills a silent half from a sibling installation` — the case the ruling
  is about, visible only when the halves live on different rows;
- `takes the pair from the oldest _id even when that row was inserted last` —
  explicit ObjectIds, newer row inserted first, so insertion order and `_id`
  order disagree and only the find's sort can pick the older pair.

Mutations (27 passing across the two suites):
- per-field fill -> 1 failed, the silent-half test alone;
- `.sort({ _id: -1 })` -> 3 failed (pair-atomic, silent half, ordering);
- dropping `.sort()` -> 1 failed, the ordering witness alone. That clause was
  an unwitnessed tripwire in the parked fold; it now has its witness.
Vera's residual on 268370c: the predicate tested the raw key, so an OLDER row
whose environment the allow-list reduces to nothing (`environment: {}`, or only
fields the projection drops) became the source of the pair and a NEWER row's
real environment was never read. She is right that it fails toward the daemon
baseline rather than acting as a bypass, but the oldest-wins rule then picks the
row that does not describe the seat, and the seat loses the environment it
declares. "Declares" now means delivers: a half counts when it projects to
something (or, for runtime, when it is present at all).

Mutations, each run against the change (28 passing across the two suites):
- raw-truthiness predicate, i.e. the defect above -> 1 failed, the new test alone;
- marking the identity sourced on the first row regardless of whether it
  declared anything -> 1 failed, the new test alone.

Not fixed here, and named rather than left implied: `runtime: {}` still counts
as a declaration. Runtime is not passed through a projection, so the equivalent
test is a schema question (an empty object, and a nested Map that
`Object.keys` reads as empty) rather than a reduce, and guessing it here could
skip a genuine runtime fleet-wide.
@lilyshen0722
lilyshen0722 force-pushed the kai/task019-i-pair-atomic-recut branch from f0a64cf to d706f85 Compare September 19, 2026 16:47
@lilyshen0722
lilyshen0722 merged commit 5267dff into main Sep 19, 2026
14 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant