Skip to content

fix(metadata-protocol): S5 carry-overs — per-term boot report isolation, one naming of a code-datasource row, seal TSDoc wordings, two changeset rows - #22833

Merged
objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-15206-s5-carry-overs
Oct 11, 2026
Merged

objectstack-fleet[bot] merged 4 commits into
mainfrom
claude/issue-15206-s5-carry-overs

Conversation

@objectstack-fleet

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

Copy link
Copy Markdown
Contributor

Part of #15206
Clause-②: no

The carry-over PR of #15206: what the S5 records carried out of the stage (ACCEPT 6097795222 "Carried", contract review 6097501912 ③, landing record 6098305643), scoped by the triage unlock 6107774330, plus the seat's round-2 rulings on this PR. #15206 stays open after this lands: the gap round 1 measured — the datasource restore reads stored datasource rows with no environment predicate, so a legacy organization-scoped row is loaded and served — is #15206's own promise, and its derived child #22835 carries it. ⛔ This PR does not touch it. Not here either: S6 and the registry's bare copy of a non-object sealed row (both #15211, C7), and S7 (the ADR-0005 / ADR-0029 D9 note, Tier H, the maintainer's).

What changes

  1. Per-term isolation in [metadata_org_scoped_unserved] (reportUnhydratableOrgScopedRows, packages/metadata-protocol/src/protocol.ts). The projected sys_metadata read and each of the three ledger counts (sys_metadata_commit, sys_metadata_history, sys_metadata_audit) is its own term. A term whose read fails for any reason but a missing table is printed NOT MEASURED with its reason, next to the terms that were measured; before, one failing read took the whole line, sys_metadata detail included, with it. A missing table still reads as zero (isMissingTableError, unchanged). Nothing is invented as a zero and the boot verdict (storeUnavailable) is untouched.
  2. A stored row under a code-defined datasource name is named once at boot (loadMetaFromDb, new private isHostCodeDatasource, protocol.ts). The row is left to the datasource restore's boot warning (restoreRuntimeDatasources, @objectstack/service-datasource), which names it with its own remedy and is the reporter declinesStoredRow's own TSDoc already designates (the seat's ruling on this PR's open question: this arm, A). The exclusion is exactly the host's code-datasource set, the set the restore reads; isDeclaredCodeDatasource now reads that set through the new helper (one reading). A datasource that only a package manifest declares is still listed by [metadata_sealed_overlay_unserved]. Every read still declines both (pinned).
  3. Three TSDoc wordings — reportSealedOverlayRows, declinesStoredRow, refusePackagedBaseRemoval — now state the seal's reach as the code does (isSealedManagedItem = isArtifactBacked && !registryAllowsOverlay: every type but the five regime-O ones, permission excepted), and that removing a merge-at-read row changes what is served for permission alone. The isUnservedSealedOverlay paragraph feat(plugin-security,rest,spec)!: retire the permission-set overlay discard (ADR-0131 cutover stage 7-pre) #22776 rewrote is untouched. Comments only.
  4. .changeset/15206-reads-environment-only.md — two FROM → TO rows inserted and one sentence corrected; the section below lists each.
  5. The real-engine reading of [metadata_org_scoped_unserved] (6097501912 ③, optional) — taken, below.

A new patch changeset for @objectstack/metadata-protocol (.changeset/15206-boot-report-carry-overs.md) covers items 1 and 2. content/docs/concepts/metadata-lifecycle.mdx gains one sentence, because item 2 made its "boot names each such row" false for a code-defined datasource.

check-empty-changeset is red on purpose — DELIBERATE CORRECTION of one pending release note

The gate's refusal names one file, .changeset/15206-reads-environment-only.md: it exists on the merge base and this PR edits it. Nothing in it is deleted or restored; no other open PR touches it. ⛔ No skip-changeset: the gate stays red and this section is the input for the sentence-by-sentence review. Three edits, each of which must ride this PR:

# Where Edit Why it must move with this PR
1 FROM → TO table, after the ListDraftsResponse row inserted row: SysMetadataRepository.listDrafts row organizationId (@objectstack/metadata-protocol) → gone: every listed row is an environment draft, whichever repository asks S5 narrowed this entry export's row type (contract review 6097501912 ②: a published type narrowing the table did not name); triage 6107774330 orders the row before the v18 release, and the release consumes this note. Read by symbol: at c8b062f011^ each row carried organizationId; on b7cd1af9df none does and the read is packageScopedRowWhere('draft', …) = organization_id: null.
2 FROM → TO table, after the MetadataAuthoringGateContext.organizationId row inserted row: organizationId on projectPermissionMutation's evt (@objectstack/plugin-security) → drop it: the projection reads the environment → code layered read, whatever organization the event names Same record and same order, for the plugin-security entry export. Read by symbol: at c8b062f011^ the evt type carried organizationId and forwarded it to getMetaItemLayered; on b7cd1af9df neither.
3 The [Triage ruling Q1 → C] bullet, its last sentence was: a stored fork of a code-declared permission set "keeps its own ruling (detection reading and Discard Overlay) and is still served". now: it "keeps its own ruling instead: the reads do not decline it, a cold boot refuses a deployment that holds one (ADR-0048), and the offline os migrate security-catalog-overlays removes it." #22776 retired the Discard Overlay action in the same pending release line, so the old sentence would publish a retired action as this release's disposition. The new text is main's (isUnservedSealedOverlay's TSDoc as #22776 left it; the cold-boot refusal is ObjectQLPlugin.refuseEnvironmentHeldSecurityCatalogNames, ADR-0048). Round-2 seat ruling: folded into this PR.

Every other line of that note is unchanged. Left as it stands, pending #22835: the first bullet's "A row stored organization-scoped by an earlier release is served by no read and loaded by no boot", and the boot line's own "no read serves them, boot does not load them" — both become true with #22835's fix of the datasource restore, so neither is edited here.

Measured

Real boot — the showcase through bootShowcase (@objectstack/verify, sqlite-wasm driver) over a database file: rows planted with the system context between two boots, lines read from the second boot, a temporary uncommitted dogfood file (deleted).

  • Before item 2, tree baaa75e036: an environment row under the showcase's code-declared datasource name was named twice — [metadata_sealed_overlay_unserved] … datasource×1 (…) with the clone/delete remedies, and datasource restore: sys_metadata holds a stored row for '…' with the DELETE /api/v1/meta/datasource/… remedy.
  • After, tree 1fec8e5dd0: the datasource restore's warning alone names it; no [metadata_sealed_overlay_unserved] line (no other sealed row was planted).
  • [metadata_org_scoped_unserved] on both trees named the planted legacy organization row per type, and printed no NOT MEASURED term: the three engine.count ledger reads answered on the real engine. This closes the NOT MEASURED of 6097501912 ①7.

Pins (packages/metadata-protocol/src):

  • protocol.org-scoped-cold-boot-audit.test.ts — new case: a ledger count that throws costs its own term; the sys_metadata detail and the other two ledgers are reported, the failing one is NOT MEASURED with its reason. The existing failing-probe case keeps its boot-verdict assertions and now asserts the failed read is said (it pinned the silence before).
  • protocol.reads-environment-only.test.ts — new case: a host-set datasource row is not listed by the sealed line, a manifest-only one is, and both are declined at read.
  • Ablation (committed state baaa75e036, scripts/ablation-replace.mjs WRAP mode, the subject is ./protocol.js from src, so no build is in the path): the per-term catch made to rethrow (the pre-fix behaviour) — anchor 1 → 0, blob e09bc9f2578c → c9b85129dc0b → 2 failed / 5 passed (both isolation cases red: no line at all); restore proven, blob == HEAD and git diff HEAD empty.

Suites (on 1fec8e5dd0; round 2 changes only the changeset text): @objectstack/metadata-protocol test 225 files passed / 3 skipped, 28,131 tests passed / 19 skipped; typecheck exit 0, and --listFiles shows both edited test files in the program. Consumers of the boot report (every test file outside the package that calls loadMetaFromDb / restoreMetadataFromDb), against the rebuilt dist: @objectstack/objectql 15 files / 302 tests, @objectstack/runtime 3 / 36, @objectstack/plugin-email 1 / 40, @objectstack/plugin-security 1 / 30, @objectstack/service-automation 1 / 14, @objectstack/metadata 1 / 9, @objectstack/dogfood 4 files / 22 tests (datasource-restore-code-wins, meta-door-code-datasource, permission-set-clone-boot-unowned-warning, showcase-object-extension-scalar-divergence) — all green.

Gates: re-derived on the round-2 head 75b4c850fa with node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack: 95 families, the same list as round 1 (the dispatch-time 59 plus the docs and test-file families). All 95 run one by one, each exit code captured before any pipe; --ran reconciles 95 derived / 95 run / 0 NOT-MEASURED. 94 exit 0; check-empty-changeset exits 1, the DELIBERATE CORRECTION verdict on .changeset/15206-reads-environment-only.md explained above. The derivation warned that the tree is three commits behind main and that scripts/sdui-manifest.record.json moved there; no gate in this list reads that file's content (check-changeset-no-major names the path only, to assert the console build does not copy it), and CI judges the merge ref with main anyway.

