Repository navigation
fix(metadata-protocol): the metadata door serves a code-defined datasource's code definition while a stored row exists - #21985
Conversation
… code-defined datasource name getMetaItem, the flattened list and the layered read's effective layer now ask one predicate, declinesStoredRow, before they serve a stored row: a shipped flow name (the existing #20946 shape) or a datasource name the host registers from code (isDeclaredCodeDatasource). The read falls through to the MetadataService's in-memory code definition. The row stays found, so deletable still offers the /meta DELETE repair; a draft is answered as a draft; every other type keeps ADR-0005's order. Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi Co-authored-by: Claude <noreply@anthropic.com>
…hile a stored row exists One case in the restore file reads GET /api/v1/meta/datasource/:name and the /meta list over the showcase after a restart with a stored row under showcase_external and default: both serve the code definition, the envelope still offers the repair, and a runtime datasource's row is still served. The sibling file's post-restart read, which pinned the row's label, now expects the code definition. Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi Co-authored-by: Claude <noreply@anthropic.com>
The REST by-name answer is the served item; its envelope flags are pinned in metadata-protocol's unit suite, and the repair case that follows is the public proof that each row is still removable. Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi Co-authored-by: Claude <noreply@anthropic.com>
…fter the datasource decline isShippedFlowName's docblock names the layered read as deciding its effective layer with this predicate. It now asks it through declinesStoredRow, which also declines a code-defined datasource name's row; the published doors still ask this predicate alone. Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi Co-authored-by: Claude <noreply@anthropic.com>
…ow decline Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi Co-authored-by: Claude <noreply@anthropic.com>
…asource-row Claude-Session: https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 1 package(s): 4 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 3 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 11 page(s) merely mention a changed package (the pre-#9192 predicate, kept for the deliberately-wide backstop): Which tree this was computed onThis run read A worktree cut from an older # while this PR is open — GitHub drops the merge commit once it closes
git fetch origin 367b728fa515d782e99df401b2ed019cdc7d31dc && git checkout 367b728fa515d782e99df401b2ed019cdc7d31dc
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 8a399b2b150dcae74aebbfe9f28e0d63f527f349 65d7b740b2bda5e0f56c62292dde80fb62dd164c && git checkout -B drift-repro 8a399b2b150dcae74aebbfe9f28e0d63f527f349 && git merge --no-ff 65d7b740b2bda5e0f56c62292dde80fb62dd164c
node scripts/docs-audit/affected-docs.mjs --json 8a399b2b150dcae74aebbfe9f28e0d63f527f349
|
ACCEPT (seat review) — PR #21985 at head
|
…stored row under a code-defined datasource name The read half of #21922 landed on main after the 17.7.0 publish (1abfc58, #21985; not an ancestor of the version commit 4e4e881). Claude-Session: https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn Co-authored-by: Claude <noreply@anthropic.com>
…objectstack-ai#21994) Fixes objectstack-ai#21989 Clause-②: no ## What this is This PR adds the curated release page for 17.7.0, `content/docs/releases/v17/17-7.mdx` (1,402 lines). It was written after the publish: npm `latest` moved on 2026-10-06, and `@objectstack/spec@17.7.0` went out at 12:22:14Z. It also wires the page in: - `content/docs/releases/v17/meta.json`: `17-7` comes ahead of `17-6`. - `content/docs/releases/v17/index.mdx`: the status blockquote, the minors warning, the per-release list and the checklist links now name 17.7.0 as current. This is the same shape objectstack-ai#21362 used for 17.6.0. - `scripts/docs-audit/handwritten-docs.json`: the page joins the docs-accuracy audit scope. - `content/docs/releases/v17/17-6.mdx`: one dated correction (2026-10-06) on the anonymous-endpoints known issue. objectstack-ai#21158 was closed as not planned on 2026-10-04. The page follows the 17.6 page's structure: 1. Highlights. 2. What's new: the counts, and the runtime changes that happen silently. 3. Breaking changes and migration, triaged by door and subject. It includes a coverage table that names the section carrying the migration for every ADR-0087 entry added since 17.6.0 (8 conversions, 41 D3 entries). 4. New capabilities. 5. Notable fixes. Security fixes are described only as classes and doors. 6. New in Console: each objectui declared-breaking change, with this repo's answer. 7. The code that shipped but is not listed. 8. The upgrade checklist. Every line is marked *Not exercised.* ## Sources and counts - The version commit `4e4e881427` (objectstack-ai#21352) consumed 323 changesets. 322 are new. One, `748b240` (objectstack-ai#21270), shipped in 17.6.0 and is listed again; the page explains this in its own section. - 69 CHANGELOGs carry a 17.7.0 section, and 48 of them have entries. Their 444 entries (194 minor, 250 patch) de-duplicate to the 323 changesets. - Two commits landed on `main` after the Version Packages PR's last refresh and before it merged: - `8a399b2b15` (objectstack-ai#21977, for objectstack-ai#21923) - `04e776b39a` (objectstack-ai#21976, for objectstack-ai#21968) Both are ancestors of the version commit (exit 0 for each), so the 17.7.0 packages carry their code. But the version commit did not consume their changesets, so no 17.7.0 CHANGELOG line names them. The release-integrity audit named both in a warning. - objectui: the pin moved five times and ends at `0abd4f9f8769`: - `89cad75d5570` (objectstack-ai#21380) - `ab1879721595` (objectstack-ai#21625) - `2e818d0b51ec` (objectstack-ai#21710) - `9dfaca654311` (objectstack-ai#21800) - `0abd4f9f8769` (objectstack-ai#21827) Across 173 commits, 254 changesets were added and 229 of them release something. 65 are declared breaking, plus one commit marked `!`. The Console table answers each. - `PROTOCOL_VERSION` is still 17.0.0. ## Premise corrections - The dispatch named two pin moves ending at `9dfaca654311`. The tree has five, ending at `0abd4f9f8769` (`8832655`, objectstack-ai#21827). The page covers all five. - One new D3 id contains a word that `check:role-word` refuses on docs pages, and release pages are not in its baseline. The coverage table therefore names that entry by its subject and points at `os migrate meta --from 17`. ## Fact-check A second pass checked every cited SHA, PR number, key name and behavioural claim against the commits, changesets and registry entries. It corrected **31 claims** (commit `e4a6280b46`). Two more corrections came earlier, while drafting: - objectstack-ai#21361 is closed; it is not tracking the issue. - There are seventeen `ui-object-*` members, not eighteen. Mechanical checks on the final page: - All 276 cited SHAs resolve, in objectstack or in an objectui clone carrying the final pin's history. - Each of the 216 distinct sha–PR pairs matches its commit subject. - Every printable new D3 id (40) and all 8 conversions appear on the page. - The MDX for 17-7, 17-6 and index compiles with @mdx-js/mdx 3.1.1 and remark-gfm 4.0.1. After the fact-check, `main` gained `1abfc58` (objectstack-ai#21985), which lands the read half of objectstack-ai#21922. It is not an ancestor of the version commit: the test returned exit 1, and the control commit `753e7a1` returned exit 0. So in 17.7.0, the metadata door's reads still serve a stored row under a code-defined datasource name. Commit `8347e0d172` says so in the datasource migration bullet. ## Independent fact-check and fix round An independent, read-only fact-check of head `8347e0d172` returned **FAIL** with 13 findings (record: objectstack-ai#21989 comment 6018402030): 2 wrong facts (a flow's `get_record` node still reads the stored-metadata tables, in projected form), 1 security line that named the filter shapes and depth threshold evading 17.6.0's refusal, 1 breaking change missing from the Breaking section (`0fc8087`, objectstack-ai#21626), 1 link whose label did not match its target, 4 overstatements, 1 missing security fix (`49524f6`, objectstack-ai#21420), 1 missing rollback caveat on `os secret rewrap --apply`, and 2 minor wording issues. All 13 were re-verified against their sources and applied in `a53c972fa7`; none was refuted. A scan for any other line naming a bypass shape of a fixed issue reduced two more lines to their class (`0728cbf`, `fb69825`). ## Measured on `a53c972fa7` - Derived gates: `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` derives 57 commands; all 57 exited 0 (`--ran`: "57 run, 0 NOT-MEASURED (a DERIVED zero)"). The verdicts include: - check-doc-anchors: 458 links resolve. - check-issue-citations: 305 resolve as a PR, 9 resolve, 15 are cross-repo; every citation this change adds resolves. - `check:role-word`, `check:release-notes` and `check:release-page-status`: OK. - check-release-section-coverage: OK, both plain and with `--strict` (10 minors). - The docs-audit scope check and `check:nul-bytes`: OK. - Docs production build: `TURBO_FORCE=true pnpm turbo run build --filter=@objectstack/docs`, run under the verify lock, reports `Tasks: 2 successful, 2 total` and `Cached: 0 cached`. The built `releases/v17/17-7.html` carries the corrected text and none of the removed text. - Mechanical checks: 231 distinct sha–PR pairs, 0 unresolved, 0 subject mismatches; the MDX of 17-7, 17-6 and index compiles. - Not measured locally: the repo-wide lint and the CI-only families. CI owns them. ## Not in this PR - No changeset. The PR touches only docs and a docs-audit list, nothing a package ships (`skip-changeset`). - Nobody has walked the 17.6.0 → 17.7.0 upgrade. The checklist says so on each line. --- _Generated by [Claude Code](https://claude.ai/code/session_016tKoy8NJa35Yih1FdzrVmn)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
Fixes #21922
Clause-②: no
What changes
The metadata door no longer serves a stored
sys_metadatarow under a datasource name the host registers from code. While such a row exists,GET /api/v1/meta/datasource/:name, theGET /api/v1/meta/datasourcelist and theeffectivelayer of/layersall serve the in-memory code definition. That is what the admin door and the boot restore already serve. This is the remaining half of this card, after the boot restore and admin door half landed in PR #21965.The change is the shape triage's answer A names (comment 6013780883), the one the shipped-flow read already uses. In
packages/metadata-protocol/src/protocol.ts:declinesStoredRow(type, name)asks "is this a name whose stored row the active reads never adopt?". It holds for exactly two kinds of name: a shipped flow name (isShippedFlowName, unchanged) and a code-defined datasource name (isDeclaredCodeDatasource, unchanged, reading the host'scode-datasource-namesset and then the installed packages). Its registry twin,isStoredEntryOfDeclinedName, catches the hydrated bare-key copy of such a row (!isCodeArtifactBody). ⛔ No third precedence path, and ⛔ no decision read from anyorigin.getMetaItem: the stored-row step skips the row on the ACTIVE read (storedRowDeclined). Step 2 then serves the MetadataService's in-memory registration. The row is still FOUND (storedRowServed), soservedLockStatekeepsdeletable: truewhile it exists. A draft read (state: 'draft', orpreviewDrafts) is answered as a draft.readFlattenedMetaItems, both faces): the registry half drops the hydrated copy, and the stored-row half keeps the row out of the merge and out of the package stand-ins. The MetadataService base, merged in below, supplies the code definition.getMetaItemLayered:effectiveasks the same predicate. The spec defineseffectiveas "the value an ordinaryGET /meta/:type/:namewould return underitem" (GetMetaItemLayeredResponseSchema), so it has to move with the by-name read. The row stays reported inoverlay./metaDELETE path is untouched: its own row probe still finds and removes the row.Hypotheses (Partition 2), measured at
f76c6221acgetMetaItemadopted the row atprotocol.ts:10038(findServedOverlayRow) and:10047(if (record && !shippedFlowActiveRead)), whatever the MetadataService held.:9952–:9978), the list's registry half (:8923–:8926) and the list's stored-row half (:8979–:8998).isDeclaredCodeDatasource(:16326) reads the host set at:16328. Measured addition: the list needs the registry half too. The boot pull (loadMetaFromDb) and an unscoped list hydrate the row under the bare key, and the registry layer is merged OVER the MetadataService base (:9287). Ablation B below shows that half is load-bearing on its own.getMetaItemneeds no registry half for datasource, because step 2 answers before step 3.servedLockState(:16488) readsstoredRowServed, which is set from the row FOUND (:10045) before the adoption check, so a declined row keepsdeletable: true. That is exactly whatoriginGatedRemovalRefusal's docblock states ("deletabletrue exactly while the read found a stored row").deleteMetaItem(:25771) decides from its own probe and never reads the served body. The_lockgate's overlay layer is read whether or not the row is adopted, as for flows. Triage's stop condition is not met.protocol.tshunks at:2003,:9051–:9107,:9610–:9715and:19206(read from its pushed branch). None of this PR's sites overlaps them.origin/mainwas merged (65d7b740b2); finding(metadata-protocol): a saved env-wide copy of a view container leaves its own expansion alone per name in the env-wide view list, so the anonymous form doors can miss another package's withdrawal, saved or shipped (#21934 item 1's residual) #21967 had not landed.Pins
metadata-protocol(protocol.code-defined-datasource-door.test.ts, a new[#21922]block on both kernel shapes, 14 cases):defaultthrough the host set, and the layeredeffective;effective;findnow honourswhereandlimit(it returned every row), and its registry gains an opt-in hydrating slot plusisPackageDisabled, which the list calls.datasource-restore-code-wins.dogfood.test.ts, one case): after a restart over stored rows undershowcase_externalanddefault,GET /api/v1/meta/datasource/:nameand the/metalist serve the code definition (label,origin: 'code',_packageId, and never the row's file). A runtime datasource's row is still what both doors serve. The repair case that follows still removes each row.meta-door-code-datasource.dogfood.test.tsreadSHADOW_LABELafter its restart. That read pinned exactly the branch this change removes, so it now expects the code label. The restore file's header paragraph saying the meta door serves the row is replaced.Reverse verification (on committed HEAD, restore proven by blob)
return typeof name === 'string' && name !== '' && this.isDeclaredCodeDatasource(type, name);was replaced byreturn false; // ABLATION-21922viascripts/ablation-replace.mjs(anchor 1 to 0, blob7890c4b99f6eto6d70364fc822). 10 failed / 41 passed: (a)x4 and (d) on both kernels. (b) and (c) stay green, as predicted, because they do not depend on the decline. Restored: blob == HEAD (7890c4b99f6e),git diff HEADempty.isStoredFlowEntryOfShippedName. 4 failed / 47 passed: exactly the post-hydration list cases (showcase_externalanddefault) on both kernels. Restored by blob.dist/): the same arm replaced byreturn name === 'ABLATION-21922';, thenpnpm --filter @objectstack/metadata-protocol build, thenablation-dist-preflight.mjs @objectstack/metadata-protocol ABLATION-21922(exit 0, marker in dist), then both dogfood files: 3 failed / 9 passed. The two direct reds are the new case and the flipped read. The third ("after the repair and a restart") is a cascade: the flipped case failed before its DELETE, so the row survived the restart and the ablated read served it. Restore leg: blob == HEAD, rebuild, thenablation-dist-preflight --absent(exit 0).Readings at head
65d7b740b2(after theorigin/mainmerge)pnpm --filter @objectstack/metadata-protocol test: 218 files passed, 3 skipped; 28010 tests passed, 19 skipped.typecheck(tsc --noEmit): exit 0. The packagetsconfigincludessrc/**/*, so the edited test file is compiled.pnpm --filter @objectstack/spec check:generated: all 15 artifacts up to date (spec moved on themainside of the merge).node scripts/pm/dispatch-gates.mjs --commands(no paths; 5 paths vs merge base04e776b39): 68 derived;--ran: 68 run, 0 NOT-MEASURED, 0 UNRUN.check:dual-build-cjs-loadsfirst answeredPREREQUISITE NOT MET(exit 3, eight packages outside the dogfood closure had nodist/). It answered exit 0 after those were built, and the record carries that reading.check-closing-target-claim,check-partof-closing-keywordandcheck-single-claim-pathsanswered NOT WIRED (exit 2: no PR context existed yet). They are NOT MEASURED here, and the report carries their run against this PR.check:adr-symbol-anchors,check:scripts-symbol-anchors,check:spec-docblock-symbol-anchorsandcheck:adr-anchorsall exit 0.pnpm lintis CI's):eslint --no-inline-config --format jsonover the four touched TypeScript files at65d7b740b2reports 4 files linted, none ignored, 0 errors and 0 warnings. The config never enables type-aware linting (noparserOptions.project, aseslint.config.mjsstates), so this diff cannot move any untouched file's verdict.Changeset, measured against the built entry declarations
@objectstack/metadata-protocolpatch, withClause-②: no. The BASE (f76c6221ac) and HEAD builds ofdist/index.d.tsandindex.d.ctsdiffer in two non-comment lines only:private declinesStoredRow;andprivate isStoredEntryOfDeclinedName;onObjectStackProtocolImplementation. No public member, parameter or return type moves.Named gap, not moved here: the
/publisheddoorMeasured with a throwaway probe at
8c4bd140de(never committed): after a restart over a stored row,GET /api/v1/meta/datasource/showcase_externalserves the code definition, and/layersanswerseffective= code withoverlay= the row.GET /api/v1/meta/datasource/showcase_external/publishedstill serves the row (origin: runtime). The REST door and its runtime dispatcher twin decide by asking the publicisShippedFlowNamealone. The spec describes that route as serving the active overlay row, so no published text is made false. But following the layered read there needs a public predicate on the exported class, which is a widening this claim'sClause-②: nodoes not cover. It is reported to the seat with its options.Acceptance notes
getMetaItem's step-3 registry half stays flow-only. A code datasource is never a SchemaRegistry item, and step 2 has already served it.git grepfinds no reader of registrydatasourceentries outsideprotocol.ts.isDeclaredCodeDatasource(manifest) with no in-memory registration. For such a name the reads now fall through to whatever the MetadataService loaders hold, not the row.git checkout HEAD -- protocol.ts) discarded one uncommitted docblock edit. It was detected by a marker count, re-applied and committed as0b2a164598. Every reading above was taken after it.Generated by Claude Code