fix(agent-binding): the runtime+environment pair comes from the oldest installation that declares either half (TASK-019 ruling i) - #1772
Merged
Conversation
…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
force-pushed
the
kai/task019-i-pair-atomic-recut
branch
from
September 19, 2026 16:47
f0a64cf to
d706f85
Compare
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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 (
_idascending) 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.podIdsremains 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/assignedhandler inroutes/agentBinding.ts. TASK-063 (#1765, #1766) then refactored that handler to callprojectSeatEnvironments, and that helper is now shared with the grant read — so the handler no longer holds the projection. Re-applying the parked patch onto82eabcdconflicted on exactly that hunk. The behaviour therefore lands once, inservices/seatEnvironmentProjection.ts, where both consumers resolve it:/assignedand 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,agentBindingservice test) — 27 passing: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_idorder disagree.Mutations (each run independently against the change):
.sort({ _id: -1 }).sort()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 — nocli/srcorcommonly-mcp/src, so no version slot.npm run lint:ts: 0 errors. Request: @vera's gate.Revision
f0a64cfc— Vera's residual on268370c1. 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 thatObject.keysreads as empty) rather than a reduce.