Skip to content

spec: one authority for enableOnInstall, and a read-out of its other two declarations - #19130

Merged
os-sam merged 3 commits into
mainfrom
claude/issue-18605-enable-on-install-one-authority
Sep 20, 2026
Merged

os-sam merged 3 commits into
mainfrom
claude/issue-18605-enable-on-install-one-authority

Conversation

@os-steve

Copy link
Copy Markdown
Collaborator

Fixes #18605

Clause-②: yes — carrier: the changeset .changeset/18605-enable-on-install-one-authority.md (@objectstack/spec, minor). Three published declarations' stated meaning moves; the accept set does not move at all.

Ruling bullet 1 was already discharged by PR #18752 — this PR did not skip it

Batch #153 item 5, letter 1 carries two bullets. The first one — the install door writes the registry row's enabled from enableOnInstall ?? true — landed with PR #18752 (card #18058), and this claimant re-derived that against the merged diff before planning, rather than inheriting the card's text:

  • packages/runtime/src/domains/packages.ts reads body.enableOnInstall === false off the WRAPPED body, flips the registry row through the same call PATCH /packages/:id/disable uses, and then persists the row the door returned to the durable state file.
  • packages/runtime/src/domains/packages-install-enable-on-install.test.ts pins it, header and all: false installs disabled in all three records, false also moves status, true installs enabled, an absent key defaults to true, the disable is durable across a restart, and a re-install with true clears it.
  • Both still exist on origin/main as of this branch's merge (asserted by quoted-exact-name git grep against origin/main).

So the card's own premise — "honoured by no handler" — is false for the authority's door on today's main, and this PR carries bullet 2 and only bullet 2.

Bullet 2, verbatim

One authority: the request contract in package-api.zod.ts. The claimant re-reads the two other declarations — a copy of the request key is folded to a reference; a declaration that means something else (a stored-row field, a marketplace listing attribute) stays and says so. ⛔ No silent unification of three published declarations.

The read-out, per declaration

All three read enableOnInstall: z.boolean().default(true) with byte-identical description text, so identical shape carried no information. What distinguishes them is the request each sits on and the door that serves it.

declaration what it is disposition
api/PackageInstallRequestSchema the HTTP wire contract of POST /api/v1/packages, the door that honours the key the one authority
kernel/InstallPackageRequestSchema the request type of the in-process protocol primitive ObjectStackProtocol.installPackage a COPY of the request key — referenced
marketplace/MarketplaceInstallRequestSchema the marketplace channel's install-from-listing request, served by the control plane means something else — stays, and says so

⚠️ Both of the ruling's parenthetical guesses were falsified by the re-read, and that is recorded rather than quietly worked around. The kernel declaration is not the stored-row field: the stored-row field is InstalledPackage.enabled, a different key in the same file. The marketplace declaration is not a listing attribute: MarketplaceListingSchema does not carry it; it sits on the install request beside listingId.

The authority — api/PackageInstallRequestSchema.enableOnInstall

It is the authority because it is the request contract of the door that honours the key. Its published description now says so, so a reader of the reference page can tell which of three rows is the one that acts: "honoured at POST /api/v1/packages: the installed row's enabled is written from this key". Its doc block carries the map to the other two, so nobody has to re-derive this reading a third time.

Re-read ① — kernel/InstallPackageRequestSchema.enableOnInstall is a COPY

Same type, same default, same meaning, restated one layer down on the in-process protocol primitive. Two measured facts decide it:

  • MetadataProtocol.installPackage reads request.manifest and request.settings and nothing else (packages/metadata-protocol/src/protocol.ts). The key reaches no code that acts on it there.
  • The authority's own door does not forward it down that seam: it calls installPackage({ manifest, settings }) and performs the enable/disable flip itself afterwards, because the durable half must follow the ROW that door returned rather than the request's intent. That is deliberate and documented at the call site.

It is therefore a copy, and per the ruling it must not be left unreferenced. The reference is documentary in the declaration and MECHANICAL in a pin, for a reason that was measured rather than assumed — see the next section. packages/spec/src/api/package-install-one-authority.test.ts parses the authority and the copy over one matrix (absent, false, true, a string, null) and reds on any cell where they disagree, so the copy can no longer drift from the authority silently.

