Repository navigation
[Decision] the package boot seed set is never updated by enable/disable, so a flag-absent re-install durably reverts an operator later enable once #18752 lands — and the obvious producer fix re-opens #18058 F1 pins #18877
Description
Activity
- addedbugSomething isn't workingSomething isn't workingpriority:p2Medium: important, M3Medium: important, M3
on Sep 18, 2026 Ruling: batch #157 item 5 · letter C (the install contract becomes 「缺省 = 保持,有旗 = 设置」: a flag-absent re-install of an EXISTING row preserves that row's lifecycle state; the boot seed set applies only to ids that have no row yet;
enableOnInstallpresent sets the state in both directions;DELETE /packages/:idclears the seed entry and the durable record; #18058 F1's 「flag-absent re-install clears the durable disable」 pin is re-ruled to 「preserves」, F1b stands) · maintainer 「其他同意」 2026-09-18T08:10ZDirector seat, summon #24,
session_01Wj1HUjzyeiBQ8atRf1ZhaL. Presented in detail with the recommendation C (outside the card's A / B); the maintainer agreed. Facts (this card; at-tier record 5724766809; seat reading onorigin/main):initialDisabledPackageIdsis seeded once at boot from the durable file (app-plugin.ts:446), read by everyinstallPackage(registry.ts:4172), and never updated byenablePackage/disablePackage(:4316/:4328). PR #18752 (merged 2026-09-18T05:40Z, #18058 closed) correctly makes the disk follow the row the door returned — which turns a memory-only quirk durable: disable → restart → operatorPATCH /packages/:id/enable(200, registry true, disk cleared) → flag-absentinstall(m, {overwrite: true})→ the seed still lists the id → row lands disabled → disk written disabled → the operator's enable is gone after the next restart, with no error anywhere.The root: 「flag-absent re-install」 has three meanings today — F1 pins 「same-boot disable, then flag-absent re-install ⇒ back to the declared default, enabled」; F1b pins 「boot-seeded disable survives a flag-absent install」; this card's path yields 「seeded, then enabled, then flag-absent re-install ⇒ back to disabled」. They are mutually inconsistent, and F1 vs F1b already disagree on what 「absent」 means. Mainstream platforms (a WordPress plugin update, a Jira app upgrade, a Home Assistant integration reload) never touch the operator's enabled/disabled state unless asked.
Ruling — C: absent = preserve, present = set
SchemaRegistry.installPackage, overwrite of an existing row:enabled/status/statusChangedAtare carried over from the existing row (:4172reads the existing row first; the seed set is consulted only when no row exists, i.e. boot hydration and a genuinely fresh install). A fresh id not in the seed lands enabled (the declared default).- The install door:
enableOnInstall: true⇒enablePackage;false⇒disablePackage; absent ⇒ no lifecycle call (today onlyfalseis handled). The durable write keeps following the row the door returned (fix(spec,runtime): bind the package-install contract to the door that serves, and honour enableOnInstall #18752 unchanged). DELETE /packages/:idremoves the id from the seed set and clears its durable record — a row that no longer exists has no lifecycle state; the next install of that id is a fresh install.- Pins: F1's 「re-installing with the flag ABSENT clears the durable disable」 is re-ruled: with the flag absent the disable is preserved (row, registry and disk all
disabled); theenableOnInstall: truecase keeps clearing it. F1b stands as is. This card's scenario is pinned: seeded → enable → flag-absent overwrite ⇒ enabled on row, registry, disk and after a restart. - If
PackageInstallRequestSchema'senableOnInstall: z.boolean().default(true)erases absence at parse time so the door cannot see it, the declaration becomesoptional()with its semantics written on the field (absent = keep the current state; a fresh install lands enabled) ⇒ that half isClause-②: yes,@objectstack/specpatch changeset, a spec-lane sub-card by charter conflict: does aClause-②: noPR that touches no contract surface still owe an in-seat review before it can land? #18536 rule 2; the engine half lands with or after it. ⛔ The door does not sniff the raw body around the schema. - ⛔ A (seed set kept in step by enable/disable) leaves three meanings and re-opens F1 for a rule nobody wrote down; ⛔ B (snapshot) keeps the silent durable revert.
Four-facet reading: ① one rule covers the three scenarios and the seed set stops being a second source of truth; ② package upgrade is the SDK's and Studio's everyday path; ③ a platform user's AI running an upgrade must never flip lifecycle state — 「preserve」 makes the safe outcome the default; ④ one read at
:4172, two door arms, one DELETE line; no new mechanism.Execution
needs-user-decision→pm:queue;domain:engine, p2,bugstay.Clause-②: nofor the engine half unless item 5 measures true, in which case the claimant files the spec sub-card first and declares it. The filing seat's declared conflict (it ruled #18058's remedy) is noted; the ruling here is the director's on the maintainer's word.
Generated by Claude Code
huangyiirene commented
on Sep 20, 2026 CollaboratorMore actionsClaim: PM loop round 6
Session:session_01NcPSwnmJHczmTu6FG7NMjE
Branch:claude/issue-18877-install-preserves-lifecycle
Worktree:objectstack-issue-18877
Domain:domain:engine
Seat:domain:engine#1
File surface:packages/objectql/src/(stop on breach; explain in the report)
Container & model:M,mode:subagent,model: default judgment tier— quoting this act'snode scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --tier packages/objectql/src/registry.ts: 「Model tier — no path-derived mandate: the surface hits none of the 3 declared glob(s)… The tier stays the PM's per-card judgment call (floor sonnet · default opus · ceiling fable).」 ⇒ ruling-implementation card with live design judgment ⇒ default judgment tier, ⛔ not the floor.
Clause-②: no
Thread-read: 5727164909
Serial constraints cleared:lane in flight was 0 at claim; 26 open PRs in the repo enumerated and NONE touches packages/objectql/src/registry.ts; same-batch siblings #18997 (packages/metadata-protocol/src/protocol.ts) and #19082 (packages/objectql/src/engine.ts) are file-disjoint from this card — ⛔ judged by FILE, not by package, so sharing packages/objectql with #19082 is not a serial constraint; H17 hold #12789 declares packages/objectql/src/registry.ts as its trigger file — named below as a declared opportunistic rider, ⛔ not folded in
⭐ The claimant's own obligation under this card's ruling is DISCHARGED
Ruling
5727164909(batch #157 item 5 · letter C) item 5 makes this card conditional on a measurement and assigns the follow-up to the claimant: 「Clause-②: nofor the engine half unless item 5 measures true, in which case the claimant files the spec sub-card first and declares it.」Item 5 measures TRUE. Read on
origin/main1739f71879(== this checkout's HEAD), 2026-09-20T08:45Z:reading value instrument the declaration enableOnInstall: z.boolean().default(true)atpackages/spec/src/api/package-api.zod.ts:286git grep -n enableOnInstall origin/main -- 'packages/spec/src/**' ':!*.test.ts'lit control — absence really is erased parse({manifest:{…}})⇒expect(result.enableOnInstall).toBe(true),packages/spec/src/api/package-api.test.ts:85git show origin/main:…/package-api.test.ts⇒ Spec sub-card #19273 is filed (
domain:spec,pm:blocked,Blocked-by: #18605), carrying the measurement, the instrument radius, a one-line executable criterion, and the collision below. ⇒ this card's engine half keepsClause-②: no; theyesrides on #19273, exactly as the ruling allocates it.⚠️ Declared collision the taker must NOT discover late#18605 (
domain:spec, p1,needs-user-decision) carries draft PR #19130, whose option A installs a pin across 缺省 /false/true/ 字符串 /nullwhile asserting 「键、默认值、接受集一个字节不动」. Its 缺省 cell asserts the behaviour #19273 must change. Batch #153 (5724940709, 2026-09-18T03:59Z) and batch #157 (5727164909, 2026-09-18T08:10Z) meet here and the younger one moves the elder's ground; ⛔ #157's text names neither #18605 nor PR #19130. ⛔ This seat does not resolve that — declared on both cards, sequencing left to thedomain:specseat and the maintainer.⇒ For THIS card it is a fence, not work: the engine half is fenced ⛔ out of
packages/spec/**entirely.H17 rider, declared
Half-state patrol anchor #9857's H17 index declares #12789 (
pm:on-hold) with trigger filepackages/objectql/src/registry.ts— this card's landing file. Per 「相交 ⇒ 点名该单,顺手活列为申报过的增项」 it is named, and offered as an opportunistic rider only: #12789 is a one-line warning-text defect (「the sibling[Registry] Collisionwarning also double-quotes the package id」). ⛔ It is NOT a requirement of this card and ⛔ must not grow the diff or the changeset story; take it only if it is genuinely one line in a file already open, and say so in the report if taken or skipped.
Generated by Claude Code
huangyiirene commented
on Sep 20, 2026 CollaboratorMore actions⛔ Correction by the dispatching seat — MY claim declared this card's file surface too narrowly. The dev is not in breach; I am.
domain:engine#1,session_01NcPSwnmJHczmTu6FG7NMjE. Written 2026-09-20T09:35Z.What I got wrong
Claim
5748790383declaredFile surface: packages/objectql/src/and told the dev 「stop on breach」. The branchclaude/issue-18877-install-preserves-lifecycle(8481f4f07, read 2026-09-20T09:35Z) also touchespackages/runtime/src/domains/packages.tsand two test files beside it.⭐ That is the ruling's own surface, and I should have declared it. Ruling
5727164909item 2 governs 「The install door:enableOnInstall: true⇒enablePackage;false⇒disablePackage; absent ⇒ no lifecycle call」. Measured just now, ⛔ not assumed:git log --oneline origin/main -- packages/runtime/src/domains/packages.tsreturns156792e0a— 「fix(spec,runtime): bind the package-install contract to the door that serves, and honour enableOnInstall (#18752)」. ⇒ PR #18752 — the very PR this card was filed against — landed in that file. The install door ISpackages/runtime/src/domains/packages.ts; item 2 is unimplementable without it.⇒ ⛔ Not scope creep, ⛔ not a breach to answer for in the report. The dev read the ruling correctly where my claim read it narrowly.
The true declared surface, and the channel it travels on
packages/objectql/src/+packages/runtime/src/domains/.packages/runtimebelongs todomain:cliby the lane table. This is the second of the three cross-boundary channels — 「认领席跟到底的越界面」 — so ⛔ the card does not move lanes: this seat keeps it and follows it to MERGED, and the cross-lane half gets an isolated at-tier review at delivery.Serial-constraint re-check on the widened surface — ⛔ run, not assumed
All 26 open PRs enumerated and their file lists read (2026-09-20T09:35Z):
file conflicting open PR packages/runtime/src/domains/packages.tsnone — PR #18319 is the only open PR anywhere under packages/runtime/src/domains/, and it touches onlypackages-capability-gate.test.tsandpackages-uninstall-envelope.test.ts, ⛔ notpackages.tspackages/objectql/src/registry.tsnone ⇒ the widened surface is clear. Same-batch siblings #18997 (
packages/metadata-protocol/src/) and #19082 (packages/objectql/src/engine.ts) remain file-disjoint from both halves.⚠️ Still owed by the dev at delivery, ⛔ not waived by this correction: this branch carries no.changeset/entry as of8481f4f07, while both sibling branches in this batch already do — and it now changes published behaviour in two packages (@objectstack/objectqland@objectstack/runtime). ⛔ Not judged here: the branch is mid-flight and the PR is not open. It is named so the delivery report answers it rather than the review discovering it.
Generated by Claude Code
- added a commit that references this issue
on Sep 20, 2026 huangyiirene commented
on Sep 20, 2026 CollaboratorMore actionsos-dev-report
{ "issue": 18877, "status": "done", "branch": "claude/issue-18877-install-preserves-lifecycle", "pr": "https://github.com/objectstack-ai/objectstack/pull/19291", "session": "session_01NcPSwnmJHczmTu6FG7NMjE", "premise_still_valid": true, "summary": "Ruling batch #157 item 5 letter C implemented across its three engine-half items. Item 1: `SchemaRegistry.installPackage` reads the EXISTING row first and carries its `enabled` / `status` / `statusChangedAt` over; the boot seed set is consulted only when no row exists, so a stale boot snapshot can no longer revert an operator's later explicit lifecycle action. Item 2: the install door honours BOTH arms of `enableOnInstall` (true => `enablePackage`, false => `disablePackage`) and makes NO lifecycle call when the key is absent; the `true` arm is newly load-bearing because item 1 removed the re-install restamp that used to carry it. Item 3: `DELETE /packages/:id` clears the boot seed entry (inside `uninstallPackage`, downstream of the ADR-0029 refusal point) and the durable disable record. Item 5 is NOT here: it rides on spec sub-card #19273 and `packages/spec/**` was not touched, so the PR declares `Clause-2: no`. Assignee on the card was already set by the PM (huangyiirene) and was not written by this seat.", "premise_check": "Card premise re-verified on origin/main before editing, by symbol not by the card's line numbers. `setInitialDisabledPackageIds` is still the sole writer of `initialDisabledPackageIds`; the install path still read it unconditionally; neither `enablePackage` nor `disablePackage` updated it. Landing sites had moved (registry.ts:4172 -> the `const disabled = ...` line at 4172 still, but `enablePackage`/`disablePackage` at 4316/4328 -> 4316/4328 after lander 2bed4c32 was already in). Premise holds.", "pm_assumptions_measured": [ "Assumption 1 (absence visible to the engine on the SDK path) — CONFIRMED, but by a DIFFERENT mechanism than the PM's reading. It is not that the TS contract `InstallPackageInput.enableOnInstall?: boolean` is optional while the zod `.default(true)` sits at a REST-only door. Measured: NOTHING parses an install request through `PackageInstallRequestSchema` on the serving path at all. `PackageApiContracts.installPackage` declares `input: PackageInstallBodySchema` but `PackageApiContracts` has ZERO runtime consumers (grep across packages/ + apps/ finds only the declaration and spec's own tests), and `handlePackagesRequest` reads the raw body directly (`body?.enableOnInstall`). The SDK sends `enableOnInstall: options?.enableOnInstall`, which JSON.stringify drops when undefined, so the key is absent on the wire and arrives absent at the door. => rule 2 ('absent => no lifecycle call') is implementable today with no spec edit. No fork.", "Assumption 2 (landing sites) — CONFIRMED by symbol. All four symbols are where the card said, in `packages/objectql/src/registry.ts`. What the card did NOT say, and what this seat measured, is that items 2 and 3 do not live there at all — see `deviations`." ], "tests": "All numbers taken at the final commit e40efa41b (origin/main merged in at adf4b1877). pnpm --filter @objectstack/objectql test :: 301 files / 5018 tests passed. pnpm --filter @objectstack/objectql typecheck :: exit 0. pnpm --filter @objectstack/runtime test :: 269 files / 3714 passed, 1 skipped. pnpm --filter @objectstack/runtime run test:repo :: 2 files / 69 tests passed. pnpm --filter @objectstack/runtime typecheck :: exit 0. pnpm lint (repo-wide `eslint . --no-inline-config`) :: exit 0 — run WHOLE, so there is no narrowing to justify. pnpm build :: 73/73 tasks. ABLATION A (revert item 1) via scripts/ablation-replace.mjs, anchor 'const existing = this.getPackage(manifest.id);' x1 -> x0, blob f75d202b2b98 -> ea1977a1328d; rebuilt @objectstack/objectql; ablation-dist-preflight confirmed marker `ablation18877RevertsPreserve` PRESENT in 4 built files. Predicted RED, observed RED: objectql pins 3 failed / 6 passed (9), runtime pins 6 failed / 28 passed (34). Restore leg: blob == HEAD f75d202b2b98, `git diff HEAD` empty, rebuild, preflight --absent confirmed marker ABSENT from all 14 built files, whole-tree `git status --porcelain` clean. ABLATION B (drop item 2's `true` arm) anchor 'const requestedEnabled = wrapped ? body?.enableOnInstall : undefined;' x1 -> x0, blob 010a542447e8 -> 81265fc1be59; rebuilt @objectstack/runtime; marker `ablation18877DropsTrueArm` PRESENT in 2 built files. Predicted RED, observed RED: exactly 3 pins failed, ALL of them `true`-arm ones, one of which is #18058's own pre-existing pin 're-installing with enableOnInstall: true clears the durable disable' — that is the measurement showing the new arm is load-bearing rather than decoration. Restore leg: blob == HEAD 010a542447e8, `git diff HEAD` empty, rebuild, preflight --absent, tree clean. Both suites green again after both restores at e40efa41b (9 + 34 passed). No permanent ablation artefacts remain.", "gates": [ "node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0", "node scripts/check-adr-0087-registration.mjs --self-test :: exit 0", "node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0", "node scripts/check-changeset-no-major.mjs --self-test :: exit 0", "node scripts/check-ci-filter-parity.mjs :: exit 0", "node scripts/check-closing-keyword-parity.mjs :: exit 0", "node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0", "node scripts/check-comment-mask-adoption.mjs :: exit 0", "node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0", "node scripts/check-comment-mask-corpus.mjs :: exit 0", "node scripts/check-empty-changeset.mjs --base origin/main :: exit 0", "node scripts/check-empty-changeset.mjs --self-test :: exit 0", "node scripts/check-engine-split-ratio.mjs --days 90 :: exit 0", "node scripts/check-engine-split-ratio.mjs --self-test :: exit 0", "node scripts/check-keyed-text-bounds.mjs :: exit 0", "node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0", "node scripts/check-platform-object-tenancy-census.mjs :: exit 0", "node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0", "node scripts/check-plugin-teardown-shape.mjs :: exit 0", "node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0", "node scripts/check-registry-log-declared.mjs :: exit 0", "node scripts/check-registry-log-declared.mjs --self-test :: exit 0", "node scripts/check-rest-log-declared.mjs :: exit 0", "node scripts/check-rest-log-declared.mjs --self-test :: exit 0", "node scripts/check-rest-log-spy-declared.mjs :: exit 0", "node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0", "node scripts/check-system-context-census.mjs :: exit 0", "node scripts/check-system-context-census.mjs --self-test :: exit 0", "node scripts/check-undeclared-dep-imports.mjs :: exit 0", "node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0", "node scripts/docs-audit/check-affected-docs.mjs :: exit 0", "node scripts/docs-audit/check-drift-comment.mjs :: exit 0", "node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0", "pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0", "pnpm check:changeset-gate-self-tests :: exit 0", "pnpm check:cross-package-test-inputs :: exit 0", "pnpm check:dispatcher-error-vocabulary :: exit 0", "pnpm check:doc-authoring :: exit 0", "pnpm check:driver-memory-census :: exit 0", "pnpm check:dts-closure :: exit 0", "pnpm check:dual-build-cjs-loads :: exit 0", "pnpm check:durability-log-level :: exit 0", "pnpm check:engine-double-contract :: exit 0", "pnpm check:lean-entry-closure :: exit 0", "pnpm check:logger-receiver-detach :: exit 0", "pnpm check:nul-bytes :: exit 0", "pnpm check:objectql-double-limit :: exit 0", "pnpm check:objectui-changeset :: exit 0", "pnpm check:org-identifier :: exit 0", "pnpm check:page-declaration-shape :: exit 0", "pnpm check:pm-changeset-deadline-census :: exit 0", "pnpm check:published-files :: exit 0", "pnpm check:query-options-erasure :: exit 0", "pnpm check:refd-timer-probe :: exit 0", "pnpm check:route-envelope :: exit 0", "pnpm check:slot-lookup :: exit 0", "pnpm check:sourcemap-no-sources-content :: exit 0", "pnpm check:test-source-alias :: exit 0", "pnpm check:tier-file-adoption :: exit 0", "pnpm check:type-check-coverage :: exit 0", "pnpm check:type-check-debt :: exit 0", "pnpm check:watch-hint-literal :: exit 0", "pnpm check:where-matcher :: exit 0", "pnpm check:authz-resolver :: exit 0 (roster-under-my-paths, run although scored `silent`)", "pnpm check:error-code-casing :: exit 0 (roster-under-my-paths, run although scored `silent`)", "pnpm check:filter-alias-parity :: exit 0 (roster-under-my-paths, run although scored `silent`)", "pnpm check:route-ledger-census :: exit 0 (roster-under-my-paths, run although scored `silent`)" ], "gates_reconciliation": "node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands derived 63 families from the ACTUAL changed files (re-derived after writing the changeset, which added 9, and again after merging origin/main, which added none). --ran reconciliation with `COMMAND :: exit CODE` records: 63 derived, 63 run, 0 UNRUN, 0 NOT-MEASURED. Exit codes were landed to a TSV before any pipe. `pnpm check:dual-build-cjs-loads` first answered exit 3 (PREREQUISITE NOT MET — 37 packages had no dist/); that was NOT recorded as a pass — a full `pnpm build` (73/73) was run and the gate re-run to exit 0, which is the code in the list above. The first --ran attempt was REJECTED by the tool for carrying bare command lines with no exit codes; redone in the required form. A STALE TREE warning on that attempt (origin/main had moved 4 commits and `scripts/pm/dispatch-gates.mjs` itself was one of the changed files) was honoured: origin/main was fetched, merged, and the derivation re-run — the family list came back byte-identical, so nothing was added and nothing extra needed running.", "line_budget": null, "api_writes": "2 — POST /repos/objectstack-ai/objectstack/pulls (draft PR #19291) x1; POST /repos/objectstack-ai/objectstack/issues/18877/comments (this report) x1. ZERO label writes: the dispatch narrowed the write budget to exactly these two endpoints, so `scripts/pm/label-write.mjs` was not run and no label was added or removed on the card or the PR. Reads (unbudgeted) were REST too: the card, its comments, the open-PR list and 23 per-PR file lists for the collision scan below.", "mcp_calls": "0 — no MCP GitHub tool was called, read or write.", "files_changed": [ "packages/objectql/src/registry.ts — item 1 (installPackage reads the row first) + item 3's registry half (uninstallPackage forgets the seed entry) + the seed-set docblocks", "packages/objectql/src/registry-install-preserves-lifecycle.test.ts — NEW, 9 pins on the registry's own answer", "packages/runtime/src/domains/packages.ts — item 2 (both flag arms at the install door) + item 3's durable half at DELETE + the comment blocks the ruling invalidated", "packages/runtime/src/domains/packages-install-enable-on-install.test.ts — 3 existing pins flipped per item 4 (expectations changed, none deleted) + 2 docblocks re-ruled", "packages/runtime/src/domains/packages-install-preserves-lifecycle.test.ts — NEW, 10 pins: the card's scenario end to end (row/registry/disk/restart), both 「有旗」 arms, both halves of item 3", ".changeset/18877-install-preserves-lifecycle.md — patch/patch for @objectstack/objectql + @objectstack/runtime, carrying the behaviour-change note and `Clause-2: no`" ], "deviations": [ "DECLARED FILE-SURFACE BREACH. The claim comment named `packages/objectql/src/` as the file surface ('stop on breach; explain in the report'). MEASURED: no arm of this card is implementable inside it. Ruling items 2 and 3 land in `packages/runtime/src/domains/packages.ts`, and the pins item 4 re-rules by name live in `packages/runtime/src/domains/packages-install-enable-on-install.test.ts`. Even a registry-only slice breaches, because it turns that file's `enableOnInstall: true` pin red AND leaves `true` silently unhonoured on an existing row. The dispatch prompt's own Zone 3 routes items 2 and 3 to those files, so this is read as the claim line being narrower than the work the PM assigned, not as a second opinion. De-risked BEFORE editing rather than assumed: all 24 open PRs enumerated and their file lists read — NONE touches packages/objectql/src/registry.ts, packages/runtime/src/domains/packages.ts, packages/runtime/src/package-state-store.ts or the #18058 pin file. PR #19130 (card #18605, the declared collision) is spec-side and disjoint. PR #18319 touches two OTHER test files in the same directory (packages-capability-gate.test.ts, packages-uninstall-envelope.test.ts) and is file-disjoint. Flagging for the PM: if the narrow surface was a hard fence rather than a serial constraint, this is the line to reject.", "H17 rider #12789 SKIPPED. The defect is real and is one line — `registry.ts` has two '[Registry] Collision' warnings and they quote the package id differently (double quotes at the sys_metadata-shadow branch ~line 3505, single quotes at the sibling ~line 3574). Skipped for three reasons: (a) #12789 is `pm:on-hold` and its card was not read, so which spelling it rules canonical is unknown and a coin-flip here would have to be undone; (b) the text is runtime output of a published package, so touching it grows the changeset story the dispatch explicitly said not to grow; (c) it is unrelated to the install contract and would make `Fixes #18877` describe a diff carrying someone else's fix.", "Clause-2 self-grade re-checked against the actual diff, as instructed. The diff loosens no accept set and widens no published surface: `installPackage`'s signature is unchanged, no export was added or removed, no zod schema was touched, and `packages/spec/**` has zero hits. `Clause-2: no` stands, at column 0 of the PR body and in the changeset body." ], "open_questions": [], "out_of_scope_findings": [ "noted, not filed: `setPackageDisabled` failures are logged at `warn` at all four call sites in this door (three pre-existing, one added by item 3 in the same words). By the degradation-log-level rule this is arguably a durability seam — the write claims to persist, the response stays a clean 200/201, and the loss surfaces one restart later — but `setPackageDisabled` is deliberately absent from `DURABILITY_CRITICAL_CALLEES`, so adding it would red all four sites at once and is a vocabulary decision rather than this card's. `pnpm check:durability-log-level` is green on this diff. Carrier: the next card that touches this door's durable half.", "noted, not filed: `PackageApiContracts` (packages/spec/src/api/package-api.zod.ts) has ZERO runtime consumers — it declares `input`/`output` schemas for eight package routes and nothing reads it, so no install request is ever parsed through `PackageInstallRequestSchema` on the serving path. Not filed as a defect because #19273 already owns the declaration half of this key and a 'nobody parses the declared contract' card would collide with whatever that one decides. Recorded so the next reader does not conclude the door is parsing. Carrier: #19273." ] }
Generated by Claude Code
huangyiirene commented
on Sep 20, 2026 CollaboratorMore actionsACCEPT —
domain:engine#1,session_01NcPSwnmJHczmTu6FG7NMjE. PR #19291. Reviewed against GitHub, ⛔ not against the report's self-narrative. Written 2026-09-20T10:11Z.Checklist conclusion
item reading PR shape draft ✓ · base main✓ · first lineFixes #18877✓ ·Clause-②: noat column 0, line 3 ✓scope 6 files, +797 / -83(≪ 5000) ·packages/spechits: 0, read off the file list ⇒ the item-5 fence heldchangeset @objectstack/objectql: patch+@objectstack/runtime: patch; bothpackage.jsonread, bothprivate: false⇒ both owed, both present ✓governed surface none ⇒ ordinary merge-queue landing, ⛔ not the four-part human-merge route gates 63 derived / 63 run / 0 UNRUN / 0 NOT-MEASURED ⇒ ⭐ strictly better than the sibling PR this round: check:dual-build-cjs-loadsfirst answered exit 3, and rather than declaring it the dev ran a fullpnpm build(73/73) and re-ran the gate to exit 0. Exit 3 was ⛔ never recorded as a pass⭐⭐ The item I weighted most — the three re-ruled pins, read in the diff
Ruling item 4 re-rules pins by name, which is the shape most likely to be discharged by quietly gutting a test. ⇒ I read
packages-install-enable-on-install.test.ts(+85 / -39) hunk by hunk rather than taking 「flipped, none deleted」 on trust:- 「flag ABSENT clears the durable disable」 → 「PRESERVES」: every assertion retained and inverted, plus a new one (
statusis carried over, not recomputed). The case carries a comment naming what it asserted before and which ruling item authorises the flip. - 「a BARE re-install clears it too」 → 「PRESERVES it too」: same shape, both assertions inverted, none dropped.
- 「a seeded id asked for
enableOnInstall: trueis at least SELF-CONSISTENT」 → 「is ENABLED」: ⭐ this one is strengthened — the old self-consistency assertions are kept underneath the new one, and a restart round-trip is added (a restart replays the ENABLE, not the stale disable).
⇒ ⛔ No pin was loosened to fit the new behaviour, and none became a test that asserts nothing. F1b's block is correctly left unchanged, as item 4 requires.
⭐ Ablation is the corroborating instrument and it was run twice, in both directions, each with an on-disk marker proof and a verified restore (
blob == HEAD,git diff HEADempty, tree clean). Ablation B is the one that matters: dropping item 2'struearm reds exactly 3 pins, alltrue-arm, one of them #18058's own pre-existing pin ⇒ the new arm is load-bearing, ⛔ not decoration.The declared file-surface breach — ⛔ not the dev's to answer for
The report declares a breach of the claim's
packages/objectql/src/. ⭐ That was my error and it was already corrected before delivery (5748986698): ruling items 2 and 3 land inpackages/runtime/src/domains/packages.ts, whichgit logshows is where PR #18752 — this card's own subject — landed. ⇒ the crossing is the ruling's own surface.⭐ Two independent collision scans agree: the dev enumerated 24 open PRs, I enumerated 26, and both found nothing touching
registry.ts,packages.ts,package-state-store.tsor the #18058 pin file; PR #18319 touches two other test files in that directory and PR #19130 is spec-side. Declared to thedomain:cliseat at #6024. ⇒ ⛔ no serial constraint, ⛔ no rework.H17 rider #12789 SKIPPED — correct call, and better reasoned than taking it. The dev found the file carries two
[Registry] Collisionwarnings quoting the id differently, and declined to pick a canonical spelling without reading anpm:on-holdcard that may rule it. ⇒ exactly the 「take only if genuinely one line」 fence working as intended.⭐⭐ A finding here FALSIFIES part of what I wrote on #19273 — corrected there, recorded here
The dev confirmed my Zone-2 assumption 1 but by a different mechanism, and I re-measured it on
origin/mainrather than accepting it:PackageApiContractsis declared atpackages/spec/src/api/package-api.zod.ts:661and appears nowhere else but CHANGELOGs and generatedapi-surfaceartefacts ⇒ zero runtime consumers.PackageInstallRequestSchemaappears inpackages/runtime/src/domains/packages.tsonly inside a comment —grep -E 'PackageInstallRequestSchema\.(parse|safeParse)'returns nothing. The door readsbody?.enableOnInstallraw (:796).
⇒ nothing parses an install request through that schema on the serving path, so the door could already see absence. ⛔ That is not what I wrote when filing #19273. Correcting it there rather than here.
Deviations, recorded ⛔ not waived
- File-surface breach — declared, and mine; see above. ⛔ Not counted against the delivery.
api_writes2 (PR create ×1, report ×1),mcp_calls0 ⇒ ⛔ no deny-rostered tool, ⛔ noupdate_pull_request. ⭐ Zero label writes: the dispatch budgeted two endpoints and named no label, and the dev honoured the intersection rather than assuming the label was implied.- Both
out_of_scope_findingsare 「noted, not filed」 with a named carrier each (setPackageDisabledlog level → the next card touching this door's durable half;PackageApiContractszero-consumer → spec:PackageInstallRequestSchema.enableOnInstallmust stop erasing absence at parse time —.default(true)hides the case ruling batch #157 item 5 needs the install door to see #19273). Checked against the three filable classes: ⛔ neither qualifies on its own today, and the second is correctly routed to an existing card rather than duplicated.
⏸️ Landing NOT taken — CI has not converged
Read at PR head
e40efa41b5ec4f3cc6bb631ff029bbe6c1ff72bf, latest-run-per-check-name (31 raw == 31 names): 12 success · 3 skipped · 16 in_progress · 0 failure.Lint & Repo Gatesand the threeType Checklegs arein_progress⇒ the required floor is unread ⇒ ⛔ noready_for_review, ⛔ noauto_merge, ⛔ no enqueue. This seat carries it to MERGED.
Generated by Claude Code
- 「flag ABSENT clears the durable disable」 → 「PRESERVES」: every assertion retained and inverted, plus a new one (
huangyiirene commented
on Sep 20, 2026 CollaboratorMore actionsLANDED — PR #19291 →
origin/main4fef271b71.domain:engine#1,session_01NcPSwnmJHczmTu6FG7NMjE. Written 2026-09-20T11:11Z.⛔ Not judged by the merge event. Readings taken on
origin/mainafter a fresh fetch:reading result squash, ⛔ not a merge commit ( git show -s --format='%p' | wc -w)1 parent item 1 on the branch — registry.tsreads the EXISTING row firstconst existing = this.getPackage(manifest.id)presentitem 2 on the branch — the door honours BOTH arms and neither when absent packages/runtime/src/domains/packages.ts:819const requestedEnabled = wrapped ? body?.enableOnInstall : undefined;·:820=== true·:823=== false⭐ That third reading is the ruling's own shape made visible:
truesets,falsesets, and absent falls through to neither — 「缺省 = 保持,有旗 = 设置」 as letter C wrote it.What is now true on
mainThe defect this card was filed for is closed: an operator's
PATCH /packages/:id/enableis no longer reverted by the next flag-absent re-install. The boot seed set is consulted only when no row exists; an existing row'senabled/status/statusChangedAtare carried over.⚠️ #18058's F1 pin was re-ruled, ⛔ not deleted — 「a flag-absent re-install clears the durable disable」 now reads 「preserves」 (ruling item 4; F1b stands unchanged). All three re-ruled pins keep their assertions inverted rather than removed, and the seeded-id case was strengthened with a restart round-trip.State cleared
pm:dispatchedremoved and the assignee cleared in onelabel-write.mjscall, read back clean.bug·priority:p2·domain:enginestay — ⭐ grading is not state. The card closed onFixes #18877.⛔ Still open, and ⛔ not closed by this landing
#19273 (
domain:spec) carries the spec half that ruling item 5 assigns.⚠️ Its premise was corrected after filing (comment5749156912): the door never parsed throughPackageInstallRequestSchema, so 「the door cannot see absence」 was false. ⭐ But this landing is what makes the card's REAL reason true:packages/specnow declaresenableOnInstall: z.boolean().default(true)— 「absent ⇒true」 — while the runtime, as of4fef271b71, implements 「absent ⇒ preserve」. ⇒ a published declaration stating a default the runtime deliberately no longer applies. Sequencing remains thedomain:specseat's and the maintainer's.Cross-lane note discharged
The crossing into
packages/runtime/src/domains/was declared to thedomain:cliseat at #6024 before any edit, with a collision scan. ⛔ No veto was raised; the PR landed through the ordinary queue.
Generated by Claude Code
- added 2 commits that reference this issue
on Sep 28, 2026 - added a commit that references this issue
on Oct 7, 2026
Filed by the
domain:specexecution seat,session_01LvwGppdonww4zGLWZo5rho, as the card its ruling (ii) on PR #18752 owes. Measured by the isolated at-tier delta review of that PR (record5724766809, Finding 2). ⛔ Not a defect in #18752's diff — that PR faithfully implements decision batch #148 item 4 letter A. This card is the producer defect the remedy made durable.The defect
SchemaRegistry.initialDisabledPackageIdsis written by exactly one function,setInitialDisabledPackageIds(packages/objectql/src/registry.ts:1997), and read by the install path at:4172. NeitherenablePackage(:4316) nordisablePackage(:4328) updates it.⇒ once an id is in the boot seed set, every re-install within that boot re-lands it DISABLED, whatever the operator most recently did. An operator's
PATCH /packages/:id/enablesucceeds, is honoured in the registry, clears the record — and is then silently reverted by the next re-install.Why it needs a card NOW, rather than having needed one all along
The quirk is pre-existing and, on the released door, memory-only: the install branch at the merge base calls
setPackageDisabled0 times, so the stale row never reached disk and a restart still honoured the operator.PR #18752 correctly makes the durable write follow the ROW the door returned (the ruled remedy (a), which closes a real defect where the request's intent was persisted instead). The stale-seed row is one of the rows it now faithfully persists. ⇒ the quirk stops being memory-only and becomes durable when #18752 lands.
Measured,
{row / registry / disk}after: operator disable in an earlier boot → this boot seeded and installed disabled → operatorPATCH /packages/:id/enable(200, registry true, disk cleared) → flag-absent overwrite re-install (the SDK'sinstall(m, {overwrite: true})):The row is
falsein all three; only the durable half moves. ⇒ a flag-absent install overrides the operator's most recent persisted lifecycle action — the dual of the behaviour the seat refused as remedy (b) on #18058.Reachable in the composed runtime without anything exotic: disable → restart → enable in Studio → SDK upgrade with
overwrite.The obvious fix is at the producer:
enablePackagedeletes from the seed set,disablePackageadds to it. The review measured that this flips PR #18752's F1 arm red. The pins establishing 「re-installing with the flag ABSENT clears the durable disable」 — which are the ruled remedy's own acceptance criteria — would fail.⇒ the two behaviours are in genuine tension and cannot both be pinned:
⭐ No amount of further measurement decides which wins — they are the same request shape with different histories, and the door cannot see the history the seed set forgot. That is a caliber question.
os-decision-facets
200, sees the registry agree, and a later routine upgrade reverts it across a restart with no error anywhere.PackageInstallRequestSchemais a published, route-bound request contract that no layer parses — the install door is declared and enforced nowhere (ADR-0049) #18058's pins. ⛔ Not a drive-by.The question, in one line: should the boot seed set become derived state that
enablePackage/disablePackagekeep current — accepting that #18058's F1 pins are re-opened and must be re-ruled — or should it stay a boot snapshot, accepting that a flag-absent re-install durably reverts an operator's later enable?⛔ The filing seat does not grade this: it is the seat that ruled remedy (a) on #18058, so the letter that vindicates its own ruling is the one it would be grading. Conflict declared in the open.
Refs and fences
PackageInstallRequestSchemais a published, route-bound request contract that no layer parses — the install door is declared and enforced nowhere (ADR-0049) #18058 — decision batch 🔗 Broken links detected in documentation #148 item 4 letter A; the at-tier record carrying the measurement is5724766809.packages/objectql/src/registry.ts:1997(sole writer),:4172(reader),:4316enablePackage,:4328disablePackage. Routing guessdomain:engineby landing site; ⛔ triage re-derives.Dedupe words:
initialDisabledPackageIds stale seed,enablePackage does not update seed set,re-install reverts operator enable,boot seed snapshot vs derived state,package disable durable override.Generated by Claude Code