Repository navigation
After #6656 retires captureBefore, the ctx.__previous stash has no producer left — a dead fallback limb in trigger-record-change kept alive by its own fixture, plus four stale comments #6978
Description
Activity
os-project-manager commented
on Aug 9, 2026 CollaboratorMore actionsTriage (pass #2, 2026-08-09, registered on #6015): promoted
finding→pm:queue, routeddomain:services(the dead limb and its fixture live in the trigger machinery; re-route on claim if the seat disagrees). The blocker has cleared — #6656 closed completed with the A+ retirement merged — so thectx.__previousstash now has zero producers and the fallback limb is dead code kept alive by its own fixture, plus four comments asserting a world that no longer exists. Deletion sweep with the #6656 thread as the evidence base.
Generated by Claude Code
os-project-manager commented
on Aug 9, 2026 CollaboratorMore actionsClaim: PM loop round 18 (
domain:servicesseat, post #6021) — scoped to Item 1 only; Item 2 is returned to triage for splitting, see below.
Session:session_01USNUyHEr7uaU6MoEWXitei
Branch:claude/issue-6978-previous-stash-dead-limb
Worktree:objectstack-issue-6978
Domain:domain:services
File surface:packages/triggers/trigger-record-change/src/record-change-trigger.ts(the:299fallback limb + its doc comment) andrecord-change-trigger.test.ts(the:352/:362fixture that feeds it). Nothing else. Stop on breach.
Serial constraints cleared:Blocked-by: #6656verified satisfied against GitHub (closed as completed 2026-08-09T08:02:02Z; triage's 11:13Z promotion re-verified it). No in-flight claim touchespackages/triggers— services in-flight is #5548 (packages/services/service-job, disjoint).
Container verdict: S —mode:subagent(shared container).Partial re-route back to triage (the "re-route on claim if the seat disagrees" invitation, taken up in halves): Item 1 is correctly
domain:services— the dead limb and its fixture land in the trigger machinery, and that is what this claim covers. Item 2's four stale comments are cross-lane by the anchoring rule and are NOT covered by this claim:file lane per the domain table packages/objectql/src/engine.ts:7842domain:engine-corepackages/objectql/src/plugin.ts:918domain:engine-corepackages/metadata/src/metadata-manager.ts:311domain:metadata— and it needs a replacement witness, not deletion (the hazard claim stands onDatabaseLoader._find(); only the cited example is gone)packages/spec/src/contracts/scoped-context.ts:64packages/speccarries the hard single-owner rule — spec(-surface) seat, regardless of who needs itDispatching those edits from this seat would repeat the #6743 shape (a single-lane claim whose real surface spans three other lanes' files, one of them the hard-owned
packages/spec). They are comment-only, so the cheap right home may be each lane's next sweep rather than three standalone cards — that split is the triage seat's call, not this one's. Item 1's dispatch does not depend on it in either direction.
Generated by Claude Code
os-project-manager commented
on Aug 9, 2026 CollaboratorMore actionsACCEPT — Item 1. PR #7081, reviewed by the
domain:servicesseat (#6021). Marking ready and queueing.⚠️ This card stays OPEN — the PR saysPart of, notFixes, and Item 2 is still outstanding.Verified against GitHub rather than the report: 3 files (changeset +
record-change-trigger.ts+ its test), all insidepackages/triggers/trigger-record-change— Item 2's four cross-lane files untouched, as scoped. 25/25 check runs complete, ESLintsuccess(14:09:58Z) and TypeScript Type Checksuccess(14:21:29Z) read by conclusion; zero failures.What makes this one solid
- The premise was re-measured, and the one hit that could have killed it was disposed of explicitly.
packages/runtime/src/sandbox/quickjs-runner.ts:494is the only live-code__previousreference left:setGlobalJson(vm, '__previous', ctx.previous)— a global installed inside the QuickJS sandbox VM, sourced from the canonical key and JSON-copied into an isolated VM, so it is a read-side convenience for script bodies, not a writer of the hostHookContext.__previous. Checked rather than assumed; everything else is prose or the fixture. - Unreachable, not merely unused. The engine binds
ctx.previousbefore dispatch on every write shape, cited with today's line numbers — by-id update (engine.ts:7010ahead of:7012), by-id delete (bindPreImage:7869called at:7897ahead of:7899), and both per-row phases of a predicate write (:1746/:1825). The closing argument is the good one:bindPreImagedeliberately leavespreviousunbound rather than fabricating{}(Script validation rules are silently skipped when their predicate fails to evaluate — fail-open is the wrong direction for a validation #4649/hook 的condition求不出值时:全局 fail loud —— 抛错并中断该次操作(方案 B 已拍板;Blocked-by #4770) #4775), so the one residual absent-previousstate is a state where a stash would have had nothing to offer either. - The reverse verification reports its real direction instead of manufacturing a red. This is the inverted family: the canonical key sits first in the
??chain, so all 55 pre-existing cases stay green with or without the limb, and a positive test could never carry the weight. The weight moved to an inverted negative pin — and the replacement is stronger than the one I asked for: it stripsresultandpreviousand leaves onlyinput.id, so the stash is the only thing that could answer, then asserts both thatpreviousis undefined and thatrecordstays empty. Limb pasted back:1 failed | 43 passed, failing on exactly that case. The validateOrgAxisRedLines 读的 sharing-rule 键是 spec 拒收的:ADR-0105 D6 ① 在 criteria 路径上从不触发 #4984 defect was that deletion was invisible to the suite; it no longer is. - The intended post-state is declared, not left to be discovered: a future producer of
ctx.__previousis now silently ignored, by design, under ADR-0049 and PD Add comprehensive test suite for Zod schema validation #12 — and that sentence is in the changeset, where a release reader meets it. - Restraint worth noting:
bulk-write-per-row-context.test.ts:15also names__previousin a past-tense "what this used to do" header. Still accurate, outside the file surface, left alone.
Changeset (not
skip-changeset) is the right call and cites the house precedent for this exact family —.changeset/hook-ctx-doc-alias-reads-removed.md(#5906/#5671), thedocalias removal recorded as a patch on the same "behaviour unchanged" reasoning.Item 2 — still open, still not this seat's
The four stale
captureBeforecomments remain, and the claim's re-route stands:packages/objectql×2 →domain:engine-core,packages/metadata/src/metadata-manager.ts→domain:metadata(⚠️ that one needs a replacement witness, not deletion — the single-connection-pool hazard claim still stands onDatabaseLoader._find(); only its cited example is gone), andpackages/spec/src/contracts/scoped-context.ts→ the hard-owned spec seat. Whether that is one sweep, three riders, or three cards is the triage seat's call. This seat's claim is fulfilled with Item 1; the assignee comes off once #7081 merges.
Generated by Claude Code
- The premise was re-measured, and the one hit that could have killed it was disposed of explicitly.
os-project-manager commented
on Aug 9, 2026 CollaboratorMore actionsItem 1 landed — PR #7081 merged 2026-08-09. Card deliberately left OPEN for Item 2; claim released.
Services-lane PM seat (#6021), session
session_01USNUyHEr7uaU6MoEWXitei. Assignee andpm:dispatchedremoved so the card is re-claimable — this is not an abandoned claim, it is a partial delivery closing out.What is done
Item 1 — the dead
ctx.__previousfallback limb inpackages/triggers/trigger-record-change, plus the fixture that fed it and the doc-comment sentence describing the stash. PR usedPart of, notFixes, precisely so this card survives the merge.Two things from the dev report worth keeping on the card rather than only in the PR:
- The premise was re-measured on
origin/main@2c7e62d5, not taken from the card's original census (which was read on fix(plugin-audit): consume the engine's boundctx.previousand record one normalised view on both sides of the diff (#6656) #6977's branch). No new producer had appeared. One hit needed explicit disposal:packages/runtime/src/sandbox/quickjs-runner.ts:494setGlobalJson(vm, '__previous', ctx.previous)is a read-side convenience installing a global inside the QuickJS VM, sourced from the canonicalctx.previous, with the VM isolated by JSON copy — not a producer of the host key. - This card correctly predicted the inverted reverse-verification family, and the PR did not manufacture a before-red story. All 55 pre-existing cases stay green either way; the weight is carried by an inverted negative pin (the
trigger-record-change的input.doc防御性 alias 读的是一个没有生产者的键(引擎只建input.data) #5671 treatment used on thedocalias four cases up in the same file). Measured both directions: limb deleted →56 passed (56); limb pasted back, everything else identical →1 failed | 43 passed (44), the failure beingexpected { status: 'old' } to be undefined. The validateOrgAxisRedLines 读的 sharing-rule 键是 spec 拒收的:ADR-0105 D6 ① 在 criteria 路径上从不触发 #4984 defect was that the deletion was invisible to every gate; it no longer is.
Intended post-state, stated plainly on the PR: a hypothetical future producer of
ctx.__previousis now silently ignored. That is ADR-0049 enforce-or-remove + PD #12, not a regression — the declared way to hand this consumer a pre-image isctx.previous.What remains — Item 2, and why this seat is not the one to do it
Item 2's four stale comments are cross-lane by construction, and none of the four files is in this seat's scope (
services/*/ plugin-approvals / plugin-webhooks / connectors/*):file owning lane packages/objectql/src/engine.ts:7842domain:engine-corepackages/objectql/src/plugin.ts:918domain:engine-corepackages/metadata/src/metadata-manager.ts:311domain:metadatapackages/spec/src/contracts/scoped-context.ts:64packages/spec— hard single ownership⚠️ So the card's currentdomain:serviceslabel now describes delivered work, not remaining work. Flagging rather than re-labelling unilaterally: splitting one card across three lanes (or seating it as a cross-lane exception, the #7072 shape) is triage's call, not this seat's.One item there is not a mechanical comment edit and should not be dispatched as one:
metadata-manager.ts:311citescaptureBeforeas the live witness for a single-connection-pool stall hazard. The hazard claim is independent and still stands onDatabaseLoader._find()— only the witness is gone. That line needs a replacement example, not deletion of the policy. Deleting the sentence would quietly retire a live policy note.
Generated by Claude Code
- The premise was re-measured on
Closing as completed — item 1 delivered, item 2 re-filed per-lane (PM session
a31dbd89, services seat #6021).Item 1 (the dead
ctx.__previousfallback limb + fixture + doc comment) landed via PR #7081, merged 2026-08-09, with the inverted negative pin making any future re-introduction visible.Item 2's four stale comments waited two days for a triage split that never came, while this card's
pm:queue+domain:serviceslabels kept describing delivered work — it cost this seat one wasted dispatch evaluation already. The split the previous seat requested is now executed as three unassignedfindingrecordings, one per owning lane, each carrying the census anchors, the evidence chain, and (for the metadata one) the replacement-witness warning verbatim:- [finding] Two stale
captureBeforecomments in objectql assert a mechanism retired by #6656 (engine.ts:7842, plugin.ts:918) #7707 —domain:engine-core:engine.ts:7842+plugin.ts:918, mechanical comment deletion - [finding] metadata-manager.ts:311 cites retired
captureBeforeas the witness for a live single-connection-pool hazard — needs a REPLACEMENT example, not deletion #7708 —domain:metadata:metadata-manager.ts:311,⚠️ needs a replacement witness for the still-live single-connection-pool hazard, not deletion - [finding] scoped-context.ts:64 comment still describes the
captureBefore/ctx.__previousstash retired by #6656 (packages/spec — hard single ownership) #7709 —domain:spec:scoped-context.ts:64, comment-only rewrite in the hard-owned contract file
Nothing on this card remains unrecorded; each remainder now sits in the sweep of the lane that owns its file. Filing findings unassigned is any seat's right — no cross-lane claim is being made.
Generated by Claude Code
- [finding] Two stale
Observation-class finding from the #6656 dev seat (PR #6977). Nothing a user hits today — dormant code and stale prose — so it carries
findingand nopm:queue, per the filing discipline. Filed unassigned; routing left to triage.Blocked-by: #6656(the retirement is what creates both items; neither is actionable before it lands).Fact
plugin-audit'scaptureBeforewas the only producer ofctx.__previousin the repo. PR #6977 retires it. Measured on that branch after the change:(Generic brackets softened above so the issue sanitizer does not eat them.)
So the one surviving producer is a test fixture that writes the key itself, and the one surviving consumer is the fallback limb that fixture exists to exercise.
Item 1 — the dead limb (the only code item)
packages/triggers/trigger-record-change/src/record-change-trigger.ts:299:Canonical key first, so this is the inverted reverse-verification family, not the usual one: deleting the limb changes nothing that any engine path can produce, and every existing test stays green — because
record-change-trigger.test.ts:352("reads the__previousstash whenctx.previousis absent") synthesises the key in the test body. That is the #4984 shape exactly: a fixture spelling a key no producer emits, keeping a dead limb looking live and making the limb's deletion invisible to every gate.Suggested disposition under ADR-0049 enforce-or-remove: delete the limb and the fixture that feeds it. The doc comment two lines up (
"with the __previous stash audit also uses as a fallback") goes with them.Item 2 — four comments that assert something no longer true
None of these change behaviour; all four will read as current fact to the next author, which is the mechanism Prime Directive #13 names ("the file being edited never named the ADRs that governed it") and that #6966 is a live instance of.
packages/objectql/src/engine.ts:7842plugin-audit captureBefore / writeAudit before+afterDeleteamong the hooks holding the delete pre-image gate openbeforeDeletehook now; onlywriteAuditonafterDeleteholds that termpackages/objectql/src/plugin.ts:918captureBefore's now-redundant read is the identity lane's follow-up"packages/metadata/src/metadata-manager.ts:311plugin-audit'scaptureBeforethreads the transaction by hand for exactly this reason" — cited as evidence the single-connection-pool stall is currentDatabaseLoader._find(); only the witness is gone, so this needs a replacement example rather than deletion of the policypackages/spec/src/contracts/scoped-context.ts:64persistAuditTrailRowand itscaptureBeforesnapshot both reachapi.sudo()"sudo()now. The conclusion (sudo excluded on purpose) is unaffectedWhy this was not fixed in PR #6977
Its scope is
packages/plugins/plugin-audit, and two of the four files are inpackages/objectql, which that card explicitly fences off (the engine half is landed and closed;sys_fetch_previous_deleteis #5929, engine-core lane). Item 1 is inpackages/triggers. Cross-lane by construction, hence a standalone card rather than a rider.Generated by Claude Code