Re-read ② — marketplace/MarketplaceInstallRequestSchema.enableOnInstall means something else

Same words, a different commitment, and the difference is the subject of the request it sits on:

  • Its subject is a marketplace LISTING (listingId, version, licenseKey, tenantId). The authority's subject is a MANIFEST. Neither body can be sent where the other is expected, which the pin asserts in both directions.
  • Its door is the control plane's POST /api/v1/marketplace/install; a runtime mounts /api/v1/marketplace/* only as a read-only proxy to the configured control plane (MarketplaceProxyPlugin). docs/design/marketplace-publishing.md §4.3 spells the flow out: the channel fetches the artefact and validates the licence and only THEN maps what it holds into a platform install. So this key is what a caller asks the marketplace to request on its behalf — one translation upstream of the door key.
  • It is a different party's contract on a different release cadence: the declaration was cloud/MarketplaceInstallRequest before it moved into this namespace (packages/spec/scripts/lib/renamed-defs.ts). One shared declaration would let a narrowing at the platform door silently narrow a control-plane contract that no PR in this repo can see.

So it stays, and its published description now says which of the two it is.

The prescription that is not executable as written, and the measurement

"A copy of the request key is folded to a reference" reads naturally as enableOnInstall: PackageInstallRequestSchema.shape.enableOnInstall at the copy's site. That spelling is not available in this direction, and it is not a style preference — it is measured.

The authority sits ABOVE both copies in the module graph: PackageInstallRequestSchema is built from ManifestSchema and InstalledPackageSchema (declared in kernel/package-registry.zod.ts) and from ArtifactReferenceSchema (declared in marketplace/marketplace.zod.ts). A reference from either copy up to the authority is therefore an import cycle, and it is not a cycle the lazySchema proxy absorbs: under OS_EAGER_SCHEMAS=1 — the mode gen:schema, gen:authorable-surface-base and check:authorable-surface run in — the factory bodies evaluate at module load and the cycle dies.

Measured on this branch, both directions, each against a control that passes on the unmodified tree:

leg command result
control · kernel eager load of kernel/package-registry.zod, unmodified exit 0, enableOnInstall defaults to true
treatment · kernel the same load with enableOnInstall: PackageInstallRequestSchema.shape.enableOnInstall exit 1, ReferenceError: Cannot access 'InstalledPackageSchema' before initialization, raised from api/package-api.zod.ts through lazySchema
control · marketplace eager load of marketplace/marketplace.zod, unmodified exit 0, enableOnInstall defaults to true
treatment · marketplace the same load with the same structural reference exit 1, ReferenceError: Cannot access 'ArtifactReferenceSchema' before initialization

Under the default lazy mode both treatments load fine, which is the dangerous half: the runtime would be green and the generator would die.

Both treatments were reverted and the revert proven by blob hash against HEAD (git hash-object equal, git diff HEAD empty) before anything else was written.

⇒ The only structural fold available would be to move the key's literal into a module BELOW both copies and have the authority import it. That was deliberately not taken unilaterally: it moves the declaration out of package-api.zod.ts, which is the file the ruling names as the one authority, so it changes the ruling's own terms. It is recorded as an open question below rather than performed.