Lint: eslint --no-inline-config --format json over the three edited TypeScript files — 3 files, 0 errors, 0 warnings. eslint.config.mjs enables no type-aware linting (no parserOptions.project), so this diff cannot move a verdict on an untouched file; the repo-wide run is CI's.

Size

6 files, +153 / −34 = 187 changed lines. protocol.ts +83 / −31, of which code +40 / −18 (most of the −18 / +17 is the re-indent of the two reads wrapped in the per-term helper) and comments +41 / −13.

Acceptance notes

  • Engine doubles without count now produce a [metadata_org_scoped_unserved] line naming the three ledgers NOT MEASURED (this.engine.count is not a function) where they produced nothing before: 32 such lines in the objectql run above, every test green. A real engine has count (the reading above). Recorded, not filed.
  • Stated limit of item 2: a host that composes no datasource-admin plugin names such a row at boot nowhere — as before S5. Every read still declines it.
  • Deviations from the dispatch: the sys_metadata read is isolated as a term too (the hypothesis named the three ledgers; one failing read silenced the line through it the same way); isSealedManagedItem's TSDoc carried the same "restores the managed definition" wording as refusePackagedBaseRemoval and is corrected in the same stroke (same file, comments only); the docs sentence and the new patch changeset above.
  • Branch base: b7cd1af9df. main has since moved three commits (security(mcp): a nested-relation filter condition through the MCP stdio data bridge is evaluated without asking the target's exposure (census row 8 of #22661) #22777 among them); the branch merges cleanly with it and was pushed fast-forward only, per the seat.

Generated by Claude Code

…boot report; correct three seal TSDoc wordings; complete the changeset's FROM → TO table

- reportUnhydratableOrgScopedRows: the sys_metadata read and each of the
  three ledger counts is its own term. A term whose read fails for any
  reason but a missing table is printed NOT MEASURED with its reason; the
  other terms are reported as measured, and the line no longer vanishes.
- TSDoc of declinesStoredRow, refusePackagedBaseRemoval and
  isSealedManagedItem: the seal's reach is every type without an overlay
  channel (permission excepted), and removing a merge-at-read row changes
  what is served for permission alone.
- .changeset/15206-reads-environment-only.md: two rows the table lacked
  (projectPermissionMutation's evt.organizationId and
  SysMetadataRepository.listDrafts' row organizationId).

Claude-Session: https://claude.ai/code/session_01JfJfBUC3cQ6hhgm9MQK76T
Co-authored-by: Claude <noreply@anthropic.com>
…ation boot report

A ledger count that fails costs its own term only: the sys_metadata rows
and the other ledgers are still reported, and the failing term is NOT
MEASURED with its reason. The failing-probe case now asserts the failed
read is said rather than swallowed.

Claude-Session: https://claude.ai/code/session_01JfJfBUC3cQ6hhgm9MQK76T
Co-authored-by: Claude <noreply@anthropic.com>
…urce name once at boot

Measured on a real showcase boot over a database file: an environment
sys_metadata row under the showcase's code-declared datasource name was
named twice, by [metadata_sealed_overlay_unserved] and by the datasource
restore's boot warning, with two different remedies.

loadMetaFromDb now leaves a row whose name the host's code-datasource set
holds to the restore's warning (isHostCodeDatasource, the first half of
isDeclaredCodeDatasource, which now reads through it). A datasource only a
package manifest declares is still listed. reportSealedOverlayRows' TSDoc
states the seal's whole reach and this exception; the lifecycle page says
where such a row is named; a metadata-protocol patch changeset covers this
and the per-term isolation of the legacy-organization report.

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

github-actions Bot commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

This PR changes 1 package(s): @objectstack/metadata-protocol, touching 6 documentable anchor(s).

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

  • content/docs/api/error-catalog.mdx (via sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))
  • content/docs/api/index.mdx (via sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))
  • content/docs/automation/flows.mdx (via sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))
  • content/docs/concepts/metadata-lifecycle.mdx (via ObjectStackProtocolImplementation (symbol, a top-level class), sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))
  • content/docs/data-modeling/drivers.mdx (via sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))
  • content/docs/data-modeling/objects.mdx (via sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))
  • content/docs/deployment/cli.mdx (via sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))
  • content/docs/deployment/environment-variables.mdx (via sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))
  • content/docs/deployment/validating-metadata.mdx (via sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))
  • content/docs/kernel/cluster.mdx (via sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))
  • content/docs/kernel/contracts/metadata-service.mdx (via sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))
  • content/docs/kernel/services-checklist.mdx (via sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))
  • content/docs/permissions/authorization.mdx (via sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))
  • content/docs/permissions/permission-sets.mdx (via sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))
  • content/docs/plugins/packages.mdx (via sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))

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

  • content/docs/releases/implementation-status.mdx (via sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))
  • content/docs/releases/v16.mdx (via ObjectStackProtocolImplementation (symbol, a top-level class), sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))
  • content/docs/releases/v17/17-0.mdx (via ObjectStackProtocolImplementation (symbol, a top-level class), sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))
  • content/docs/releases/v17/17-3.mdx (via sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))
  • content/docs/releases/v17/17-5.mdx (via sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))
  • content/docs/releases/v17/17-6.mdx (via sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))
  • content/docs/releases/v17/17-7.mdx (via sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))
  • content/docs/releases/v17/index.mdx (via sys_metadata (literal, a string literal in reportUnhydratableOrgScopedRows))

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
  • 1 anchor(s) matched too much of the corpus to be a work list: organization_id (literal, 33 pages)
  • 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 — 11 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 efcbac73cc58510278b970949ac3b65cb9712815 → packageMentionDocs.

