Skip to content

fix(spec): enableOnInstall becomes optional() so absence survives the parse - #19690

Merged
os-justin merged 8 commits into
mainfrom
claude/issue-19273-enableoninstall-optional
Sep 22, 2026
Merged

os-justin merged 8 commits into
mainfrom
claude/issue-19273-enableoninstall-optional

Conversation

@os-justin

Copy link
Copy Markdown
Collaborator

Fixes #19273

Clause-②: yes

Ruling batch #210 item 4 · letter A · maintainer 「210 同意」 (5770455384, 2026-09-22T02:41Z). The direction was ruled, not chosen here.

The defect

packages/runtime/src/domains/packages.ts:1095 has honoured 「缺省 = 保持,有旗 = 设置」 since PR #19291 landed:

const requestedEnabled = wrapped ? body?.enableOnInstall : undefined;

true calls enablePackage, false calls disablePackage, and an absent key makes no lifecycle call at all — so a package an operator disabled stays disabled across an upgrade. Verified unchanged on this branch; the runtime is not touched by this PR.

The published declarations said something else. z.boolean().default(true) resolves absence at parse time, so a request that omitted the key came out of the parse byte-identical to one that set true. The third state did not exist on the published surface while the door went on acting on it — a declared default the runtime deliberately stops applying, on a contract this repo does not own both ends of.

What changed

All three declarations now spell z.boolean().optional(), with the semantics on the field in both the describe and the docblock — absent = keep the row's current lifecycle state; explicit true / false unchanged; a fresh install lands enabled:

declaration file
api/PackageInstallRequest — the authority packages/spec/src/api/package-api.zod.ts
kernel/InstallPackageRequest — the copy packages/spec/src/kernel/package-registry.zod.ts
marketplace/MarketplaceInstallRequest — a different party's key packages/spec/src/marketplace/marketplace.zod.ts

The executable criterion, both directions

Read off the built package (packages/spec/dist), not src/, at head f5b094a96:

api/PackageInstallRequest    | absent => undefined (key in parse output: false) | true => true | false => false
kernel|api/InstallPackageReq | absent => undefined (key in parse output: false) | true => true | false => false
marketplace/MarketplaceInst. | absent => undefined (key in parse output: false) | true => true | false => false

The true and false arms are re-read after the change on all three declarations, never assumed — the card's control in the other direction: a fix that makes absence visible by making the key mean nothing would be worse than the defect. The two refusal cells are unmoved: a string 'false' and null are still refused by name.

PR #19130's consistency pin — flipped with its trigger registered, ⛔ not patched green

packages/spec/src/api/package-install-one-authority.test.ts asserted true on every 缺省 reading. Only the 缺省 cell moves; the false, true, string and null cells are untouched, and the authority/copy agreement is still judged cell by cell.

The flip-trigger phrase registered in the test is:

缺省 = 保持,有旗 = 设置

It is a named FLIP_TRIGGER const with its own docblock explaining that while the declarations spelled .default(true) the 缺省 reading was living on borrowed time — the phrase says absence is a state the door ACTS ON, and a .default() resolves absence at parse time so that state cannot survive to the published surface. It is quoted into the 缺省 cell's name so a test run prints it, and into the two flipped assertion titles. The file's header docblock carries a section stating that the cell FLIPPED, that this was expected on the day the pin landed, and that reading the red as "the pin needs updating" and writing the new value in silently is the failure the const exists to prevent.

⚠️ DECLARED file-surface expansion, with the mechanism that forces it

Beyond the three declarations, their tests and the changeset, four more paths are in this diff. Each is mechanically forced; none is a discretionary edit.

  1. packages/spec/scripts/lib/default-changes.ts (+101). check:authorable-surface refuses the build on an undeclared move of an authorable key's default, and prints the copy-pasteable block naming each key and both fingerprints. The build exits 1 until the entries exist. Four entries are required, not three: InstallPackageRequestSchema is re-exported through src/api/protocol.zod.ts, so one declaration publishes under two def keys (kernel/InstallPackageRequest and api/InstallPackageRequest, byte-identical but for the $id) — the CreateImportJobRequest / ImportRequest shape already in that table. The ratchet names keys, not schemas, so dropping either row leaves that def unauthorised and the gate red.
  2. packages/spec/authorable-defaults/{api,kernel,marketplace}.json (-4 lines total). Generated. pnpm --filter @objectstack/spec build writes them; exactly the four … = true entries are removed and nothing else moves.
  3. content/docs/references/{api/package-api,api/protocol,kernel/package-registry,marketplace/marketplace}.mdx (+5 / -5). Generated by gen:docs, run via check:generated --fix, which regenerated only the one artefact it proved stale. The four projected rows lose their (default: true) cell and gain the three-state prose. No other row moves.