What moved on the published surface

  • Three .describe() strings — the text content/docs/references/** renders, and the only half of a doc block an author reading the reference page ever sees.
  • Three doc blocks in the source.
  • The four generated reference pages that follow from those strings (five table rows; the authority appears twice because PackageInstallBody renders its wrapped branch).
  • One new test file.

What did not move: no key added, removed, renamed or retyped, no default changed. check:api-surface, check:api-surface-declarations, check:authorable-surface, check:export-origins and check:declaration-map are all green with no regeneration — the api-surface-declarations shards this card was flagged for do not move, because a .describe() change does not change a .d.ts type.

Verification

Run on the merged tree (git merge origin/main through scripts/pm/os-regen-merge.sh), exit codes captured before any pipe.

what result
pnpm --filter @objectstack/spec test 496 files / 14536 tests passed
pnpm --filter @objectstack/spec typecheck exit 0
pnpm --filter @objectstack/spec check:generated all 16 generated artifacts up to date
eslint . --no-inline-config --format json 6879 files reached by eslint's own config, 0 errors, 0 warnings — the union, not a narrowing
gate families derived by scripts/pm/dispatch-gates.mjs and run see below

Gate families run locally, all exit 0: check:nul-bytes, check-spec-docblock-symbol-anchors, check:duration-unit-keys, check:cross-package-test-inputs, check:test-source-alias, check-adr-0087-registration --base origin/main, check-changeset-no-major --base origin/main, check-empty-changeset --base origin/main, check:changeset-gate-self-tests, check:pm-widening-tells, check:exported-any, check:dual-source-exports, check:entry-nameability, check:variant-docs, check:empty-state, check:llms-txt, check:browser-reachable-entries, check:skill-examples, check-doc-frontmatter, check-docs-section-name, check-doc-route-spelling --advisory, docs-audit/check-affected-docs, check:doc-anchors, check:docs-single-h1.

check:skill-examples first exited 1 on a build prerequisite (@objectstack/client-react had no .d.ts), not on this diff; after pnpm --filter '@objectstack/client-react^...' build it exits 0 over 258 prose examples. The remaining families the derivation names are CI's farm and are not claimed here.

Reverse verification of the new pin — the fix was committed first, the mutation landed through scripts/ablation-replace.mjs (anchor hit x1, blob 64a17a8bc364 to f2cacf8c304b), and the restore was proven against HEAD rather than against an exit code:

  • kernel's enableOnInstall default mutated true to false
  • predicted direction: the parity cells go red, the rest stay green
  • observed: Tests 2 failed | 10 passed — the two that fail are the absent-key parity cell and the same-default assertion
  • restored: blob equals HEAD (64a17a8bc364), git diff HEAD empty

Acceptance notes

Observed while reading, deliberately not fixed here and not filed:

  • packages/spec/src/contracts/package-service.ts declares a FOURTH enableOnInstall, on the plain TS interface InstallPackageInput for IPackageService. It is outside the ruling's three schemas (not Zod, not on the authorable surface), and IPackageService has no implementation in this repo — the only place the key is read is an inline fake inside package-service.test.ts (enabled: input.enableOnInstall !== false). Noted, not filed: the interface is a contract with no consumer here, so nobody is currently misled by it. Carrier if it ever needs one: whoever implements IPackageService.
  • content/docs/api/metadata-api.mdx documents the install body inline rather than from the contract, so it will not follow a future change to it. Noted, not filed: a hand-written page drifting from a schema is not one of the three filing classes, and no PR or person is presently heading for that file. Carrier: none.

Open question recorded for the seat, not answered here

The structural fold is available in exactly one shape: move the key's single literal into a module below both copies and have the request contract import it. That would give literally one Zod declaration of the key instead of a pin holding two in step — but it takes the declaration out of package-api.zod.ts, which the ruling names as the one authority. Whether the ruling prefers one literal in a lower module or the authority's file keeping its own literal with a mechanical pin is a question about the ruling's terms, so it is recorded rather than decided by the claimant. The current shape is the one that changes nothing the ruling said.

A second, separate question the re-read surfaced: the copy's own door (ObjectStackProtocol.installPackage) still does not honour the key. Making it honour the key would be new runtime behaviour at a door the ruling did not name — it is safe (every present caller omits the key, so nothing changes today), but it is not this card's to authorise.


Generated by Claude Code

…ne authority

`enableOnInstall` is declared in three published schemas. The install door's
request contract (`api/package-api.zod.ts`) is the one authority: it is the
contract of the door that honours the key. The other two are re-read here.

- `kernel/InstallPackageRequestSchema` is a COPY of the request key, restated
  on the in-process protocol primitive. It is held to the authority by a
  parity pin rather than by a structural reference: the authority sits above
  `kernel/` in the module graph, so `…Schema.shape.enableOnInstall` spelled
  there is an import cycle that dies under `OS_EAGER_SCHEMAS=1`.
- `marketplace/MarketplaceInstallRequestSchema` means something else and
  stays: its subject is a marketplace listing, its door is the control
  plane's, and its key is one translation upstream of the door key.

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

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/spec, touching 9 documentable anchor(s).

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))
  • 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))
  • 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
  • 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 9bb059dbfe1fdf6443cb9f76266d2a0ebff71781packageMentionDocs.

Which tree this was computed on

This run read content/docs from 20c6f3722cd63d92db0efd93c42c0b3937a7e2f6 — the merge of head 5234daa021ad86dfd5502406ad1926adb9c82ed0 into base 9bb059dbfe1fdf6443cb9f76266d2a0ebff71781, 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 20c6f3722cd63d92db0efd93c42c0b3937a7e2f6 && git checkout 20c6f3722cd63d92db0efd93c42c0b3937a7e2f6
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 9bb059dbfe1fdf6443cb9f76266d2a0ebff71781 5234daa021ad86dfd5502406ad1926adb9c82ed0 && git checkout -B drift-repro 9bb059dbfe1fdf6443cb9f76266d2a0ebff71781 && git merge --no-ff 5234daa021ad86dfd5502406ad1926adb9c82ed0

node scripts/docs-audit/affected-docs.mjs --json 9bb059dbfe1fdf6443cb9f76266d2a0ebff71781

⚠️ 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 9bb059dbfe1fdf6443cb9f76266d2a0ebff71781 → pass the list as
args.docs, on the commit named under Which tree this was computed on.

@github-actions github-actions Bot added documentation Improvements or additions to documentation tests tooling labels Sep 18, 2026

os-sam commented Sep 20, 2026

Copy link
Copy Markdown
Collaborator

Contract review

Served-tier: 27/27 CONTRACT_REVIEW_TIER
Head-sha: 5234daa021ad86dfd5502406ad1926adb9c82ed0

Isolated contract review of PR #19130 (card #18605), rendered by an isolated review subagent dispatched by seat domain:spec#3 (adopting session session_01HnRAeVTLJevtQ5iCPX6JSm), which measures below tier and did not self-review. Readings taken 2026-09-20T10:12Z–10:35Z. Tree readings are against PR head 5234daa021 (fetched into an owned ref, checked out in a dedicated worktree with its own install and build) and origin/main tip f93beea0a6 (merge-base a675ad4ef0). The stamp on the Served-tier: line counts harness per-request model stamps on this isolated transcript, the instrument platform-readings.md names: 27 harness requests up to and including the one that posts this record, every one at tier; the seat verifies against the transcript. A line-level count is not a reading this lane's references define, so the request count is the number stated and the two are not conflated.

Ruling of record, quoted as what it is

① Derived judgments — from the diff, per declaration

declaration (line at head) what the diff moves accept set describe text checked against the tree
api/PackageInstallRequestSchema.enableOnInstall (package-api.zod.ts:320) .describe() + doc block unchanged, z.boolean().default(true) true. POST /api/v1/packages reads body?.enableOnInstall === false off the wrapped body (packages/runtime/src/domains/packages.ts:796), flips through registry.disablePackage, the call PATCH /packages/:id/disable uses (:868+), and persists via setPackageDisabled (:845). Same lines on origin/main f93beea0a6. Pin packages-install-enable-on-install.test.ts present on both.
kernel/InstallPackageRequestSchema.enableOnInstall (package-registry.zod.ts:324) .describe() + doc block unchanged true. MetadataProtocol.installPackage (packages/metadata-protocol/src/protocol.ts:22313, 69-line body) reads request.manifest and request.settings only; 0 enableOnInstall hits in that file and under packages/objectql/src (lit control: request.manifest 1 hit; packages.ts 6 hits). The HTTP door calls installPackage({ manifest, settings: body.settings }) (packages.ts:769). "this protocol primitive does not read it" holds.
marketplace/MarketplaceInstallRequestSchema.enableOnInstall (marketplace.zod.ts:524) .describe() + doc block unchanged true. Keyed by listingId, carries no manifest; docs/design/marketplace-publishing.md §4.2 item 5 names MarketplaceProxyPlugin forwarding read-only requests to the control plane (wired at packages/cli/src/commands/serve.ts:3716), §4.3 maps to InstallPackageRequest only after licence validation; packages/spec/scripts/lib/renamed-defs.ts:195 records cloud/MarketplaceInstallRequestmarketplace/MarketplaceInstallRequest.
  • No key added, removed, renamed or retyped; no default moved; no new export (the test is not an entry). packages/spec/api-surface-declarations/{api,kernel,marketplace}.txt still read enableOnInstall as z.ZodDefault wrapping z.ZodBoolean and are untouched. The one other carrier of the old describe text, api-surface-declarations/contracts.txt:7310, is the plain-interface InstallPackageInput (contracts/package-service.ts:45) — the fourth declaration the PR's acceptance notes name and deliberately leave; not a stale artefact.
  • node scripts/pm/check-widening-tells.mjs --declaration no --diff (the PR diff): true exit 0 — "3 judged against a declared surface (no widening tell), 6 NOT MEASURED" (changeset, four generated pages, the test). Under --declaration yes also 0.
  • The doc blocks' module-graph claim, re-measured in my worktree (each restored, blob == HEAD): control OS_EAGER_SCHEMAS=1 load of kernel/package-registry.zod.ts unmodified → loaded, default true (exit 0); treatment spelling the copy as PackageInstallRequestSchema.shape.enableOnInstall with the import → ReferenceError: Cannot access 'InstalledPackageSchema' before initialization (exit 1); the same treatment in default lazy mode → loads (exit 0). The block's statement, "dangerous half" included, is true of what shipped.
  • Generated pages: packages/spec built at head, check:docs true exit 0 ("224 generated files in sync"), then gen:docs re-run → git status --porcelain 0: the four content/docs/references/** pages are byte-identical to the generator's own output, not hand-edited. check:generated true exit 0, "All 16 generated artifacts are up to date" (10:23Z).
  • Merged-tree probe (PR head + origin/main f93beea0a6, git merge --no-commit --no-ff in my worktree, 10:30Z): 0 conflicts. The local os-regen merge driver deferred content/docs/references/api/protocol.mdx and kept the PR side only — main's two kanban rows (:1674, :1759) dropped, which is the driver's documented behaviour (AGENTS.md Multi-agent §11), not a PR defect — so check:docs on that local merge read exit 1 naming exactly that file; gen:docs then produced a 22-line diff restoring main's two kanban rows and touched none of the PR's three describe strings (0 hits in the regen diff). Control: origin/main f93beea0a6 alone, built, check:docs exit 0, regen porcelain 0. GitHub runs no custom driver and the two hunks are disjoint (mergeable_state: clean), so the queue's rebuilt generation carries both rows; nothing is owed by this PR. Worktree restored to head after each probe (porcelain 0).

The pin (packages/spec/src/api/package-install-one-authority.test.ts), lit cell by cell

Baseline at head: 12/12 pass (exit 0, 10:18Z). Each mutation applied in my worktree, run, restored with git checkout HEAD -- and proven by git hash-object equal to the HEAD: blob, porcelain 0 after each:

mutation prediction observed
kernel .default(true).default(false) absent cell + same-default red 2 failed / 10 passed: agrees with the authority — absent, declares the key with the same type and default
marketplace .default(true).default(false) its own default cell red 1 failed / 11 passed: declares enableOnInstall in its own right, defaulting to true
kernel z.boolean()z.union([z.boolean(), z.string()]) string cell red 1 failed: agrees with the authority — 'false' — a string
kernel z.boolean()z.boolean().nullable() null cell red 1 failed: agrees with the authority — null
kernel .default(true).optional() absent cell + same-default red 2 failed, the same two as row 1
authority .default(true).optional() — the #19273 preview 3 failed / 9 passed: the authority's defaults to true, the copy's absent cell, the same-default assertion

Coverage as measured: the five cells (absent / false / true / string / null) are held pairwise between the authority and the kernel copy, and absent, string and null each go red on drift alone; the false and true cells stayed green under every mutation above because none changed how a boolean is answered (a z.literal(true)-style narrowing would light them; not run). The marketplace declaration is NOT in the five-cell matrix: the pin holds its difference (listing-keyed, refuses the door's body in both directions) and its own absent → true cell. That is the ruling's reading of it ("means something else and stays"), so five-cell parity there would assert a sameness the ruling denies — recorded as the shape, not as a defect.

Forward reference: the pre-registered flip on #19273 exists in prose only (the ruling comment, the claim, the bodies of #19273 and #19277). Neither the test file nor any doc block names #19273; the last row above is what #19273's author will hit — three reds, the absent cell named in two of them. Non-blocking under letter A ("stands as is"); recorded so #19273's dispatch can cite it.

② Semver level

.changeset/18605-enable-on-install-one-authority.md: @objectstack/spec minor, body carries Clause-②: yes, no direction arm. check-changeset-no-major.mjs --base refs/review/main-19130 --event event-19130.json (the PR payload): true exit 0 — "LEVEL AXIS: this PR declares clause-② yes, and no package whose packages/**/src/** it moves is graded patch"; check-empty-changeset.mjs exit 0 (1 declaring changeset added, none from the merge base modified); check-adr-0087-registration.mjs exit 0 (no declared-breaking changeset). minor is consistent with yes (「yes 取至少 minor」) and with a published-description change on a released package (@objectstack/spec 17.4.0). No model identifier lands anywhere in the diff (0 hits for the id shape; lit control 43 hits for enableOnInstall).

On the Clause-②: yes itself (claim 5749153758), measured rather than inherited: the diff widens NO accept set and expands NO public surface — three .describe() strings, three doc blocks, four generated pages, one test, one changeset; the widening-tells detector fires nothing under no; every api-surface shard is byte-unchanged. Mechanically, the answer to the criterion (「本卡放宽接受集或扩大公开面吗」) is no. The yes is an over-declaration on the conservative arm: it is what the ruling's execution clause directs (「re-Claim:s with Clause-②: yes inline」), it is defensible under contract-review.md's content limb (the describe strings make falsifiable contract-semantics claims, which is exactly what this review verified), and its only effect is to route the PR through this review. Auditable, recorded here as what it is, and not a reason to fail the PR.

③ Boundary flags

CI on head 5234daa021 (read 2026-09-20T10:13Z, conclusion per check name)

Lint & Repo Gates success · TypeScript Type Check success · Test Core success (shards 1–6 success) · Dogfood Regression Gate success (1–3 success) · Build Core success · Temporal Conformance (live PG + MySQL) success · Governed Surface Queue Guard success · Type Check · source gates / · consumer gates / · debt ledger / · workspace success · Check Changeset success · Build Docs success · Spec property liveness success · Dogfood Verify CLI success · Check Documentation Links success · Flag docs affected by code changes success · No other open PR may claim the same issue success · No other open PR may claim the same single-writer path success · Part-of PR must not also close its card success · The card this PR closes must claim this branch success · filter success · Auto Label and Check PR Size success (plus one skipped re-run each) · Console Pin Gate skipped · Packed-tarball smoke (opt-in) skipped. 0 reviews on the PR; auto_merge null; draft; mergeable_state: clean.

Carrier check: PM_SWEEP_REPO=objectstack-ai/objectstack node scripts/pm/check-clause2-carriers.mjs --pair 19130 — true exit 0 (10:13Z; run.script.blob a76e4ce0c1ce), governing claim 5749153758 with Clause-②: yes, needs:contract-review on both carriers, the earlier os-steve claim listed RETRACTED by its own Release: (5736832755).

Readings not taken: the pre-claim exit-4 (C2) state no longer exists on the board and could not be re-measured; pnpm typecheck for packages/spec was not re-run locally (CI's TypeScript Type Check and the four Type Check · jobs are the reading); a z.literal(true)-style narrowing to light the true / false cells individually was not run.

Implemented-by: claude/issue-18605-enable-on-install-one-authority
Reviewed-by: session_01HnRAeVTLJevtQ5iCPX6JSm

VERDICT: PASS


Generated by Claude Code

@os-sam
os-sam marked this pull request as ready for review September 20, 2026 10:40
@os-sam
os-sam added this pull request to the merge queue Sep 20, 2026
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

Development

Successfully merging this pull request may close these issues.

[finding] enableOnInstall is declared in three schemas and honoured by no handler — an author sets it and the runtime silently ignores it

3 participants