Repository navigation
Commit 8684079
fix(metadata-protocol,metadata): the legacy-organization read seam's loose ends: paged reads, the loader's count, the rollback's named refusal (ADR-0131 D6/D7) (#23778)
Fixes #23609
Clause-②: no
Six of the card's seven items are closed here, one commit each, measured
on `main` first. Item 2 is measured but stopped: its fix needs files
outside the claimed surface (below). Every path matters only on an
unmigrated (v17-shape) database, which `os serve` refuses to boot until
`os migrate organization-ownership` has verified. Each fix acts only
where legacy organization rows are present, and each pin runs beside a
migrated CONTROL whose answer does not change.
The items come from the PASS record on PR #23469 (#23429, C7b S2 + S5);
neither is closed by this PR.
## Items
1. **Done here** (`0546039d`). `findServedOverlayRow`'s legacy filter
gets its own pin: the `_lock` case in
`meta-stored-legacy-organization.integration.test.ts` now asserts that
the served `item` is not the organization's body.
- H1 held on `main` `2906cd8`. With only that filter removed (the raw
`findOne`, rebuilt, marker verified in `dist/`), the file stayed 7/7
green.
- With the pin, the same ablation turns only the new assertion red
(`expected 'Org A locked' not to be 'Org A locked'`, 1 failed / 6
passed).
2. **Measured, NOT done: stopped at the claim's file surface.** H2
holds.
- On a v17-shape SQLite file, `protocol.publishPackageDrafts` for a
package with one draft had not answered after a **25 s** bound.
- CONTROL: the same file with the column dropped published in **97 ms**
(`outcome: published`).
- ObjectQL exposes no ambient-transaction accessor to a raw read.
`txStore` and `privilegedReadDriverOptions` are private, and
`engine.execute` does not thread the ambient handle.
- So the narrow route (take the ambient transaction inside
`legacyOrganizationRowsOf`) needs a public ObjectQL accessor in
`packages/objectql`, outside the claim.
- Threading `trxCtx` instead goes through `promoteDraftForPublish`, its
lock read and the repository's `get` / `promoteDraft` draft reads.
`repo.get` alone keeps the hang, because the repository's own legacy
read has the same gap. Those are outside the claimed surface, and next
to the promote door's D9.9 call.
- The route and its trade-offs are in the report's `open_questions`.
- **Seat ruling 6206299338: C.** Left latent. The seam is transitional
code for a database `os serve` refuses, and no caller reaches the path.
A future reader of the family inside an ambient transaction off the
served boot carries the fix (route A) in its own card. So this PR closes
the card. _(Body edit by the `domain:engine` seat, 2026-10-16T01:58Z.)_
3. **Done here** (`a6f23cd9`). A paged family read leaves the legacy
rows out before the page is cut. This covers `auditMetaItem`,
`listCommits`, `revertStoredPackage`'s `limit: 1` probe (it answered 409
"never been published") and the repository's `list({ limit })`.
- When the page holds a legacy row, or an `offset` could have counted
one, the read is repeated with `id $nin` of them in the query. Otherwise
the single read is the answer, unchanged.
- Ablations: (a) send every paged read down the old post-filter path,
and the 4 v17-shape protocol cases go red (audit, commits, revert,
offset); (b) the repository's `list`, and its 1 v17-shape case goes red.
The control cases stay green in both.
4. **Done here** (`7a52179d`). `DatabaseLoader._count` counts what
`_find` serves. Before, `exists()` answered true where `load()` answered
null, and `queryHistory`'s `total` counted legacy rows.
- It counts first. Only a non-zero count over a table holding legacy
rows is re-counted with them excluded, so the unprovisioned-table
discrimination in `exists()` is untouched.
- Ablation: both v17-shape cases red, controls green.
5. **Done here** (`c39c8c8e`). `rollbackToPackageCommit` answers a
legacy commit with `revertCommit`'s named refusal (`NOT_OVERRIDABLE`,
403, the same sentence) instead of `COMMIT_NOT_FOUND` (404).
- Both doors read the commit through one helper, `readRevertableCommit`.
`revertCommit`'s behaviour is unchanged: the same read, refusal and
text, moved.
- Ablation: put the rollback's read back through `findEnvironmentRow`
with its old 404, and only the v17-shape "one answer from both doors"
case goes red.
6. **Done here** (`8052436a`). The inert plumbing is removed:
`mergePackageAwareOverlay`'s `rowsRead`,
`ServedOverlayRowCandidate.scope` / `organizationId`,
`StoredRowPlace.organizationId`, and the always-null `orgId` /
`itemOrgId` locals.
- Correction to the card: `rowsRead` was a presence flag, not dead. It
is dropped because every caller that passed it also sends each record's
`stored` place, and every caller that omitted it sends none.
`servedStoredRow` returns `undefined` over records with no place, so the
merge's answer is identical.
- Proof: the full `@objectstack/metadata-protocol` suite gives **237
files passed / 3 skipped, 20593 tests passed / 19 skipped**, both with
this commit and with `protocol.ts` restored to its parent (blob
`ea9fa58d`).
- Left in place: `StoredOverlayEntry.organizationId` and the
`organizationId` parameters of `restoreArtifactRegistryView` /
`hydrateOverlayIntoRegistry`. They are the #6780 registry-scope API and
outside the card's list.
7. **Done here** (`6bdaa0e0`; changeset `042ba4c5`). Prose only.
- The four
`18.sys-metadata{,-audit,-commit,-history}-organization-column-retired`
entries and `metadata-lifecycle.mdx` name the ceremony's reviewed apply:
`--out PLAN`, then `--apply --plan-file PLAN`.
- `os migrate apply`'s E1 sentence now reads "a drop the
organization-ownership ceremony owns, and the ceremony has not verified
on this database". Its `--json` payload is unchanged.
- No entry id, category, version or engine-read field changes. `pnpm
--filter @objectstack/spec check:generated` reports all 14 artifacts up
to date, so nothing checked in derives from the entries' prose.
## Verification (head `81aa285d`)
- `@objectstack/metadata-protocol`: full suite 237 files passed / 3
skipped, 20593 tests passed / 19 skipped; `typecheck` exit 0. The new
file `protocol.legacy-organization-read-seam.test.ts` passes 16/16.
- `@objectstack/metadata`: full suite 63 files, 892/892 passed;
`typecheck` exit 0. The new file
`database-loader.legacy-organization-count.test.ts` passes 4/4.
- `@objectstack/cli`:
- `--project unit`: 297 files, 4377/4377 passed;
- `--project integration`: `meta-stored-legacy-organization` 7/7, and
`apply-organization-ownership-gate` +
`organization-ownership-platform-global-column-drop` 11/11 (on a rebuilt
`dist/`);
- `typecheck` exit 0.
- `@objectstack/spec`: `build` + `check:generated` exit 0 (14/14 up to
date); `typecheck` exit 0.
- Derived gates (`node scripts/pm/dispatch-gates.mjs --commands --repo
objectstack-ai/objectstack`, no paths, after the last commit): 123
derived; `--ran` gives **119 run, 4 NOT MEASURED, 0 unrun** (exit 0).
- The first run went over the tree at `042ba4c5`. It found one real
failure, `check:engine-double-contract`: the loader pin's fake `findOne`
skipped `assertEngineFindOnePredicate`, and the ledger lacked the new
doubles. `81aa285d` fixes both.
- At `81aa285d`, every family reading the two files that commit changed
was re-run, plus the 8 the new `scripts/` path adds: 13/13 exit 0.
- NOT MEASURED (each exited 3, PREREQUISITE NOT MET, on packages this
diff does not touch; CI runs them): `check-plugin-teardown-shape
--self-test` (needs unshallow history), `spec check:skill-examples`
(`client-react` unbuilt), `check:dual-build-cjs-loads` (whole-workspace
build) and `check:i18n-coverage` (`connector-slack` unbuilt).
- Every ablation ran through `scripts/ablation-replace.mjs`: anchor hit,
blob moved, restore proved blob == HEAD and `git diff HEAD` empty. The
CLI ablation (item 1) also passed `ablation-dist-preflight` on both
legs.
## Acceptance notes
- `DatabaseLoader.queryHistory`'s page read (`_find` with `limit` +
`offset`) still filters after the cut, the item 3 shape on the loader.
It is not in the claimed surface (`_count` only), and it is latent like
every item here. Carrier: none.
- `listCommits` passes `limit` with no `orderBy` and sorts afterwards,
so its `limit` picks store-order rows, not the newest. This predates the
card and is unrelated to the legacy filter.
🤖 Generated with [Claude Code](https://claude.com/claude-code)
https://claude.ai/code/session_01TekAoE8QbLrxFVJsdNuRgg
---
_Generated by [Claude
Code](https://claude.ai/code/session_01TekAoE8QbLrxFVJsdNuRgg)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent d3df3ba commit 8684079
14 files changed
Lines changed: 564 additions & 126 deletions
File tree
- .changeset
- content/docs/concepts
- packages
- cli/src/commands/migrate
- metadata-protocol/src
- metadata/src/loaders
- spec/src/migrations/entries/semantic
- scripts
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
106 | 106 | | |
107 | 107 | | |
108 | 108 | | |
109 | | - | |
| 109 | + | |
110 | 110 | | |
111 | 111 | | |
112 | 112 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
512 | 512 | | |
513 | 513 | | |
514 | 514 | | |
515 | | - | |
516 | | - | |
| 515 | + | |
| 516 | + | |
517 | 517 | | |
518 | 518 | | |
519 | 519 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
307 | 307 | | |
308 | 308 | | |
309 | 309 | | |
| 310 | + | |
| 311 | + | |
| 312 | + | |
| 313 | + | |
| 314 | + | |
310 | 315 | | |
311 | 316 | | |
312 | 317 | | |
| |||
0 commit comments