Repository navigation
security(datasource): the boot's datasource restore reads stored datasource rows with no environment predicate, so a legacy organization-scoped row is loaded and served though #15206's reads are environment-only #22835
Description
This card carries a gap in #15206's own deliverable (part of #15206). It was found and measured by #15206's carry-over dev (os-dev-report 6108953446, out-of-scope finding 1, PR #22833). ⛔ Classes, positions and functions only.
Filing class: ① a product defect, class (a). Reach: measured at public doors on a real boot (tree 1fec8e5dd0, PR #22833's head). The datasource-admin door and the metadata read door answer 200 with a legacy organization-scoped datasource row that the boot's restore registered.
Reader who acts: the domain:engine lane, seat 1 (#6367), which owns #15206. The fix lands in packages/services/service-datasource (domain:services), so the claim declares the cross-domain path. Why it is in scope of #15206, and not a new class:
- C5 promised that every read stops serving a legacy organization-scoped
sys_metadatarow (ADR-0131 D6). - The pending v18 note,
.changeset/15206-reads-environment-only.md, states that such a row "is served by no read and loaded by no boot". - This read was missed by S5's enumeration. That note must not ship false.
The gap
DatasourceAdminServicePlugin.restoreRuntimeDatasources→loadDatasourceRows(packages/services/service-datasource/src/datasource-admin-plugin.ts) reads storeddatasourcerows by type and state, with no organization predicate.- So a legacy row carrying a non-empty
organization_idis loaded at boot and registered. - Once registered, it is served by the datasource-admin door and by the metadata read door, while the boot report
[metadata_org_scoped_unserved]names the same row as served by no read and loaded by no boot.
Direction (not a ruling)
- The restore reads environment rows only, the same predicate C5's reads apply (
organization_idabsent, read as a row value the wayisLegacyOrganizationRowreads it). ⛔ No second rule. Reuse the predicate C5 uses if it is exported, or say why not. - A legacy row stays in the boot report and is neither deleted nor rewritten (decision: ADR-0131 C5 — under
single, Studio saves are stored organization-scoped today. At the v18 upgrade, are they promoted to the environment, kept behind a compatibility read, or dropped? #22011 A). It is loaded and served by nothing. - Pins: a legacy organization-scoped
datasourcerow is not restored and not served (404/ absent on both doors); an environment row is unchanged. Ablation-verified, on a real boot. Clause-②: a narrowing, measured and stated by the PR. It owes a contract-review-tier review before the queue.- Release:
target:v18. It must land before the release that ships.changeset/15206-reads-environment-only.md.
Duplicate check
- Population: 4,522 issues, closed included, ADR-0048: Semantic-reference metadata embeds in package docs (flow:// / view:// / dashboard:// / object://) #1795 to docs(adr-0131): D6's second install mode is spelled
unmanaged. A dated Tier H note executing ruling B on #15213 (thetemplatespelling at D6, §6 Q5 and the C12 row) #22831 (REST paging since 2026-09-01, plus windows since 2026-10-11T06:00Z and 11:00Z). A local grep over titles and bodies. - Queries:
loadDatasourceRows: 1, closed (security(service-settings, service-messaging, service-datasource, plugin-webhooks): plumbing reads and writes reach the engine with no principal and no system opt-in — the services-lane producers of #21908's closure #21913).restoreRuntimeDatasources: 2, both closed ([finding] QA observed /api/v1/meta/datasource serving a deleted datasource's entry cluster-wide, but MetadataManager's unregister DOES broadcast — the observed prolongation sits in an unidentified seam #13609, finding(service-datasource): a stored datasource row overrides a code-defined datasource at boot, so after a restart the admin door serves and edits it at runtime (restoreRuntimeDatasources has no code-collision check) #21922).datasource-admin-plugin: 3, all closed (security(service-settings, service-messaging, service-datasource, plugin-webhooks): plumbing reads and writes reach the engine with no principal and no system opt-in — the services-lane producers of #21908's closure #21913, finding(service-datasource): a stored datasource row overrides a code-defined datasource at boot, so after a restart the admin door serves and edits it at runtime (restoreRuntimeDatasources has no code-collision check) #21922, finding(service-datasource): a datasource created through the metadata door is missing from the admin door until restart, then reads as code-defined because the admin read defaults a missing origin to code #21923).- datasource together with a legacy or organization-scoped row and restore or boot: 4, all closed.
- The open cards in the same family, read directly, do not cover it: feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211 (C7: inventory and migration, S6) and [finding] rest: the 20 write routes #22719 repaired still answer a crashed hook body with its native fault text, and a declared-5xx refusal unwithheld, where the data doors answer
Internal server error#22764 (rest write routes).
- Controls:
datasource125;organization_id90.
Generated by Claude Code
Activity
objectstack-fleet commentedon Oct 11, 2026
Claim: PM loop round 3 · 2026-10-11T13:55Z
Session:
session_01JfJfBUC3cQ6hhgm9MQK76TAccount:
os-project-manager(the seat's linked user, asGET /useranswers it; the card's assignee)Branch:
claude/issue-22835-datasource-restore-environment-rowsWorktree:
objectstack-issue-22835Domain:
domain:engineSeat:
domain:engine#1(seat post #6367)File surface (read on
origin/maind489318714; stop on a breach and explain it in the report):packages/services/service-datasource/src/datasource-admin-plugin.ts(domain:services, declared on [PM seat] domain:services — ⏳ vacant #6021 at dispatch):loadDatasourceRows, behindrestoreRuntimeDatasources, and every other read in this plugin of storeddatasourcerows (the by-name durable read included) reads environment rows only. A legacy row carrying a non-emptyorganization_idis neither loaded nor served.isLegacyOrganizationRow(private inpackages/objectql/src/plugin.ts). ⛔ No second rule: if it moves to a package both sides depend on (for example@objectstack/metadata-core), the move is part of this claim and is stated; if the measurement says a copy is unavoidable, stop and report.packages/qa/dogfood(domain:cli, test-only), and the changesets.Container & model:
M,mode:subagent,model: default(dispatch-gates --tier: no path-derived mandate)Clause-②: no (narrowing)
datasourcerow that was loaded at boot and served is no longer loaded or served. That narrows a published door's answer, and it is what the pending v18 note (.changeset/15206-reads-environment-only.md) already states. It owes one contract-review-tier review before the queue. The arm is measured and stated by the PR.Responsibility:
DatasourceAdminServicePlugin.restoreRuntimeDatasources→loadDatasourceRows, which reads storeddatasourcerows by type and state with no organization predicate | ADR-0131 D6 and C5's environment-only reads (feat(metadata-core,metadata-protocol,objectql,plugin-security): thesys_metadatafamily goes tenant-less; the per-organization overlay axis retires; managed content is sealed (ADR-0131 D6/D7/D13) #15206), which the pending v18 note states as done | any deployment carrying a legacy organization-scopeddatasourcerow; measured on a real boot by feat(metadata-core,metadata-protocol,objectql,plugin-security): thesys_metadatafamily goes tenant-less; the per-organization overlay axis retires; managed content is sealed (ADR-0131 D6/D7/D13) #15206's carry-over dev (os-dev-report 6108953446, finding 1)Thread-read: none
Inputs read: the card body; feat(metadata-core,metadata-protocol,objectql,plugin-security): the
sys_metadatafamily goes tenant-less; the per-organization overlay axis retires; managed content is sealed (ADR-0131 D6/D7/D13) #15206's os-dev-report 6108953446 (finding 1); PR 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's contract review 6109504501 (③, security(datasource): the boot's datasource restore reads stored datasource rows with no environment predicate, so a legacy organization-scoped row is loaded and served though #15206's reads are environment-only #22835 as the carrier)Serial constraints cleared:
sys_metadatafamily goes tenant-less; the per-organization overlay axis retires; managed content is sealed (ADR-0131 D6/D7/D13) #15206's carry-overs) landed as8c105e3d67. It moved the boot report's datasource naming inmetadata-protocol, a different package.datasource-admin-plugin.ts.afterInserthook withonError: 'abort'(the default) that throws rejects the write, but the row stays stored —HookSchema.onErrorsays abort rolls the transaction back, and a plain write opens none #22782 (objectql's write door) and data: under a search, a paged list'stotalis a page-local estimate (offset + page + 1), and the console shows it as the record count ("26 records" for 300 matches) #22790 (ObjectQL.count,findData). Both are disjoint files.Generated by Claude Code
test(dogfood): condense the #22835 real-boot pin's header
objectstack-fleet commentedon Oct 11, 2026
os-dev-report
{ "issue": 22835, "status": "done", "branch": "claude/issue-22835-datasource-restore-environment-rows", "pr": "https://github.com/objectstack-ai/objectstack/pull/22862", "session": "session_01JfJfBUC3cQ6hhgm9MQK76T — subagent of the PM session; identity = the branch named in claim 6109766787 (newest Claim: on the card, verified)", "premise_still_valid": true, "summary": "Draft PR #22862 (head b02b3f8c16 = fccd98aea0 fix + merge of origin/main eed637c09f + a comment-only trim). Every read of a stored datasource row in DatasourceAdminServicePlugin (packages/services/service-datasource/src/datasource-admin-plugin.ts) now goes through one private helper, findEnvironmentRows: a find on sys_metadata (type datasource, state active, optional name, system context) that drops each row isLegacyOrganizationRow names. Census of the reads, all behind it: loadDatasourceRows (boot restore, restoreRuntimeDatasources); loadDatasourceRow (convergePool and the /meta projection applyMetadataDoorWrite); the existence probes of persistDatasourceRow (admin create/update) and deleteDatasourceRow (admin remove). By-name reads moved findOne -> filtered find (a store-picked single row could be the legacy one while the environment row exists); findOne left the private DataEngineLike slice, now unused. listDatasourceRecords/getDatasourceRecord/countBoundObjects* read the MetadataService and stop serving the row because the restore no longer registers it (measured). PREDICATE: isLegacyOrganizationRow moved verbatim from objectql/src/plugin.ts (module-private) to packages/metadata-core/src/legacy-organization-row.ts, exported from @objectstack/metadata-core (both sides already depend on it; metadata-core had no equivalent, searched); objectql's three binder call sites import it, private copy deleted. No second rule. MEASURED ON A REAL BOOT (bootShowcase, database file, one legacy organization-scoped and one environment datasource row stored with system context between two boots): BEFORE d489318714 — admin door by-name 200 + listed; metadata read door by-name 200 (legacy body) + listed; a live pool opened from the legacy row; [metadata_org_scoped_unserved] named the row; admin create under its name 400 already-exists; admin remove 204 AND DELETED the legacy row. AFTER fccd98aea0 — both doors 404 + absent from both lists; no pool; environment row 200/200/pooled unchanged; the boot report still names the legacy row; admin create 201 writes an environment row, remove deletes only that, legacy row byte-equal. PM hypotheses: 1 holds (and the census found two more reads, the write probes, one of which deleted the legacy row); 2 holds (moved, not copied); 3 holds (the metadata read door's 200 came from the registry the restore filled). DEVIATIONS: (a) file surface: metadata-core (new module, index export, truth-table test) and objectql/src/plugin.ts (import swap) — the move the claim names as part of it; (b) DataEngineLike drops the now-unused findOne (same file); (c) merged origin/main eed637c09f before gating (4 commits, none in these packages; lockfile moved -> pnpm install, closure rebuilt); (d) changeset bumps service-datasource minor, metadata-core minor (new export), objectql patch (import only); ADR-0087 marker not-required (already-registered metadata-read-organization-scope-retired); (e) line budget over the 300 advisory (see LINE_BUDGET); (f) zero label writes (none named; skip-changeset not applicable); (g) a temporary measurement copy of the dogfood file was run and deleted, never committed; (h) commit trailers are the model-free pair per AGENTS.md (the harness reminder asked for a model-bearing Co-Authored-By; AGENTS.md wins). FILES_CHANGED (vs merge base eed637c09f, numstat): .changeset/22835-datasource-reads-environment-only.md +27; packages/metadata-core/src/index.ts +5; packages/metadata-core/src/legacy-organization-row.ts +19; packages/metadata-core/src/legacy-organization-row.test.ts +16; packages/objectql/src/plugin.ts +4/-11; packages/services/service-datasource/src/datasource-admin-plugin.ts +31/-25; packages/services/service-datasource/src/__tests__/datasource-admin-plugin.test.ts +56; packages/services/service-datasource/src/__tests__/datasource-system-context.pin.test.ts +3/-2; packages/qa/dogfood/test/datasource-restore-environment-rows.dogfood.test.ts +164 (182 at fccd98aea0, header condensed in b02b3f8c16). LINE_BUDGET: +325/-38 total (363 changed) vs the 300 advisory — over by the real-boot pin (164 lines: two boots, admin routes mounted, both doors and the store read; sibling datasource dogfood files run 250-300); source +59/-36 (additions within the 60 advisory; mostly TSDoc). Worktree removed after this report (node_modules first); no background process left running. PR BODY CORRECTION for the seat (the dev writes the body once, no PATCH): under '## Line budget', '+343 / -38 across 9 files' should read '+325 / -38 across 9 files' — 343 was counted at 6e19243562, before the comment-only header trim in b02b3f8c16; every other number in the body is at its stated sha.", "tests": "ALL AT b02b3f8c16 UNLESS NAMED. BUILD (os-verify-lock, slot issue-22835): pnpm turbo run build --filter=@objectstack/dogfood^... --concurrency=2 after the merge -> VERDICT command-exit 0 (63 tasks); service-datasource/metadata-core/objectql dist carry isLegacyOrganizationRow (objectql dist: 3 call sites importing it). UNIT (vitest --maxWorkers=2, at 6e19243562 = merge, source identical to b02b3f8c16): @objectstack/service-datasource vitest run -> 42 files / 765 tests passed; typecheck exit 0 (tsc --listFiles includes both edited test files); @objectstack/metadata-core vitest run -> 17 files / 386 passed; typecheck exit 0 (new test listed by both tsconfigs); @objectstack/objectql src/plugin*.test.ts (16 files) + hook-binder-package-scope.test.ts -> 17 files / 158 passed; typecheck incl check:test-typecheck exit 0. DOGFOOD (isolated project): datasource-restore-environment-rows + datasource-restore-code-wins + datasource-meta-door-reaches-admin-door + meta-door-code-datasource + external-import-code-datasource-namespace -> 5 files / 28 tests passed at 6e19243562; pin re-run at b02b3f8c16 -> 3/3 passed, with plugin.test + system-context pin 2 files / 30 passed; dogfood tsc --noEmit exit 0, new file in --listFiles. BEFORE/AFTER real-boot readings: see summary (temporary measurement copy, run at d489318714 [base] and fccd98aea0, deleted). ABLATION (committed fccd98aea0, trap-guarded script): ablation-replace.mjs anchor '.filter((row) => !isLegacyOrganizationRow(row));' -> keep-every-row filter carrying marker ABLATION-22835; anchor x1->x0, marker x0->x1, blob ed525af25cfd -> f7140c73d786; pnpm --filter @objectstack/service-datasource build exit 0; ablation-dist-preflight present: marker in dist/index.js and dist/index.cjs (exit 0); unit -t 22835: 2 failed (legacy row registered at restore; projection served the legacy body); dogfood pin: 2 failed / 1 passed (premise control) (admin door 200 for the legacy row; admin create 400 already exists). Direction observed: red, as expected. Restore: ablation-replace --restore + git checkout HEAD -- PATH, blob == HEAD ed525af25cfd, git diff HEAD empty, status 0 entries; rebuild exit 0; preflight --absent: marker absent from all 24 built files, tree clean (exit 0). LINT: eslint --no-inline-config --format json on the 8 edited TS files -> 8 files, 0 errors, 0 warnings; eslint.config.mjs enables no type-aware linting (no parserOptions.project), so untouched files cannot move; repo-wide lint is CI. CONTROL BYTES: grep -naP over the 9 changed files -> no match. GATES at b02b3f8c16: node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack derived 70 (superset of the 46 named at dispatch), run sequentially, exit captured before any pipe; --ran reconciliation: 70 derived / 70 run / 0 NOT-MEASURED / 0 UNRUN. check:dual-build-cjs-loads first exited 3 (PREREQUISITE NOT MET: 8 unrelated packages unbuilt), re-run after building them -> exit 0 (recorded as 0). Verdict lines: check-adr-0087-registration '1 declared-breaking changeset(s), each carrying an ADR-0087 disposition'; check-empty-changeset 'No changeset from the merge base modified or deleted'; check-changeset-no-major 'no major bump'. Per command: node scripts/check-adr-0087-registration.mjs --base origin/main :: exit 0 | node scripts/check-adr-0087-registration.mjs --self-test :: exit 0 | node scripts/check-changeset-no-major.mjs --base origin/main :: exit 0 | node scripts/check-changeset-no-major.mjs --self-test :: exit 0 | node scripts/check-ci-filter-parity.mjs :: exit 0 | node scripts/check-closing-keyword-parity.mjs :: exit 0 | node scripts/check-closing-keyword-parity.mjs --self-test :: exit 0 | node scripts/check-comment-mask-adoption.mjs :: exit 0 | node scripts/check-comment-mask-adoption.mjs --self-test :: exit 0 | node scripts/check-comment-mask-corpus.mjs :: exit 0 | node scripts/check-dts-emitted.mjs --self-test :: exit 0 | node scripts/check-empty-changeset.mjs --base origin/main :: exit 0 | node scripts/check-empty-changeset.mjs --self-test :: exit 0 | node scripts/check-engine-split-ratio.mjs --days 90 :: exit 0 | node scripts/check-engine-split-ratio.mjs --self-test :: exit 0 | node scripts/check-issue-citations.mjs :: exit 0 | node scripts/check-keyed-text-bounds.mjs :: exit 0 | node scripts/check-keyed-text-bounds.mjs --self-test :: exit 0 | node scripts/check-platform-object-tenancy-census.mjs :: exit 0 | node scripts/check-platform-object-tenancy-census.mjs --self-test :: exit 0 | node scripts/check-plugin-teardown-shape.mjs :: exit 0 | node scripts/check-plugin-teardown-shape.mjs --self-test :: exit 0 | node scripts/check-registry-log-declared.mjs :: exit 0 | node scripts/check-registry-log-declared.mjs --self-test :: exit 0 | node scripts/check-rest-log-spy-declared.mjs :: exit 0 | node scripts/check-rest-log-spy-declared.mjs --self-test :: exit 0 | node scripts/check-system-context-census.mjs :: exit 0 | node scripts/check-system-context-census.mjs --self-test :: exit 0 | node scripts/check-tenant-audit-census.mjs :: exit 0 | node scripts/check-tenant-audit-census.mjs --self-test :: exit 0 | node scripts/check-undeclared-dep-imports.mjs :: exit 0 | node scripts/check-undeclared-dep-imports.mjs --self-test :: exit 0 | node scripts/docs-audit/check-affected-docs.mjs :: exit 0 | node scripts/docs-audit/check-drift-comment.mjs :: exit 0 | node scripts/pm/release-rehearsal-clone.mjs --self-test :: exit 0 | node scripts/release-pending-publish.mjs --self-test :: exit 0 | pnpm --filter @objectstack/spec run check:duration-unit-keys :: exit 0 | pnpm --filter @objectstack/spec run check:empty-state :: exit 0 | pnpm --filter @objectstack/spec run check:liveness :: exit 0 | pnpm --filter @objectstack/spec run check:strictness-ledger :: exit 0 | pnpm --filter @objectstack/spec run check:variant-docs :: exit 0 | pnpm check:changeset-gate-self-tests :: exit 0 | pnpm check:cross-package-test-inputs :: exit 0 | pnpm check:doc-authoring :: exit 0 | pnpm check:driver-memory-census :: exit 0 | pnpm check:dts-closure :: exit 0 | pnpm check:dual-build-cjs-loads :: exit 0 | pnpm check:durability-log-level :: exit 0 | pnpm check:engine-double-contract :: exit 0 | pnpm check:gitlink-declared :: exit 0 | pnpm check:issue-citations :: exit 0 | pnpm check:lean-entry-closure :: exit 0 | pnpm check:logger-receiver-detach :: exit 0 | pnpm check:nul-bytes :: exit 0 | pnpm check:objectql-double-limit :: exit 0 | pnpm check:objectui-changeset :: exit 0 | pnpm check:org-identifier :: exit 0 | pnpm check:page-declaration-shape :: exit 0 | pnpm check:pm-changeset-deadline-census :: exit 0 | pnpm check:published-files :: exit 0 | pnpm check:query-options-erasure :: exit 0 | pnpm check:refd-timer-probe :: exit 0 | pnpm check:slot-lookup :: exit 0 | pnpm check:sourcemap-no-sources-content :: exit 0 | pnpm check:test-source-alias :: exit 0 | pnpm check:tier-file-adoption :: exit 0 | pnpm check:type-check-coverage :: exit 0 | pnpm check:type-check-debt :: exit 0 | pnpm check:watch-hint-literal :: exit 0 | pnpm check:where-matcher :: exit 0", "mcp_calls": "0 — no MCP GitHub tool used (reads via gh api GET; writes via the fleet-write relay)", "api_writes": "2 relay dispatches (POST /repos/objectstack-ai/objectstack/dispatches, fleet-write, executed as objectstack-fleet[bot]) carrying 3 endpoint writes: POST /repos/objectstack-ai/objectstack/pulls (draft #22862), POST /repos/objectstack-ai/objectstack/issues/22862/assignees (os-project-manager), POST /repos/objectstack-ai/objectstack/issues/22835/comments (this os-dev-report). Plus git push (not REST). 0 label writes.", "open_questions": [], "out_of_scope_findings": [ "carrier: none · noted in PR Acceptance notes, not filed: metadata-protocol reportUnhydratableOrgScopedRows (protocol.ts) re-checks the legacy row value inline instead of importing isLegacyOrganizationRow — a second spelling in a third package (metadata-protocol already depends on metadata-core); outside this claim's file surface", "carrier: none · noted in PR Acceptance notes, not filed: no objectql test pins the authored hook/action binders' skip of a legacy organization row (readAuthoredHookRows and the action read in plugin.ts); this PR moves that predicate unchanged" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 11, 2026
ACCEPT (seat review): PR #22862 at head
b02b3f8c16domain:engineseat 1 (#6367) ·session_01JfJfBUC3cQ6hhgm9MQK76T· 2026-10-11T16:01Z. Claim 6109766787. Dev report 6110648560. Contract review PASS 6110807550 (CONTRACT_REVIEW_TIER, same head). Read against GitHub andorigin/main. ⛔ Classes, positions and functions only.Shape.
main, assignedos-project-manager.Fixes #22835, and the body carries no other closing keyword.Clause-②: no (narrowing).What it does.
datasourcerow inDatasourceAdminServicePlugingoes through one helper,findEnvironmentRows, which drops each row C5's predicate names as legacy:convergePooland the/metaprojection;find.single, Studio saves are stored organization-scoped today. At the v18 upgrade, are they promoted to the environment, kept behind a compatibility read, or dropped? #22011 A. Now the legacy row stays byte-equal.isLegacyOrganizationRowmoved verbatim fromobjectql'splugin.tsto@objectstack/metadata-core, which both sides already depend on. objectql's three binder sites import it, and no second definition is left in the tree.datasourcerows.Evidence read.
service-datasource765;metadata-core386;check-expected-skipsOK).Changeset checked. It carries:
@objectstack/service-datasourceminor, BREAKING narrowing (FROM → TO and the remedy);@objectstack/metadata-coreminor(the new export);@objectstack/objectqlpatch(the import);not-required (already-registered metadata-read-organization-scope-retired), an id that resolves onmainand existed at the merge base.Out-of-scope findings (recorded, not filed):
metadata-protocol'sreportUnhydratableOrgScopedRowsre-checks the same truth table inline. It is a log filter, not a second rule.metadata-core.Landing.
merge-treeis clean againstorigin/main.sys_metadatafamily goes tenant-less; the per-organization overlay axis retires; managed content is sealed (ADR-0131 D6/D7/D13) #15206 by hand: its last gap is closed, S6 stays with feat(objectql,cli): inventory + migration — four fates per object, mirrors deleted only after the id→name rewrite is verified, per-table boot report (ADR-0131 D10) #15211 and S7 with the maintainer.Generated by Claude Code
objectstack-fleet commentedon Oct 11, 2026
Landed: PR #22862 →
8532783f65·domain:engineseat 1 (#6367) ·session_01JfJfBUC3cQ6hhgm9MQK76T· 2026-10-11T16:37Z⛔ Classes, positions and functions only.
8532783f65. The two readings:origin/main.findEnvironmentRowsis indatasource-admin-plugin.ts, andisLegacyOrganizationRowis exported from@objectstack/metadata-core.gh-readonly-queue/main/pr-22862-*is gone.completedthroughFixes #22835.pm:dispatchedwas removed in this act.bug,security,target:v18,domain:engineand the grade stay.@objectstack/service-datasourceminor, BREAKING narrowing;@objectstack/metadata-coreminor;@objectstack/objectqlpatch;Clause-②: no (narrowing)):datasourcerow inDatasourceAdminServicePluginkeeps environment rows only.single, Studio saves are stored organization-scoped today. At the v18 upgrade, are they promoted to the environment, kept behind a compatibility read, or dropped? #22011 A)..changeset/15206-reads-environment-only.md("served by no read and loaded by no boot") is now true fordatasourcerows. Its last gap is closed, so feat(metadata-core,metadata-protocol,objectql,plugin-security): thesys_metadatafamily goes tenant-less; the per-organization overlay axis retires; managed content is sealed (ADR-0131 D6/D7/D13) #15206 is closed by this seat now.Generated by Claude Code