Skip to content

fix(metadata-core): the artifact forward conversion walks every packages[].manifest body of an option-B artifact (ADR-0130 D4) - #23045

Merged
objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/issue-23027-package-body-conversion
Oct 12, 2026
Merged

objectstack-fleet[bot] merged 9 commits into
mainfrom
claude/issue-23027-package-body-conversion

Conversation

@objectstack-fleet

@objectstack-fleet objectstack-fleet Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #23027
Clause-②: no

What this changes

Two halves, one policy. Both use the one ADR-0087 conversion function, under one window.

1. @objectstack/metadata-core, the conversion's reach. applyArtifactForwardConversions (artifact-forward-conversion.ts) ran the conversion over the definition's own collections only. A multi-package artifact (ADR-0130 D4, option B) carries its collections in packages[i].manifest, and the strict parse gates each body. Now ONE walk runs over every root: the definition first, then each body. It is the same applyConversions call, with the same registry, the same includeRetired and the same excludeConversionIds. There is no second walk and no entry list of its own. Copy-on-write holds at both levels. A malformed packages or entry comes back as given: this never gates, and resolveArtifactPackageOrder stays the refusal. A body's notices are located from the root: path, and the one place message quotes it, read packages[1].manifest.dashboards[0].refreshIntervalSeconds.

2. @objectstack/runtime, converting once at load. The runtime read the bundle raw before any conversion. On os dev -a and os start the first reader to refuse was createStandaloneStack, through resolveArtifactCollections and resolveArtifactPackageOrder. loadArtifactBundle now calls convertArtifactAtLoad once on the loaded bundle, before mergeRuntimeModule and before any reader. Measured, every boot-path reader goes through it: createStandaloneStack's surfaced stack, AppPlugin's collections getter, AppPlugin.init's packages[] branch (and from there the manifest service), mergeRuntimeModule and os verify's loader. One reader reads the FILE by another path, readConfigDeclaredDefault in resolve-project-database.ts, and it now calls the same function on what it read. That is one function at each of the boot's two disk reads; no reader converts a bundle it was handed (option C, refused). MetadataPlugin's own conversion of the same file stays and is idempotent; the idempotence is pinned once. The only app-plugin.ts change is the one comment sentence that said package bodies were what "no conversion reaches".

convertArtifactAtLoad is a module export, not a package export. packages/runtime/src/index.ts and packages/metadata-core/src/index.ts are unchanged, and dist/index.d.ts carries no such name, so no exported member changes (Clause-②: no, re-checked on the real diff).

Which window governs a body: the artifact's (measured; accepted by the seat)

Measured on os build of examples/app-multi-package: the artifact's own manifest is its LAST package's, and each body carries its own engines.protocol. The window is decided once from the artifact's own engines.protocol, and a body's own range is not read. Why:

  • One os build serializes every body (ADR-0130 D1).
  • The artifact-boot handshake judges the artifact's own manifest.
  • A per-body window would split the two copies of one definition that an additive artifact carries.

Both directions are pinned.

The public door, before and after

BASE 0ede0f8ab3 this PR
os dev -a optionb.json --fresh (two-package os build output, refreshInterval: 300 in a body) exits at boot, health 000: "packages[1] is not a package entry (ADR-0130 D4): manifest.dashboards.0.refreshInterval…" health 200; GET /api/v1/meta/dashboard/crm_overview and the list serve refreshIntervalSeconds: 300, valid: true, _packageId: com.example.multi.core
os start -a optionb-multi.json (both bodies carry the key, 300 and 120) not measured on BASE; it composes through the same createStandaloneStack reader health 200; both are served converted, each under its own package
boot log (no boot) ONE line per conversion: "converted 2 site(s) forward via ADR-0087 conversion 'dashboard-refresh-interval-to-refresh-interval-seconds' (first at packages[0].manifest.dashboards[0].refreshIntervalSeconds)". The loader converts silently, the metadata door converts the raw file and prints, and AppPlugin finds nothing left to convert.
definition-collection CONTROL (no packages[]) health 200, served 300 valid unchanged: health 200, item and list 300 valid

The database resolver, measured at the door. Without --fresh, take an option-B artifact whose body declares its default datasource (datasourceMapping default: true and a sqlite datasources entry) and still carries refreshInterval. With only the loader converting, it booted against the unified default database, because the resolver's own read refused the body and declined. The same artifact with the canonical key opened the declared crm.db. After the resolver called the loader's function, both open crm.db. This is why the resolver is routed: converting at load alone would have turned a loud refusal into a silent wrong-database boot.