Which tree this was computed on

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

node scripts/docs-audit/affected-docs.mjs --json efcbac73cc58510278b970949ac3b65cb9712815

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

…k disposition main has

The sentence said a stored fork of a code-declared permission set keeps
its ruling "(detection reading and Discard Overlay)" and is still served.
The Discard Overlay action was retired in the same pending release line;
the disposition on main is that the reads do not decline such a fork, a
cold boot refuses a deployment holding one (ADR-0048), and the offline
`os migrate security-catalog-overlays` removes it. Only that sentence moves.

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

Copy link
Copy Markdown
Contributor Author

Contract review

Served-tier: CONTRACT_REVIEW_TIER
Head-sha: 75b4c850fa3d6eec9790f37ccbb422edfc5eca38
Local-runs: none

Inputs: card #15206's triage unlock 6107774330, the claim 6108222334, the os-dev-reports 6108953446 (round 1) and 6109357048 (round 2); S5's ACCEPT 6097795222 and contract review 6097501912 for what was carried; card #22835's body; PR #22833 (its body, its 6-file list, and the net diff against the merge base b7cd1af9df: 6 files, +153 / −34, equal to the file list, over the four branch commits e4cb38f3fd, baaa75e036, 1fec8e5dd0, 75b4c850fa); PR #22776's merged diff (60bcfe726c, an ancestor of the merge base) where it touches the permission-set fork's disposition — its changeset, its metadata-lifecycle.mdx hunk and its protocol.ts hunk; the 42 check-runs and the one commit status on the head, read at 2026-10-11T13:25Z; git merge-tree of the head against origin/main at 1eff3224d7. Source at the head, read by symbol: protocol.ts (isSealedManagedItem, isArtifactBacked, registryAllowsOverlay, OVERLAY_ALLOWED_TYPES, mergesOverlayAtRead, isDeclaredCodeDatasource, isHostCodeDatasource, declinesStoredRow, isUnservedSealedOverlay, refusePackagedBaseRemoval, loadMetaFromDb, reportUnhydratableOrgScopedRows, reportSealedOverlayRows, the protocol's and SysMetadataRepository's listDrafts, packageScopedRowWhere), permission-set-projection.ts (projectPermissionMutation), datasource-admin-plugin.ts (restoreRuntimeDatasources, loadDatasourceRows), objectql's plugin.ts (refuseEnvironmentHeldSecurityCatalogNames), the cli's migrate/security-catalog-overlays, the two package entries and DEFAULT_METADATA_TYPE_REGISTRY; the same symbols at c8b062f011^ for each FROM side; on main: scripts/check-empty-changeset.mjs's header, pr-automation.yml and lint.yml where they run it, and the fleet's landing rule. ⛔ Not the dispatch order, not the dispatching seat's conclusions. Nothing was checked out, built, run or re-run; the dev's dispatch-gates run is read as the dev's report, never as a gate verdict. This is a security card: classes, positions and functions only.

① Derived judgments