authorable-surface/*.json and authorable-surface.base.json are not in this diff: the keys stay authorable, and the base anchor is only ever written by the explicit gen:authorable-surface-base, never by a build.

Verification

Reconciliation line, verbatim, derived and run at head f5b094a96:

Run reconciliation — 108 derived, 108 run, 0 NOT-MEASURED, 0 UNRUN.

✓ dispatch-gates --ran: 108 derived famil(ies) accounted for — 108 run, 0 NOT-MEASURED (a DERIVED zero — all 108 recorded an exit code and none of them is 3). Every command's exit code was captured before any pipe; no command answered exit 3, so nothing in the derived set measured nothing.

Everything below ran in the foreground; each heavy run went through scripts/pm/os-verify-lock.sh with OS_VERIFY_LOCK_SLOT=issue-19273, and each verdict is that wrapper's own VERDICT command-exit line, never a bare shell status.

run verdict
pnpm --filter @objectstack/spec test VERDICT command-exit 0 — 512 files, 14955 passed, 1 todo
pnpm --filter @objectstack/rest test VERDICT command-exit 0 — 194 files, 3265 passed, 1 skipped
pnpm --filter @objectstack/runtime test VERDICT command-exit 0 — 272 files, 3799 passed, 1 skipped
pnpm exec turbo run typecheck VERDICT command-exit 0 — 143 tasks successful
pnpm build VERDICT command-exit 0 — 73 tasks successful
pnpm --filter @objectstack/spec check:generated VERDICT command-exit 0 — all 15 generated artifacts up to date
pnpm lint exit 0, run WHOLE (eslint . --no-inline-config), not narrowed — so no narrowing evidence is owed

origin/main was merged and the build state refreshed before the final push; the generated re-check and the union above were both taken after that merge, on the head this PR carries.

⚠️ The open reading the ruling hands the dev, reported as a zero WITH its radius

Zero consumers found that parse an install request through the published schema. The instrument's reachable radius, stated because a zero without one is not a reading:

  • Reached: objectstack-ai/objectstack at f5b094a96packages/**, apps/**, examples/**, scripts/**, content/**, docs/**, skills/**, excluding node_modules. And objectstack-ai/objectui at 0cf2d66, the only sibling checkout in this container, excluding node_modules.
  • objectui reading, with a positive control: enableOnInstall0 hits. PackageInstall (the schema name) — 0 hits. Control that proves the instrument reads that tree: packages.install / /api/v1/packages52 hits. So objectui calls the install route and never names the key, never parses through the published schema.
  • ⛔ NOT reached, and so NOT established in either direction: objectstack-ai/cloud (no checkout exists in this container) and any third-party consumer of the published @objectstack/spec. The changeset body and all four DEFAULT_CHANGES_BY_MAJOR reasons are written for exactly that unreachable consumer — the caller who validates before sending — because they are the only channel that reaches them.

Changeset grade

minor for @objectstack/spec, ⛔ not the patch ruling #157 item 5 wrote. Ruling #210 item 4 overrode it and the override is measured: check-changeset-no-major.mjs's judgeLevel verdict enforce refuses a clause-②-carrying diff whose moved packages are graded patch with none at minor or above. Judged against packages/spec/package.json's files[] after a build as usual — dist/ and json-schema/ both ship, and both move here — so the floor and the measurement agree. node scripts/check-changeset-no-major.mjs --base origin/main and node scripts/check-adr-0087-registration.mjs --base origin/main both exit 0 on this head.

⛔ Fences honoured

  • Not the engine half. packages/runtime/src/domains/packages.ts:1095 verified to still read const requestedEnabled = wrapped ? body?.enableOnInstall : undefined;. The runtime is not in this diff.
  • The door does not sniff the raw body around the schema. Nothing in this PR adds a parse on the serving path.
  • No label writes of any kind, and no new issues filed — findings go back to the dispatching seat.

Acceptance notes

None. Nothing outside this card's scope was surfaced that meets the filing bar.

维护者速读(草稿)

改了什么 — 三处 enableOnInstall 声明从「默认 true」改成「可缺省」。安装接口的实际行为半年前就被裁决改成了「不写这个键 = 保持这个包当前的启用/停用状态」,但对外发布的协议声明一直还写着「不写 = 启用」。这次让声明跟上已经生效的行为。

为什么改 — 声明与实际不一致,受伤的是仓库外面的调用方。一个会先按协议校验请求再发送的客户端,会从「默认 true」里自动补出一个 enableOnInstall: true 发过来;而这个显式的 true 的含义是「强制启用」。结果就是:同样一个请求体,先校验的那一方会在每次升级时把运维手动停用的包悄悄重新打开,不校验的那一方则正常保持停用。两边行为相反,差别只在于有没有先校验。

风险与代价(含回滚) — 本仓内运行时行为零变化:安装接口读的是原始请求体,没有任何服务路径经过这几个 schema 解析,接受集也一个字节没动(缺省、true、false 照收,字符串和 null 照拒)。真正受影响的是仓外那位会校验的调用方,处方已写进 changeset 和四条默认值台账记录里:想要每次都强制启用,就把 enableOnInstall: true 显式写出来。回滚代价低——三处声明改回 .default(true)、撤掉四条台账记录、重跑生成即可,但回滚会把「声明 ≠ 实际」这个问题原样退回去。

席位意见

你要做的 — 确认一件事就够了:仓外(尤其 cloud 侧和第三方)有没有会先按发布的 schema 校验安装请求、再把校验后的对象发出去的调用方。本次探测半径只到本仓和 objectui 两棵树,读数为零且带正控(objectui 会调安装接口但从不提这个键);cloud 在本容器里没有检出,所以那边是未测,不是「没有」。若那边确实有这样的调用方,它就是这次改动唯一会碰到的对象,而 changeset 里的处方正是写给它的。


Generated by Claude Code

The published declarations claimed `.default(true)` — "absent means
enabled" — while the install door has honoured 「缺省 = 保持,有旗 = 设置」
since the runtime half landed: absent makes no lifecycle call, so a
package an operator disabled stays disabled across a re-install.

A `.default()` resolves absence at parse time, which erases the third
state from the published surface and leaves a declared-but-unenforced
default on a contract this repo does not own both ends of. `optional()`
keeps the state visible; the `true` and `false` arms are unchanged.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
The #18605 consistency pin asserted `true` on every 缺省 (absent) reading,
which was the behaviour this change moves. The cell is flipped with its
registered flip-trigger phrase rather than patched green, so the next
reader meets the reason instead of a silently edited expectation.

Only the 缺省 cell moves. The `true` and `false` arms are re-read after
the change on all three declarations — a fix that makes absence visible
by making the key mean nothing would be worse than the defect.

`check:authorable-surface` refuses an undeclared default move, so the
four published def keys are declared in DEFAULT_CHANGES_BY_MAJOR. Four
keys from three declarations: `InstallPackageRequestSchema` is
re-exported through `src/api/protocol.zod.ts` and publishes under both
`kernel/` and `api/`.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
`gen:docs` reprojects the four published defs whose `describe` moved: the
`(default: true)` cell is gone and the prose now states all three states.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions

github-actions Bot commented Sep 22, 2026

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 9 documentable anchor(s). ⚠️ 3 changed file(s) yielded no anchor (packages/spec/authorable-defaults/api.json, packages/spec/authorable-defaults/kernel.json, packages/spec/authorable-defaults/marketplace.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

10 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:

  • content/docs/api/client-sdk.mdx (via packages.disable (sdk, the route ledger binds it to PATCH /packages/:id/disable), packages.enable (sdk, the route ledger binds it to PATCH /packages/:id/enable))
  • content/docs/api/environment-routing.mdx (via /api/v1/packages (route, a path literal in a comment in InstallPackageRequestSchema; a path literal in a comment in PackageInstallRequestSchema))
  • content/docs/api/metadata-api.mdx (via /packages/:id/disable (route, a path literal in a comment in PackageInstallRequestSchema), /packages/:id/enable (route, a path literal in a comment in PackageInstallRequestSchema))
  • content/docs/getting-started/examples.mdx (via /api/v1/packages (route, a path literal in a comment in InstallPackageRequestSchema; a path literal in a comment in PackageInstallRequestSchema))
  • content/docs/kernel/contracts/metadata-service.mdx (via /api/v1/packages (route, a path literal in a comment in InstallPackageRequestSchema; a path literal in a comment in PackageInstallRequestSchema))
  • content/docs/kernel/services-checklist.mdx (via /api/v1/packages (route, a path literal in a comment in InstallPackageRequestSchema; a path literal in a comment in PackageInstallRequestSchema))
  • content/docs/permissions/permission-sets.mdx (via /api/v1/packages (route, a path literal in a comment in InstallPackageRequestSchema; a path literal in a comment in PackageInstallRequestSchema))
  • content/docs/protocol/kernel/error-handling.mdx (via /api/v1/packages (route, a path literal in a comment in InstallPackageRequestSchema; a path literal in a comment in PackageInstallRequestSchema))
  • content/docs/protocol/kernel/http-protocol.mdx (via /api/v1/packages (route, a path literal in a comment in InstallPackageRequestSchema; a path literal in a comment in PackageInstallRequestSchema))
  • content/docs/ui/apps.mdx (via /api/v1/packages (route, a path literal in a comment in InstallPackageRequestSchema; a path literal in a comment in PackageInstallRequestSchema))

2 release-owned page(s) also name something this change touched. These are read-only:

  • content/docs/releases/v17/17-0.mdx (via /api/v1/packages (route, a path literal in a comment in InstallPackageRequestSchema; a path literal in a comment in PackageInstallRequestSchema))
  • content/docs/releases/v17/17-4.mdx (via /api/v1/packages (route, a path literal in a comment in InstallPackageRequestSchema; a path literal in a comment in PackageInstallRequestSchema))

content/docs/releases/ is RELEASE-OWNED (AGENTS.md "Documentation Guardrails"): release
notes are written centrally at release time, and a code PR that edits them is the exact PR
that guardrail exists to stop. They are still audited — read-only. If one of them is actually
wrong, file an issue or open a dedicated docs-only PR; do not edit it here.

What this run could not see
  • 3 changed file(s) yielded no anchor (packages/spec/authorable-defaults/api.json, packages/spec/authorable-defaults/kernel.json, packages/spec/authorable-defaults/marketplace.json) — pages documenting those are invisible to this run
  • the SDK route bridge reached 60 of 215 client-bound route-ledger rows — the other 155 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 155: 0 are remediable by widening that discovery convention (an in-repo file declares the path; the convention did not scan it); 55 are structural — on a ledger where NOT ONE row is declared in-repo, so no discovery change reaches them at any price; 100 are undecided (no in-repo declaration, on a ledger that has other in-repo registrars — absence and an unreadable spelling are not distinguishable here). The rows themselves: node scripts/docs-audit/affected-docs.mjs --bridge-coverage
  • a page that states a rule by its inputs shares no identifier with the emitter that implements the rule, so an emitter-only diff cannot list it — not on this run and not on any run. Measured on fix(driver-sql): emit varchar(maxLength) for a text field a declared index keys on #11430: content/docs/protocol/objectql/types.mdx documents the text-family column mapping by the ObjectQL type names it maps FROM (text / textarea / html) while the diff changed createColumn; it went unlisted, and it was the page that diff falsified, in four places. No shared token exists to detect this on, so a rule your change carries has to be re-read by hand in the pages that restate it.
  • a key NAME is not a key, so the hand re-read the line above prescribes can land on the wrong schema. The same spelling is authorable on one governed type and a [REMOVED] tombstone on another for each of active, aria, joins, objects, template, tools and version (censused on [finding] tools is a key on BOTH AgentSchema (tombstoned, dead) and SkillSchema (live, cloud-attested), so a name-based search attributes skill examples to the agent key — it produced a false stop-the-line alarm on PR #19059 #19093 over the liveness ledger's governed types, top-level keys); nothing in a search result distinguishes the two, so a grep hit on a LIVE example reads as evidence about the DEAD key. Measured on fix(spec): the agent.tools liveness row says dead — it claimed live on a key the schema tombstoned #19059: content/docs/ai/agents.mdx was reported as contradicting the agent.tools tombstone over its tools: example at :161, which is inside the defineSkill({ block opened at :155 — the page was already correct. Settle ownership by PARSING the value against both schemas, never by the name: that literal PASSES SkillSchema, and as an AgentSchema it FAILS at tools with the tombstone prescription. ⛔ These names are not the whole class — a key retired through a .strict() guidance map leaves no tombstone in the walked shape and none of them here (tool.category, live as AIToolDefinition.category).

Coarse fallback — 136 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): node scripts/docs-audit/affected-docs.mjs --json 1d41aa88553754276eb350c1a068a817d3560e2dpackageMentionDocs.

Which tree this was computed on

This run read content/docs from 107281972e65586c76e512e72c205c16f3153436 — the merge of head 6584b9dcd72706e4ca9af7291424297fbc50461a into base 1d41aa88553754276eb350c1a068a817d3560e2d, which is what actions/checkout gives a pull_request run. Not the PR head.

A worktree cut from an older main holds a different content/docs, so re-deriving there can legitimately return a different list — that is a different tree, not a wrong row. To answer on the same tree:

# while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 107281972e65586c76e512e72c205c16f3153436 && git checkout 107281972e65586c76e512e72c205c16f3153436
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 1d41aa88553754276eb350c1a068a817d3560e2d 6584b9dcd72706e4ca9af7291424297fbc50461a && git checkout -B drift-repro 1d41aa88553754276eb350c1a068a817d3560e2d && git merge --no-ff 6584b9dcd72706e4ca9af7291424297fbc50461a

node scripts/docs-audit/affected-docs.mjs --json 1d41aa88553754276eb350c1a068a817d3560e2d

⚠️ That checkout carried uncommitted changes, so the commit above does not fully identify what was read.

Advisory only, and a precision-first one (#9192): a page is listed because it names a
symbol, wire route or SDK method this diff touched — not because it mentions a changed
package. Each row says which anchor put it there, so a wrong row is reportable rather than
merely annoying. To re-verify, run the docs-accuracy-audit workflow scoped to these files:
node scripts/docs-audit/affected-docs.mjs 1d41aa88553754276eb350c1a068a817d3560e2d → pass the list as
args.docs, on the commit named under Which tree this was computed on.

The header block claimed this file "registered in advance" the phrase the
缺省 cell would later flip on. Measured on the parent commit: the file
carried no such phrase and no flip or trigger note of any kind — zero hits
for 缺省 / 保持 / 有旗 / flip / trigger, with `MATRIX` and `absent` as the
controls proving the file was read. The claim was false about this
repository, and it contradicted the const's own docblock twenty lines
below, which said correctly that the phrase is registered here.

The const's version is the true one and is kept. The header now states
that the trigger is registered WITH the flip rather than ahead of it, and
records the gap as the lesson: a pin written against a contested cell
needs its trigger when the pin lands, while the contest is known, not
when the cell finally moves.

Comment-only. No assertion, matrix cell, changeset or declaration moves.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>

Copy link
Copy Markdown
Collaborator Author

Contract review

b3ec2123c4afa44620851c161d91bffa749d1d67

Reviewed-by: domain:spec execution seat 5 (os-justin, session_01Sfe5YjBLwB9J3y8fvm2xq1, seat post #19357)
Served-tier: CONTRACT_REVIEW_TIER — at tier, so the clause-② judgement is made in-seat.

⛔ Reviewed against the diff and origin/main, ⛔ not against the dev's report. Clause-②: yes here, so this is an adjudication rather than a record of one.

Which limb of clause ② this is — and which it is NOT

The accept set does not move, in either direction. z.boolean().optional() accepts exactly what z.boolean().default(true) accepted: a boolean, or the key absent. 'false' (a string) and null are refused before and after, and the matrix pins both cells on all three declarations. ⇒ neither 收紧 nor 放宽.

What moves is the published PARSE OUTPUT and the published surface — two of the other three forms at once:

  • 公开导出面: the default keyword disappears from four published defs, and the three authorable-defaults/*.json rows go with it. Reading PackageInstallRequestParsed.enableOnInstall now yields boolean | undefined where it yielded boolean.
  • declared ≠ enforced, CLOSED: the runtime has implemented 「缺省 = 保持,有旗 = 设置」 since PR fix(objectql,runtime): a flag-absent install preserves the package's lifecycle state #19291 (packages/runtime/src/domains/packages.ts), while the declaration went on claiming a default the door deliberately stopped applying. That divergence is what this PR retires.

The consumer this actually protects is the sharpest thing in the diff, and it survives my own re-reading: a client that VALIDATES its request through the published schema materialised enableOnInstall: true from the declared default and SENT it explicitly. Under letter C an explicit true is a force-enable ⇒ that caller silently re-enabled a package an operator had deliberately disabled, on every upgrade, while a caller sending the identical body WITHOUT validating preserved the disable. Identical request bodies, opposite behaviour, decided by whether the caller validated before sending. ⛔ Nothing inside this repo changes: nothing parses an install body through these schemas on the serving path — the door reads the raw body — so this is a correction to the declaration, not to any deployed behaviour.

What this seat verified by reading the diff, ⛔ not the report

claim reading
all three declarations move api/package-api.zod.ts, kernel/package-registry.zod.ts, marketplace/marketplace.zod.ts — each z.boolean().optional()
semantics in BOTH describe and docblock, per the ruling ✅ all three: true enables / false disables / ABSENT keeps the row's current lifecycle state, with 「a fresh install lands enabled」 named as the registry's own new-row value rather than a default this schema applies
the generated default ledger follows ✅ three authorable-defaults/*.json rows removed; four DEFAULT_CHANGES_BY_MAJOR rows added — including both def keys the re-exported InstallPackageRequestSchema publishes under (kernel/ and api/), which is exactly where a diff of this shape lands one row short
PR #19130's pin FLIPPED, ⛔ not patched green FLIP_TRIGGER registered and quoted into the cell name and the assertion titles; only the 缺省 cell moves; false, true, string and null cells untouched; the true/false arms RE-READ after the change rather than assumed
the lit control flipped with its reason package-api.test.tstoBe(true)toBeUndefined(), with the note recording that the old assertion was the control proving absence really was erased
the engine half untouched ✅ no packages/runtime/** file in the diff
changeset '@objectstack/spec': minor — the #210 override, ⛔ not the patch that #157 item 5 wrote, which check-changeset-no-major.mjs refuses on a clause-② diff

One item was sent back before this record, ⛔ not waved through

The pin's new header claimed FLIP_TRIGGER was 「the phrase this file registered in advance」. Measured on the parent commit: zero hits for 缺省 / 保持 / 有旗 / flip / trigger in that file, with MATRIX and absent as the lit controls proving the grep read it — and the const's own docblock twenty lines below said the opposite, correctly. One file self-contradicting about its own history. Fixed in b3ec2123c, comment-only: the header now says the trigger is registered HERE, with the flip, and records that the card's protocol 「在用例内预登记翻转触发词」 expected a pre-registration that did not happen. ⭐ Leaving that gap recorded is worth more than the sentence it replaced.

Declared, so it is not discovered later

⚠️ The dev's derivation printed STALE TREE — 3 commits behind origin/main, 1 family file changed in that range (scripts/pm/check-governed-queue-guard.mjs). It deliberately did not merge, so the head under review would not move. This seat accepts that: the changed file is the governed-surface queue guard, this diff touches ⛔ no governed surface, and the merge queue runs full CI against the updated base before it lands, which is the control that matters. ⛔ Not waved away — stated, with the reason.

⚠️ Five gates answered exit 3 PREREQUISITE NOT MET on the dev's first pass over this head (a recreated worktree with only one package built). Those rows were deleted from the record and re-run after pnpm build; all five then exited 0. ⇒ the reconciliation (108 derived, 108 run, 0 NOT-MEASURED, 0 UNRUN) stands on five genuine readings, ⛔ not five unmeasured ones. That is the correct handling of a prerequisite failure and is recorded because the wrong handling is invisible afterwards.

Verdict

ACCEPT. CI on this head: 33 success · 2 skipped · 0 incomplete, latest-per-name, mergeable_state: clean. check-clause2-carriers --pair 19690 reads exit 0 with needs:contract-review in the same state on both carriers. Not a governed surface ⇒ no maintainer merge is owed. Landing now.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Carrier stripped on PASS, and the record it cites. 2026-09-22T09:10Z

needs:contract-review removed from both carriers — card #19273 and this PR — in the stroke before this comment, per 「PASS ⇒ 同席剥标并引记录、ready、auto-merge」.

The record: the in-seat contract review at 5773652504, against head b3ec2123c4afa44620851c161d91bffa749d1d67, Reviewed-by: + Served-tier: CONTRACT_REVIEW_TIER, verdict ACCEPT. ⛔ The strip is the consequence of that verdict, not a way past the check: 「Stripping the label to get past this check, with no verdict on record, is the defect this leg was built from」 — the verdict is on record, posted before the strip, and this comment is the citation the protocol asks for.

⛔ Why this PR was ejected at 08:48:24Z — the fault is this seat's, stated rather than left as a mystery

Governed Surface Guard on the merge group queue/main/pr-19690-40626bdca exited 6 at 08:47:35Z, and the queue removed the PR 49 seconds later. The guard's own text says why: that leg reads the LABEL, not the verdict — a PR carrying needs:contract-review is held out of the queue by design, and 「outside the queue is the SAFE state, not a stalled one」.

⇒ this seat enqueued with the carrier still hung. The protocol's step 3 (strip on PASS, cite, re-enqueue) was written into this seat's own notes as a POST-landing step; it is a PRE-enqueue one. Corrected here, and corrected for the sibling PR #19685, which was ejected by the identical leg at 09:00:35Z for the identical reason.

⚠️ ⛔ Nothing about the PR changed to get past the gate: no code, no label other than the carrier the protocol says to strip, and no re-run. Re-enqueueing now.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

⛔⛔ WITHDRAWN AS THE REVIEW OF RECORD — this seat is not at CONTRACT_REVIEW_TIER, and it measured that only after posting. 2026-09-22T09:14Z

domain:spec seat 5. This is a declared deviation, ⛔ not a correction of the reading and ⛔ not a retraction of the substance.

What changed under this seat, measured in this act

CONTRACT_REVIEW_TIER on origin/main reads claude-fable-5-1 (scripts/pm/dispatch-gates.mjs:12282). It was restored there by PR #19684 / card #19680, merge commit 372931e51, which landed this hour, under the maintainer's ruling 5771798588 — 「复核档应该就是 fable 啊」·「某个agent临时没有fable给的特殊授权,不应该改变skills」.

⚠️ This seat began its shift holding the OPPOSITE reading — the constant then named the other tier, and this seat recorded that as 「档位冲突已由事实关闭」. That fact was reverted by ruling while this seat was mid-round, and this seat did ⛔ not re-measure it before signing. The standing rule it wrote for itself — 「唤醒文里的每一条 standing fact 都当作待复验项读」 — is exactly the rule it broke.

Why that is not a formality

dispatch-gates.mjs states clause ② without ambiguity: a card that changes contract accept/reject behaviour or widens the public surface is built at the default tier and REVIEWED at CONTRACT_REVIEW_TIER — 「the spec and skills lanes owe it on every round they deliver — in-seat when the seat's served tier is that tier, otherwise by the at-tier review subagent the seat spawns」.

⇒ the in-seat record this seat posted above is not the review the rule demands. ⛔ It is not void as a reading — every measurement in it was taken against the diff and origin/main and stands as the seat's own work — but it ⛔ cannot be the record that clears this PR.

What this seat did about it, in this order

  1. Paused the landing. Auto-merge disabled on PR fix(spec): every composeStacks conflict refusal carries an ADR-0112 envelope #19685 while it sat in the queue (it had not merged); PR fix(spec): enableOnInstall becomes optional() so absence survives the parse #19690 was already held by a merge conflict. ⛔ Neither landed on an unauthorised record.
  2. Re-hung needs:contract-review on BOTH carriers of both pairs — the carrier comes off on a PASS that the rule recognises, and this seat's does not qualify. That also keeps both PRs out of the queue, which is the safe state by the guard's own words.
  3. Spawned the at-tier review subagent, which is the remedy the rule itself names (「otherwise by the at-tier review subagent the seat spawns — the fastest route, per the maintainer」). It was told ⛔ not to inherit this seat's conclusions and to re-derive every judgement.
  4. Recorded the reversal in this seat's wake template so the next round starts from the measured constant, ⛔ not from the seat post's stale claim.

Nothing here asks anyone to do anything. The at-tier record will land on this thread; the carrier comes off when it does; the landing follows. If that review reads FAIL, this is a patch round and the work goes back to the dev.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Docs drift — receipted by the seat, with the reading that answers the run's own gap. 2026-09-22T09:29Z

domain:spec seat 5, on docs-drift-check's comment 5773107890. ⛔ Not a dismissal: the 10 named pages and the 3 uncovered files were taken as a work list and read.

The gap the run declared, answered rather than inherited

⚠️ 3 changed file(s) yielded no anchor (packages/spec/authorable-defaults/{api,kernel,marketplace}.json), so the pages documenting them are NOT COVERED by this run — this is not a clean bill of health for those files.

Those three files are the GENERATED authorable-default ledgers, and what documents them is the served JSON Schema rendered into content/docs/references/**. All four of those pages are IN this diffreferences/api/package-api.mdx, references/api/protocol.mdx, references/kernel/package-registry.mdx, references/marketplace/marketplace.mdx — regenerated by check:generated, which reads 「all 15 generated artifacts are up to date」 on this head. ⇒ the pages the uncovered files document are not merely covered, they moved with the change.

The question that actually matters, and its measurement

This PR removes a published DEFAULT. The page it could falsify is one that states 「absent means enabled」. Measured over content/docs/** minus the generated references/ tree:

enableOnInstall appears in exactly ONE hand-written pagecontent/docs/api/metadata-api.mdx:108, inside a request-body example: { manifest: {…}, settings?: {…}, enableOnInstall?: true, overwrite?: false }.

⇒ it shows the key optional and written explicitly as true, and ⛔ states no default. Under the new semantics an explicit true still means exactly 「enable this row」 — unchanged in every respect by this diff. Nothing to edit.

The other nine pages

Every one is matched through a ROUTE literal (/api/v1/packages, /packages/:id/enable, /packages/:id/disable) appearing in the docblock comments this PR rewrote. ⛔ No route moves here, and no page states the absence semantics. The anchors are real; what they anchor to did not change.

⚠️ Stated, not papered over: this receipt measures the pages, ⛔ not every reader of the published JSON Schema. The consumer this change is FOR lives outside this repo — a client that validates through the schema and materialised enableOnInstall: true from the declared default — and no docs sweep can reach it. That reader is addressed in the changeset, which is what ships to npm.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Contract review

b3ec2123c4afa44620851c161d91bffa749d1d67

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: b3ec2123c4afa44620851c161d91bffa749d1d67

Reading time: 2026-09-22T09:33Z — checks, mergeability and carriers read at this instant. Rendered by the isolated at-tier review subagent spawned by the domain:spec execution seat 5 (os-justin, seat post #19357), for that seat to adopt verbatim. ⛔ Re-derived from the diff and origin/main at 7e1b048a1 (merge-base 408ca2e36), ⛔ not from the dev's report and ⛔ not from the seat's earlier in-seat record 5773652504, which was rendered while the constant read a value that is not the served tier and is therefore not the review of record. Verified first: CONTRACT_REVIEW_TIER at scripts/pm/dispatch-gates.mjs:12282 on origin/main names the tier PR #19684 (merged, card #19680) restored under the maintainer's ruling recorded as comment 5771798588 (a temporary lack of authorization on one session is never a retirement).

① Derived judgments

Limb. ⛔ Not 收紧, ⛔ not 放宽. On all three declarations the only change is z.boolean().default(true)z.boolean().optional(); for a boolean base the two accept the identical input set — absent, true, false in; a string or null refused — and the one-authority matrix pins every cell on all three. I also read the exported input types: all three are z.input (package-api.zod.ts:425, package-registry.zod.ts:354, marketplace.zod.ts:575), on which a defaulted key is already optional, so the INPUT type does not move either. What moves is two things at once, and both hit clause ②:

  • declared ≠ enforced, closed. Both doors read the raw body with === true / === false and make no lifecycle call on absence: the HTTP door at packages/runtime/src/domains/packages.ts:1095 and the in-process primitive at packages/metadata-protocol/src/protocol.ts (the requestedEnabled arms under the [#19277] block). The published declaration claimed a default neither applies. My own instrument for "nothing parses through these schemas on the serving path": zero .parse( / .safeParse( of PackageInstallRequestSchema, InstallPackageRequestSchema, MarketplaceInstallRequestSchema or PackageInstallBodySchema outside packages/spec, non-test — the only hits are two comments in packages.ts (:931, :999); and zero consumers of the three *Parsed types anywhere but the surface snapshots.
  • 公开导出面 moved. The published parse OUTPUT: the three *Parsed (z.infer) types' enableOnInstall goes from a required boolean to optional — the one compile-visible effect, for an out-of-repo consumer that reads the parsed key as a boolean; the changeset's FROM → TO table names it in its fourth row. The JSON-Schema default keyword leaves four published defs — api/PackageInstallRequest, api/InstallPackageRequest, kernel/InstallPackageRequest, marketplace/MarketplaceInstallRequest — and the authorable-defaults ledger loses four rows (api −2, kernel −1, marketplace −1; the seat's record said three — the diff says four), matched by four DEFAULT_CHANGES_BY_MAJOR rows under major 17 (spec is 17.4.0), each carrying the consumer prescription. authorable-surface keeps all four keys (verified: 4 rows on main, 4 at head) — the keys stay authorable, correctly.

So the seat's two readings hold, tested independently: the accept set does not move, and the clause is hit by the published parse output plus the default keyword leaving four defs. The declaration Clause-②: yes with no arm is right — ⛔ not (narrowing) (no input newly refused, no symbol removed), ⛔ not (widening) (no input newly accepted).

Consumer observability. In-repo: none — no serving path parses through the schema, so no deployed behaviour moves. Out-of-repo: only a caller that validates through the published schema and sends the PARSED object; for that caller absence used to materialise as an explicit true, which the ruled door reads as force-enable. The change removes a silent re-enable, and the prescription (write enableOnInstall: true to keep an unconditional enable) is in the changeset and all four ledger rows. cloud and third-party consumers stay unmeasured, as the dev declared with its radius — the changeset is the channel that reaches them, and no re-measure is owed here.

Pin discipline. PR #19130's consistency pin FLIPPED with FLIP_TRIGGER registered and quoted into the cell name and both flipped titles; only the 缺省 cell moves; the true and false arms are re-read after the flip on all three declarations, and the 'false'-string and null cells still refuse. The header records that the trigger was NOT pre-registered when the pin landed — the honest reading. The lit control in package-api.test.ts (toBe(true)toBeUndefined()) flipped with its reason.

Direction. Ruled, not chosen: batch #210 item 4 letter A (5770455384) names optional() with the semantics on the field and explicitly refuses the runtime-back-to-default-on direction. The diff is exactly that ruling; the runtime is untouched (no packages/runtime/** file in the 16-file diff — confirmed by my own three-dot diff against 408ca2e36).

⚠️ Joint with origin/main — the PR is dirty, and the resolution CAN change the verdict if done wrong. My driverless three-way preview per file against 7e1b048a1: exactly one hand-written file conflicts — packages/spec/src/kernel/package-registry.zod.ts, two hunks — plus the two generated pages references/api/protocol.mdx and references/kernel/package-registry.mdx (regenerated, never hand-merged). package-api.zod.ts, package-api.test.ts and package-api.mdx auto-merge (#19373's hunks sit at :9/:77/:97, this PR's at :369+; the merged authority line still reads .optional()). The two hunks: (1) the docblock heading — this PR keeps 「This contract's own implementation does not read the key」, main (PR #19691) says 「HONOURS the key — on the registry row」; (2) the declaration — this PR .optional() + a .describe() ending 「this protocol primitive does not read it」, main .default(true) + 「honours it on the registry row: true enables, false disables, absent makes no lifecycle call」. #19691 measured the 「does not read it」 sentence FALSE: the primitive has honoured the key since 482d584121, which is already an ancestor of this PR's merge-base (compare API: behind_by 0) — so this PR carried an already-false sentence rather than introducing one, and that is not a defect of this diff. The resolution is where it becomes one. Conditions the merged head must meet: ① z.boolean().optional() survives; ② the .describe() takes #19691's truthful reading (honours it on the registry row) AND the three-state semantics, and 「does not read it」 is gone; ③ the docblock carries both main's 「HONOURS」 section and this PR's 「THREE STATES」 section, and main's sentence at :311 (「The .default(true) below never reaches that path」) is reworded — it names a default that no longer exists; ④ the two reference pages are regenerated from the merged describe (check:generated --fix), never text-merged; ⑤ the one-authority matrix is re-run. Because ② and ③ are hand-authored prose inside a published .describe(), the merged head is ⛔ NOT a pure regeneration: this record governs b3ec2123c alone, and the merged head owes a fresh, scoped record citing this one.

Non-blocking, declared so it is not discovered later. Two comment sentences elsewhere go stale once this lands — packages/runtime/src/domains/packages.ts:1090 and packages/metadata-protocol/src/protocol.ts:22709 both say "the declaration's own .default(true) never reaches here". The invariant they describe (nothing parses through the schema) still holds; the spelling names a default that will not exist. Comment-only follow-up, ⛔ not a patch round and ⛔ not a card.

② Semver level

'@objectstack/spec': minor — correct, and the ruling's own grade. files[] ships src/**/*.zod.ts, dist and json-schema, all of which move, so the diff publishes. Clause-②: yes takes at least minor; check-changeset-no-major.mjs refuses patch on a clause-② diff. Not breaking under the declaration signals the ADR-0087 gate reads (no major, no BREAKING banner, no !, no (narrowing) arm), and none is owed: no input is newly refused and no symbol is removed; the consumer prescription rides the FROM → TO table plus the four default-change rows, which is this class's own ledger.

