Repository navigation
Commit 1abfc58
fix(metadata-protocol): the metadata door serves a code-defined datasource's code definition while a stored row exists (#21985)
Fixes #21922
Clause-②: no
## What changes
The metadata door no longer serves a stored `sys_metadata` row under a
datasource name the host registers from code. While such a row exists,
`GET /api/v1/meta/datasource/:name`, the `GET /api/v1/meta/datasource`
list and the `effective` layer of `/layers` all 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`:
- **One predicate.** `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's
`code-datasource-names` set 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 any `origin`.
- **`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`), so
`servedLockState` keeps `deletable: true` while it exists. A draft read
(`state: 'draft'`, or `previewDrafts`) is answered as a draft.
- **The list** (`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`**: `effective` asks the same predicate. The
spec defines `effective` as "the value an ordinary `GET
/meta/:type/:name` would return under `item`"
(`GetMetaItemLayeredResponseSchema`), so it has to move with the by-name
read. The row stays reported in `overlay`.
- Every other type keeps ADR-0005's read order. The `/meta` DELETE path
is untouched: its own row probe still finds and removes the row.
## Hypotheses (Partition 2), measured at `f76c6221ac`
- **H1, confirmed.** `getMetaItem` adopted the row at
`protocol.ts:10038` (`findServedOverlayRow`) and `:10047` (`if (record
&& !shippedFlowActiveRead)`), whatever the MetadataService held.
- **H2, confirmed and extended.** The #20946 shape is the by-name
stored-row half (`: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.
`getMetaItem` needs no registry half for datasource, because step 2
answers before step 3.
- **H3, confirmed with no new rule.** `servedLockState` (`:16488`) reads
`storedRowServed`, which is set from the row FOUND (`:10045`) before the
adoption check, so a declined row keeps `deletable: true`. That is
exactly what `originGatedRemovalRefusal`'s docblock states ("`deletable`
true exactly while the read found a stored row"). `deleteMetaItem`
(`:25771`) decides from its own probe and never reads the served body.
The `_lock` gate's overlay layer is read whether or not the row is
adopted, as for flows. Triage's stop condition is not met.
- **H4, confirmed disjoint.** #21967's branch touches `protocol.ts`
hunks at `:2003`, `:9051`–`:9107`, `:9610`–`:9715` and `:19206` (read
from its pushed branch). None of this PR's sites overlaps them.
`origin/main` was merged (`65d7b740b2`); #21967 had not landed.
## Pins
- **Unit, `metadata-protocol`**
(`protocol.code-defined-datasource-door.test.ts`, a new `[#21922]` block
on both kernel shapes, 14 cases):
- (a) by name (both spellings), in the list before and after the row is
hydrated, `default` through the host set, and the layered `effective`;
- (b) the DELETE still removes the row, and the reads serve code after
it;
- (c) a runtime datasource's row is still served by name, in the list
and as `effective`;
- (d) the strict draft read and the preview arm serve the draft row.
- The block reuses the file's pinned engine double, so the pinned ledger
is untouched. That double's `find` now honours `where` and `limit` (it
returned every row), and its registry gains an opt-in hydrating slot
plus `isPackageDisabled`, which the list calls.
- **Dogfood** (`datasource-restore-code-wins.dogfood.test.ts`, one
case): after a restart over stored rows under `showcase_external` and
`default`, `GET /api/v1/meta/datasource/:name` and the `/meta` list
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.
- **Fixture triage:** `meta-door-code-datasource.dogfood.test.ts` read
`SHADOW_LABEL` after 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)
- **Ablation A (unit):** `return typeof name === 'string' && name !== ''
&& this.isDeclaredCodeDatasource(type, name);` was replaced by `return
false; // ABLATION-21922` via `scripts/ablation-replace.mjs` (anchor 1
to 0, blob `7890c4b99f6e` to `6d70364fc822`). **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 HEAD` empty.
- **Ablation B (unit, registry half only):** the list's filter reverted
to `isStoredFlowEntryOfShippedName`. **4 failed / 47 passed**: exactly
the post-hydration list cases (`showcase_external` and `default`) on
both kernels. Restored by blob.
- **Ablation C (public door, through `dist/`):** the same arm replaced
by `return name === 'ABLATION-21922';`, then `pnpm --filter
@objectstack/metadata-protocol build`, then `ablation-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, then `ablation-dist-preflight --absent`
(exit 0).
## Readings at head `65d7b740b2` (after the `origin/main` merge)
- `pnpm --filter @objectstack/metadata-protocol test`: **218 files
passed, 3 skipped; 28010 tests passed, 19 skipped**. `typecheck` (`tsc
--noEmit`): exit 0. The package `tsconfig` includes `src/**/*`, so the
edited test file is compiled.
- Dogfood, both files: **12/12 passed**.
- `pnpm --filter @objectstack/spec check:generated`: all 15 artifacts up
to date (spec moved on the `main` side of the merge).
- `node scripts/pm/dispatch-gates.mjs --commands` (no paths; 5 paths vs
merge base `04e776b39`): **68 derived; `--ran`: 68 run, 0 NOT-MEASURED,
0 UNRUN**. `check:dual-build-cjs-loads` first answered `PREREQUISITE NOT
MET` (exit 3, eight packages outside the dogfood closure had no
`dist/`). It answered exit 0 after those were built, and the record
carries that reading.
- Artifact-roster block (53 rows, outside the total): 50 exit 0.
`check-closing-target-claim`, `check-partof-closing-keyword` and
`check-single-claim-paths` answered NOT WIRED (exit 2: no PR context
existed yet). They are NOT MEASURED here, and the report carries their
run against this PR.
- Symbol-anchor sweeps: `check:adr-symbol-anchors`,
`check:scripts-symbol-anchors`, `check:spec-docblock-symbol-anchors` and
`check:adr-anchors` all exit 0.
- **Lint, a proven narrowing** (repo-wide `pnpm lint` is CI's): `eslint
--no-inline-config --format json` over the four touched TypeScript files
at `65d7b740b2` reports 4 files linted, none ignored, 0 errors and 0
warnings. The config never enables type-aware linting (no
`parserOptions.project`, as `eslint.config.mjs` states), so this diff
cannot move any untouched file's verdict.
## Changeset, measured against the built entry declarations
`@objectstack/metadata-protocol` patch, with `Clause-②: no`. The BASE
(`f76c6221ac`) and HEAD builds of `dist/index.d.ts` and `index.d.cts`
differ in two non-comment lines only: `private declinesStoredRow;` and
`private isStoredEntryOfDeclinedName;` on
`ObjectStackProtocolImplementation`. No public member, parameter or
return type moves.
## Named gap, not moved here: the `/published` door
Measured with a throwaway probe at `8c4bd140de` (never committed): after
a restart over a stored row, `GET
/api/v1/meta/datasource/showcase_external` serves the code definition,
and `/layers` answers `effective` = code with `overlay` = the row. `GET
/api/v1/meta/datasource/showcase_external/published` still serves the
row (`origin: runtime`). The REST door and its runtime dispatcher twin
decide by asking the public `isShippedFlowName` alone. 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's
`Clause-②: no` does 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.
- A declined row is still hydrated into the registry as the tenant row
it is, following the flow precedent. `git grep` finds no reader of
registry `datasource` entries outside `protocol.ts`.
- Read-only inference, not measured: a name declared by a package
installed after boot is code to `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.
- Deviation, self-inflicted and recovered: a declaration measurement's
restore (`git checkout HEAD -- protocol.ts`) discarded one uncommitted
docblock edit. It was detected by a marker count, re-applied and
committed as `0b2a164598`. Every reading above was taken after it.
---
_Generated by [Claude
Code](https://claude.ai/code/session_017ErfyP2Rx7XWHJA27QjyUi)_
---------
Co-authored-by: Claude <noreply@anthropic.com>1 parent 1c563af commit 1abfc58
5 files changed
Lines changed: 384 additions & 30 deletions
File tree
- .changeset
- packages
- metadata-protocol/src
- qa/dogfood/test
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
111 | 111 | | |
112 | 112 | | |
113 | 113 | | |
| 114 | + | |
| 115 | + | |
| 116 | + | |
| 117 | + | |
| 118 | + | |
| 119 | + | |
| 120 | + | |
114 | 121 | | |
115 | 122 | | |
116 | 123 | | |
117 | 124 | | |
| 125 | + | |
| 126 | + | |
118 | 127 | | |
119 | 128 | | |
120 | 129 | | |
| |||
126 | 135 | | |
127 | 136 | | |
128 | 137 | | |
129 | | - | |
| 138 | + | |
130 | 139 | | |
131 | 140 | | |
132 | | - | |
| 141 | + | |
| 142 | + | |
| 143 | + | |
| 144 | + | |
| 145 | + | |
| 146 | + | |
133 | 147 | | |
134 | 148 | | |
135 | 149 | | |
| |||
158 | 172 | | |
159 | 173 | | |
160 | 174 | | |
161 | | - | |
| 175 | + | |
| 176 | + | |
| 177 | + | |
| 178 | + | |
| 179 | + | |
162 | 180 | | |
163 | | - | |
164 | | - | |
165 | | - | |
166 | | - | |
167 | | - | |
| 181 | + | |
| 182 | + | |
| 183 | + | |
| 184 | + | |
| 185 | + | |
| 186 | + | |
168 | 187 | | |
169 | 188 | | |
| 189 | + | |
170 | 190 | | |
171 | 191 | | |
172 | 192 | | |
| |||
530 | 550 | | |
531 | 551 | | |
532 | 552 | | |
| 553 | + | |
| 554 | + | |
| 555 | + | |
| 556 | + | |
| 557 | + | |
| 558 | + | |
| 559 | + | |
| 560 | + | |
| 561 | + | |
| 562 | + | |
| 563 | + | |
| 564 | + | |
| 565 | + | |
| 566 | + | |
| 567 | + | |
| 568 | + | |
| 569 | + | |
| 570 | + | |
| 571 | + | |
| 572 | + | |
| 573 | + | |
| 574 | + | |
| 575 | + | |
| 576 | + | |
| 577 | + | |
| 578 | + | |
| 579 | + | |
| 580 | + | |
| 581 | + | |
| 582 | + | |
| 583 | + | |
| 584 | + | |
| 585 | + | |
| 586 | + | |
| 587 | + | |
| 588 | + | |
| 589 | + | |
| 590 | + | |
| 591 | + | |
| 592 | + | |
| 593 | + | |
| 594 | + | |
| 595 | + | |
| 596 | + | |
| 597 | + | |
| 598 | + | |
| 599 | + | |
| 600 | + | |
| 601 | + | |
| 602 | + | |
| 603 | + | |
| 604 | + | |
| 605 | + | |
| 606 | + | |
| 607 | + | |
| 608 | + | |
| 609 | + | |
| 610 | + | |
| 611 | + | |
| 612 | + | |
| 613 | + | |
| 614 | + | |
| 615 | + | |
| 616 | + | |
| 617 | + | |
| 618 | + | |
| 619 | + | |
| 620 | + | |
| 621 | + | |
| 622 | + | |
| 623 | + | |
| 624 | + | |
| 625 | + | |
| 626 | + | |
| 627 | + | |
| 628 | + | |
| 629 | + | |
| 630 | + | |
| 631 | + | |
| 632 | + | |
| 633 | + | |
| 634 | + | |
| 635 | + | |
| 636 | + | |
| 637 | + | |
| 638 | + | |
| 639 | + | |
| 640 | + | |
| 641 | + | |
| 642 | + | |
| 643 | + | |
| 644 | + | |
| 645 | + | |
| 646 | + | |
| 647 | + | |
| 648 | + | |
| 649 | + | |
| 650 | + | |
| 651 | + | |
| 652 | + | |
| 653 | + | |
| 654 | + | |
| 655 | + | |
| 656 | + | |
| 657 | + | |
| 658 | + | |
| 659 | + | |
| 660 | + | |
| 661 | + | |
| 662 | + | |
| 663 | + | |
| 664 | + | |
| 665 | + | |
| 666 | + | |
| 667 | + | |
| 668 | + | |
| 669 | + | |
| 670 | + | |
| 671 | + | |
| 672 | + | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
| 676 | + | |
| 677 | + | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
| 699 | + | |
| 700 | + | |
| 701 | + | |
| 702 | + | |
| 703 | + | |
| 704 | + | |
| 705 | + | |
| 706 | + | |
| 707 | + | |
| 708 | + | |
| 709 | + | |
| 710 | + | |
| 711 | + | |
| 712 | + | |
| 713 | + | |
| 714 | + | |
| 715 | + | |
| 716 | + | |
| 717 | + | |
| 718 | + | |
| 719 | + | |
| 720 | + | |
| 721 | + | |
| 722 | + | |
| 723 | + | |
| 724 | + | |
| 725 | + | |
| 726 | + | |
| 727 | + | |
| 728 | + | |
| 729 | + | |
0 commit comments