Gate verdicts on this head, as read. 42 check-runs, all completed, none in progress: 36 success, 4 skipped, 2 failure. Every one of the seven required contexts is success: Lint & Repo Gates, TypeScript Type Check (and its four Type Check · legs), Test Core (and its six shards), Dogfood Regression Gate (rollup and 1/3 to 3/3), Build Core, Temporal Conformance (live PG + MySQL), Governed Surface Queue Guard. Also green: Build Docs, Dogfood Verify CLI, Check PR Size, Spec property liveness, Check Documentation Links, Flag docs affected by code changes, filter, Auto Label, and the four claim and closing guards, twice each — Part-of PR must not also close its card and The card this PR closes must claim this branch among them. Skipped by contract: Console Pin Gate (path filter; the pin is unchanged), Packed-tarball smoke (opt-in), and the second run's Auto Label / Check PR Size twins (the edited event after the round-2 body write). Red: Check Changeset, twice — 114469951637 on the push run and 114477462305 on the edited run, the latter completed 2026-10-11T13:09:17Z — attributed in ③. The one commit status, Vercel, is success ("Canceled by Ignored Build Step"). Not governed: no path under .claude/, docs/adr/, skills/, docs/NORTH-STAR.md, AGENTS.md or CLAUDE.md; head repo is the base repo; 187 changed lines over 6 files, under every threshold; draft, auto_merge unset, no skip-changeset label (right — ruling D on #18375 refuses it for this class), mergeable: true with mergeable_state: unstable (the red advisory check, not a conflict).

  1. Per-term isolation in [metadata_org_scoped_unserved] (reportUnhydratableOrgScopedRows). On the merge base one try held the projected sys_metadata find and the three engine.count ledger reads; any failure other than a missing table left the function through its outer empty catch and the line was never printed. At the head each of the four reads runs inside isolated(object, read), whose catch appends object (message) to unmeasured; the early return now requires rows, ledgers and unmeasured all empty; and the line gains one clause, NOT MEASURED, so this account may be incomplete — the read failed: …, after the measured terms. RIGHT: a failing read prints NOT MEASURED with its reason and never a zero — a failed ledger pushes nothing to ledgers (only a positive count is ever pushed, and no count exists for it), a failed sys_metadata read leaves rows empty so the per-type clause is omitted rather than printed as ×0; the missing-table arm is untouched (orNoTable still asks isMissingTableError and returns the empty value — an unprovisioned ledger still reads as zero, by design); the boot verdict is unchanged — storeUnavailable is set only in loadMetaFromDb's own catch, which this diagnostic's own try/catch never reaches, and the diff does not touch that site. Pinned: the new cold-boot case (history count throws → flow×1 (org_sweep@org_a), sys_metadata_commit×1, sys_metadata_audit×1, NOT MEASURED, sys_metadata_history (history count refused), and no sys_metadata_history× term), and the existing failing-probe case re-premised from "no line" to "one line naming sys_metadata (probe exploded)", with its storeUnavailable: false / errors: 0 assertions kept; the dev's ablation (the per-term catch made to rethrow: 2 red) is the recorded direction; Test Core is green. The sys_metadata term the dev added beyond the three ledgers the hypothesis named is the same failure shape and the right reading of "per-ledger". Cosmetic, not a ground: when NOT MEASURED is the only clause, the closing sentence's "carries these rows" names no row.

  2. The host-code-datasource leave-out (isHostCodeDatasource, loadMetaFromDb). The new private predicate is exactly the first half of isDeclaredCodeDatasource — the kernel service code-datasource-names, the set the datasource-admin restore reads (codeDatasourceNamesOf(ctx) in restoreRuntimeDatasources) — and isDeclaredCodeDatasource now reads that half through it (one reading; the type check it repeats is redundant and harmless), so its answer is unchanged. In loadMetaFromDb the exclusion sits inside the isUnservedSealedOverlay branch: a sealed row whose name is in the host set is no longer pushed to sealedOverlays, while the object skip and the hydration that follows are untouched, so what boot loads does not move. RIGHT on the three counts the brief asks: (i) every read still declines both kinds — declinesStoredRow is unchanged in code and still answers isDeclaredCodeDatasource(type, name) || isUnservedSealedOverlay(type, name), true for a host-set name and for a manifest-declared one; (ii) manifest-only names stay listed — the package-manifest half lives only in isDeclaredCodeDatasource, so isHostCodeDatasource is false for them and the push happens; (iii) the row IS named once, by restoreRuntimeDatasources' warning (datasource restore: sys_metadata holds a stored row for '…', a datasource this host defines in code … Remove it with DELETE /api/v1/meta/datasource/…), which fires on exactly codeNames.has(name) — the same set. Pinned: the new reads-environment-only case (host set host_ds, manifest pkg_ds: both declinesStoredRow true; the sealed line names datasource×1 (pkg_ds) and not host_ds); the dev's real boot through bootShowcase (named twice on baaa75e036, once on 1fec8e5dd0). The stated limit holds and is the ruling's cost (③): a host that composes no DatasourceAdminServicePlugin names such a row nowhere at boot — os serve (packages/cli/src/commands/serve.ts) and the verify harness both compose it, so the shipped compositions are covered.

  3. The TSDoc against the code. isSealedManagedItem at the head is isArtifactBacked(type, name) && !registryAllowsOverlay(type); registryAllowsOverlay reads OVERLAY_ALLOWED_TYPES, built from allowOrgOverride on DEFAULT_METADATA_TYPE_REGISTRY, which has exactly five such entries (view, dashboard, report, translation, email_template); isUnservedSealedOverlay is false for permission and otherwise isSealedManagedItem; refusePackagedBaseRemoval refuses when isSealedManagedItem holds and mergesOverlayAtRead(type) does not, and mergesOverlayAtRead reads the supportsOverlay set (permission, position, page, app, dataset, book, tool, skill, plus the five above). Judged line by line: declinesStoredRow — "ANY type whose registry entry opens no overlay channel … every type but the five regime-O ones, so a merge-at-read page or app as much as a flow or an object, permission excepted" is the predicate's reach, RIGHT; reportSealedOverlayRows — "which stored ENVIRONMENT rows no read serves" (not "boot did not load", which held for object alone), the same reach, "an object such row is not even loaded", and the host-datasource sentence — each true at the head, RIGHT; refusePackagedBaseRemoval — "removing a row of a type that merges at read removes no managed definition. Only for permission, whose stored fork is still served, does it change what is served; every other such row is served by no read already" — true: among the sealed merge-at-read types, permission is the one isUnservedSealedOverlay excepts, so its fork is served and its removal changes the answer; every other is declined at read, RIGHT; isSealedManagedItem — the carve-out "removes no managed definition (and changes what is served for permission alone)" replaces "restores the managed definition", the same correction, RIGHT. The isUnservedSealedOverlay paragraph feat(plugin-security,rest,spec)!: retire the permission-set overlay discard (ADR-0131 cutover stage 7-pre) #22776 wrote is byte-untouched in the diff. Comments only; nothing executable moves.

  4. The two FROM → TO rows, by symbol. Row SysMetadataRepository.listDrafts row organizationId (@objectstack/metadata-protocol): at c8b062f011^ the repository's listDrafts returned organizationId: string | null on every row and read packageScopedRowWhere(this.organizationId, 'draft', filter) (an $or of the instance's organization and null); at the head the row type has no organizationId and the read is packageScopedRowWhere('draft', filter), which is state: 'draft' plus organization_id: null plus the filter — "every listed row is an environment draft, whichever repository asks" is the code's own comment and true for a legacyOrganizationRepo(id) instance too. SysMetadataRepository is an entry export of the package, so the narrowing is published; RIGHT. Row organizationId on projectPermissionMutation's evt (@objectstack/plugin-security): at c8b062f011^ the evt type carried organizationId?: string | null and forwarded it into getMetaItemLayered; at the head the type has type, name, state only and the layered read is { type: 'permission', name: evt.name } — "the projection reads the environment → code layered read, whatever organization the event names" is true at runtime (the handler passes evt: any; the key is ignored). projectPermissionMutation is an entry export; RIGHT. Both are the two narrowings 6097501912 ② named and 6097795222 carried; 6107774330 orders them before the v18 release.

  5. The metadata-lifecycle.mdx sentence. "A row under the name of a datasource the host defines in code is named once, by the datasource restore's boot warning, with that warning's remedy." True at the head for every composition that carries the restore (item 2), and needed: the paragraph's "Boot names each such row, per type ([metadata_sealed_overlay_unserved])" became false for exactly that row on 1fec8e5dd0. The permission sentence that follows is feat(plugin-security,rest,spec)!: retire the permission-set overlay discard (ADR-0131 cutover stage 7-pre) #22776's and untouched. RIGHT. A hand-written tree (content/docs/concepts/, not references/ or releases/); Check Documentation Links and Build Docs green.

  6. The DELIBERATE CORRECTION, ① part. The modified note is .changeset/15206-reads-environment-only.md, S5's pending v18 release note (PR feat(metadata-protocol,objectql,rest,spec)!: every metadata read is environment → code; legacy organization rows and sealed overlays are reported at boot, not served (#15206 S5) #22628, c8b062f011, an ancestor of the merge base); it is unchanged on origin/main since the merge base, so what the diff rewrites is what main holds. Three edits against main, each judged:

Nothing is deleted or rewritten. No added line calls a store write; the two reads the diff wraps are the same find and count with the same where, fields and system context.

② Semver level

.changeset/15206-boot-report-carry-overs.md (new, this PR's own — an A row, outside the foreign rule): '@objectstack/metadata-protocol': patch, Clause-②: no, two bullets naming items 1 and 2. The level is right: a behaviour change in a released package — the boot log's content (one clause gained on the legacy line; one subclass of row leaving the sealed line) — with no public type, request, response, enum, export or error code moving: isHostCodeDatasource and reportUnhydratableOrgScopedRows are private, declinesStoredRow changes in TSDoc only, and SysMetadataRepository and the plugin-security entry are untouched in code (their rows above describe S5's narrowing, already bumped minor by the pending note). @objectstack/service-datasource, @objectstack/objectql and @objectstack/plugin-security are not in the file list and owe nothing. Nothing published moves in shape; what moves is observable boot output, which is what a patch is for and what the Post-Task Checklist refuses skip-changeset for. The pending note's frontmatter (six packages, minor, the ADR-0087 marker) is untouched by the diff, so the correction changes no level. Clause-②: no on PR body line 2, in the new changeset and in the claim 6108222334 — matches the diff: no read refused today is admitted, no accept set widens or narrows; the one membership change is a boot report's, not a door's. Not a (narrowing): no caller loses an answer. Right.

③ Boundary flags

Check Changeset red — attributed to the foreign changeset rule ONLY. Both check-runs carry one file-level failure annotation, on .changeset/15206-reads-environment-only.md, whose text is check-empty-changeset.mjs's #17712 two-class refusal ("exists on the merge base and was not added by this PR … COLLISION … DELIBERATE CORRECTION — … do NOT restore it — say so on the PR and get it confirmed"); there is no #5471 empty-frontmatter annotation, and the PR's own new changeset declares a non-empty frontmatter, so rule 1 has nothing to say. The job log through actions/jobs/114477462305/logs is NOT MEASURED: blob storage answered 403 Forbidden on the redirect; the attribution rests on the check-run's annotations (the gate's own ::error is the annotation), corroborated by the dev's local check-empty-changeset.mjs --base origin/main :: exit 1 with its ::error on that one file, read as the dev's report. The class is DELIBERATE CORRECTION, not COLLISION: the file was not overwritten, its frontmatter and every other sentence are intact, and no rename or deletion is in the diff. This record is the confirmation the landing rule names — a same-head contract-review-tier PASS naming the modified note and judging each rewritten sentence (①6). The three conditions for carrying the red into the queue hold on the head: the gate's own source names the by-design red for this class and the workflow's route 0 says to leave it red; the real scan runs only in pr-automation.yml's changeset-check on pull_request events — lint.yml's required job runs the --self-test halves alone — so no merge_group build sees it and Check Changeset is not a required context; and the PR body plus this comment record the gate and the reason. ⛔ skip-changeset stays off, as the body says and ruling D requires.

The open question (ruled A), judged. The implementation is A exactly: the sealed line leaves out the host's code-datasource set and nothing more, through the one helper that names the restore's set; a manifest-declared name is still listed; both kinds are declined at every read (①2). B would reverse the #21922 "code wins on collision" warning in @objectstack/service-datasource across a package boundary, with its pinned assertions and a changeset, for a cosmetic double naming — a larger change for a smaller item. The cost of A is real and stated (a host composing no datasource-admin plugin names such a row nowhere at boot, the pre-S5 state) and it is a boot-log membership question with no product contract moving, inside the seat's discretion under SKILL.md's non-escalation classes. Confirmed.

Deviations, both reports, each answered. Round 1: (a) the sys_metadata read isolated as a term too — the same failure shape, pinned, accepted (①1); (b) isSealedManagedItem's TSDoc corrected in the same stroke — same file, comments only, judged true (①3), accepted; (c) metadata-lifecycle.mdx +1 sentence, a file-surface addition beyond the claim's list, explained in the report as the claim requires and judged true (①5), accepted; (d) the new patch changeset — the claim's "a changeset only if a published package's behaviour moves" is met by the boot log (②), accepted; (e) check-empty-changeset red by design — confirmed above; (f) zero label writes — right. Round 2: none new; the body's rewrite from a closing line to Part of #15206 is the seat's ruling on finding 1 and is consistent with the green Part-of PR must not also close its card guard. The claim's ⛔ Not list holds: no S6 column work, no registry bare-copy change, no S7 note — none of those files is in the diff.

#22835 as the carrier, and Part of. Right on both. The measured gap (restoreRuntimeDatasources → loadDatasourceRows, a sys_metadata read by type and state with no organization predicate, in packages/services/service-datasource) is #15206's own C5 promise, not this PR's regression, and lives in another package and lane; fixing it here would widen a carry-over PR into a cross-domain narrowing owing its own contract review. Closing #15206 on this landing would leave the pending note's "served by no read and loaded by no boot" with no open card on the lane that owns the note; Part of keeps the card open on exactly that sentence. For the seat at landing (landing-operations B): re-grade the card's label on MERGED rather than leaving pm:dispatched on a card whose only in-flight item is #22835, and record what this PR delivered. For the release board: #22835 (target:v18, p1) is a condition of the pending note's truth — it must land before the release that consumes .changeset/15206-reads-environment-only.md; triage 6107774330's "this card closes when the carry-over PR lands" is overtaken by that finding.

main has moved. origin/main is at 1eff3224d7, eight commits past the merge base (the PR body's "three" is the dev's round-2 reading; a stale count, not a ground). git merge-tree --write-tree origin/main 75b4c850fa writes a tree with no conflict ("Auto-merging packages/metadata-protocol/src/protocol.ts"); main's protocol.ts hunks since the base are the import list and the relationConditionSites region (#22777), none near this PR's hunks; the pending changeset and the mdx are unchanged on main since the base. The six paths are hand-written files, so no os-regen artifact is in the merge. The queue rebuilds on the merge ref; nothing here asks for a pre-merge of main.

Check-runs red or in progress. Red: Check Changeset ×2 (above). In progress: none. Skipped: four, each by the workflow's own filter (①). Every required context green. The PR's "no other open PR touches .changeset/15206-reads-environment-only.md" is the PR's claim, outside this record's inputs; the seat's mergeable_state re-read at arming covers a textual collision.

Not a ground, for the seat: the NOT MEASURED-only line's closing sentence (①1, cosmetic); the stated limit of item 2 for a custom host without the datasource-admin plugin (every read still declines the row; both shipped compositions name it); engine doubles without count now print the legacy line naming the three ledgers NOT MEASURED in 32 objectql test runs (test-double noise, every test green, the engine-double contract gate green; a real engine has count) — recorded, not filed, as the dev says.

Verdict grounds, all on this head: every required context is green and the one red check is the by-design DELIBERATE CORRECTION refusal, attributed to the foreign changeset rule alone and confirmed sentence by sentence above; the per-term isolation prints NOT MEASURED and never a zero and leaves the boot verdict alone; the host-code leave-out declines both kinds at every read and keeps manifest-only names listed; the four TSDoc wordings and the docs sentence state what the code does; both inserted rows and the rewritten sentence are true at the head and the old sentence was main's falsity from #22776; the patch level and Clause-②: no match a diff that moves nothing published in shape; the gap left out is filed and carried, and Part of is the right line.

Implemented-by: claude/issue-15206-s5-carry-overs
Reviewed-by: session_01JfJfBUC3cQ6hhgm9MQK76T

VERDICT: PASS


Generated by Claude Code

@objectstack-fleet

Copy link
Copy Markdown
Contributor Author

Queued with one red check, by design: Check Changeset (check-empty-changeset, the foreign changeset rule, #17712). domain:engine seat 1 (#6367) · session_01JfJfBUC3cQ6hhgm9MQK76T · 2026-10-11T13:26Z.

  • The gate and why it is red. This PR modifies .changeset/15206-reads-environment-only.md, a pending release note it did not add. The gate refuses that shape by name. This case is its DELIBERATE CORRECTION class, whose stated remedy is "do NOT restore it -- say so on the PR and get it confirmed".
  • Confirmed. The contract-review-tier record 6109504501 on this head (75b4c850fa) names the note and judges each changed sentence, and it attributes both red runs (114469951637 and 114477462305) to this rule alone.
  • Why the queue is not blocked by it. Check Changeset runs in pr-automation.yml, which triggers on pull_request only and never on merge_group. Every required context is green on this head.

Generated by Claude Code

@objectstack-fleet
objectstack-fleet Bot marked this pull request as ready for review October 11, 2026 13:28
@objectstack-fleet
objectstack-fleet Bot enabled auto-merge October 11, 2026 13:28
@objectstack-fleet
objectstack-fleet Bot added this pull request to the merge queue Oct 11, 2026
Merged via the queue into main with commit 8c105e3 Oct 11, 2026
42 of 44 checks passed
@objectstack-fleet
objectstack-fleet Bot deleted the claude/issue-15206-s5-carry-overs branch October 11, 2026 13:52
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.

2 participants