③ Boundary flags

  • STALE TREE at derivation time (3 behind, now 6 behind and dirty): accepted for THIS head; the queue rebuilds on the merged base — but see the resolution conditions above, which are the real re-check.
  • Five exit 3 rows on the first pass, deleted and re-run after pnpm build: correct handling; the reconciliation stands on genuine readings.
  • The dev's open question (the carrier unreadable, claim-branch-unparsed) was repaired by the seat: claim re-posted as 5773156479 with a Branch: line naming this branch.
  • Out-of-repo validators (cloud, third parties): unmeasured and declared as such — stays unmeasured; no escalation.
  • CI on this head: 33 success · 2 skipped · 0 incomplete, latest-per-name; all seven required contexts success. Not a governed surface.

Implemented-by: claude/issue-19273-enableoninstall-optional
Reviewed-by: session_01Sfe5YjBLwB9J3y8fvm2xq1 — the seat that adopts this verdict; rendered for it by its isolated at-tier review subagent, ⛔ not by the seat in-seat.

VERDICT: PASS — on head b3ec2123c as diffed against its merge-base. The conflict resolution moves the head and owes its own record under the five conditions above.


Generated by Claude Code

…ableoninstall-optional

# Conflicts:
#	packages/spec/src/kernel/package-registry.zod.ts
Discharges the merge commit's os-regen deferral. The three pages both
sides moved were restored to main's side by the regen-merge script's
step 2 — the driver had merged them with exit 0 while silently dropping
one side — and are re-derived here from a dist built on the merged
source, never text-merged.

