Repository navigation
fix(service-datasource,metadata-core)!: the datasource-admin plugin reads environment rows only; a legacy organization-scoped datasource row is neither restored nor served (ADR-0131 D6) - #22862
objectstack-fleet[bot] merged 3 commits into
Conversation
…t rows only (ADR-0131 D6) Every read of a stored datasource row in DatasourceAdminServicePlugin -- the boot restore, the by-name read behind convergence and the /meta projection, and the existence probes behind its own writes -- goes through one find filtered by isLegacyOrganizationRow, now exported from @objectstack/metadata-core; objectql's binders import the same predicate. Claude-Session: https://claude.ai/code/session_01JfJfBUC3cQ6hhgm9MQK76T Co-authored-by: Claude <noreply@anthropic.com>
…tasource-restore-environment-rows
Claude-Session: https://claude.ai/code/session_01JfJfBUC3cQ6hhgm9MQK76T Co-authored-by: Claude <noreply@anthropic.com>
📓 Docs Drift CheckThis PR changes 3 package(s): 14 hand-written doc(s) NAME something this change touched and may need an implementation-accuracy re-verification:
⛔ 7 release-owned page(s) also name something this change touched. These are read-only:
What this run could not see
Coarse fallback — 19 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 3a78f8bf4e6c14b90e02f888429c7746bd7bc465 && git checkout 3a78f8bf4e6c14b90e02f888429c7746bd7bc465
# afterwards, rebuild it from the two parents, which stay fetchable
git fetch origin 55382dc02a2168585a024ba142b98d8bce9910e6 b02b3f8c16ea80da01d042242fd60bf83764f03b && git checkout -B drift-repro 55382dc02a2168585a024ba142b98d8bce9910e6 && git merge --no-ff b02b3f8c16ea80da01d042242fd60bf83764f03b
node scripts/docs-audit/affected-docs.mjs --json 55382dc02a2168585a024ba142b98d8bce9910e6
|
Contract reviewServed-tier: Inputs: card #22835's body and both of its comments (claim 6109766787, os-dev-report 6110648560); #15206's os-dev-report 6108953446 (finding 1) and PR #22833's contract review 6109504501 (③); PR #22862 (its body, its 9-file list, and the net diff against the merge base ① Derived judgmentsGate verdicts on this head, as read at 2026-10-11T15:51Z. 41 check-runs: 31
Nothing is deleted or rewritten. No added line calls a store write; the inserts, updates and deletes are the same calls as before, now keyed on an environment row's id only. ② Semver level
③ Boundary flagsDeviations (a) to (h), each answered. (a) file surface: the The two out-of-scope findings. (1)
Check-runs red or in progress. Red: none. In progress at the reading: For the seat at landing. The PR's Verdict grounds, all on this head: every read of a stored Implemented-by: VERDICT: PASS Generated by Claude Code |
Fixes #22835
Clause-②: no (narrowing)
Summary
DatasourceAdminServicePluginnow reads the environment's storeddatasourcerows only. This is the ADR-0131 D6 read narrowing that #15206's C5 applied to every metadata read, carried to@objectstack/service-datasource, which S5's enumeration missed. A row stored in a legacy organization's layer is no longer restored at boot, served by the datasource-admin door or the metadata read door, pooled, updated or deleted by this plugin. The boot report[metadata_org_scoped_unserved]keeps naming it, and it stays at rest: neither deleted nor rewritten (#22011 A).This makes the pending v18 note
.changeset/15206-reads-environment-only.md("served by no read and loaded by no boot") true fordatasource. That note is not edited. #21922's "code wins on collision" restore design is unchanged.Classes, positions and function names only (security card).
What changed
isLegacyOrganizationRowwas module-private inpackages/objectql/src/plugin.ts. It moves verbatim topackages/metadata-core/src/legacy-organization-row.tsand is exported from@objectstack/metadata-core, which bothobjectqlandservice-datasourcealready depend on (no new edge).metadata-corehad no equivalent (searched for organization-row predicates).objectql's three binder call sites now import it, unchanged, and the private copy is deleted.findEnvironmentRows(datasource-admin-plugin.ts) runs afindonsys_metadata(typedatasource, stateactive, optionally one name, system context) and drops each rowisLegacyOrganizationRownames. Census of the plugin's reads of stored rows, each now behind it:loadDatasourceRows: the boot restore (restoreRuntimeDatasources);loadDatasourceRow: the by-name read behind cluster convergence (convergePool) and the/metadatasource projection (applyMetadataDoorWrite);persistDatasourceRow: the existence probe behind admin create and update;deleteDatasourceRow: the existence probe behind admin remove.findOneto a filteredfind. A single row picked by the store could be the legacy one while the environment's row exists.findOneleaves the plugin's private engine slice type (DataEngineLike), where it is now unused.listDatasourceRecords,getDatasourceRecord,countBoundObjects*) read the MetadataService, notsys_metadata. They stop serving the legacy row because the restore no longer registers it. The measurement below shows this; hypothesis 3 holds.Measurement: real boot, before and after
Showcase via
bootShowcaseover a database file. Twodatasourcerows were stored with system context between two boots: one legacy organization-scoped, one environment. Readings come from the second boot.d489318714)fccd98aea0)[metadata_org_scoped_unserved]The last row is the same missing predicate on a write path's existence probe (
deleteDatasourceRow). The same fix closes it.Pins
packages/qa/dogfood/test/datasource-restore-environment-rows.dogfood.test.ts(new): a real boot, a restart and both doors. Three cases:/metasave under the name is what the admin door then serves. The legacy row equals its stored bytes at the end.packages/services/service-datasource/src/__tests__/datasource-admin-plugin.test.ts, describe[#22835](two cases, on the file's existingsys_metadatadouble):organization_idis the empty string counts as the environment's, per the one predicate./metaprojection, admin update, remove and create all reach the environment row, and a legacy-only name is neither registered nor pooled. The legacy row stays byte-equal.datasource-system-context.pin.test.ts: population count moved fromfindOnetofind(5 reads); the system-context assertion is unchanged.packages/metadata-core/src/legacy-organization-row.test.ts(new): the predicate's truth table.Ablation (direction observed: red, as expected)
fccd98aea0, mutated withscripts/ablation-replace.mjs. InfindEnvironmentRows, the anchor.filter((row) => !isLegacyOrganizationRow(row));was replaced by a filter that keeps every row, which reproduces the pre-fix reads.ed525af25cfdbecamef7140c73d786.pnpm --filter @objectstack/service-datasource build, thenablation-dist-preflight.mjs @objectstack/service-datasource ABLATION-22835found the marker present indist/index.jsanddist/index.cjs.-t 22835): 2 failed. Readings: the legacy row was registered at restore, and the projection served the legacy body.ablation-replace --restorethengit checkout HEAD -- PATH. The blob equals HEAD (ed525af25cfd),git diff HEADis empty and the tree is clean. After a rebuild, the preflight with--absentconfirms the marker is absent from all 24 built files.Tests and gates
Head
b02b3f8c16is a merge oforigin/maineed637c09f(four commits, none touching these packages), plus a comment-only trim of the dogfood header.@objectstack/service-datasource:vitest run, 42 files and 765 tests passed.typecheckexit 0, andtsc --listFilesincludes both edited test files.@objectstack/metadata-core:vitest run, 17 files and 386 tests passed.typecheckexit 0, covering the new test through both tsconfigs.@objectstack/objectql: the 16src/plugin*.test.tsfiles plushook-binder-package-scope.test.ts, 17 files and 158 tests passed.typecheck(includingcheck:test-typecheck) exit 0.@objectstack/dogfood, isolated project: this pin plus the four sibling datasource files (datasource-restore-code-wins,datasource-meta-door-reaches-admin-door,meta-door-code-datasource,external-import-code-datasource-namespace), 5 files and 28 tests passed at6e19243562. The pin was re-run atb02b3f8c16: 3 of 3 passed. Dogfoodtsc --noEmitexit 0 and lists the new file.eslint --no-inline-config --format jsonon the 8 edited TS files reported 8 files, 0 errors and 0 warnings.eslint.config.mjsenables no type-aware linting (noparserOptions.project), so untouched files cannot move. A repo-widepnpm lintis left to CI.node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstackderived 70 commands atb02b3f8c16, a superset of the 46 named at dispatch. All 70 ran sequentially, with the exit code captured before any pipe.--ranreconciliation: 70 derived, 70 run, 0 NOT-MEASURED, 0 UNRUN.check:dual-build-cjs-loadsfirst exited 3 (PREREQUISITE NOT MET: eight unrelated packages unbuilt). After building them it re-ran and exited 0.check-adr-0087-registrationreports 1 declared-breaking changeset, carrying its disposition.check-empty-changesetreports no foreign changeset modified.check-changeset-no-majorreports nomajor.Changeset
.changeset/22835-datasource-reads-environment-only.md:@objectstack/service-datasourceminor (the narrowing),@objectstack/metadata-coreminor (the new export),@objectstack/objectqlpatch (the import, no behaviour change).Clause-②: no (narrowing)with a FROM to TO row.metadata-read-organization-scope-retired). That entry already retires every read of an organization-scoped row, boot hydration included.Line budget
+325 / -38 across 9 files against the merge base. Source is +59 / -36:
metadata-core+24,objectql+4 / -11,datasource-admin-plugin.ts+31 / -25, mostly comments.The advisory total of 300 is exceeded by the real-boot pin, 164 lines. It boots twice, mounts the admin routes and reads both doors and the store; its sibling datasource dogfood files run 250 to 300 lines.
Acceptance notes (observations, not filed)
metadata-protocol'sreportUnhydratableOrgScopedRowsre-checks the same row value inline instead of importing the predicate. That is a second spelling in a third package, thoughmetadata-protocoldepends onmetadata-core. It is outside this claim's file surface. Carrier: none.objectqltest pins the hook and action binders' skip of a legacy organization row. This PR moves that predicate without changing it. Carrier: none.Contract review
This PR owes one contract-review-tier review before the queue (
Clause-②narrowing), to be arranged by the seat.Generated by Claude Code