Skip to content

Commit e9bc60b

Browse files
refactor(rest): move the /meta per-item write routes into meta-item-write-routes.ts (#23050 slice 13c) (#24257)
Part of #23050 Clause-②: no Sub-slice **13c** of the `rest-server.ts` split: the fifth `/meta` group. Group G5 of `RestServer#registerMetadataEndpointsInner` is two statements, the per-item writes, of 405 lines (base `rest-server.ts:5284`–`:5688` at `9975c26ae`): `registerPerItemRoute({ method: 'PUT', … })` at `:5288` (the item save, `PUT {metaPath}/:type/:name`) and `registerPerItemRoute({ method: 'DELETE', … })` at `:5528` (the reset to the artifact default, `DELETE {metaPath}/:type/:name`). They move, re-indented one level, to a new module, `packages/rest/src/meta-item-write-routes.ts`, as `registerMetaItemWriteRoutes`. It is the first `registerPerItemRoute` pass-through: the module registers nothing on the route manager itself. This follows the slicing plan (`os-dev-report` `6239704287`, sub-slice 13c), seat 1's ruling on it `6239783526` (quoted below), static-receiver ruling A `6205442867`, 13b's `handover_13c` list (report `6244193505`) and the claim `6244810707`. PR #24240 (13b, `646303eae`) 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; - one token of the moved body changed, the ruled receiver (below). `rest-server.ts` goes from 7,746 to 7,363 lines. G4 and G6–G8, the umbrella `registerMetadataEndpoints` and its guarded swap, the inner head's bindings and `registerPerItemRoute` itself, every handler body, the class head, `meta-item-read-gate.ts`, `docs/adr/**` and `packages/spec/src/**` are untouched. #23050 stays open for 13d–13e. ## 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). > - **Q2, A.** 13a's `meta-collection-routes.ts` hosts `MetaRouteScope` and takes `TransportScopedMetaRequest` from `ui-routes.ts`, re-pointing its readers in that PR. ⛔ Not `meta-item-read-gate.ts` while #24208 is open. > - **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`). > - **Q5, A.** G4 (891 lines, one `if`) moves whole in 13b, with its switch. Slice 1 moved 1,181 lines. The fallback, if the review budget binds, is B: a move-only split called inside the remaining brace. ⛔ C (re-shaping the registrations) is not a move and not this card's. > - **Q6.** A for the two body-only 13b helpers (`notImplementedRefusalAnswer`, `anonymousFormIntakeFingerprint`): they move with the body. B for `refuseNonAuthoringCaller` (13d): it stays and is exported, because its callee `mayReadAuthoringTrail` is a docs anchor (`system-context.mdx:108`). Two-home helpers (`sendMetaItemAbsent`, `stampAnonymousFormIntakeWarnings`) stay and gain `export`, as in slices 1 and 2. > > **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. Cross-lane edits are declared on #6017 (spec: liveness and gate tables) and #6023 (devx: checklist) before the PR. The `system-context.mdx` rows change with the sub-slice that moves their code. ## The shape **Moved, at its position.** In the inner method, right after 13b's `registerMetaItemReadRoutes.call(itemReadHost, scope);`, the head now builds the host literal `itemWriteHost` and calls `registerMetaItemWriteRoutes.call(itemWriteHost, scope)` with the ONE existing `scope` (no second one is built). Registration order is unchanged: after the per-item reads, above the history family. **The host, `MetaItemWriteRouteHost`: 5 members in first-read order** (a symbol-resolved walk of G5 with the TypeScript 6.0.3 compiler API, 13b's method; H1 of the dispatch holds: `PUT` at `:5288`–`:5523`, `DELETE` at `:5528`–`:5688`, the history family's first statement at `:5696`, the one static receiver at `:5340`): | member | reads | handed over as | |---|---:|---| | `resolveExecCtx` | 2 | closure `(...a) => this.resolveExecCtx(...a)`, spelled exactly as the execctx census's forwarder | | `metaSaveVerdict` (private static) | 1 | closure `(...a) => RestServer.metaSaveVerdict(...a)`, typed `(typeof RestServer)['metaSaveVerdict']` | | `walledCatalogRefusal` | 2 | closure | | `resolveProtocol` | 2 | closure | | `resolveMetaWriteActor` | 2 | closure | **No `routeManager` member (the plan's 5, H1).** The body reads the route manager nowhere: both 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 both routes still mount behind the wrapping registrar and still answer to `metadata.endpoints.item`, which the same closure reads. The module header and the comment above the literal say why. The walk found no bare `this`, no `this[...]`, no `function` keyword and no assignment to a `this` member, and the body reads no instance FIELD at request time, so no member is a getter. **Host placement (binding).** The literal is built inside `registerMetadataEndpointsInner`, at G5's old position. It is not built in `registerRoutes`, the constructor, an instance cache or the umbrella above the swap. For a pass-through module the swap is read through `registerPerItemRoute`'s call-time `this.routeManager`, so the guard ablation below plants the wrong shape this module could take: a module-local registrar reading a manager captured above the swap. **The scope (Q1 A).** The module opens with `const { metaPath, isScoped, registerPerItemRoute } = scope;`, the three bindings it reads. It does not read `metadata`: the per-item switch is read inside `registerPerItemRoute`, where it always was. **The one changed token (ruling A, named in the claim).** At base `rest-server.ts:5340` the save door calls `RestServer.metaSaveVerdict(ctx)`. It is now `this.metaSaveVerdict(ctx)` through the host (module `:187`). The static reads nothing but its argument, so the receiver is the whole difference. **Module-level names** (the same walk, base vs head): - **Imported from `./rest-server.js`:** `META_ITEM_RESET_PARAMS` (stays: `rest-server-meta-member-selector.test.ts` imports it from there) and `metaItemPackageBinding` (on the package entry). Both are read only at request time, so the two modules importing each other cannot see one uninitialised. - **Imports that left `rest-server.ts`** (read only by the moved code, so `noUnusedLocals` would refuse them; a pointer comment is left at each): the local `META_ITEM_SAVE_PARAMS` import (`./meta-item-read-gate.js`; the re-export that names it stays), `metaSaveRequestOptions` (`./meta-save-request.js`), and `SaveMetaItemRequest` and `DeleteMetaItemRequest` (`@objectstack/spec/api`). Exactly the plan's four. `META_ITEM_RESET_PARAMS` stays, as the plan says. - **Unchanged provenance:** every other name (`rethrowAuthzStoreUnavailable`, `metaWriteCapabilityVerdict`, `handleRouteError`, `META_ITEM_MEMBER_PARAM`, `metaMemberSelector`, `refuseUnknownQueryParams`, `refuseRepeatedQueryParams`) is imported from the module `rest-server.ts` imports it from, and is still read there too. `MetaRouteScope` and `TransportScopedMetaRequest` come from `./meta-collection-routes.js`. ## Proof All runs went through `os-verify-lock`, with every exit captured before any pipe, unless noted. The base is `9975c26ae`, measured in a read-only comparison worktree. The head is `3750ddecc`; the merge head `39912f9e1` adds one merge of `origin/main` `cbfa5e433` (#24244), which touches `packages/runtime` only, outside `@objectstack/rest`'s dependency closure (`core`, `metadata`, `metadata-core`, `observability`, `platform-objects`, `service-package`, `spec`, `types`), so main vs merge head reads as base vs head; the final commit `892a86538` is a comment in a gate script. **1. The moved text.** `git show 9975c26:packages/rest/src/rest-server.ts | sed -n 5284,5688p`, with one 4-space indent removed, was compared with `meta-item-write-routes.ts` `:131`–`:535`, the body after the destructuring line. - The raw `diff` exits 1 on exactly one line: the receiver token (`RestServer.metaSaveVerdict(ctx)` → `this.metaSaveVerdict(ctx)`). - With `this.metaSaveVerdict` mapped back to `RestServer.metaSaveVerdict`, `diff` exits 0 over all 405 lines. Both sides are sha256 `a8935ad228caf0c790c7c1314782c6eb4405ca639cd95d8f5b453982f8069bb6`. - Every non-empty line of the range carried the 8-space indent, and no line has trailing whitespace. **2. `.d.ts` and exports.** `index.d.ts` and `index.d.cts` are sha256 `02f1c167ccbea3298a15ea77ac634b50500346fc489efec21828eb5a7ab5928a` at base, at head and at the merge head. - The head-only marker `registerMetaItemWriteRoutes` hits 2 in each of `index.js` / `index.cjs` at head and at the merge head, and 0 at base. - Both bundles load with 39 exports and one sorted name list, sha256 `b3246be6a1718700…`, ESM == CJS, base and head. **3. Full-boot legs (slice 12's method), 20 configurations:** slice 12's thirteen, then the `/meta` switches under scoped-optional: `metadata.endpoints.types`, `.items`, `.item` and `.maintenance` each false; all four false; `metadata.prefix: '/metadata'`; `api.enableMetadata: false`. - **Source leg** (a `tsx` boot of `src/rest-server.ts`): `routes.json`, `calls.json` and `handlers.json` are byte-identical in all 20 configs: 60 of 60 files, concatenation sha256 `bd4f6685175a61ec…` on both trees. - **Bundle leg** (the same dump against the built `dist/index.js`): 60 of 60 identical, concatenation `20103136b32ff534…`, 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 `itemWriteHost`, `registerMetaItemWriteRoutes` and two renumbered aliases (`rethrowAuthzStoreUnavailable5`, `metaWriteCapabilityVerdict2`), and loses none; those two names are the ones normalized. (The CJS bundle gains `itemWriteHost`, `registerMetaItemWriteRoutes`, `import_core38` and `import_metadata_core17`.) - Both concatenation hashes equal 13b's own readings: the full boot is unchanged across both moves. **4. The inner-registration leg (the plan's `proof_method`).** 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, digested whitespace-free with the ruled receiver tokens mapped back (13b's three plus `metaSaveVerdict`), together with method, path, metadata and arity. - **Source leg:** 688 of 688 identical, same order; the digest-list sha256 is `4f306a2a19ffdd181b36b43e4d8b49d0844dc396c50a3144e8221aeab48b4511` on both trees (13b's reading too). Unnormalized, exactly 36 digests differ: `PUT …/:type/:name` on both bases in the 18 configs that mount it, the receiver token. The `DELETE` digests are identical raw. - **Bundle leg:** 688 of 688 identical under the two alias names; digest-list sha256 `535abfd10375066c385a596bf97dd8039252b8fe3f38ebb7fc78e073cf8f98c6` on both trees. - **Positive controls** (one character each, through `scripts/ablation-replace.mjs`, anchor 1 → 0): the `PUT` door's 501 message gained a `!` (blob `395d5e6ec2c9` → `82dd302865c5`), then, separately, the `DELETE` door's (→ `7b4819a6f8d5`). 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 `routes` / `calls` / `handlers` 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 identical: - `endpoints.item` on: `PUT` and `DELETE` answer `401 UNAUTHENTICATED` with zero protocol calls (4 of 4), and all four server-visible handlers are the umbrella's wrapper. - `endpoints.item: false`: both routes are gone on both bases (150 → 126 routes, 0 `PUT` / `DELETE` item mounts), on both trees. **6. The guard ablation** (the plan's shape for 13c, 13d and 13e-published). Two literal anchors, nested: the umbrella stashes its `realRouteManager` (captured above the swap) and `itemWriteHost` carries it as a `routeManager` member (`rest-server.ts` blob `2816281785c6` → `1528afc7f2dd`, a 281-line anchor from the umbrella's `const realRouteManager = this.routeManager;` through `itemWriteHost`'s opening line), and the module replaces the scope's `registerPerItemRoute` with a module-local registrar that honours the same switch but registers on that host member (`meta-item-write-routes.ts` blob `395d5e6ec2c9` → `6fdf0913c66a`). - **Ablation:** an anonymous `PUT` and `DELETE` on both bases answer `403 FORBIDDEN` from the door's own capability gate, not `401`, and none of the four 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 1 failed | 4 passed (5)`, `Tests 9 failed | 121 passed (130)`: `meta-type-write-capability.test.ts` is red 9 times (#21124: the datasource and datasources write shapes for member, other-grant and author, the existing-vs-absent byte identity, and the admin-door parity for the author bearer). `rest-meta-auth.test.ts`, `meta-type-read-capability.test.ts`, `rest-config-mount-table.pin.test.ts` and `meta-route-registration-order.test.ts` stay green. - Restored, both files: the blob equals `HEAD` and `git diff HEAD` is empty. No permanent test was added. **7. The late-binding ablation** (one-off script). The 4 instance methods are planted on the server and the static on the class before `registerRoutes()`, as recorders that delegate to the originals. Six request shapes are driven over the two moved routes (a plain save, `?mode=draft`, `?force=true` with `If-Match`, a plain reset, `?state=draft`, `?dropStorage=true` with `If-Match`), each answering 200 after the expected protocol call. Then everything is planted again and the shapes are driven again. - **Head** (byte-identical `MEASURE` lines to base): all 5 closure members are reached before and after the re-plant, with `early calls after re-plant: 0`. - **Ablation:** `walledCatalogRefusal: this.walledCatalogRefusal.bind(this)` (blob `2816281785c6` → `fde53002af3c`) gives `early calls after re-plant: 6 ["early:walledCatalogRefusal"]` and 4 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-write-routes.ts` in `REST_SERVER_REGISTRAR_FILES` (length 15 → 16) and in the named `/meta` subset `REST_SERVER_META_ROUTE_FILES` (2 → 3). The dogfood control counts `.call(` inside the inner method, now 3, so the subset and the call moved together. | reader | sum over the list | change | |---|---|---| | `execctx-consumer-census.test.ts` | 62 sites = 10 + 9 + 5 + 7 + 8 + 1 + 5 + 2 + 1 + 1 + 1 + 0 + 6 + 3 + 1 + 2; mentions 137 → 141 | The two sites are locally caught on their invocation lines, as before, so the split stays 16 / 46 (12 on the invocation line). `rest-server.ts` stays at 55 mentions (two sites out, one forwarder's two in); the module carries 4. Sixteen forwarders: (16 − 1) + 3 − 2 | | `meta-draft-read-door-census.test.ts` | 8 = 0 + 0 + 0 + 0 + 0 + 2 + 0 ×7 + 3 + 2 + 1 | The reset's `?state=draft` (a `write` row) moved; keys name routes, so no ledger row changed | | `rest-server-query-number-census.test.ts` | 9 = 8 + 1 + 0 ×14 | — | | `rest-server-canonical-query-ast.test.ts` (`repo`) | 4 = 0 + 3 + 0 ×10 + 1 + 0 + 0 + 0 | Floor `meta-item-write-routes.ts: 0` (the save mapping's one `query` key sits mid-line) | | `rest-server-docblock-position.test.ts` | floors sum 59 → 62 | The module carries 3: the header, the host interface and the function. `rest-server.ts` keeps its 161 blocks | | `ui-view-route-identity` / `-tenancy` measurement tests | `this.enforceAuth(` 60, `this.resolveExecCtx(` 77 → 78 | +1 for the forwarder. The ownership guard is still 1 call site and 1 forwarder | | dogfood `authz-conformance.test.ts` | 0 tripwire hits over sixteen files | Length 15 → 16 | | dogfood `authz-probe-blind-spot.census.ts` | 70 / 19 / 51 unchanged | Reachable holds by 13a's Q3 rule, with no rule change. Two controls re-keyed, as ruled (Q3: "From 13c on, a module with no direct register site is controlled on `registerPerItemRoute(` instead"): the module's own control is `meta-item-write-routes.ts: registerPerItemRoute(` 2 (its `this.routeManager.register(` count is 0, which this census reads as instrument failure); the derivation now emits one control per spelling a module spells, so a module that stops spelling its recorded one makes that control vanish, loudly. The whole-population `registerPerItemRoute(` control is re-anchored over the population rule's own haystack (this file plus every module on the list), where it stays 8 = 6 + 2; over `rest-server.ts` alone it would read 6. `/meta subset ↔ .call(` 3; `this.routeManager.register(` 63, `private register*Endpoints(` 16, `const registerPerItemRoute =` 1 and `enforceAuth` 112 unchanged | Readers outside the list: - **`scripts/check-route-envelope.mjs`** (`MODULES` only; declared on #6017, `6244817361`). `meta-item-write-routes.ts` is declared `dialectOnly` with ratchet #9559 at `stringError 0` / `siblingCode 0`: both doors' 403, 400 and 501 bodies are the nested `{ error: { code, message } }`. `rest-server.ts` keeps `7` / `3`. Output: 26 → 27 modules audited, 16 → 17 ratcheted; `--self-test` exit 0. - **`content/docs/permissions/system-context.mdx` row 50** (declared on #20163, `6244820755`; H4 holds). The reset's `isSystem` read (`metaWriteCapabilityVerdict({ isSystem: ctx?.isSystem === true, … })`) moved with it, so at the move commit `check-system-context-census` exited 1: `[file-without-a-row] packages/rest/src/meta-item-write-routes.ts holds 1 elevation read site(s) in 1 symbol(s) (registerMetaItemWriteRoutes)`. Row 50 gains `packages/rest/src/meta-item-write-routes.ts#registerMetaItemWriteRoutes` beside the inner anchor, which keeps resolving (publish and rollback still read there). Made green by the anchor, ⛔ not by a `NON_READ_ANCHORS` row: `142 elevation read sites … across 65 files, living in 123 symbol(s); the page cites 136 symbol(s) against 136 required, over 159 anchors` (base: 64 files, 122 symbols, 135 / 135, 158 anchors). - **`scripts/adr-anchors/packages__rest__src__meta-item-write-routes.ts.json`** (NEW; declared as possible on #6017). `check-adr-anchors` checks `ADR-NNNN Dk` letters only in anchored files. The move took G5's eight letter citations (ADR-0066 D1 ×2, ADR-0131 D3 ×2, ADR-0131 D6 ×4) out of the anchored `rest-server.ts`, and the gate read 1783 → 1775 while green: a reader that narrowed silently. This anchors ADR-0066, ADR-0131 and ADR-0106; its invariant adds no letter citation; the reading is back to 1783 (`73 anchored file(s)`). - **Examined, unaffected:** - `packages/runtime/src/domains/meta-list-projection-parity.test.ts` derives `GET` handlers only (the item, tree, layers and list reads); G5 holds none, and the test was green at the move commit (`Tests 739 passed (739)`). - `packages/spec/liveness/metadata_endpoints.json` (the plan: "No liveness edit"): the `endpoints.item` row's second citation, `rest-server.ts#registerMetadataEndpointsInner` (`registerPerItemRoute` gates the per-item `PUT` / `DELETE` and the history family), stays true, because the switch is read in that closure, which stays in the inner head. `check:liveness` was green at the move commit and at head. - `docs/qa/platform-checklist/areas/api-backend.json`' inner anchor ("metaPath and registerPerItemRoute") stays true; `check:platform-checklist` was green at the move commit. No other checklist or docs anchor names a G5 symbol. **Red before the re-points**, each measured with the readers at their base text: - **At the move commit `344cb18d8`:** - Six list readers read `Test Files 2 failed | 4 passed (6)`, `Tests 5 failed | 121 passed (126)`. The execctx census failed three times (`expected 60 to be 62`, the caught split `expected 10 to be 12`, and §7's control `expected 10 to be greater than or equal to 12`); the draft census twice (`expected [ …(7) ] to deeply equal [ …(8) ]`, `to have a length of 8 but got 7`). - The dogfood pair read `Tests 2 failed | 106 passed (108)`: population `expected 68 to be 70`, and `/meta subset ↔ .call(` `expected 3 to be 2`. - `check:route-envelope` exited 1: `meta-item-write-routes.ts` NOT DECLARED. `check-system-context-census` exited 1 (above). - `check-adr-anchors` stayed green at 1775 (base 1783); `check:liveness`, `check:platform-checklist`, the runtime parity test and `repo` (`Tests 266 passed | 15 skipped (281)`) stayed green. Nine standing behaviour pins of the moved doors read `Tests 205 passed (205)`. - **At `9ce268e26`** (the list grown, the readers not yet re-pointed): seven list readers read `Test Files 6 failed | 1 passed (7)`, `Tests 7 failed | 127 passed (134)` (the length pins and the unfloored module); `repo` read `Tests 2 failed | 268 passed | 16 skipped (286)`; the dogfood pair read `Tests 2 failed | 106 passed (108)` (`registrar list length` and the tripwire length). - **Green after the re-points** (`cbd7d7a4c`, then the merge head). ## Changeset `.changeset/23050-s13c-meta-item-write-routes-move.md` is a `@objectstack/rest` patch (`Clause-②: no`, move-only). The shipped `index.js` / `index.cjs` bytes change (the marker above); the declarations and exports do not. `skip-changeset` does not apply. ## Acceptance notes - The plan's 13c list held: the host's 5 members, the one receiver, the four leaving imports, the 16 / 3 list lengths, the census's two control changes, `MODULES` at 0 / 0 and the row-50 anchor. Beyond it, the ADR anchor (the handover's conditional item) was owed: the letter count dropped by 8. No reader outside the plan's list and 13b's handover read anything G5 held. - The moved comments keep their deixis: "the `PUT` twins", "next door" and "a few hundred lines up" describe the inner method's order as it was, in which `POST {metaPath}/_migrate-stored` (now in `meta-collection-routes.ts`) sat above these doors. The module header says so rather than editing moved text. - The inner method's `registerPerItemRoute` docblock still says the first four members of the per-item face "are inside the `if` block further down" (13b moved that block to `meta-item-read-routes.ts`). It is the inner head's text, a hold-off for this slice, and is left for the slice that re-reads that head. - Carried from earlier slices, untouched: `FOLLOW-UPS.md:613` (E1) prose, `ui-routes.ts`' "52 sites" comment and `email-routes.ts`' double-mount header. ## Gates and tests The final head is `892a86538`: one merge of `origin/main` `cbfa5e433` (#24244, `packages/runtime` only), then a comment-only fix to the new `MODULES` note in `scripts/check-route-envelope.mjs`. The tests below ran at the merge head `39912f9e1`; that last commit changes no file any test reads. The gate union ran at `892a86538`. - **Build.** At base, `pnpm turbo run build --filter=@objectstack/rest... --concurrency=2` exited 0 (`Tasks: 25 successful, 25 total`). At the move commit, `pnpm turbo run build --filter=@objectstack/rest... --filter=@objectstack/dogfood... --filter=@objectstack/runtime... --concurrency=2` exited 0 (`Tasks: 66 successful, 66 total`). At the merge head, a whole-workspace `pnpm turbo run build --concurrency=2` exited 0 (`Tasks: 77 successful, 77 total`), for the dist readers. No package source was edited while turbo built it; the base dist reads 0 head-only markers. - **Typecheck.** `pnpm --filter @objectstack/rest typecheck` exits 0 (`check:test-typecheck: OK … 0 file(s) / 0 error(s)`). `pnpm --filter @objectstack/dogfood typecheck` exits 0. - **`@objectstack/rest`.** `local`: `Test Files 294 passed (294)`, `Tests 6032 passed | 326 skipped (6358)`, including `meta-route-registration-order.test.ts`, `rest-config-mount-table.pin.test.ts` and the moved doors' pins (`meta-type-write-capability`, `rest-server-meta-member-selector`, `rest-server-meta-door-closed-sets`, `meta-501-envelope`, `meta-write-actor-identity`, `meta-write-door-capability-enumeration`). `repo` (its `test:repo`, run locally because this diff edits `rest-server-canonical-query-ast.test.ts`): `Test Files 5 passed (5)`, `Tests 270 passed | 16 skipped (286)`. - **Dogfood pair** (`authz-probe-blind-spot.test.ts`, `authz-conformance.test.ts`): `Test Files 2 passed (2)`, `Tests 108 passed (108)`. The dogfood shards skip on PRs and in the queue, so this local run is the evidence. - **`test:repo`.** `@objectstack/runtime`: `Test Files 3 passed (3)`, `Tests 808 passed (808)`, `meta-list-projection-parity.test.ts` among them. `@objectstack/core`: `Test Files 6 passed (6)`, `Tests 86 passed (86)` (its security tests read `rest-server.ts` from disk). - **Lint.** `pnpm exec eslint --no-inline-config` over the 13 changed `.ts` / `.mjs` files exits 0, with no output. The repo-wide `pnpm lint` belongs to CI. - **Gate union.** `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack` (no paths) at `892a86538` derives 117 commands; its stderr names the commit, and the `--repo` assertion holds. The change set is 16 paths vs merge base `cbfa5e433`, 1,152 changed lines (+722 / −430), under 3,000. - All 117 exit 0, each captured before any pipe. - `--ran`: `Run reconciliation — 117 derived, 117 run, 0 NOT-MEASURED, 0 UNRUN.`, exit 0. It also printed a STALE TREE note: `origin/main` moved to `f5ef054a7` (#24248) after the merge, and changed `scripts/check-single-authz-resolver.mjs`, whose new `DELEGATORS` row keys `rest-server.ts` on `resolveAuthzContext` (the class head, untouched here). No path of that commit is one of this diff's 16, so it was not merged again; PR CI runs the merge ref. - Selected readings: `check:route-envelope` (`27 route module(s) audited: 9 conformant, 17 ratcheted, 1 exempt`); `check:adr-anchors` (`73 anchored file(s)`, `1783 decision-letter citation(s)`); `node scripts/check-system-context-census.mjs` (`… across 65 files, living in 123 symbol(s); the page cites 136 symbol(s) against 136 required, over 159 anchors`); `check:liveness` (`947 pointer(s) … 947 naming a symbol`, `737 … asked, 736 anchored, 1 exempt`, as at base); `check:undeclared-dep-imports`; `check:cross-package-test-inputs`; `check:authz-resolver`; `check:durability-log-level`; `check:dispatcher-error-vocabulary`; `check:error-status-conformance`; `check:dual-build-cjs-loads` (`111 published require entry point(s) across 69 package(s) load`); `check:pm-dispatch-gates` (`1884 cases pass`); `check:type-check-coverage`; `check:type-check-debt`; `check:nul-bytes`. `node scripts/check-platform-checklist.mjs --changed-from origin/main`: `10 anchor(s) judged, 0 lost`. - **Not run locally:** `check:dev-prereqs` and `check:skill-frame-freshness` (local-only families). This diff touches no `packages/spec` build input and no four-axis frame file. - **Not governed.** `check-governed-merges --branch HEAD`: `0 of 16 path(s) hit the register`, and the size is under the human-merge threshold. Commits: - `344cb18d8`, the move; - `9ce268e26`, the list and the `/meta` subset; - `cbd7d7a4c`, the re-points, with the module's ADR anchor and the row-50 anchor; - `3750ddecc`, the changeset; - `39912f9e1`, the merge; - `892a86538`, a comment-only fix to the new `MODULES` note (which door builds the 400). --- _Generated by [Claude Code](https://claude.ai/code/session_01QyoWKK9RwUNxBxmevy9Neb)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent 8eccf51 commit e9bc60b

16 files changed

Lines changed: 722 additions & 430 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` per-item write routes live in their own module, with no route, handler or answer changed
6+
7+
Clause-②: no (a move: the two 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 two statements that register `PUT {metaPath}/:type/:name` (the item save) and `DELETE {metaPath}/:type/:name` (the reset to the artifact default) moved, re-indented, to `packages/rest/src/meta-item-write-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/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/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` |
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` |

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

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1636,7 +1636,8 @@ describe('#9084 — the rest-server.ts transport tripwire keys off mechanics', (
16361636
// [#23050 slice 12] Length 12 → 13 (`crud-routes.ts`), still 0 over it.
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.
1639-
expect(REST_SERVER_REGISTRAR_FILES).toHaveLength(15);
1639+
// [#23050 slice 13c] Length 15 → 16 (`meta-item-write-routes.ts`), still 0 over it.
1640+
expect(REST_SERVER_REGISTRAR_FILES).toHaveLength(16);
16401641
expect(REST_SERVER_REGISTRAR_PATHS[0]).toBe(REST_SERVER);
16411642
const listed = REST_SERVER_REGISTRAR_PATHS.map((file) => readFileSync(join(REPO_ROOT, file), 'utf8'));
16421643
for (const probe of [MECHANICS, ROUTES]) {

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

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -540,17 +540,18 @@ export const PROBE_FILE_CENSUS: readonly ProbeFileReading[] = [
540540
'module on the registrar list (`packages/rest/src/rest-server-registrar-files.ts`) — ' +
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`, ' +
543-
'`search-routes.ts`, `ui-routes.ts`, `form-routes.ts`, `crud-routes.ts`, `meta-collection-routes.ts` and ' +
544-
'`meta-item-read-routes.ts`, ' +
543+
'`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`, ' +
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 four `/meta` ' +
550+
'`registerUiEndpoints`, `registerFormEndpoints` and `registerCrudEndpoints`, and the first five `/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` and `meta-item-read-routes.ts`), whose ' +
553+
'subset (`REST_SERVER_META_ROUTE_FILES`: `meta-collection-routes.ts`, `meta-item-read-routes.ts` and ' +
554+
'`meta-item-write-routes.ts`), whose ' +
554555
'registrations the inner method ' +
555556
'runs inside the umbrella\'s swap',
556557
// [#15542 / #15854] ⭐ THE POPULATION RULE LEARNED A SECOND SPELLING, and
@@ -866,10 +867,34 @@ export const PROBE_FILE_CENSUS: readonly ProbeFileReading[] = [
866867
// 6 + 6 + 4), `private register*Endpoints(` stays 16, `registerPerItemRoute(`
867868
// stays 8 and `enforceAuth` stays 112: the moved group spells neither term,
868869
// and neither does the seam.
870+
// [#23050 slice 13c] 70 / 19 / 51 UNCHANGED, `reachable` again held by 13a's
871+
// rule with no rule change, and TWO controls re-keyed, as ruled for this
872+
// slice. The fifth `/meta` group, the per-item writes (`PUT` and `DELETE
873+
// {metaPath}/:type/:name`, two helper-routed registration sites, REACHABLE
874+
// through the same umbrella key), moved to `meta-item-write-routes.ts`, which
875+
// joins the list (length 15 -> 16) and the named `/meta` subset (2 -> 3). It
876+
// is the first module on the list with NO direct site: both of its routes
877+
// register through the inner method's per-item registrar, which reaches it in
878+
// the `/meta` scope as the one closure the inner head declares and which
879+
// still reads the route manager at call time, inside the swap. So (1) its own
880+
// control counts `registerPerItemRoute(` (2), the population rule's second
881+
// spelling, instead of `this.routeManager.register(`, which reads 0 there and
882+
// would be a zero read as a reading; and (2) the `registerPerItemRoute(`
883+
// control is RE-ANCHORED, not relaxed, over the same haystack as
884+
// `this.routeManager.register(` and the population rule (this file plus every
885+
// module on the list), where it stays 8 (6 here + 2 in the module); over this
886+
// file alone it would read 6. The subset's length and the module calls
887+
// inside the inner method move together (both 3). Measured at the move
888+
// commit, before the list learned the module: population 68, the two sites
889+
// out of reach, and the `.call(` control at 3 against a subset of 2.
890+
// `this.routeManager.register(` stays 63 over the sixteen files (the module
891+
// spells it nowhere), `private register*Endpoints(` stays 16,
892+
// `const registerPerItemRoute =` stays 1 and `enforceAuth` stays 112: the
893+
// moved group spells none of them, and neither does the seam.
869894
controls: {
870895
'private register*Endpoints(': 16,
871896
'this.routeManager.register(': 63,
872-
'registrar list length': 15,
897+
'registrar list length': 16,
873898
'data-action-routes.ts: this.routeManager.register(': 9,
874899
'batch-routes.ts: this.routeManager.register(': 5,
875900
'approvals-routes.ts: this.routeManager.register(': 7,
@@ -884,6 +909,9 @@ export const PROBE_FILE_CENSUS: readonly ProbeFileReading[] = [
884909
'crud-routes.ts: this.routeManager.register(': 6,
885910
'meta-collection-routes.ts: this.routeManager.register(': 6,
886911
'meta-item-read-routes.ts: this.routeManager.register(': 4,
912+
// [#23050 slice 13c] Controlled on the helper's call spelling: this module
913+
// has no direct site (see above).
914+
'meta-item-write-routes.ts: registerPerItemRoute(': 2,
887915
// [#23050 slice 13a] Recorded AS the subset's length, not as a literal: the
888916
// measured side is the module calls inside the inner method, so the two
889917
// can only move together.
@@ -1405,9 +1433,21 @@ export function deriveProbeFileCensus(): {
14051433
// [#23050 slice 1] The moved module's own share, so a reading that
14061434
// silently stopped appending it cannot hide behind the total above.
14071435
// [#23050 slice 3] One such control per module on the list.
1408-
...Object.fromEntries(movedModules.map(([file, text]) =>
1409-
[`${file}: this.routeManager.register(`, occurrences(text, /this\.routeManager\.register\(/g)])),
1410-
'registerPerItemRoute(': occurrences(src, /registerPerItemRoute\(/g),
1436+
// [#23050 slice 13c] …one per SPELLING the module registers through, as
1437+
// the population rule counts both: a module whose routes all go through
1438+
// the per-item registrar has no direct site, and a control that read 0
1439+
// there would be instrument failure by this census's own rule. A
1440+
// spelling a module does not spell mints no control, so a module that
1441+
// stops spelling the one its control was recorded on makes that control
1442+
// VANISH, which is as loud as moving it.
1443+
...Object.fromEntries(movedModules.flatMap(([file, text]) => [
1444+
[`${file}: this.routeManager.register(`, occurrences(text, /this\.routeManager\.register\(/g)] as const,
1445+
[`${file}: registerPerItemRoute(`, occurrences(text, /registerPerItemRoute\(/g)] as const,
1446+
].filter(([, count]) => count > 0))),
1447+
// [#23050 slice 13c] Over `all`, the population rule's own haystack, since
1448+
// a `/meta` module now carries helper call sites too (re-anchored, not
1449+
// relaxed: 6 here + 2 in `meta-item-write-routes.ts` = 8).
1450+
'registerPerItemRoute(': occurrences(all, /registerPerItemRoute\(/g),
14111451
'const registerPerItemRoute =': occurrences(src, /const\s+registerPerItemRoute\s*=/g),
14121452
// ⭐ The one control here that is NOT a whole-file count, deliberately:
14131453
// it is the SHAPE of the slice the population rule subtracts, and it is

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

Lines changed: 19 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -129,6 +129,10 @@ const SOURCE = readFileSync(resolve(HERE, 'rest-server.ts'), 'utf8');
129129
* `meta-item-read-routes.ts`, with its one: the item read's, locally caught on
130130
* its continuation line, as before — one of §7's and §8's four continuation
131131
* sites, which those sections find there because they read every file here.
132+
*
133+
* [#23050 slice 13c] …and the fifth, the per-item writes, to
134+
* `meta-item-write-routes.ts`, with their two: the item save's and the reset's,
135+
* each locally caught on its invocation line, as before.
132136
*/
133137
const SITE_FILES = REST_SERVER_REGISTRAR_FILES;
134138
const SOURCES = new Map<string, string>(SITE_FILES.map((file) => [
@@ -146,7 +150,7 @@ const siteKey = (file: string, line: number) => `${file}:${line}`;
146150
* `registerSearchEndpoints`, `registerUiEndpoints`,
147151
* `registerCrudEndpoints`) hands its moved body — and each one
148152
* `registerMetadataEndpointsInner` builds for a moved `/meta` group's module
149-
* (#23050 slices 13a and 13b), counted as that module's registrar —
153+
* (#23050 slices 13a, 13b and 13c), counted as that module's registrar —
150154
* forwards each member to the server with `(...a) => this.<member>(...a)`, so
151155
* a moved site's stack shows
152156
* that forwarder above the site itself. The site is the first censused frame
@@ -766,9 +770,21 @@ describe('[#13160] §2 the consumer surface, counted from the tree', () => {
766770
// out, one forwarder's two mentions in). Fifteen forwarders now:
767771
// (15 - 1) + 3 - 2, the module carrying one host literal that reads the
768772
// seam, so neither map gains an entry.
769-
expect(SITE_FILES).toHaveLength(15);
773+
//
774+
// [#23050 slice 13c] 62 sites UNCHANGED / 137 → 141 mentions — the same MOVE.
775+
// The fifth `/meta` group, the per-item writes, left for
776+
// `meta-item-write-routes.ts` with its two LOCALLY CAUGHT sites (the item
777+
// save's and the reset's, each on its invocation line, as before):
778+
// 10 + 9 + 5 + 7 + 8 + 1 + 5 + 2 + 1 + 1 + 1 + 0 + 6 + 3 + 1 + 2 = 62 sites. The
779+
// four new mentions are this slice's seam, the two spellings once (the third
780+
// host literal the inner method builds, its forwarder, two; the host
781+
// interface's member, two): `rest-server.ts` stays at 55 (the two sites out,
782+
// one forwarder's two mentions in). Sixteen forwarders now: (16 - 1) + 3 - 2,
783+
// the module carrying one host literal that reads the seam, so neither map
784+
// gains an entry.
785+
expect(SITE_FILES).toHaveLength(16);
770786
expect(SITES.length).toBe(62);
771-
expect([...SOURCES.values()].reduce((n, text) => n + text.split('resolveExecCtx').length - 1, 0)).toBe(137);
787+
expect([...SOURCES.values()].reduce((n, text) => n + text.split('resolveExecCtx').length - 1, 0)).toBe(141);
772788
const extraMovedRegistrars = Object.values(EXTRA_MOVED_REGISTRARS).reduce((n, k) => n + (k ?? 0), 0);
773789
expect(extraMovedRegistrars).toBe(3);
774790
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
@@ -272,7 +272,11 @@ describe('[#20338] census: every draft switch in rest-server.ts is ledgered', ()
272272
// with two more: the item read's `?state=draft` and `?preview=draft`. Keys
273273
// name the route, so again no ledger row changed:
274274
// 8 = 1 + 0 + 0 + 0 + 0 + 2 + 0 + 0 + 0 + 0 + 0 + 0 + 0 + 3 + 2, length 14 → 15.
275-
expect(REST_SERVER_REGISTRAR_FILES).toHaveLength(15);
275+
// [#23050 slice 13c] The per-item writes moved to `meta-item-write-routes.ts`
276+
// with the last one `rest-server.ts` held: the reset's `?state=draft` (a
277+
// `write` row). Its key names the route, so again no ledger row changed:
278+
// 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);
276280
expect(SOURCE_FILES.map((file) => file.fileName)).toEqual([...REST_SERVER_REGISTRAR_FILES]);
277281
expect(SITES).toHaveLength(8);
278282
});

0 commit comments

Comments
 (0)