Skip to content

Commit 68748c3

Browse files
refactor(metadata-protocol): move the legacy-organization row filters out of protocol.ts into protocol-legacy-organization-rows.ts (#25467 cut 29) (#27029)
<!-- claude-projects-attribution: {"github_login":"os-project-manager"} --> _Requested by **Leehom** · [project thread](https://claude.ai/code/project/chan_01TenzsRopZj4jMbvjX8uz1X?thread=cmsg_01TenzsRopZj4jMbvjX8uz1X7JKgiM29GUcitRvoGGj5BU)_ Part of #25467 Clause-②: no Metadata-protocol: none Ruling-ref: 6270097840 Cut 29 of the `protocol.ts` split (#24586 ruling A, recorded in `6270097840`). Cuts 1–28 landed as #25494, #25518, #25538, #25589, #25601, #25623, #25642, #25719, #25731, #25740, #25797, #25939, #25972, #26014, #26104, #26168, #26264, #26317, #26350, #26366, #26404, #26449, #26654, #26704, #26807, #26873, #26933 and #26980. The claim is `6457847737`. It takes the cut 29 proposal of the cut 28 report (`6456729821`), a themed pair. Both members passed the AST screen and the read-seam census, and both moved unedited. Two `ObjectStackProtocolImplementation` members move unedited into one new module, `packages/metadata-protocol/src/protocol-legacy-organization-rows.ts` (#23050 shape B). They are the legacy-organization row filters. On a database the ADR-0131 ceremony has not migrated, these find the rows that still belong to one organization rather than to the environment: - `legacyOrganizationRowsOf`: those rows of one table, read off the physical column through the table's driver. A read inside a transaction joins it. - `withoutLegacyOrganizationRows`: a row list with those rows left out. `findEnvironmentRow` stays in the class, as the claim says: a census trial moving it dropped the count 88 → 81 in the cut 28 report. `protocol.ts` grows by 4 net lines (29,384 to 29,388): 9 body lines left, and 11 stub lines plus the 2-line import came in. The cut is move-only, and the card stays open for later cuts. Branch `claude/project-thread-ybjm3h` is the harness-pinned branch for this thread. It was restarted from `main` `3271a0834c` (after cut 28's merge). It names no issue, so the claim comment is the identity record. It holds the move commit `0522e4d4ea` and, as the claim asked, one merge of `main`, `ad684a0ce3` (7 commits). That merge touches nothing in `packages/metadata-protocol/`. The merged tree was still measured against `main` (see "The merged tree against `main`"). ## Serial with the file's in-flight PRs The claim named **#26920** as the open PR that also edits `protocol.ts` and asked for a `git merge-tree` against it before this PR was opened. - **#26920** (head `380f37a55a`) edits `protocol.ts` at `:16153–16206` and `:20256` (+54 / −17), plus other files of this package. It names neither moved member. This cut's hunks are at `:425` (the import) and `:10212–10225`. - Its `git merge-tree` against this head `ad684a0ce3` is **clean**. - That merged tree was type-checked: `git merge-tree` to a tree, then `tsc --noEmit` in `packages/metadata-protocol` on that tree exits 0. On it, the moved bodies still compare 187 tokens, 0 differ. - Its merge-tree against `main` alone is clean too. All 30 open PRs were checked against this head as well. Five touch this package (#26937, #26920, #26831, #26556, #26517), none names either member, and every one is merge-tree clean against this head and against `main`. #26831's earlier conflict with `main` is gone. `main` has not moved since the merge (0 behind at the derivation). ## The order's screen (private statics, `new Error(`, the read-seam census) Both members passed: - **No private-static read.** The AST walk found no access on the `ObjectStackProtocolImplementation` identifier and none on `this.constructor`. - **No error constructed.** Neither body has `new Error(`, so no `PRODUCERS` row is owed. - **Read-seam census.** `check-durability-degradation-log-level --list` was taken on the base and again on the cut. Both read `89 read seam(s)`, the base the claim quotes, and the lists are identical once `[@:][0-9]+` is stripped. The full readings are under "The durability `--list`". ## What moves | Member | Base line | Visibility | Body lines | AST tokens (params + return + body) | Reads | |---|---|---|---|---|---| | `withoutLegacyOrganizationRows<R>` | `:10212` | private async, generic | 3 | 102 | `legacyOrganizationRowsOf` (method) | | `legacyOrganizationRowsOf` | `:10218` | private | 6 | 85 | `engine` (field); module-level `readLegacyOrganizationRows` | `protocol-legacy-organization-rows.ts` has 74 lines. ## The shape (#23050 shape B, as cuts 1–28 apply it) - **The class keeps both members.** Each keeps its name, signature and position (neither has a docblock). Each body now delegates, marked `[#25467 cut 29]`. - **`LegacyOrganizationRowsHost`** types the two members the bodies read as the class members themselves (`ObjectStackProtocolImplementation['engine']` and `['legacyOrganizationRowsOf']`). Each function's `this` is a `Pick` of the members it reads. - **The `engine` field is handed over as a getter**, so the read finds it at call time, as before. - **`withoutLegacyOrganizationRows`'s call to `legacyOrganizationRowsOf` is handed over as a closure**, `(...a) => this.legacyOrganizationRowsOf(...a)`, as the claim asked. So that call still reaches the class method (now its stub), not the moved function directly. - **The generic member is called through an instantiation expression**, `(withoutLegacyOrganizationRows<R>).call(host, table, rows, context)`, as cut 11 did for `materializeFromRegistry<T>`. So the stub still returns `Promise<R[]>`, not `Promise<unknown[]>`. - **No member changes visibility.** - **Every caller still calls the class method.** None changes: - `withoutLegacyOrganizationRows`: `findEnvironmentRows` (`:10185`, base). - `legacyOrganizationRowsOf`: `findEnvironmentRows` (`:10190`), `findEnvironmentRow` (`:10206`), `withoutLegacyOrganizationRows` (above) and `:26506`. ## Module-level declarations, by reader: no import leaves `protocol.ts` Measured by an AST walk over each body's identifiers, against `protocol.ts`'s top-level declarations and import bindings (property names excluded). The one module-level name the bodies read is `readLegacyOrganizationRows`, from `@objectstack/metadata-core`. It has two other code readers in `protocol.ts` (`:21892` and `:28524`, base), so it stays, and so does its `[ADR-0131 D6/D7]` import comment. The new module imports it from there too. So the top level of `protocol.ts` changes by the new module's import alone. ## The published `.d.ts` The series bar is ruling `6348891160`: sorted-union identity, measured first, with raw identity reported when it holds. **Both hold for this cut**, including on the merged tree against `main`. The cut adds one import statement and its comment ahead of every class docblock, so the cut 25 comment-offset trap was watched. It did not fire: the raw hash is identical at both ends. Base `3271a0834c` and head `0522e4d4ea` were built with `pnpm --filter @objectstack/metadata-protocol build` against one dependency tree. The closure was built at head by a full `pnpm build` (116/116). For the base build, the package's `src/` was swapped tree-only to base (the new module removed) and restored from `HEAD` under a trap. Afterwards `git diff HEAD` was empty, `git status` was clean, and both touched blobs equalled `HEAD`'s. - **Normalised sha256** (the ruling's bar): `a9338265ddd795ccfd7fda56ee209f3c2e162f9153766351149994f20e92715f` at both ends, on `dist/index.d.ts` and `dist/index.d.cts`. The normaliser sorted 80 string-literal union runs. **Sorted-union identity holds.** - **Raw sha256**: `7307bcccb6026da6c004fa2daaca634d3419d202d6b742c122a7a3c64a552712` at both ends, 781,687 bytes. **Raw identity holds.** - **The two builds differ where they should.** - `index.js` hashes `a604e2fd4c5cb76c…` at base and `16b25c206072e2f3…` at head (`index.cjs` `5beb42aa247530b3…` → `7aec221248f758d4…`). - `….call(host` goes 0 → 1 for each of the two members in both bundles. - Each `function …(` declaration goes 0 → 1. - **Positive control** on the head tree. `private` was dropped from `legacyOrganizationRowsOf` on disk (restored from `HEAD` under a trap) and the package rebuilt. Both `.d.ts` files move to `38b33a942d07398f…` (normalised `a0c35bd1e1a5b943…`, which differs from the bar's `a9338265…`) and print `legacyOrganizationRowsOf(table: string, context?: unknown): Promise<ReadonlyMap<string, string>>;`. So the bar can see a change on these members. - **Export tables.** The ESM and CJS bundles, loaded at runtime from the package, each export the same 78 names at base and head. The sorted list hashes to `9f18a0177ac736ed…` on all of them. Neither function, and not `LegacyOrganizationRowsHost`, appears. ### The merged tree against `main` The merged tree `ad684a0ce3` was measured against `main` `7eb613c39f`, its second parent, in the same way. The closure was rebuilt by a full `pnpm build` (116/116), and `main`'s package `src/` was swapped in tree-only and restored under a trap. The merged tree differs from `main` only by this cut: 2 files, +87 / −9. - **`.d.ts`**: raw `7307bcccb6026da6…` (781,687 bytes) and normalised `a9338265ddd795cc…`, the same at `main` and merged. Both identities hold. - **`index.js`**: `a604e2fd4c5cb76c…` at `main`, `16b25c206072e2f3…` merged. Export tables: 78 names, `9f18a0177ac736ed…` on both. - **AST compare**, `main`'s `protocol.ts` against the merged one: 187 tokens, 0 differ. - **Suite**: `main` and merged both give 21,748 tests (21,722 passed, 0 failed, 26 skipped, 279 files), per test identical. No changeset: `skip-changeset`, per ruling `6348891160`. ## The durability `--list` `check-durability-degradation-log-level` exits 0 at both ends with identical output: `89 read seam(s)` at both ends, the base the claim quotes. Its `--list` is **identical once `[@:][0-9]+` is stripped** (165 lines, and no entry changes file). - Neither body has a `try`. `legacyOrganizationRowsOf` hands its read to `readLegacyOrganizationRows`, and neither body calls the gate's read vocabulary (`find`/`findOne`/`count`). - No seam enters or leaves the census. `findEnvironmentRows` and `findEnvironmentRow`, the seam members that call these two, stay in the class. - On the merged tree the list is identical too, `89 read seam(s)`. ## Proof of move-only **Bodies, by AST token.** The compare reads the TypeScript compiler's AST leaf tokens, with JSDoc skipped, over the parameters (less the added `this`), the return type and the body. It compares base `protocol.ts` at `3271a0834c` against the head module: ``` withoutLegacyOrganizationRows: base tokens 102, head tokens 102, differ 0, text-equal(ws-collapsed, comments incl.) true legacyOrganizationRowsOf: base tokens 85, head tokens 85, differ 0, text-equal(ws-collapsed, comments incl.) true TOTAL tokens=187 differ=0 members=2 CLASS members base=303 head=303 order-same=true byte-identical=301 stubs-with-identical-docblock+signature=2 other=[] TOP base=262 head=263 byte-identical=262 ``` **Negative controls**, one per member. Each changed one token in an in-memory copy of the head module, and only the mutated member moved (differ 1; the other stayed 0): ``` "rows.length === 0) return" -> "rows.length === 1) return" withoutLegacyOrganizationRows differ 1 "transaction !== undefined ?" -> "transaction === undefined ?" legacyOrganizationRowsOf differ 1 ``` - **Class side.** There are 303 members at base and at head, in the same order. The 301 that did not move are byte-identical to base, leading comments included. Both stubs sit at their base positions and keep their signatures (and type parameter) byte-identical. - **Top level of `protocol.ts`.** All 262 base statements are byte-identical, leading comments included. Head adds one statement, the new module's import. - **Size.** +87 / −9 over 2 files, 96 changed lines (the claim's estimate was 80–130). ## Readers of the moved lines These were grepped for the two member names and for the bodies' distinctive text (`readLegacyOrganizationRows(`, `getDriverForObject`): - the 24 files that name `protocol.ts` and call `readFileSync`; - the test files in `packages/`, `docs/qa/` and `scripts/`; - the `@objectstack/spec` liveness ledgers. - **By path: none reads a moved line.** - **Same names elsewhere, not readers.** `sys-metadata-repository.ts` and `packages/metadata/src/loaders/database-loader.ts` each have their own `withoutLegacyOrganizationRows` method on another class. As the claim says, those are not part of this cut. - **Behaviour, no edit owed.** The doors that read through `findEnvironmentRows` / `findEnvironmentRow` are driven by the package suite, which passes at both ends. `readLegacyOrganizationRows` itself is tested in `metadata-core` (`legacy-organization-row.test.ts`). - **The liveness ledgers.** No row points at either member. Carried forward: `manifest.json`'s `dependencies` row → `protocol.ts#resolveWritePackageScope` (cut 19), and `seed.json` → `protocol.ts#applySeedBodies` (cut 14). These are `packages/spec/**`, outside this card. - **ADR anchors.** `check-adr-anchors` exits 0 at both ends. - The decision-letter count is unchanged at 1,896. - The citation count goes 60,108 → 60,109 and the file count 8,432 → 8,433. That is the new module's header, which names the ADR-0131 ceremony it describes. No moved line cites an ADR. - **Engine-double contract** (`check-engine-double-contract`): exits 0 at both ends. - The `updateData`/`deleteData` seams keep their entries at new line numbers (14005 → 14009, 14030 → 14034). - `productionFiles` is 3,819 at base and 3,821 at head. The +2 are the new file and the gitignored `packages/spec/src/migrations/registry.ts` that the spec build writes in the head tree, as in cuts 20–28. - **Identical output at both ends:** `check-adr-symbol-anchors`, `check-system-context-census`, `check-tenant-audit-census`, `check-query-options-erasure-ratchet`, `check-filter-alias-parity` and `check-durability-degradation-log-level`. `check-nul-bytes` differs only in the scanned file count, +1. The base-end readings were taken in a clean detached worktree at `3271a0834c`. ## Verification - `tsc --noEmit` in `packages/metadata-protocol` exits 0 at head, on the merged tree, and on the merge-tree with #26920. - `pnpm exec eslint --no-inline-config packages/metadata-protocol` exits 0 with no output (the whole touched package, with the root `lint` script's flags). - **`packages/metadata-protocol` suite**, compared per test name and status. Each leg ran vitest `--maxWorkers=2` with the JSON reporter under the shared verify lock. - Base `3271a0834c`: 21,748 tests (21,722 passed, 0 failed, 26 skipped), 279 files. - Head `0522e4d4ea`: 21,748 tests (21,722 passed, 0 failed, 26 skipped), 279 files. - None lost, none added, none changed status. The base leg is the package `src/` swapped tree-only from `3271a0834c` (the new module removed) and restored from `HEAD` under a trap. - The merged head `ad684a0ce3` and `main` `7eb613c39f` both give 21,748 tests, per test identical (0 lost, 0 added, 0 changed). - `check-platform-checklist --changed-from origin/main` (merged tree): OK, 31 anchors judged, 0 lost. - **Governed surface.** `check-governed-merges --branch claude/project-thread-ybjm3h` finds 0 of 2 paths in the register (13 surfaces), so the PR is NOT governed. It has 96 changed lines, under the 3,000-line human-merge bar. - **Derived gates.** `dispatch-gates --commands --repo objectstack-ai/objectstack` derives 46 gates on 2 changed paths from `ad684a0ce3`, with no stale-tree finding. All 46 exit 0 after the full `pnpm build` (116/116). `dispatch-gates --ran` reports 46 derived, 46 run, 0 NOT-MEASURED and 0 UNRUN, and exits 0. ## Carry-forward These stay in `protocol.ts`: - **The read-seam members** (cut 21): `findEnvironmentRows`, `findEnvironmentRow`, `packagelessAddress`, `findServedOverlayRow`, `foldStoredCollection`, `applyObjectRegistryMutation`, `unmanagedImportCollisions`, `rollBackUnmanagedImport`, `readRevertableCommit` and `storedFlowBindingAgrees`. `rethrowUnlessMetadataStoreUnprovisioned` cannot move at all. - Public members whose return type is inferred. - **The bodies kept by a by-path reader:** - `probeRecord`, `getMetaItemCached`, `findReferencesToMeta`, `refuseUnmintableMetaType` and `metaTypeNamespaceExists`. - `viewContainerNameCollisionRefusal` with `overlaidShippedContainerViewNames`, and `reportPageRequiresAbsentAtLoad`. - The 7 `applyRegistryWriteThrough` callers, `readCodeLayerForCarryForward`, `ensureObjectStorage`, `dropObjectStorage` and `applyRemoteMetadataMutation`. - `emitMetadataMutation`, and `getOverlayRepo`. - `isArtifactBacked`, `isNestedArtifactField` and `packagedArtifactBase` (the `lookupArtifactItem` site pins). - The private-static readers: `refusePackagedBaseRemoval`, `packagedBaseRefusal`, `runPublishSideEffects`, `promoteDraftForPublish`, `publishPackageDrafts` and `discardPackageDrafts`. - The field writers: `warnClosureReadUnavailable` and `resolveSduiManifest`. 🤖 Generated with [Claude Code](https://claude.com/claude-code) https://claude.ai/code/session_016SP1spj613tyGCgxoD1An6 Co-authored-by: Claude <noreply@anthropic.com>
1 parent cd609a4 commit 68748c3

2 files changed

Lines changed: 87 additions & 9 deletions

File tree

Lines changed: 74 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,74 @@
1+
// Copyright (c) 2026 ObjectStack. Licensed under the Apache-2.0 license.
2+
3+
/**
4+
* [#25467 cut 29] The legacy-organization row filters, from
5+
* `ObjectStackProtocolImplementation`, moved out of `protocol.ts` whole: on a
6+
* database the ADR-0131 ceremony has not migrated, the rows that still belong
7+
* to one organization rather than to the environment.
8+
*
9+
* - `legacyOrganizationRowsOf` — those rows of one table, read off the
10+
* physical column through the table's driver (a read inside a transaction
11+
* joins it).
12+
* - `withoutLegacyOrganizationRows` — a row list with those rows left out.
13+
*
14+
* A MOVE, not a redesign. Each function below is the body of the
15+
* `ObjectStackProtocolImplementation` method of the same name as it stood,
16+
* re-indented one level and otherwise unedited.
17+
*
18+
* ## How the moved bodies reach the class
19+
*
20+
* The shape #23050 set for `rest-server.ts` (its `data-action-routes.ts`
21+
* header carries the full reasoning), as `protocol-batch-data.ts` applies it.
22+
* The class keeps each method — the published `dist/*.d.ts` lists its name,
23+
* docblock and declared signature — and its body is one literal of exactly
24+
* the {@link LegacyOrganizationRowsHost} members it reads, plus a `.call`.
25+
* The `engine` field is handed over as a getter, so each read finds it as it
26+
* stands at call time; `withoutLegacyOrganizationRows`'s call to
27+
* `legacyOrganizationRowsOf` is handed over as a closure, so it still reaches
28+
* the class method. Every caller keeps calling the class methods.
29+
*
30+
* What the bodies read from elsewhere, they import from where `protocol.ts`
31+
* imports it. Nothing re-exports this module, so the published surface does
32+
* not move.
33+
*/
34+
35+
import { readLegacyOrganizationRows } from '@objectstack/metadata-core';
36+
import type { ObjectStackProtocolImplementation } from './protocol.js';
37+
38+
/**
39+
* The `ObjectStackProtocolImplementation` members the functions below read —
40+
* every one of them, and no other; each function's `this` is the `Pick` of
41+
* the members it reads. Each is typed AS the class member (an indexed access
42+
* on the class), so a change to a member's signature on the class is a change
43+
* here too, and naming a member the class does not have is a compile error.
44+
*/
45+
export interface LegacyOrganizationRowsHost {
46+
readonly engine: ObjectStackProtocolImplementation['engine'];
47+
readonly legacyOrganizationRowsOf: ObjectStackProtocolImplementation['legacyOrganizationRowsOf'];
48+
}
49+
50+
/** The body of `ObjectStackProtocolImplementation#withoutLegacyOrganizationRows`; the method's docblock there is its contract. */
51+
export async function withoutLegacyOrganizationRows<R>(
52+
this: Pick<LegacyOrganizationRowsHost, 'legacyOrganizationRowsOf'>,
53+
table: string,
54+
rows: R[] | null | undefined,
55+
context?: unknown,
56+
): Promise<R[]> {
57+
if (!rows || rows.length === 0) return rows ?? [];
58+
const legacy = await this.legacyOrganizationRowsOf(table, context);
59+
return legacy.size === 0 ? rows : rows.filter((row) => !legacy.has(String((row as { id?: unknown }).id)));
60+
}
61+
62+
/** The body of `ObjectStackProtocolImplementation#legacyOrganizationRowsOf`; the method's docblock there is its contract. */
63+
export function legacyOrganizationRowsOf(
64+
this: Pick<LegacyOrganizationRowsHost, 'engine'>,
65+
table: string,
66+
context?: unknown,
67+
): Promise<ReadonlyMap<string, string>> {
68+
const transaction = (context as { transaction?: unknown } | undefined)?.transaction;
69+
return readLegacyOrganizationRows(
70+
(this.engine as { getDriverForObject?: (objectName: string) => unknown }).getDriverForObject?.(table),
71+
table,
72+
transaction !== undefined ? { transaction } : {},
73+
);
74+
}

‎packages/metadata-protocol/src/protocol.ts‎

Lines changed: 13 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -421,6 +421,8 @@ import { hierarchyResolverMounted, orgWallEnforced, rlsMembershipResolverKeys, t
421421
import { flowRegistrationsOf, resolveFlowCanonicalizer, resolveFlowRegistrationReader } from './protocol-automation-flow-reads.js';
422422
// [#25467 cut 28] The code-datasource predicates: declared by an installed package, or registered by the host.
423423
import { isDeclaredCodeDatasource, isHostCodeDatasource } from './protocol-code-datasource-predicates.js';
424+
// [#25467 cut 29] The legacy-organization row filters: which rows a not-yet-migrated database still holds.
425+
import { legacyOrganizationRowsOf, withoutLegacyOrganizationRows } from './protocol-legacy-organization-rows.js';
424426

425427
/**
426428
* Canonical Zod schema per metadata type lives in
@@ -10210,18 +10212,20 @@ export class ObjectStackProtocolImplementation implements
1021010212
}
1021110213

1021210214
private async withoutLegacyOrganizationRows<R>(table: string, rows: R[] | null | undefined, context?: unknown): Promise<R[]> {
10213-
if (!rows || rows.length === 0) return rows ?? [];
10214-
const legacy = await this.legacyOrganizationRowsOf(table, context);
10215-
return legacy.size === 0 ? rows : rows.filter((row) => !legacy.has(String((row as { id?: unknown }).id)));
10215+
// [#25467 cut 29] The body moved to `protocol-legacy-organization-rows.ts` (see `LegacyOrganizationRowsHost`).
10216+
const host: ThisParameterType<typeof withoutLegacyOrganizationRows> = {
10217+
legacyOrganizationRowsOf: (...a) => this.legacyOrganizationRowsOf(...a),
10218+
};
10219+
return (withoutLegacyOrganizationRows<R>).call(host, table, rows, context);
1021610220
}
1021710221

1021810222
private legacyOrganizationRowsOf(table: string, context?: unknown): Promise<ReadonlyMap<string, string>> {
10219-
const transaction = (context as { transaction?: unknown } | undefined)?.transaction;
10220-
return readLegacyOrganizationRows(
10221-
(this.engine as { getDriverForObject?: (objectName: string) => unknown }).getDriverForObject?.(table),
10222-
table,
10223-
transaction !== undefined ? { transaction } : {},
10224-
);
10223+
// [#25467 cut 29] The body moved to `protocol-legacy-organization-rows.ts` (see `LegacyOrganizationRowsHost`).
10224+
const readEngine = () => this.engine;
10225+
const host: ThisParameterType<typeof legacyOrganizationRowsOf> = {
10226+
get engine() { return readEngine(); },
10227+
};
10228+
return legacyOrganizationRowsOf.call(host, table, context);
1022510229
}
1022610230

1022710231
/**

0 commit comments

Comments
 (0)