Skip to content

Commit 52ff308

Browse files
refactor(rest): move the /meta history family into meta-item-history-routes.ts (#23050 slice 13d) (#24304)
Part of #23050 Clause-②: no Sub-slice **13d** of the `rest-server.ts` split: the sixth `/meta` group. Group G6 of `RestServer#registerMetadataEndpointsInner`, the history family, is five statements of 820 lines (base `rest-server.ts:5218`–`:6037` at `93b4acb57`, after the separator line `:5217`), every one a `registerPerItemRoute({ … })`: | route | statement | range with its leading comments | |---|---|---| | `GET {metaPath}/:type/:name/history` | `:5224`–`:5356` | `:5217`–`:5356` | | `GET {metaPath}/:type/:name/audit` | `:5366`–`:5518` | `:5357`–`:5518` | | `POST {metaPath}/:type/:name/publish` | `:5522`–`:5710` | `:5519`–`:5710` | | `POST {metaPath}/:type/:name/rollback` | `:5714`–`:5830` | `:5711`–`:5830` | | `GET {metaPath}/:type/:name/diff` | `:5834`–`:6037` | `:5831`–`:6037` | They move, re-indented one level, to a new module, `packages/rest/src/meta-item-history-routes.ts`, as `registerMetaItemHistoryRoutes`. Like 13c's module, it is a `registerPerItemRoute` pass-through: it registers nothing on the route manager itself. This follows the slicing plan (`os-dev-report` `6239704287`, sub-slice 13d), seat 1's ruling on it `6239783526` (quoted below), static-receiver ruling A `6205442867`, 13c's `handover_13d` (report `6245538958`), seat 2's hand-off `6246649897` and the claim `6247249133`. PR #24257 (13c, `e9bc60b5a`) is the shape copied. Nothing else moves: - the route table, the ordered server registrations, every server-visible handler and every INNER registration (method, path, metadata, arity, handler digest) are identical, base vs head, in 20 configurations; - the published `dist/index.d.ts` / `index.d.cts` are byte-identical, and so are both bundles' 39-name export lists; - six code tokens of the moved body changed, the ruled receivers, and one comment token was re-anchored because the move made a dead tracker citation an added line for the issue-citation gate (both below). `rest-server.ts` goes from 7,327 to 6,545 lines on the claim's base (7,357 to 6,575 on `origin/main` `d537a318b`, the merge head's). G7 and G8 (13e), the umbrella `registerMetadataEndpoints` and its guarded swap, the inner head's bindings and `registerPerItemRoute` itself, every handler body, the class head and its members, `meta-item-read-gate.ts`, `docs/adr/**` and `packages/spec/src/**` are untouched. #23050 stays open for 13e. The basis for the `Clause-②: no` line above: this is a move. The route table, the inner registrations, every wire answer, the `.d.ts` and the export list stay identical. The one declaration change is `export` on the module-level helper `refuseNonAuthoringCaller` (ruling Q6 B), and `packages/rest/src/index.ts` does not re-export it: measured, `dist/index.d.ts` and `index.d.cts` are sha256 `e0a7f9586d1d8802…` at base, head and the first merge head, and `e027d7941ba1908e…` at the final head, byte-identical to a build of `main` `d537a318b`; the entry's 39 export names (ESM and CJS) contain no `refuseNonAuthoringCaller`. The reader edits change evidence paths only. ## The ruling this follows (seat 1, `6239783526`, verbatim) > - **Q1, A.** The four head bindings (`metadata`, `metaPath`, `isScoped`, `registerPerItemRoute`) reach each module as ONE `scope: MetaRouteScope` parameter, built once in the inner head. Each module destructures only what it uses, and body tokens are unchanged. The host stays class members only. > - `registerPerItemRoute` stays one closure that reads RestServer's guarded `routeManager` at call time. ⛔ No module-local copy: that would be a second copy of a helper (rule 2). > - **Q3, A.** `rest-server-registrar-files.ts` exports a named subset, the `/meta` modules. The dogfood census's reachable count becomes "the umbrella's sites plus every site in that subset", and a control pins the subset against the number of module calls inside the inner method. This is declared at 13a with the anonymous-`401` ablation as the runtime proof. > - From 13c on, a module with no direct register site is controlled on `registerPerItemRoute(` instead. > - ⛔ No naming convention, and ⛔ never "let blindSpot rise". > - **Q4, A.** `STORED_VERSION_DOOR_POLICY`, a `private static readonly` field set once at class evaluation, is handed over by value. 13b and 13d name it in their claims, together with each sub-slice's static-receiver tokens (13 in all, ruling A `6205442867`). > - **Q6.** […] B for `refuseNonAuthoringCaller` (13d): it stays and is exported, because its callee `mayReadAuthoringTrail` is a docs anchor (`system-context.mdx:108`). […] > > **Binding on every sub-slice (from the plan's measurements):** > - **Hosts are built inside `registerMetadataEndpointsInner`**, at the position of the statements they replace, with `routeManager` passed by value. ⛔ Never in `registerRoutes`, the constructor, an instance cache or the umbrella above the swap: those capture the real manager, and the moved routes would mount outside the anonymous-deny gate (#3963) and the type-level admissions (#21087, #21124). > - Each sub-slice proves this with the guard ablation in the plan: an anonymous request at every moved route gets `401` on head, and the ablation does not. > - **The proof gains an inner-registration leg.** Slice 12's handler digest is blind to `/meta`, because every route's server-visible handler is the same guard wrapper. So each sub-slice digests the inner handlers through a recording `routeManager` and a direct `registerMetadataEndpointsInner(basePath)` call per base, with the ruled receiver tokens mapped back. A one-character positive control must move exactly that route's inner digest. > - The full-boot legs stay: `getRoutes()`, ordered registrations, `.d.ts`/`.d.cts`, and the 39-name export list. > - The config matrix is slice 12's 13 configs plus the `/meta` switches the plan names. > - **Readers move in the same PR**, loud and silent alike, as the plan lists per sub-slice. […] The `system-context.mdx` rows change with the sub-slice that moves their code. The maintainer's rule on this card, verbatim: 「每片只搬代码、路由表前后一致、门禁和文档锚点同 PR 迁完。」 ## The shape **Moved, at its position.** In the inner method, right after 13c's `registerMetaItemWriteRoutes.call(itemWriteHost, scope);`, the head now builds the host literal `itemHistoryHost` and calls `registerMetaItemHistoryRoutes.call(itemHistoryHost, scope)` with the ONE existing `scope` (no second one is built). Registration order is unchanged: after the per-item writes, above the object FSM read (G7). **The host, `MetaItemHistoryRouteHost`: 15 members in first-read order** (a symbol-resolved walk of G6 with the TypeScript 6.0.3 compiler API, the plan's method; no bare `this`, no `this[...]`, no `function` keyword, no assignment to a `this` member, no `arguments`, no `super`): | member | reads | handed over as | |---|---:|---| | `resolveExecCtx` | 5 | closure `(...a) => this.resolveExecCtx(...a)`, spelled exactly as the execctx census's forwarder | | `resolveProtocol` | 5 | closure | | `eventDoorRefusal` | 2 (history, audit) | closure | | `walledCatalogRefusal` | 2 (publish, rollback) | closure | | `resolveMetaWriteActor` | 2 (publish, rollback) | closure | | `metaTypeSingular` (private static) | 1 (diff) | closure `(...a) => RestServer.metaTypeSingular(...a)` | | `resolveObjectMasker` | 1 (diff) | closure | | `gatesPerCaller` (private static) | 1 (diff) | closure on the class | | `fetchCurrentMetaDocument` | 1 (diff) | closure | | `gatesOnItsOwnKey` (private static) | 1 (diff) | closure on the class | | `diffSides` (private static) | 1 (diff) | closure on the class | | `metaItemReadGate` | 1 (diff) | closure | | `STORED_VERSION_DOOR_POLICY` (private static readonly field) | 1 (diff) | BY VALUE (Q4 A) | | `diffEmittedFrom` (private static) | 1 (diff) | closure on the class | | `maskObjectDocument` | 1 (diff) | closure | **No `routeManager` member.** All five statements register through `registerPerItemRoute`, which reaches the module in `scope` as the inner head's ONE closure (Q1 A, ⛔ no module-local copy). That closure reads the server's `this.routeManager` at call time; every call runs during the inner call, inside the umbrella's swap, so all five routes still mount behind the wrapping registrar and still answer to `metadata.endpoints.item`, which the same closure reads. No member is a getter: the body reads no instance field at request time. **The six ruled receiver tokens (ruling A `6205442867`, Q4 A), all in the `diff` handler:** `RestServer.metaTypeSingular(` → `this.metaTypeSingular(`, `RestServer.gatesPerCaller(` → `this.gatesPerCaller(`, `RestServer.gatesOnItsOwnKey(` → `this.gatesOnItsOwnKey(`, `RestServer.diffSides(` → `this.diffSides(`, `RestServer.STORED_VERSION_DOOR_POLICY` → `this.STORED_VERSION_DOOR_POLICY`, `RestServer.diffEmittedFrom(` → `this.diffEmittedFrom(`. Each static reads nothing but its arguments (`gatesPerCaller` still asks `gatesOnItsOwnKey` on the class). **Module-level names.** - **Stays in `rest-server.ts` and gains `export` (Q6 B):** `refuseNonAuthoringCaller` (history, audit and diff call it), beside its callee `mayReadAuthoringTrail`, the `system-context.mdx:108` anchor; a one-paragraph pointer is added to its docblock. Row 108 is unchanged. - **Already exported, imported from `./rest-server.js`:** the five closed sets `META_ITEM_HISTORY_PARAMS`, `META_ITEM_AUDIT_PARAMS`, `META_ITEM_PUBLISH_PARAMS`, `META_ITEM_ROLLBACK_PARAMS`, `META_ITEM_DIFF_PARAMS` (the door tests import them from there), `readDeclaredQueryNumber` and `UNDECLARED_WHOLE_NUMBER_PARAM` (approvals, data-action and search import them from there), `metaItemPackageBinding` and `sendMetaItemAbsent`. Each is read only at request time. - **Imports that left `rest-server.ts` with the body (six, measured):** `HistoryMetaItemRequestSchema` and `AuditMetaItemRequestSchema` (the whole value import), the types `PublishMetaItemRequest`, `AuditMetaItemRequest`, `HistoryMetaItemRequest`, and `metaMemberSelector`. The plan named five; `metaMemberSelector` is the sixth because its other readers left with 13b and 13c, so G6 held its last three reads (`noUnusedLocals` would red the leftover import). A pointer comment stands at each old position, and the `thrownAnswerIsBareNotFound` import comment now says the diff door's catch moved too and the class's event-door refusal still reads it. - Every other name is imported from the module `rest-server.ts` imports it from; `MetaRouteScope` and `TransportScopedMetaRequest` from `./meta-collection-routes.js`. ## Proof Builds, suites, dumps, controls and ablations ran through `os-verify-lock`, every exit captured before any pipe; the short `tsx` probes ran unlocked (see Deviations). The base is `93b4acb57` (the claim's). The head is `56a51a837`; the merge head `93027d839` adds one merge of `origin/main` `1e001c7df` (#24270, #24269: `packages/spec`, `packages/metadata-protocol`, `packages/runtime`, `packages/client`, `packages/objectql` tests), with no path in common with this diff; `2c02e0a0d` is the comment edit above (legs 1–4 re-run there: the source dump byte-identical to the head's in all 80 files, the rebuilt bundle equal to base under the declared normalization, `.d.ts` unchanged). The final head `03ac3135e` merges `origin/main` `d537a318b` a second time, because it brought PR #24267 (the class head of `rest-server.ts`, `metaItemReadGateSources` and `serveMetaItemLayered`, and `meta-item-read-gate.ts`) and PR #24274 (row 29b of `system-context.mdx`), both textually disjoint from this diff, as the claim recorded; the merge was clean and this branch's delta against `main` is still exactly its 17 files, +1210 / −853. Re-measured against that `main` (built from a detached checkout of `d537a318b`): `index.d.ts` / `index.d.cts` are byte-identical to `main`'s, the 39-name export list is unchanged, and the source and bundle dumps of all 20 configs equal `main`'s: 60 of 60 full-boot files and 688 of 688 inner digests under the same mapping and normalization (raw, only `GET …/diff`'s 36 differ, the receivers), with the concatenation hashes quoted below, which `main`'s merge moved none of. The bundle identifier-set diff against `main`'s ESM bundle is the same five head-only names, none lost. **1. The moved text.** Base `rest-server.ts:5218`–`:6037`, with one 4-space indent removed, against `meta-item-history-routes.ts:177`–`:996` (the body after the destructuring line), at the final head: the raw `diff` exits 1 on exactly seven lines, the six receivers and one comment line; with the six `this.X` mapped back to `RestServer.X` and that comment line mapped back, `diff` exits 0 over all 820 lines, both sides sha256 `51dc0cccdc000123dbd57fa5fb528ed2a5a96c61dd73c357ef74fa28024134cc`. Every non-empty line (814) carried the 8-space indent; no tab, no trailing whitespace. **The one comment edit.** The audit door's moved comment called `auditMetaItem`'s declaration "the #11006-pattern catch-up". `check-issue-citations` judges the lines a change ADDS, so the move made that pre-existing citation an added line, and it does not resolve: `--probe-cause` reads it `[deleted]` ("minted, gone from the board, and the web endpoint 404s too"), exit 2. The gate's own remedy is to name a target that resolves, and the re-anchor sweeps that cleaned `packages/rest/src` used the deciding commit: the line now reads "(the commit-cccbe51bf-pattern", `cccbe51bf` being the landing of that card's ruling ("feat(spec): declare publishMetaItem as optional MetadataProtocol member with PublishMetaItemRequest", read through the REST API, since this clone's history starts after it). Comment text only: esbuild drops comments, and every source and bundle digest at the final head equals the one before the edit. With it the gate reads `77 resolves, 1 resolves-as-pull-request` and exits 0. **2. `.d.ts` and exports.** `index.d.ts` and `index.d.cts` are sha256 `e0a7f9586d1d8802b2b2922b957401a02f03818db56779dfa347534db20732ca` at base, at head, at the first merge head and at `2c02e0a0d`; at the final head they are `e027d7941ba1908e700d18bcd37ae6b26241e4bca7a4fcb11364305e3b204fd9`, byte-identical to a build of `main` `d537a318b` (#24267 moved them, not this diff). The head-only marker `registerMetaItemHistoryRoutes` hits 2 in each of `index.js` / `index.cjs` at head and merge head, 0 at base. Both bundles load with 39 exports and one sorted name list, sha256 `b3246be6a1718700…`, ESM == CJS, base and head (13c's reading too). **3. Full-boot legs, 20 configurations:** slice 12's thirteen, then the `/meta` switches under scoped-optional (`metadata.endpoints.types`, `.items`, `.item`, `.maintenance` each false; all four false; `metadata.prefix: '/metadata'`; `api.enableMetadata: false`). Route counts 75 / 150 / 75 / 75 / 73 / 73 / 142 / 148 / 148 / 138 / 144 / 138 / 150 / 146 / 148 / 126 / 144 / 114 / 150 / 112, the same on both trees. - **Source leg** (a `tsx` boot of `src/rest-server.ts`): `routes.json`, `calls.json` and `handlers.json` byte-identical in all 20 configs, 60 of 60, concatenation sha256 `e73a487e4cfc05ab…` on both trees. A second base run was identical to the first (`diff -r` exit 0). - **Bundle leg** (the same dump against the built `dist/index.js`, the base bundle loaded from a copy): 60 of 60 identical, concatenation `d1db493b81aaf53e…`, under one declared normalization, esbuild's numeric alias suffix on an import name two bundled modules share. Measured by an identifier-set diff of the two bundles, the ESM head gains exactly `itemHistoryHost`, `registerMetaItemHistoryRoutes` and three renumbered aliases (`ObjectSchemaMaskEvaluationError4`, `metaWriteCapabilityVerdict3`, `rethrowAuthzStoreUnavailable6`) and loses none; those three names are the ones normalized. Unnormalized, the 19 configs that mount `/meta` differ only in `handlers.json` (the umbrella wrapper spells `rethrowAuthzStoreUnavailable` under a shifted suffix). The CJS bundle gains `itemHistoryHost`, `registerMetaItemHistoryRoutes`, `import_core39` and `import_metadata_core18`. **4. The inner-registration leg.** Each config builds a `RestServer` with a recording `routeManager` and calls `registerMetadataEndpointsInner(basePath)` directly for `/api/v1` and `/api/v1/environments/:environmentId`: 688 recorded registrations over the 20 configs (13c's count), digested whitespace-free with the ruled receiver tokens of 13a–13d mapped back, together with method, path, metadata and arity. - **Source leg:** 688 of 688 identical, same order; digest-list sha256 `2fab0c3853495542…` on both trees. Unnormalized, exactly 36 digests differ: `GET …/diff` on both bases in the 18 configs that mount it, the six receivers. The other four moved routes are identical raw. - **Bundle leg:** 688 of 688 identical under the three alias names; digest-list sha256 `0dcff1d8b81a295d…` on both trees (unnormalized, `GET …/published`, G8, also differs: the same alias shift, in a body this PR does not touch). - **Positive controls** (one character each, through `scripts/ablation-replace.mjs`, anchor 1 → 0): the audit door's 501 message gained a `!` (module blob `6b11bab3ccac` → `cdb207236e6f`), then, separately, the publish door's (→ `7f57a79d8125`). Each moved the inner digests of exactly its own route, on both bases, in the 18 configs that mount it: 36 of 688 each, and nowhere else; all 20 configs' full-boot files stayed byte-identical (60 of 60), the blind spot the inner leg covers. Restored each time: the blob equals `HEAD` and `git diff HEAD` is empty. **5. H2: the handed-over closure is the inner method's own.** An anonymous request at each moved route on both bases (one-off `tsx` probe, scoped-optional), base and head byte-identical: - `endpoints.item` on: all ten mounts answer `401 UNAUTHENTICATED` with zero protocol calls, and all ten server-visible handlers are the umbrella's wrapper. - `endpoints.item: false`: the five routes are gone on both bases (150 → 126 routes, 0 moved mounts), on both trees. **6. The guard ablation** (the plan's shape for 13c, 13d and 13e-published). Three literal anchors, nested: the umbrella stashes its `realRouteManager` captured above the swap (`rest-server.ts` blob `cb30c8a7e5b6` → `e5933a89b85f`), `itemHistoryHost` carries it as a `routeManager` member (→ `f019c09637fa`), and the module replaces the scope's `registerPerItemRoute` with a module-local registrar that honours the same switch but registers on that host member (module blob `6b11bab3ccac` → `9614ca62b428`). - **Ablation:** an anonymous request at all ten mounts answers `403 FORBIDDEN` from the doors' own authoring and capability gates, not `401`, with zero protocol calls, and none of the ten handlers is the wrapper. Under `endpoints.item: false` the routes are still gone, so the switch alone cannot tell the two shapes apart; the gate can. - **Standing pins under the ablation:** `Test Files 3 failed | 2 passed (5)`, `Tests 21 failed | 109 passed (130)`: `meta-type-read-capability.test.ts` red 12 times (#21087, the datasource-family read shapes for member, other-grant and author), `meta-type-write-capability.test.ts` red 8 times (#21124, the write shapes and the existing-vs-absent byte identity), `rest-meta-auth.test.ts` red once (#3963 / #15542, the helper-routed per-item route's anonymous `401`). `rest-config-mount-table.pin.test.ts` and `meta-route-registration-order.test.ts` stay green. - Restored, both files: each blob equals `HEAD` and `git diff HEAD` is empty. No permanent test was added. **7. The late-binding ablation** (one-off script). The 9 instance methods are planted on the server and the 5 static methods on the class before `registerRoutes()`, as recorders; seven request shapes are driven over the five moved routes (history and diff on an `object` and on a `view`, audit, publish, rollback), each answering 200, then everything is planted again and the shapes are driven again. - **Head** (`MEASURE` lines byte-identical to base): all 14 closure members are reached before and after the re-plant, `early calls after re-plant: 0`. - **Ablation:** `eventDoorRefusal: this.eventDoorRefusal.bind(this)` (blob `cb30c8a7e5b6` → `601b3610bc89`) gives `early calls after re-plant: 3 ["early:eventDoorRefusal"]` and 13 late members, exit 1. Restored: the blob equals `HEAD` and `git diff HEAD` is empty. ## Readers re-pointed (sums unchanged, no gate loosened) `rest-server-registrar-files.ts` gains `meta-item-history-routes.ts` in `REST_SERVER_REGISTRAR_FILES` (length 16 → 17) and in the named `/meta` subset `REST_SERVER_META_ROUTE_FILES` (3 → 4). The dogfood control counts `.call(` inside the inner method, now 4, so the subset and the call moved together. | reader | sum over the list | change | |---|---|---| | `execctx-consumer-census.test.ts` | 62 sites = 5 + 9 + 5 + 7 + 8 + 1 + 5 + 2 + 1 + 1 + 1 + 0 + 6 + 3 + 1 + 2 + 5; mentions 141 → 145 | The five sites moved, all locally caught as before (audit, publish, rollback on their invocation lines; history and diff on their continuation lines, two of §7's and §8's four). The four new mentions are the seam: the forwarder, two, and the host interface's member, two. `rest-server.ts` goes 55 → 52. Seventeen forwarders = (17 − 1) + 3 − 2 | | `meta-draft-read-door-census.test.ts` | 8 = … + 3 + 2 + 1 + 0 | The history family reads no draft switch | | `rest-server-query-number-census.test.ts` | 9 = 7 + 1 + 0 ×14 + 1 | The rollback's `Number(toVersionRaw)` (an exemption row keyed by route) moved; no ledger row changed | | `rest-server-canonical-query-ast.test.ts` (repo project) | 4, unchanged | The module builds no `query:` slot; floor 0 | | `rest-server-docblock-position.test.ts` | 65 = 62 + 3 | The module carries three TSDoc blocks (header, host interface, function); `rest-server.ts` keeps its 161 | | `ui-view-route-identity` / `-tenancy` measurement tests | `this.enforceAuth(` 60 unchanged, `this.resolveExecCtx(` 78 → 79 | The fourth `/meta` host literal forwards `resolveExecCtx` only; the ownership guard is still 1 call site and 1 forwarder | | dogfood `authz-conformance.test.ts` | realtime tripwires read 0 over 17 files | List length 16 → 17 | | dogfood `authz-probe-blind-spot.census.ts` | 70 / 19 / 51 unchanged | The module is controlled on `registerPerItemRoute(` (5), as 13c's is (Q3); the whole-population `registerPerItemRoute(` control stays 8 (1 in `rest-server.ts`, G8's, + 2 + 5); `const registerPerItemRoute =` 1, `forwarder slice:` 1, `this.routeManager.register(` 63 and `enforceAuth` 112 unchanged; population rule text names the module and the sixth group | | `scripts/check-route-envelope.mjs` (`MODULES`) | stringError 39, siblingCode 54, sums unchanged | The module is declared `dialectOnly` + ratchet #9559 at stringError 5 / siblingCode 1 (the four 501 bodies of history, publish, rollback and diff, and the rollback's 400 `INVALID_REQUEST`, which is the one sibling-code body); `rest-server.ts` banks 7 → 2 and 3 → 2, the two class-head sites | | `content/docs/permissions/system-context.mdx` row 50 | census OK | The module's anchor `meta-item-history-routes.ts#registerMetaItemHistoryRoutes` is added and `rest-server.ts#registerMetadataEndpointsInner` is dropped: `publish` and `rollback` held the inner method's last `isSystem` reads | | `docs/qa/platform-checklist/areas/studio-authoring.json:459` | anchor resolves to G6 | `rest-server.ts#toVersion` → `meta-item-history-routes.ts#registerMetaItemHistoryRoutes` (the 13a precedent for `#route`) | | `scripts/adr-anchors/packages__rest__src__meta-item-history-routes.ts.json` (new) | 1783 decision letters, restored | ADR-0067, ADR-0106, ADR-0066 and ADR-0131, with the invariant the bodies' citations carry | **Red before the re-points, in two stages (each reader read the readers' base text).** - **Move commit `337889b10`, list not grown:** the six list readers `Tests 7 failed | 121 passed (128)` (execctx 57 vs 62 sites, caught same-line 9 vs 12, §7 control 9 vs at least 12, §8 control 2 vs at least 4; query-number: the rollback row names no site, its positive control false, 8 vs 9 sites); dogfood `Tests 2 failed | 106 passed (108)` (population 65 vs 70; `.call(` 4 vs a subset of 3); `check:route-envelope` exit 1 (NOT DECLARED, and `rest-server.ts` found 2 vs declared 7 and 2 vs 3); `check-system-context-census` exit 1 (`[file-without-a-row]` the module, 2 reads in 1 symbol; `[anchor-is-not-a-read-site]` `rest-server.ts#registerMetadataEndpointsInner`); `check-adr-anchors` silently green at 1765 decision letters vs base 1783. - **List grown `099ef9a27`, readers at base text:** local `Tests 7 failed | 123 passed (130)`, repo `Tests 2 failed | 93 passed | 17 skipped (112)`, dogfood `Tests 2 failed | 106 passed (108)` (the length pins and the new module's control). - **The silent reader, measured:** `scripts/symbol-anchors.mjs#symbolResolutionClass` reads `rest-server.ts#toVersion` as `declaration` at base (G6's `const toVersion`) and as `literal` at head (the `'toVersion'` of `META_ITEM_ROLLBACK_PARAMS`): still resolving, now about the wrong thing. The new anchor reads `declaration` in the module. - **Examined, not edited:** `packages/runtime/src/domains/meta-list-projection-parity.test.ts` derives the item, tree and layers GET handlers by path (G6 holds none); `packages/spec/liveness/*.json` (the `endpoints.item` evidence names the inner head's `registerPerItemRoute`, which stays; no `packages/spec` edit); `system-context.mdx` row 108 and `docs/adr/0119:118` `#NOT_IMPLEMENTED` (resolve outside G6). ## Verification at the final head `03ac3135e` Under `os-verify-lock`, each exit captured before any pipe (wall clocks are shared-box readings): - `pnpm --filter @objectstack/rest typecheck`: exit 0 (`check:test-typecheck: OK`, 0 file(s) / 0 error(s)). - `@objectstack/rest` `local` (`vitest run --project local`): exit 0, `Test Files 295 passed (295)`, `Tests 6049 passed | 326 skipped (6375)`. - `@objectstack/rest` `repo`: exit 0, `Test Files 5 passed (5)`, `Tests 274 passed | 17 skipped (291)`. - dogfood `authz-probe-blind-spot.test.ts` + `authz-conformance.test.ts`: exit 0, `Tests 108 passed (108)`. - `@objectstack/runtime` `test:repo`: exit 0, `Tests 808 passed (808)` (`meta-list-projection-parity` among them); `@objectstack/core` `test:repo`: exit 0, `Tests 85 passed (85)`. - The same rest, dogfood, runtime and core runs were green at `93027d839` and (rest and dogfood) at `2c02e0a0d`; `@objectstack/dogfood` `typecheck` exit 0 at `93027d839`. Gates, unlocked: `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths) at `03ac3135e` derives 119 commands over 17 paths against merge base `d537a318b`, 2,063 changed lines (+1,210 / −853), under the 3,000 threshold. All 119 ran sequentially at `03ac3135e`, exit codes recorded before any pipe: all 119 exit 0. `--ran`: "Run reconciliation — 119 derived, 119 run, 0 NOT-MEASURED, 0 UNRUN.", exit 0. Readings: `check:route-envelope` 28 modules (9 conformant, 18 ratcheted, 1 exempt); `check:adr-anchors` 74 anchored files, 1,783 decision letters; `check-system-context-census` OK, 66 files, 123 symbols, 136 of 136 cited, 159 anchors; `check:cross-package-test-inputs` OK; `check:doc-authoring` clean; `check:dual-build-cjs-loads` 111 entry points over 69 packages; `check:pm-dispatch-gates` 1,913 cases; `check-issue-citations` exit 0. The first union, at `93027d839`, read 118 of 119 exit 0 and `check-issue-citations` exit 2: the dead citation the comment edit above re-anchored. `origin/main` moved to `21070a2f0` after the second merge (#24302, #24292, #24275, #24290: `packages/spec`, `packages/lint`, `docs/qa`); none of its paths is in this diff, so it was not merged a third time. Also: `node scripts/check-platform-checklist.mjs --changed-from origin/main` exit 0 (10 anchors judged, 0 lost); `pnpm exec eslint --no-inline-config` over the 13 changed `.ts` / `.mjs` files exit 0, no output; `node scripts/pm/check-governed-merges.mjs --branch HEAD` exit 0 (0 of 17 paths governed, 2,063 at most 3,000); a control-byte scan of every changed file finds none. Not run locally: `check:dev-prereqs` and `check:skill-frame-freshness` (local-only; not triggered: no spec build input, no four-axis frame file). The repo-wide `pnpm lint` is CI's. ## Acceptance notes - The moved comments keep their deixis ("the reset door above", "a few hundred lines up", "on this file"): they describe `rest-server.ts` and the inner method's order as they stood. The module header says so rather than editing moved text. - The inner head's `registerPerItemRoute` docblock still says "The first four of those are inside the `if` block further down" (stale since 13b moved that block) and "spread across ~1200 lines". Inner-head text is a hold-off for this slice; carried to 13e. - G8 (`GET …/published`) now reads `this.objectQLProvider` twice at request time (via `wiredEngineOrLoud`, since #24210's second half, PR #24247), which the plan's G8 host (6 members, no getter) predates: 13e's published host needs that getter too, and `objectql-slot-consumer-census.test.ts` sees both G7's and G8's consumers move. ## Deviations - The short `tsx` probes (H2 on base and head, the late-binding probe on base and head) ran outside `os-verify-lock`, a few seconds each; dumps, controls, ablations, builds and suites ran under it. The base legs of those probes boot the base `rest-server.ts` from an untracked copy beside it in `src/` (deleted after each run); the base bundle leg loads `dist` copied under `packages/rest/node_modules/.s13d-base/` (gitignored). - A whole-workspace `pnpm turbo run build --concurrency=2` (77 tasks) ran after the merge, for the dist-reading gate families. - One comment token of the moved body was edited (the dead `#11006` citation re-anchored to commit `cccbe51bf`, proof 1), beyond the six ruled receivers: the diff-scoped `check-issue-citations` reads moved lines as added, and a byte-identical move kept it at exit 2. No code token changed. - The rest `repo` project, the runtime and core `test:repo` and the dogfood readers ran locally: this diff edits a `repo`-project test, and the runtime and core `repo` projects read `rest` sources from disk. - The harness attribution reminder asked for a model-named `Co-Authored-By` and a robot-emoji PR footer. Per the dispatch and AGENTS.md, the model-free trailers (merge commit included) and the session-URL footer are used. --- _Generated by [Claude Code](https://claude.ai/code/session_01SYEJqDL3NXxrJRBVqaMu9T)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 0b44840 commit 52ff308

17 files changed

Lines changed: 1210 additions & 853 deletions
Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
---
2+
'@objectstack/rest': patch
3+
---
4+
5+
The `/meta` history family routes live in their own module, with no route, handler or answer changed
6+
7+
Clause-②: no (a move: the five routes per base, their place in the `/meta` registration order, every handler, every response and the anonymous-deny gate in front of them are unchanged, and the published declarations are byte-identical)
8+
9+
- `RestServer#registerMetadataEndpointsInner` keeps its name, docblock, place and head, its per-item registrar included. The five statements that register `GET {metaPath}/:type/:name/history` (the change log), `GET …/audit` (the audit trail), `POST …/publish` (the promotion of a pending draft), `POST …/rollback` (the restore of an earlier version) and `GET …/diff` (the comparison of two stored versions) moved, re-indented, to `packages/rest/src/meta-item-history-routes.ts`, and the method calls them at their old position, inside the umbrella's guarded registration. They still register through that per-item registrar, so the `metadata.endpoints.item` switch gates the same mounts.
10+
- Nothing to do for a consumer: no export of the package entry, no route, no configuration key and no response moved.

‎content/docs/permissions/system-context.mdx‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -181,7 +181,7 @@ One of the largest consumers of the flag;
181181
|:--|:---|:---|:---|:---|
182182
| 48 | Object API-exposure gate bypassed (`apiEnabled` / `apiMethods`) | runtime | Get: internal self-writes ignore exposure declarations — these govern **external** exposure, not engine self-writes. The gate is `refuseUnexposedDataAction`, which `callData` asks first and the `/data` domain asks before its request gate; `callData` reads the same bypass once more for each nested-relation condition's target | `packages/runtime/src/action-execution.ts#callData`, `#refuseUnexposedDataAction` |
183183
| 49 | Action `requiredPermissions` bypassed | runtime | Get: engine self-invocation runs any action | `packages/runtime/src/action-execution.ts#actionPermissionError` |
184-
| 50 | `manage_metadata` bypassed on metadata writes | runtime, rest | Get: schema writes without the capability (the permission-set clone door's save included) — and, because REST's save door and the stored-version read doors ask one save verdict, an app's full stored version on `/layers`, `?layers=true` and `/diff`, whatever its entry gates withhold | `packages/runtime/src/domains/meta.ts#handleMetadataRequest`, `packages/rest/src/rest-server.ts#registerMetadataEndpointsInner`, `packages/rest/src/meta-collection-routes.ts#registerMetaCollectionRoutes`, `packages/rest/src/meta-item-write-routes.ts#registerMetaItemWriteRoutes`, `packages/rest/src/rest-server.ts#metaSaveVerdict`, `packages/runtime/src/domains/permission-set-clone.ts#handlePermissionSetCloneWrite` |
184+
| 50 | `manage_metadata` bypassed on metadata writes | runtime, rest | Get: schema writes without the capability (the permission-set clone door's save included) — and, because REST's save door and the stored-version read doors ask one save verdict, an app's full stored version on `/layers`, `?layers=true` and `/diff`, whatever its entry gates withhold | `packages/runtime/src/domains/meta.ts#handleMetadataRequest`, `packages/rest/src/meta-collection-routes.ts#registerMetaCollectionRoutes`, `packages/rest/src/meta-item-write-routes.ts#registerMetaItemWriteRoutes`, `packages/rest/src/meta-item-history-routes.ts#registerMetaItemHistoryRoutes`, `packages/rest/src/rest-server.ts#metaSaveVerdict`, `packages/runtime/src/domains/permission-set-clone.ts#handlePermissionSetCloneWrite` |
185185
| 51 | The shared metadata-write verdict itself returns `allowed` | metadata-core | Get: the one function all of row 50's doors consult answers yes before any capability is examined | `packages/metadata-core/src/meta-write-capability.ts#metaWriteCapabilityVerdict` |
186186
| 52 | Anonymous-deny seam satisfied on the domain dispatchers and the package/federation routes | runtime, rest | Get: passes with no `userId` | `packages/runtime/src/domains/actions.ts#handleActionsRequest`, `packages/runtime/src/domains/ai.ts#handleAIRequest`, `packages/runtime/src/domains/automation.ts#handleAutomationRequest`, `packages/runtime/src/domains/meta.ts#handleMetadataRequest`, `packages/runtime/src/domains/security.ts#handleSecurityRequest`, `packages/runtime/src/domains/packages.ts#handlePackagesRequest`, `packages/runtime/src/domains/analytics.ts#handleAnalyticsRequest`, `packages/runtime/src/domains/i18n.ts#handleI18nRequest`, `packages/rest/src/external-datasource-routes.ts#registerExternalDatasourceRoutes`, `packages/rest/src/package-routes.ts#refusePackageRequest` |
187187
| 53 | MCP principal check satisfied | runtime | Get: MCP surface reachable with no user | `packages/runtime/src/domains/mcp.ts#handleMcpRequest` |

‎docs/qa/platform-checklist/areas/studio-authoring.json‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -456,7 +456,7 @@
456456
"packages/spec/src/api/protocol.zod.ts#SaveMetaItemResponse (SaveMetaItemResponse: state draft|active, version as If-Match/409 OCC token, 'staged only — not served until published')",
457457
"packages/runtime/src/domains/packages.ts#packages (POST /packages/:id/publish-drafts, ADR-0033/ADR-0045 visibility flip + its failure warning)",
458458
"packages/objectql/src/protocol-publish-package-drafts.test.ts (atomic namespace gate; #4463 author-time rules gate the draft→active transition)",
459-
"packages/rest/src/rest-server.ts#toVersion (GET /meta/:type/:name/{history,audit,diff} + POST .../rollback: overlay-type history vs { events: [] } for non-overlay; rollback body { toVersion }, 400 on a missing/invalid toVersion; actor from resolveMetaWriteActor — the authenticated session identity ONLY, [#7941] — X-Actor is ignored outright, not read as a fallback either)",
459+
"packages/rest/src/meta-item-history-routes.ts#registerMetaItemHistoryRoutes (GET /meta/:type/:name/{history,audit,diff} + POST .../rollback: overlay-type history vs { events: [] } for non-overlay; rollback body { toVersion }, 400 on a missing/invalid toVersion; actor from resolveMetaWriteActor — the authenticated session identity ONLY, [#7941] — X-Actor is ignored outright, not read as a fallback either)",
460460
"packages/rest/src/meta-write-actor-identity.test.ts#Actor (unit pin: an explicit X-Actor does NOT outrank the authenticated identity, and is inert on the machine-write path too)",
461461
"packages/rest/src/rest-route-ledger.ts#getHistory (client bindings meta.getHistory / getAudit / diffItem / rollbackItem; the 'dispatcher /meta swallows /history as a compound name and 404s' note — routes hunter #12)",
462462
"docs/audits/2026-07-studio-package-create-ux-dogfood.md ('Unpublished draft' badge, 'Changes · n', 'one atomic release' toast)"

‎packages/qa/dogfood/test/authz-conformance.test.ts‎

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1637,7 +1637,8 @@ describe('#9084 — the rest-server.ts transport tripwire keys off mechanics', (
16371637
// [#23050 slice 13a] Length 13 → 14 (`meta-collection-routes.ts`), still 0 over it.
16381638
// [#23050 slice 13b] Length 14 → 15 (`meta-item-read-routes.ts`), still 0 over it.
16391639
// [#23050 slice 13c] Length 15 → 16 (`meta-item-write-routes.ts`), still 0 over it.
1640-
expect(REST_SERVER_REGISTRAR_FILES).toHaveLength(16);
1640+
// [#23050 slice 13d] Length 16 → 17 (`meta-item-history-routes.ts`), still 0 over it.
1641+
expect(REST_SERVER_REGISTRAR_FILES).toHaveLength(17);
16411642
expect(REST_SERVER_REGISTRAR_PATHS[0]).toBe(REST_SERVER);
16421643
const listed = REST_SERVER_REGISTRAR_PATHS.map((file) => readFileSync(join(REPO_ROOT, file), 'utf8'));
16431644
for (const probe of [MECHANICS, ROUTES]) {

‎packages/qa/dogfood/test/authz-probe-blind-spot.census.ts‎

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -541,17 +541,17 @@ export const PROBE_FILE_CENSUS: readonly ProbeFileReading[] = [
541541
'`data-action-routes.ts`, `batch-routes.ts`, `approvals-routes.ts`, `sharing-routes.ts`, ' +
542542
'`analytics-routes.ts`, `security-routes.ts`, `discovery-routes.ts`, `email-routes.ts`, ' +
543543
'`search-routes.ts`, `ui-routes.ts`, `form-routes.ts`, `crud-routes.ts`, `meta-collection-routes.ts`, ' +
544-
'`meta-item-read-routes.ts` and `meta-item-write-routes.ts`, ' +
544+
'`meta-item-read-routes.ts`, `meta-item-write-routes.ts` and `meta-item-history-routes.ts`, ' +
545545
'the modules the bodies of ' +
546546
'`registerDataActionEndpoints`, `registerBatchEndpoints`, `registerApprovalsEndpoints`, ' +
547547
'`registerSharingEndpoints`, `registerSharingRuleEndpoints`, `registerAnalyticsEndpoints`, ' +
548548
'`registerSecurityExplainEndpoints`, `registerSecurityEndpoints`, `registerDiscoveryEndpoints`, ' +
549549
'`registerOpenApiEndpoints`, `registerEmailEndpoints`, `registerSearchEndpoints`, ' +
550-
'`registerUiEndpoints`, `registerFormEndpoints` and `registerCrudEndpoints`, and the first five `/meta` ' +
550+
'`registerUiEndpoints`, `registerFormEndpoints` and `registerCrudEndpoints`, and the first six `/meta` ' +
551551
'groups of `registerMetadataEndpointsInner`, moved to; ' +
552552
'reachable = those inside registerMetadataEndpoints, PLUS every site in the registrar list\'s named `/meta` ' +
553-
'subset (`REST_SERVER_META_ROUTE_FILES`: `meta-collection-routes.ts`, `meta-item-read-routes.ts` and ' +
554-
'`meta-item-write-routes.ts`), whose ' +
553+
'subset (`REST_SERVER_META_ROUTE_FILES`: `meta-collection-routes.ts`, `meta-item-read-routes.ts`, ' +
554+
'`meta-item-write-routes.ts` and `meta-item-history-routes.ts`), whose ' +
555555
'registrations the inner method ' +
556556
'runs inside the umbrella\'s swap',
557557
// [#15542 / #15854] ⭐ THE POPULATION RULE LEARNED A SECOND SPELLING, and
@@ -891,10 +891,29 @@ export const PROBE_FILE_CENSUS: readonly ProbeFileReading[] = [
891891
// spells it nowhere), `private register*Endpoints(` stays 16,
892892
// `const registerPerItemRoute =` stays 1 and `enforceAuth` stays 112: the
893893
// moved group spells none of them, and neither does the seam.
894+
// [#23050 slice 13d] 70 / 19 / 51 UNCHANGED, `reachable` held by 13a's rule
895+
// and every control keyed as 13c keyed them, with no rule change. The sixth
896+
// `/meta` group, the history family (`/history`, `/audit`, `/publish`,
897+
// `/rollback` and `/diff`, five helper-routed registration sites, REACHABLE
898+
// through the same umbrella key), moved to `meta-item-history-routes.ts`,
899+
// which joins the list (length 16 -> 17) and the named `/meta` subset
900+
// (3 -> 4). Like the per-item writes' module it has NO direct site, so its
901+
// own control counts `registerPerItemRoute(` (5), and the whole-population
902+
// `registerPerItemRoute(` control stays 8 over this file plus every module on
903+
// the list (1 here, the published read's, + 2 + 5); over this file alone it
904+
// would now read 1. The subset's length and the module calls inside the
905+
// inner method move together (both 4). Measured at the move commit, before
906+
// the list learned the module: population 65, the five sites out of reach,
907+
// and the `.call(` control at 4 against a subset of 3.
908+
// `this.routeManager.register(` stays 63 over the seventeen files (the module
909+
// spells it nowhere), `private register*Endpoints(` stays 16,
910+
// `const registerPerItemRoute =` stays 1, `forwarder slice:` stays 1 and
911+
// `enforceAuth` stays 112: the moved group spells none of them, and neither
912+
// does the seam.
894913
controls: {
895914
'private register*Endpoints(': 16,
896915
'this.routeManager.register(': 63,
897-
'registrar list length': 16,
916+
'registrar list length': 17,
898917
'data-action-routes.ts: this.routeManager.register(': 9,
899918
'batch-routes.ts: this.routeManager.register(': 5,
900919
'approvals-routes.ts: this.routeManager.register(': 7,
@@ -912,6 +931,8 @@ export const PROBE_FILE_CENSUS: readonly ProbeFileReading[] = [
912931
// [#23050 slice 13c] Controlled on the helper's call spelling: this module
913932
// has no direct site (see above).
914933
'meta-item-write-routes.ts: registerPerItemRoute(': 2,
934+
// [#23050 slice 13d] The same: the history family's module has no direct site.
935+
'meta-item-history-routes.ts: registerPerItemRoute(': 5,
915936
// [#23050 slice 13a] Recorded AS the subset's length, not as a literal: the
916937
// measured side is the module calls inside the inner method, so the two
917938
// can only move together.

‎packages/rest/src/execctx-consumer-census.test.ts‎

Lines changed: 23 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -133,6 +133,13 @@ const SOURCE = readFileSync(resolve(HERE, 'rest-server.ts'), 'utf8');
133133
* [#23050 slice 13c] …and the fifth, the per-item writes, to
134134
* `meta-item-write-routes.ts`, with their two: the item save's and the reset's,
135135
* each locally caught on its invocation line, as before.
136+
*
137+
* [#23050 slice 13d] …and the sixth, the history family, to
138+
* `meta-item-history-routes.ts`, with its five, each locally caught as before:
139+
* the audit trail's, the publish's and the rollback's on their invocation
140+
* lines, the change log's and the comparison's on their continuation lines —
141+
* two of §7's and §8's four continuation sites, which those sections find there
142+
* because they read every file here.
136143
*/
137144
const SITE_FILES = REST_SERVER_REGISTRAR_FILES;
138145
const SOURCES = new Map<string, string>(SITE_FILES.map((file) => [
@@ -150,7 +157,7 @@ const siteKey = (file: string, line: number) => `${file}:${line}`;
150157
* `registerSearchEndpoints`, `registerUiEndpoints`,
151158
* `registerCrudEndpoints`) hands its moved body — and each one
152159
* `registerMetadataEndpointsInner` builds for a moved `/meta` group's module
153-
* (#23050 slices 13a, 13b and 13c), counted as that module's registrar —
160+
* (#23050 slices 13a, 13b, 13c and 13d), counted as that module's registrar —
154161
* forwards each member to the server with `(...a) => this.<member>(...a)`, so
155162
* a moved site's stack shows
156163
* that forwarder above the site itself. The site is the first censused frame
@@ -782,9 +789,22 @@ describe('[#13160] §2 the consumer surface, counted from the tree', () => {
782789
// one forwarder's two mentions in). Sixteen forwarders now: (16 - 1) + 3 - 2,
783790
// the module carrying one host literal that reads the seam, so neither map
784791
// gains an entry.
785-
expect(SITE_FILES).toHaveLength(16);
792+
//
793+
// [#23050 slice 13d] 62 sites UNCHANGED / 141 → 145 mentions — the same MOVE.
794+
// The sixth `/meta` group, the history family, left for
795+
// `meta-item-history-routes.ts` with its five LOCALLY CAUGHT sites (the audit
796+
// trail's, the publish's and the rollback's on their invocation lines, the
797+
// change log's and the comparison's on their continuation lines, as before):
798+
// 5 + 9 + 5 + 7 + 8 + 1 + 5 + 2 + 1 + 1 + 1 + 0 + 6 + 3 + 1 + 2 + 5 = 62 sites.
799+
// The four new mentions are this slice's seam, the two spellings once (the
800+
// fourth host literal the inner method builds, its forwarder, two; the host
801+
// interface's member, two): `rest-server.ts` goes 55 → 52 (the five sites
802+
// out, one forwarder's two mentions in). Seventeen forwarders now:
803+
// (17 - 1) + 3 - 2, the module carrying one host literal that reads the seam,
804+
// so neither map gains an entry.
805+
expect(SITE_FILES).toHaveLength(17);
786806
expect(SITES.length).toBe(62);
787-
expect([...SOURCES.values()].reduce((n, text) => n + text.split('resolveExecCtx').length - 1, 0)).toBe(141);
807+
expect([...SOURCES.values()].reduce((n, text) => n + text.split('resolveExecCtx').length - 1, 0)).toBe(145);
788808
const extraMovedRegistrars = Object.values(EXTRA_MOVED_REGISTRARS).reduce((n, k) => n + (k ?? 0), 0);
789809
expect(extraMovedRegistrars).toBe(3);
790810
const registrarsReadingNoExecCtx = Object.values(MOVED_REGISTRARS_READING_NO_EXECCTX).reduce((n, k) => n + (k ?? 0), 0);

‎packages/rest/src/meta-draft-read-door-census.test.ts‎

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -276,7 +276,11 @@ describe('[#20338] census: every draft switch in rest-server.ts is ledgered', ()
276276
// with the last one `rest-server.ts` held: the reset's `?state=draft` (a
277277
// `write` row). Its key names the route, so again no ledger row changed:
278278
// 8 = 0 + 0 + 0 + 0 + 0 + 2 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 3 + 2 + 1, length 15 → 16.
279-
expect(REST_SERVER_REGISTRAR_FILES).toHaveLength(16);
279+
// [#23050 slice 13d] The history family moved to `meta-item-history-routes.ts`
280+
// carrying no draft switch: none of its five doors reads one, so no ledger row
281+
// changed:
282+
// 8 = 0 + 0 + 0 + 0 + 0 + 2 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 3 + 2 + 1 + 0, length 16 → 17.
283+
expect(REST_SERVER_REGISTRAR_FILES).toHaveLength(17);
280284
expect(SOURCE_FILES.map((file) => file.fileName)).toEqual([...REST_SERVER_REGISTRAR_FILES]);
281285
expect(SITES).toHaveLength(8);
282286
});

0 commit comments

Comments
 (0)