Claude-Session: https://claude.ai/code/session_01Sfe5YjBLwB9J3y8fvm2xq1
Co-authored-by: Claude <noreply@anthropic.com>
@os-justin
os-justin added this pull request to the merge queue Sep 22, 2026
@github-merge-queue
github-merge-queue Bot removed this pull request from the merge queue due to failed status checks Sep 22, 2026

Copy link
Copy Markdown
Collaborator Author

Contract review

6584b9dcd72706e4ca9af7291424297fbc50461a

Served-tier: CONTRACT_REVIEW_TIER — the constant's NAME; verified first on origin/main at scripts/pm/dispatch-gates.mjs:12282, the value PR #19684 restored under ruling 5771798588, and it is the tier this subagent is served at.
Head-sha: 6584b9dcd72706e4ca9af7291424297fbc50461a

Reading time: 2026-09-22T10:40Z — checks, mergeability and carriers read at this instant. Rendered by the isolated at-tier review subagent spawned by the domain:spec execution seat 5 (os-justin, session_01Sfe5YjBLwB9J3y8fvm2xq1, seat post #19357), for that seat to adopt verbatim.

This record SUPERSEDES 5774225964 for the current head. That record governed b3ec2123c alone and said the merged head owes a fresh, scoped record under five conditions; this is that record. ⛔ Nothing below is inherited from it, from the seat's reading of the patch, or from the dev's account: every condition was re-measured on the committed trees, and what is carried rather than re-derived is named as carried in ③.

① Derived judgments

What moved between the two heads, measured. 6584b9dcd = a regeneration commit on top of 3b524cc36 = merge of 28f927790 (then origin/main, with #19373 eea7ccc3e and #19691 7e1b048a1 in it) into b3ec2123c. Of the PR's 16 files, git diff b3ec2123c 6584b9dcd touches exactly six: kernel/package-registry.zod.ts (the one hand resolution), the three regenerated pages, and api/package-api.zod.ts + api/package-api.test.ts — whose hunks are #19373's (:9, :77, :97) and carry no enableOnInstall line. The changeset, default-changes.ts, the three authorable-defaults rows, the marketplace declaration and every test are byte-identical to what 5774225964 reviewed. ⇒ the PR's own delta survived the merge on every file it did not deliberately resolve or regenerate.

Condition ① — .optional() survives. kernel/package-registry.zod.ts:356 reads z.boolean().optional(); the authority (api/package-api.zod.ts:422) and the marketplace copy (marketplace.zod.ts:537) unchanged since the prior head. check:authorable-surface exit 0 on this tree (my own run): the four enableOnInstall = true default rows are gone from authorable-defaults/{api,kernel,marketplace}.json and the four DEFAULT_CHANGES_BY_MAJOR rows authorise them. ✅

Condition ② — the describe. The merged .describe() reads 「…this protocol primitive honours it on the registry row: true enables, false disables, and ABSENT keeps the row's current lifecycle state (a fresh install lands enabled)」 — #19691's truthful 「honours it on the registry row」 AND the three states; 「does not read it」 is gone from the describe and from the docblock. ✅ The one phrase new to THIS def's describe, 「a fresh install lands enabled」, I tested against the in-process path rather than the authority's wording: objectql/src/registry.ts:4273-4287 gives a row with no existing entry enabled: !seeded, and an existing row keeps its own enabled/status; metadata-protocol/src/protocol.ts:22725-22731 makes no lifecycle call on absence. A boot-seeded disable is state KEPT (the seed is hydrated from the durable disable file), so 「fresh」 = no row and no seed, which is the ruled wording and true on this seam. ✅

Condition ③ — the docblock. Both sections stand: main's 「HONOURS the key — on the registry row」 (with its three-arm list, the === true/=== false sentence and the durable-record scope paragraph) and this PR's 「THREE STATES」. main's sentence 「The .default(true) below never reaches that path」 is reworded to 「The declaration below resolves nothing on an absent key — it is optional(), and ⛔ never .default(true) …」, and the stale 「enableOnInstall ?? true」 claim in the RESTATEMENT paragraph is gone with it. ✅

Condition ④ — regenerated, ⛔ never text-merged. This is the one only the generator can answer, so it was answered by the generator, four ways.

Condition ⑤ — the one-authority matrix re-run. vitest run --project local src/api/package-install-one-authority.test.ts src/kernel/package-registry.test.ts src/api/package-api.test.ts src/marketplace/marketplace.test.ts on this tree: 4 files, 159 passed, exit 0. The 缺省 cell reads undefined on all three declarations with the key ABSENT from the parse output; the true/false arms are re-read after the flip; the string and null cells still refuse. CI's Test Core shards are all success on this head. ✅

Limb, re-affirmed on this head. ⛔ Not 收紧, ⛔ not 放宽: z.boolean().default(true)z.boolean().optional() on a boolean base accepts the identical input set. What moves is the published parse OUTPUT (three *Parsed types' key goes optional) and the default keyword leaving four published defs (api/PackageInstallRequest, kernel/InstallPackageRequest, api/InstallPackageRequest, marketplace/MarketplaceInstallRequest) — the clause-② hit 5774225964 named, unchanged by the resolution. The declaration Clause-②: yes with no arm stands.

② Semver level

'@objectstack/spec': minor — the changeset file is byte-identical between the two heads, so 5774225964's reading is carried, not re-derived: files[] ships src/**/*.zod.ts, dist and json-schema, all of which move; Clause-②: yes takes at least minor; no major, no BREAKING banner, no (narrowing) arm, and none owed — nothing newly refused, nothing removed; the consumer prescription rides the FROM → TO table and the four default-change rows. Check Changeset is success on this head.

③ Boundary flags

⚠️ One sentence in the merged result contradicts #19691's landed truth — measured, ruled, ⛔ not a patch round. packages/spec/src/api/package-api.zod.ts:405, inside the authority's map to the other two declarations: 「its own implementation does not read it, and this door does not forward it down that seam」. The first clause is FALSE since 482d584121 (the primitive reads request.enableOnInstall, protocol.ts:22725); the second is still TRUE (packages.ts:1045 calls installPackage({ manifest, settings }) and flips the row itself at :1095-1100). It is published text (src/**/*.zod.ts ships), it pre-exists this PR (origin/main:380, left outside #19691's fenced surface), and this PR's hunk rewrites the line above it (「same default」 → 「same optionality」) while leaving the clause standing. I read the exemption the dev cited and the claim board rather than the dev's ground:

Joint with origin/main at reading time. main moved two commits past the merge-base 28f927790: #19697 (docs) and #19698, which touches content/docs/references/api/package-api.mdx on the Seed.locale row — a row this PR does not touch. GitHub reads mergeable_state: clean; a driverless merge-tree names only that generated page, for regeneration in the queue leg, where check:docs re-measures on the rebuilt merge. Declared; ⛔ not a condition on this record.

Carried from 5774225964, not re-derived: the consumer-observability reading (no serving path parses through these schemas; out-of-repo validators unmeasured and declared so); the direction (ruled, batch #210 item 4 letter A); the runtime untouched (re-confirmed only by the file list: no packages/runtime/** in the 16); and the two non-blocking comment sentences at packages.ts:1090 and protocol.ts:22709, which now stand as card #19711 — still comment-only, still not a patch round.

Still standing, outside this PR: .changeset/18605-enable-on-install-one-authority.md says the kernel description 「now records that this layer does not read it」; #19691 and #19710 both hand it to the review seat as a route-0 deliberate correction. Not this diff's.

CI on this head: 35 check runs — 33 success, 2 skipped (Console Pin Gate, Packed-tarball smoke opt-in), 0 incomplete, latest-per-name. Not a governed surface: none of the 16 paths is under the register. needs:contract-review hangs on both carriers at this reading; the --pair read and the strip are the seat's.

Implemented-by: claude/issue-19273-enableoninstall-optional
Reviewed-by: session_01Sfe5YjBLwB9J3y8fvm2xq1 — the seat that adopts this verdict (domain:spec execution seat 5, os-justin, seat post #19357); rendered for it by its isolated at-tier review subagent, ⛔ not by the seat in-seat.

VERDICT: PASS — on head 6584b9dcd as diffed against its merge-base, all five resolution conditions met, condition ④ by the generator's own bytes. This record supersedes 5774225964 for this head.


Generated by Claude Code

Copy link
Copy Markdown
Collaborator Author

Carrier stripped on the at-tier PASS, and the record it cites. Enqueueing. 2026-09-22T10:41Z

needs:contract-review removed from both carriers — card #19273 and this PR — in the stroke before this comment, per 「PASS ⇒ 同席剥标并引记录、ready、auto-merge」, and ⭐ before the enqueue, which is where that step belongs.

The record: the at-tier contract review at 5775054706, against head 6584b9dcd72706e4ca9af7291424297fbc50461a, Served-tier: CONTRACT_REVIEW_TIER, verdict PASS. It supersedes 5774225964, which governed b3ec2123c only. ⛔ This seat's own earlier in-seat record (5773652504) was withdrawn at 5774003190 and is ⛔ not what clears this PR.

Read back by this seat before stripping: heading present · head sha alone in a code span · Reviewed-by: present · Served-tier: naming the CONSTANT · no model identifier anywhere in the body · VERDICT: PASS.

One question this seat handed the reviewer rather than deciding — and it was right not to decide it

packages/spec/src/api/package-api.zod.ts:405 still carries 「its own implementation does not read it」, which 482d584121 made false and #19691 fixed one file over. This seat measured that and asked whether re-publishing the paragraph while leaving the clause makes this a patch round.

Ruled: no — and ⛔ do not file it either. The reviewer found what this seat had not: card #19339 (pm:dispatched, another seat) names this file and this phrase in its executable criterion, and draft PR #19710 (Fixes #19339) rewrites exactly this clause. ⇒ the in-place exemption fails on a LIVE CLAIM — ⛔ not on the ground the dev gave (「#19373 holds the file」, which was stale: #19373 merged at eea7ccc3e before this PR opened). A fix here would double-write another seat's in-flight lines, and filing it would duplicate their card.

⚠️ Sequencing, carried forward so nobody meets it cold: #19710 is based on 「Same type, same default, same meaning」 and will conflict on this paragraph once this PR lands. The resolution keeps this PR's 「same optionality」 plus #19710's honours clause. ⇒ posted on #19339 as well.


Generated by Claude Code

@os-justin
os-justin added this pull request to the merge queue Sep 22, 2026
Merged via the queue into main with commit fb59fb5 Sep 22, 2026
39 checks passed
@os-justin
os-justin deleted the claude/issue-19273-enableoninstall-optional branch September 22, 2026 11:10
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

documentation Improvements or additions to documentation size/m tests tooling

Projects

None yet

2 participants