Config boot (composeStacks) of the same shape: os dev exits before the server starts, at defineStack, with the tombstone prescription. composeStacks takes only defineStack inputs, and that is the authoring funnel, by design. No fix is needed.

Tests, at the final head

Final head 20a5c41cfb. All of it went through os-verify-lock.sh, and the VERDICT lines were read.

  • pnpm --filter @objectstack/runtime exec vitest run --maxWorkers=2 at 6a01653b53: 361 files passed; 5898 tests passed, 19 skipped. The one later commit types a literal in the new test file. That file re-ran at 20a5c41cfb: 6/6.
  • pnpm --filter @objectstack/runtime typecheck at 20a5c41cfb: exit 0, "check:test-typecheck: OK … 27 file(s) / 190 error(s) / 68 pinned signature(s) held". At 6a01653b53 this was the CI red: the new test file had one TS2345 (an untyped union literal) in a file the ledger does not cover. Fixed in 20a5c41cfb.
  • The CI lane Type Check · workspace, reproduced locally as a pull_request event at 20a5c41cfb (the step's own selector, --concurrency=2 added locally): 65 affected packages, "Tasks: 130 successful, 130 total". On GitHub at 20a5c41cfb, Type Check · workspace and TypeScript Type Check both read success.
  • pnpm --filter @objectstack/metadata-core test: 18 files, 681 tests passed. pnpm --filter @objectstack/metadata exec vitest run: 60 files, 879 tests passed. Both typechecks exit 0.
  • New packages/runtime/src/standalone-stack-option-b-forward-conversion.test.ts (6 tests, real createStandaloneStack plus kernel boot, read through getMetaItems / getMetaItem as the HTTP door does):
    • the loader hands back the converted body, and a second conversion on its output returns the same reference with no notices (the idempotence pin);
    • the subject: packages[0].manifest.dashboards[0].refreshInterval: 300 is listed and served as refreshIntervalSeconds: 300, valid, under the orders package;
    • multi-package: per body, each under its own package;
    • CONTROL: a definition-collection artifact is served unchanged;
    • WINDOW SHUT: still refused at the first reader, as INVALID_ARTIFACT_PACKAGE_ENTRY / 422, naming packages[0] and manifest.dashboards.0.refreshInterval;
    • the database resolver resolves the body's declared default with the key retired and with it canonical.
  • From the first half (unchanged since): artifact-forward-conversion-package-body.test.ts (metadata-core, 142 tests, including all 66 retired step-18 entries over a body, both directions) and plugin-artifact-forward-conversion-package-body.test.ts (the metadata door, 5 tests).
  • Lint, narrowed to the 7 touched TS files, with three proofs. Population: eslint --print-config gives 6/5/5/6/6/6/5 rules in effect. Count: --format json reports 7 files, 0 errors, 0 warnings. Invariance: every parserOptions is { ecmaVersion, sourceType } with no project, so there is no type-aware linting. pnpm lint is CI's to run.

Ablations

Every leg went through scripts/ablation-replace.mjs in wrap mode. The direction was predicted before each run. Each restore was proven by blob == HEAD with git diff HEAD empty.

  • The loader's call dropped (bundle = convertArtifactAtLoad(bundle) became void convertArtifactAtLoad; blob e49e71c00328 → b2afed453050). The runtime door file went red: the loader leg failed, and the subject and multi-package suites failed in beforeAll with the public door's own refusal, "Release artifact packages[0] is not a package entry (ADR-0130 D4): manifest.dashboards.0.refreshInterval…", which skipped their 2 tests. The CONTROL, window-shut and resolver legs stayed green, as did all of app-plugin-converted-collections.test.ts (Tests 1 failed, 11 passed, 2 skipped, out of 14). The metadata-core pins stayed green: 324 passed out of 324.
  • The resolver's routing dropped (blob a970de19be5c → 41d80e491454). Only the resolver leg went red: "expected [ 'unified-default', … ] to deeply equal [ 'config-datasource', … ]". The other 5 stayed green.
  • First half (reported before; unchanged code). Dropping the body root in metadata-core gave 72 red and 252 green out of 324: 7 body pins and 65 step-18 before-legs. The metadata door through dist/ gave 3 red and 27 green out of 30, with a rebuild and ablation-dist-preflight on both legs (--absent on the restore).

Gates

dispatch-gates --commands on the real diff at 20a5c41cfb (8 paths, merge base df7a13792) gives 65 commands, a superset of the lead list's 64. All 65 exit 0 at 20a5c41cfb. dispatch-gates --ran reads "65 derived famil(ies) accounted for — 65 run, 0 NOT-MEASURED". New since the first half: check:swallow-census-controls (exit 0). check:dual-build-cjs-loads measured on the first run this time (full build), with exit 0.

Acceptance notes

  • detectUnboundFormViewPredicateRoots (form-predicate-root-policy.ts) reads only the definition's own views, so the bare-root form-predicate notice does not see an option-B body's views. It was read, not measured at a door, and it stays a note.
  • origin/main was merged once before the gates (6a01653b53). It moved by two more commits during the gate run (6a6a4d3e9c); the derivation names check-durability-degradation-log-level.mjs and measure-durability-swallow-family.mjs as changed there. CI reads the merge ref.

Generated by Claude Code

…ges[].manifest body

An option-B artifact (ADR-0130 D4) carries its collections in package bodies,
which the strict parse gates entry by entry. The ADR-0087 conversion walked
only the definition's own collections, so a retired key inside a body refused
the boot. The same walk (same registry, same window, decided once from the
artifact's own engines.protocol) now runs over each body as a second root, and
a body's notices are located under its package.

Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX
Co-authored-by: Claude <noreply@anthropic.com>
…ce path doc names the body form

Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX
Co-authored-by: Claude <noreply@anthropic.com>
@github-actions github-actions Bot added size/l documentation Improvements or additions to documentation tests tooling labels Oct 12, 2026
@github-actions

github-actions Bot commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 2 package(s): @objectstack/metadata-core, @objectstack/runtime, 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/deployment/cli.mdx (via loadArtifactBundle (symbol, a top-level function))
  • content/docs/kernel/services-checklist.mdx (via AppPlugin (symbol, a top-level class))
  • content/docs/permissions/authentication.mdx (via AppPlugin (symbol, a top-level class))
  • content/docs/permissions/capabilities.mdx (via AppPlugin (symbol, a top-level class))
  • content/docs/permissions/system-context.mdx (via AppPlugin (symbol, a top-level class))
  • content/docs/plugins/index.mdx (via AppPlugin (symbol, a top-level class))
  • content/docs/plugins/packages.mdx (via AppPlugin (symbol, a top-level class))
  • content/docs/protocol/kernel/index.mdx (via AppPlugin (symbol, a top-level class))
  • content/docs/protocol/kernel/lifecycle.mdx (via AppPlugin (symbol, a top-level class))
  • content/docs/protocol/kernel/plugin-spec.mdx (via AppPlugin (symbol, a top-level class))

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

  • content/docs/releases/v15.mdx (via AppPlugin (symbol, a top-level class))
  • content/docs/releases/v17/17-0.mdx (via AppPlugin (symbol, a top-level class))
  • content/docs/releases/v17/17-5.mdx (via AppPlugin (symbol, a top-level class))

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 54 of 206 client-bound route-ledger rows — the other 152 have no registrar path: tail to select them, so pages documenting THEIR client methods cannot appear above, on this or any run. Of those 152: 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; 97 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 — 27 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 6a6a4d3e9c777116708486b48e0fc985cd788667 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json 6a6a4d3e9c777116708486b48e0fc985cd788667

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

… once, before mergeRuntimeModule and every reader

An option-B artifact built before a retirement was refused by the runtime's
first reader of packages[] (createStandaloneStack, through
resolveArtifactCollections), before the metadata door's own conversion ran.
The loader now runs applyArtifactForwardConversions once on the loaded bundle,
so every runtime reader sees the converted package bodies. The AppPlugin
comment that said no conversion reaches the bodies is corrected.

Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX
Co-authored-by: Claude <noreply@anthropic.com>
…ugh the loader's function; door test for option-B boots

readConfigDeclaredDefault reads the artifact file itself. With the loader now
converting, a body carrying a retired key was declined there and booted by the
loader against the unified default database instead of the declared one; the
resolver now calls convertArtifactAtLoad on what it read. The runtime door test
pins the option-B boot (single and multi-package), the definition-collection
and window-shut controls, the conversion's idempotence on the loader's output,
and the resolver.

Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX
Co-authored-by: Claude <noreply@anthropic.com>
…s at os dev -a and os start

Claude-Session: https://claude.ai/code/session_01ADzJtzYTLUfgrRZHxkagkX
Co-authored-by: Claude <noreply@anthropic.com>
… test layer compiles under tsconfig.test.json

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

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 20a5c41cfb130c7a36778b85f8d5701af92c63c6
Local-runs: none

Inputs read, and nothing else: card #23027 (body; triage 6122964017; claim 6123839199; report 6125201870; seat answer and claim amendment 6125260474; report 6127009648), PR #23045 (body, the 8-file list, the net diff against main from merge base df7a137923), and the 47 check-runs on this head. Every required context reads success (Lint & Repo Gates, TypeScript Type Check, Test Core, Build Core, Governed Surface Queue Guard); Check Changeset and Check PR Size read success; no governed path is in the file list. The at-rest texts cited below were read on origin/main ec7f7f83b6.

① Derived judgments

  1. The second root, packages[i].manifest — right. applyArtifactForwardConversions now runs ONE replay closure — the same applyConversions call, the same includeRetired: true, the same excludeConversionIds decided once above it — over the definition and then over each body (replayPackageBodies). No second entry list exists in the diff, which is what triage directed. The spec already reads a body this way: migrations/chain.ts (stackDeclaresVerdict) treats packages[].manifest as a collection carrier, and the one registry entry that reads a manifest (manifest-permissions-string-list-removed) already walks packages[i].manifest from the root, so a body run as a root finds no manifest key to strip twice. Copy-on-write holds at both levels and is pinned (an untouched body and the artifact's own manifest keep their references; a definition with nothing to convert comes back as itself). A packages that is not an array, an entry or a body that is not an object come back as given, and resolveArtifactPackageOrder stays the refusal — right, the conversion never gates. All 66 retired step-18 entries are pinned over a body in both directions, which is the structural check that no entry misreads a body's manifest fields as a collection.
  2. The window is the artifact's, decided once — right. ADR-0087's 2026-09-13 addendum reads "a compiled artifact inside the versioned window its own manifest opens": the subject is the compiled artifact, and an artifact has one manifest. composeStacks(…, { manifest: 'preserve' }) documents that the singular manifest "is retained, not replaced … so the artifact keeps an artifact-level identity (ADR-0130 D6 — one artifact, one version)"; ADR-0130 D4 says forward compatibility "rides on manifest.engines.protocol" (singular), and D1 that everything inside one artifact is delivered atomically by one publisher. The boot handshake judges that same manifest and no other: assertArtifactProtocolCompatible reads bundle.manifest ?? bundle, AppPlugin.init reads this.bundle.manifest || this.bundle, and resolveArtifactPackageOrder parses each entry against ArtifactPackageSchema without comparing a body's range to the runtime. A per-body window would let the package listed first decide whether the artifact boots after the handshake had already answered from the package listed last. The floor stands in for when the artifact was built, and one os build writes every body. Both directions are pinned in metadata-core ("a body's own engines.protocol is not read, in either direction"). Residual, named not failed: under 'preserve' the top-level manifest is whichever input stack came last, so the window follows that package's authored range — the same arbitrariness the handshake already carries, and not this PR's to change.
  3. Clause-②: no — holds. (a) Public surface: convertArtifactAtLoad is declared in packages/runtime/src/load-artifact-bundle.ts; packages/runtime/src/index.ts is not in the diff, and its line 215 re-exports only loadArtifactBundle, mergeRuntimeModule, isHttpUrl, readArtifactSource; packages/runtime/package.json exports has the one entry . (dist/index.js and dist/index.cjs), and files is dist plus two docs. The function is unreachable from the package's exports — a module export only, as the body says. ArtifactForwardConversionVerdict, ArtifactForwardConversionOptions and ArtifactConversionNotice are unchanged in the diff (one doc comment on path). (b) Accept set: the window, the entry list and the default-flip exclusions are unchanged; nothing authored past the window is newly admitted — WINDOW SHUT is pinned at all three layers (metadata-core, the metadata door, and the runtime door as INVALID_ARTIFACT_PACKAGE_ENTRY/422 naming packages[0]). The option-B artifacts that now boot were inside the accept set ADR-0087 declares for data at rest and were refused by a defect of reach; a runtime starting to deliver a declared behaviour is the reading triage applied to this lane's finding: a tree field's deleteBehavior is never honoured — the spec materializes set_null on tree, but the engine's referential walk reads only lookup/master_detail, so deleting a parent leaves children holding its id #22957, and it holds here. Not a widening.
  4. Two disk reads, one funnel — right. On origin/main the boot path reads the artifact file in exactly two places under packages/runtime/src: load-artifact-bundle.ts (readArtifactSource) and resolve-project-database.ts:239 (readConfigDeclaredDefault); default-host.ts imports only isHttpUrl and reads nothing. Both now call convertArtifactAtLoad, which is one call of applyArtifactForwardConversions — the same function MetadataPlugin._convertArtifactForward runs on the raw file through artifactSource.path. One implementation, one policy, three readers; idempotence is pinned once (a second conversion of the loader's output is the same reference with no notices), so the door prints its one summary and nothing is converted twice. Not a second opinion. Routing the resolver is load-bearing: without it the resolver's own strict read refused the body and DECLINED, booting against the unified default database — a silent wrong-database boot in place of a loud refusal, the direction AGENTS.md's "prefer failing to falling back" forbids; the dev's ablation leg and the resolver pin both show it.
  5. Order inside loadArtifactBundle — right. The conversion runs after the envelope unwrap and the grantedPermissions carry-over, before the remote runtimeModule strip and before mergeRuntimeModule, which itself parses bodies strictly through resolveArtifactCollections. mergeRuntimeModule mutates the converted copy, and every caller uses the return value.
  6. Notice location — right. locateUnder prefixes path and rewrites the one quoted path in message; the spelling it replaces (at ${path}:) is exactly what conversions/apply.ts writes, and the metadata-core test asserts the rewritten message.
  7. app-plugin.ts — comment only. The diff is one comment block; the servicePayload logic is byte-unchanged. The new sentence is also the one residual worth naming: a packages[] bundle handed to AppPlugin by a route other than loadArtifactBundle still reaches the manifest service with unconverted bodies. No public door measured on the card reaches that route; it stays a note.
  8. Config boot (composeStacks) — answered. Triage asked for it; the dev measured it refused at defineStack with the tombstone prescription. That is the authoring funnel teaching a live author, which ADR-0087 keeps distinct from data at rest. No fix owed.

② Semver level

  • .changeset/23027-package-body-conversion.md: patch for @objectstack/metadata-core and @objectstack/runtime — right. AGENTS.md Post-Task Checklist §3: a bug fix in a released package takes patch; pr-automation.yml "WHICH LEVEL": minor is owed for "a new exported symbol on an index, a new accepted key or value", and this diff adds neither (judgment ①3). The changeset body carries Clause-②: no on its own line, where check-adr-0087-registration reads it; no breaking declaration, so no ADR-0087 disposition is owed. Check Changeset reads success on this head.
  • PR line 2 Clause-②: no, no arm — right: neither a widening (no new export, no new accepted shape) nor a narrowing (nothing accepted before is refused now). The claim's own trigger for yes ("if the dev adds an exported member — a new verdict or option") did not fire.
  • Line 1 Fixes #23027 — right now that the measured door boots; the check-run "The card this PR closes must claim this branch" reads success.

③ Boundary flags

  • Report 6125201870 open_questions (who carries the runtime half; A, B or C) — answered by the seat in 6125260474 (A, on this PR; C refused), carried in this head; open_questions is empty in 6127009648.
  • Deviation: resolve-project-database.ts is outside the amended surface — accepted: it is a reader that reads the file by another path, routed through the same single function, with the measured reason (the silent wrong-database boot) and its own ablation leg. The claim's "stop on a breach and explain it" was honoured in the report.
  • detectUnboundFormViewPredicateRoots reads only top-level views, so an option-B body's form predicates get no boot notice — stays an Acceptance note: read, not measured at a door; a different mechanism from the conversion; the seat files it on a measurement.
  • pnpm lint not run locally — Lint · ESLint reads success on this head. The earlier CI red (TS2345 in the new test at 6a01653b53) is fixed in this head; TypeScript Type Check and Type Check · workspace read success.
  • 909 changed lines, under the 3,000 human-merge line; no governed surface; nothing under content/docs/releases/.
  • Observation for the seat, not a condition: the new runtime door test boots four real kernels, and AGENTS.md makes a test file over 10 s of wall its lane's card. No wall reading is in the report; Test Core is green on this head.

Adopted by domain:engine seat 2 at 2026-10-12T15:28Z, after a tool-use audit of the isolated reviewer's transcript. It made 64 tool calls: reads, git fetch into review refs, and the two record writes. It made no GitHub write and ran no build, test or gate. One self-check script went to the scratchpad root rather than this PR's scratch directory, and was deleted. One reviewer rendered this record and PR #23055's, each from its own brief.

Implemented-by: claude/issue-23027-package-body-conversion
Reviewed-by: session_01ADzJtzYTLUfgrRZHxkagkX

VERDICT: PASS

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/l tests tooling

Projects

None yet

2 participants