Skip to content

Commit 045dae8

Browse files
docs(qa): 18.0 sweep anchor round 5 — the walled-posture and second-organization themes, runner environment facts, and two carried notes (#24687)
Refs #24579 Clause-②: no Round 5 of the 18.0 release sweep's wave anchor (#24579), one PR in four parts. Rounds 1–4 landed as #24622, #24632, #24649 and #24671. This is a partial landing: the card keeps §2's other two themes and §4 for later rounds. The diff is the checklist ledger and its runner docs only: four area files, `RUNNER.md` and `FOLLOW-UPS.md`. No product code, test or pin changes. Every edited area item takes one `revision` bump and one `history` row (ref: this PR). Every finding was re-derived from source before it was edited. The base is `bfc1539fe0`, merged with `origin/main` `92ce3af404` before this PR opened. Main's 8 new commits touched none of the cited sources except `.objectui-sha` (#24660 moved the pin `4016370a` → `12dc7b47c340`), and the one fact that reads it, the map-tile entry, was re-checked at the new pin. Findings that did not hold are left alone and listed at the end, each with the reading that refutes it. ## Part (a) — §2's two wave themes **What the ledger allows.** `planned` is an item-level status only. `statusFieldProblems` in `scripts/check-platform-checklist.mjs` judges `status` per item; a planned item must carry no `steps`; no clause carries a status of its own; and `checklist-select.mjs` drops a planned item from every runnable set. So a walled leg on an otherwise runnable item can only be recorded in `knownGaps`. The gate validates the `planned` shape and does not read `knownGaps` at all (PM H3). **Where 18.0 refuses (PM H1 holds).** The walled refusal lives in one place: `walledPostureRefusal()` in `packages/cli/src/commands/serve.ts`. `Serve.run` asks it right after the posture gate, before a config is read, a plugin is mounted or the HTTP server starts. `os dev` and `os start` boot through `serve`. The organizations-runtime load and `OS_ALLOW_DEGRADED_TENANCY` come later in the same method, so nothing skips the refusal. Kernel-level boots (`bootStack`, the dogfood wall suites) never pass through `serve`, which is why the pins still prove walled behaviour. Measured live at `bfc1539fe0` with the built CLI: - `os serve`, `OS_TENANCY_POSTURE=isolated`: exit 1, `✖ FATAL: tenancy posture 'isolated' is not supported in 18.0 (NOT_IMPLEMENTED).` - `group`: the same, naming `group`. - `OS_MULTI_ORG_ENABLED=true`: the same, naming `isolated`. - `single` (the control): passes the gate and stops later, on "Nothing to serve". - `os start` under `isolated`: the same FATAL. The refusal names no release ("an 18.x release"), so the knownGaps say "a later 18.x release". **Where the second organization is refused (PM H2).** The door is `beforeCreateOrganization` in `packages/plugins/plugin-auth/src/auth-manager.ts`. It answers 403 `ORGANIZATION_CREATION_DISABLED` wherever the effective posture enforces no wall, and on 18.0 that is every boot. Two more facts close the other routes: `sys_organization` declares `apiMethods: ['get', 'list', 'update']`, so the data API has no create, and 18.0 serves fresh installs only. Per-item decision: an org-switch clause is the §2 theme ("not scored on a stock 18.0 boot"). A planted second organization is a fixture route. QA run #24560 used one, a system write, and the knownGap says such a run must declare it. That route itself stays with §4. **Shape.** The ledger has no area-level or README-level slot for a wave theme. An area file carries `$comment` and provisioning recipes, and a recipe must have a `provisioning.use` consumer. The README has no such section. `RUNNER.md`'s "Environment facts the runner should not re-derive" is the standing-facts slot runners read first. So each theme is stated once there, as the entries "18.0 boots the single tenancy posture only" and "A stock 18.0 boot holds exactly one organization". Each item's knownGap points at the entry and restates the refusal in one sentence with its anchor. That keeps the item readable on its own through `checklist-select.mjs`, and lets the anchor gate resolve each citation (a `RUNNER.md` anchor would red: the anchor floor file declares no floor for `RUNNER.md`). The entry also settles the verdict word. The live leg is recorded `skipped` with the entry cited, as the withdrawn ownership command's clauses are, and never `blocked(fixture)`. The sweep's records had scored it both `blocked(environment)` (QA runs #24557, #24559) and `blocked(fixture)` (QA run #24558). **knownGaps vs planned, per item, on the four axes.** Every item below takes `knownGaps`. The common reason on each axis: - *Business need.* Each item has runnable 18.0 clauses: its pin clause, plus single-posture clauses. The walled capability is built and pin-verified in-process. 18.0 refuses it at the CLI boot by ruling. - *Long-term.* The clause stays where it is and returns when the refusal lifts. A planned split would duplicate clause text across two ids, delete the steps a planned item may not carry, and need a passing run to promote it back. - *AI-error.* `planned` would tell the next sweep the capability is unbuilt, which is the wrong debt (README: "Scoring one as the other sends the next sweep to fix the wrong thing"). The knownGap names the exact clauses and the verdict word. - *Startup scope.* No new ids and no new surface. | item | fields edited | revision | run record | choice and why | |:--|:--|:--|:--|:--| | `platform-core.settings-organization-isolation-and-platform-rung` | `fixtures.knownGaps` (new, [0]); `acceptance[3].clause` | 1 → 2 | QA runs #24557, #24558 (c2, c3) | knownGaps. acceptance[0] (pins) and [1] run on single. acceptance[3] was a second walled leg: `settings-service.ts#organizationlessTenantKeyError` refuses only under a walled posture, and the pin asserts single does not refuse. So the clause is now marked `walled:` | | `access-security.walled-environment-metadata-operator-only` | `fixtures.knownGaps` (new, [0]); `steps[0]` | 1 → 2 | QA runs #24556, #24558, #24559 (c1, c2, N0) | knownGaps. acceptance[0]'s pins are the 18.0 evidence. steps[0] now declares `sharingModel` (round 2's note): the admitted repeats in steps[2]/[3] reach the save door's authoring rules, which refuse an object with none 422 `security-owd-unset` (`validate-security-posture.ts#SECURITY_OWD_UNSET`, error tier). The walled refusal answers ahead of them (`meta-item-write-routes.ts`) | | `access-security.activation-write-operator-gate` | `fixtures.knownGaps[0]`; `steps[8]`; `acceptance[4].clause`; `acceptance[4].verify`; `variants[1]`; `variants[2]` | 3 → 4 | #24579 §2 | knownGaps (re-grounded). The legs were `blocked(fixture)` on an "enterprise runtime" gap. On 18.0 the refusal runs ahead of the runtime load, and the runtime is open code since ADR-0132. Clause 5 keeps its unit pins as its oracle | | `access-security.grant-table-write-contract` | `fixtures.knownGaps` (new, [0]) | 2 → 3 | QA run #24559 (c4, live leg) | knownGaps. Rounds 2–3 named acceptance[4]. ⚠️ The refusal's predicate is posture-independent (pin case "the predicate is the same on every posture"), so the leg is walled by its wording, not its mechanism. Recorded, not re-pointed: a single-posture rig is not measured here | | `access-security.parent-and-organization-write-guards` | `fixtures.knownGaps` (new, [0]) | 1 → 2 | QA run #24559 (c1, live leg) | knownGaps. The emptied-organization refusal is a Layer 0 rule, and its pin's control keeps single unchanged | | `access-security.no-active-org-session-semantics` | `fixtures.knownGaps[2]` (appended) | 2 → 3 | #24579 §0 / §2 | knownGaps. Every clause is walled, and the item is already `blocked {fixture, #9334}` with a pin. `planned` would also be wrong here: the capability is built and pinned. The marker is unchanged | | `attachments-storage.chunked-upload-integrity` | `fixtures.knownGaps[0]` (round 2's pointer sentence replaced by the theme's statement, not duplicated) | 3 → 4 | QA runs #24560, #24558 (c3) | knownGaps (second-organization theme). acceptance[3] has no chunked-door pin either, so a stock 18.0 run carries no verdict on it | Taken, but not in the card's five: `no-active-org-session-semantics` (its clauses need a walled boot) and `chunked-upload-integrity` (the second-organization theme). Read and left alone: `platform-owner-email-anchor`, `suggested-binding-loop`, `public-form-withdrawal-layers` and the three attachments items carrying the CROSS-TENANT marker (see the end). No item in `automation.json` carries a walled or org-switch clause. ## Part (b) — `RUNNER.md` `RUNNER.md` records its own provenance inline: the measured-at sha, the QA run and the card section, inside each entry. The rows below follow that convention. | section | lines (head) | edit | provenance | |:--|:--|:--|:--| | Environment facts → Playwright entry | 172–197 (173–179, 184, 191 changed) | `@playwright/test` 1.62.1 → 1.63.0, chromium build 1234 → 1243 (three places). The **Check** reads `browsers.json` from the pnpm store, because the root `node_modules/playwright-core` the old line named does not exist | re-measured at `bfc1539fe0`; #24579 §6 | | Environment facts → self-registration → recipe 1 "Invite, then sign up" | 337–356 (341–356 added) | Sign-up binds the invitee as `member` and leaves the invitation `pending` (`reconcileMembership`). Any other role takes effect only after the persona's own `POST /api/v1/auth/organization/accept-invitation` with `{"invitationId": "ID"}` (`adoptExistingMembership`, never lowers a grade). Email verification is not required (`requireEmailVerificationOnInvitation: false`) | source at `bfc1539fe0`; pin `accept-invitation-adopt-membership.test.ts` 6 passed; QA run #24616 | | Environment facts → new "18.0 boots the single tenancy posture only" | 455–477 | the walled theme's shared statement, the verdict word, the pin, the live measurement | `bfc1539fe0` live + pin 5 passed; QA runs #24557, #24558, #24559 | | Environment facts → new "A stock 18.0 boot holds exactly one organization" | 479–496 | the second-organization theme's shared statement | source + pin `org-create-posture-gate.test.ts` 25 passed; QA runs #24560, #24558 | | Environment facts → new "One container builds one tree at a time" | 498–516 | the build race (`build-console.sh` runs turbo builds in this checkout), the cgroup limit, `--concurrency=2` / `--maxWorkers=1`, the `OBJECTUI_DEPS_BUILD_CMD` override, exit 137 | #24579 §6; the cgroup read `14343688192` at `bfc1539fe0`; QA run #24598's fingerprint. The OOM is not re-run | | Environment facts → new "`OS_TEST_TIERS` narrows `packages/cli`'s vitest population" | 518–531 | nightly vs queue population; only `packages/cli` owns tier files (1 package, 83 files) | `nightly-tiers.mjs --packages` at `bfc1539fe0`; #24579 round 2 (6272032736); QA runs #24556, #24558, #24598 | | Environment facts → new "`scripts/qa/qa-rollup.mjs` has two blind spots" | 533–546 | `classifySelector('area:X')` → `other` (16 records of 2026-10-16); page 2 via the `repositories/ID` path → proxy 403, and `fetchRunRecords` throws | `classifySelector` run on 5 selectors; page-1 Link header → 403, `repos/OWNER/REPO` page 2 → 200 (130 records), at `bfc1539fe0`; #24579 §6 | | Environment facts → new "A map view draws its markers under a 'Map failed to load' banner" | 548–559 | `DEFAULT_MAP_STYLE` is unreachable behind the proxy, and the amber banner keeps the markers | objectui `ObjectMap.tsx` at `4016370a` and `12dc7b47c340`; `curl` → `CONNECT tunnel failed, response 403`; QA run #24616 (round 4's note) | `scripts/qa/` is not edited (ruling). ## Part (c) — `FOLLOW-UPS.md` The file's own convention: append, never rewrite; a corrected row keeps its old content as "⚠️ This row used to …; DATE with #PR" (rows E2 and §6's second row), and a closed row carries its marker in bold (**FIXED in #N**, **closed by …**). | row | line (head) | edit | |:--|:--|:--| | §10b E3 | 615 | The stale tail ("… the other two are still owed") becomes "⚠️ This row used to end that …; **paid 2026-10-19 with #24649** (`cc95f5e63e`)". `rest-crud-config-contract` and `rest-batch-config-contract` (revision 3 each) name the mount pin in `automated.ref` and say in `knownGaps[1]` that the mount half is pinned. Measured in `api-backend.json` at `bfc1539fe0` | ## Part (d) — `automation.json` | item | fields edited | revision | run record | |:--|:--|:--|:--| | `automation.paused-run-trigger-record-masked` | `fixtures.knownGaps[0]` | 2 → 3 | round 3's report on #24579 (6277457406) | "`showcase_approver_bindings` is draft on purpose" becomes `status: 'obsolete'` on purpose (`approver-bindings.flow.ts#ApproverBindingsFlow`). Its docblock says why not draft: a draft flow binds and fires like an active one. It is still the only flow on `showcase_field_zoo`, so the gap's conclusion holds. ## Findings left alone, each with its refuting reading - **§6 "`scaffold-e2e.yml` has not run on the candidate sha; its latest scheduled run failed on `9112bcd8`"** (round 2). Re-read: still true. The newest `schedule` run is `9112bcd887`, failure, 2026-10-19T03:39Z, and only `pull_request` runs follow it. Not taken: it is a dated CI state that the next scheduled run changes, not a standing environment fact. - **§6 "objectui live e2e is still red at its own pin `4016370a`"** (round 2). Not taken. It is §2's objectui-live-e2e theme, which the dispatch gives to the next round, and the pin it names has since moved (#24660 → `12dc7b47c340`). Not re-measured: that needs the objectui live suite. - **`grant-table-write-contract` acceptance[4] "needs a walled boot"** (card §2, rounds 2–3). Half refuted: `grant-holder-membership-refusal.ts`'s predicate does not read the posture. The knownGap records this and keeps the clause as written. A single-posture rig, a grantee with no membership in the writer's organization, is a fixture route and is not measured here. - **The card's "five items" list.** Not enough on its own: `no-active-org-session-semantics` (every clause walled) and `chunked-upload-integrity` (org switch) also carry the themes and were taken. - **`platform-owner-email-anchor`.** No clause needs a walled boot: acceptance[5], the walled half, is test-only already. steps[0]'s parenthetical still reads "a walled posture … refuses to boot with it unset (plugin-auth init …)". On 18.0 the boot is refused earlier, set or unset. Noted, not edited: the item boots single and scores nothing on it. - **`suggested-binding-loop` negative[1]** ("under a walled posture only the platform operator may accept"). A negative note, not a clause. Left alone. - **`public-form-withdrawal-layers`.** Its `set-active` switches between the Default Organization and null, which a stock 18.0 boot can do, and it already puts the walled half out of scope. - **attachments-storage `download-authz-both-sides`, `read-inherits-parent-rls`, `attach-requires-parent-edit` (the CROSS-TENANT marker).** That marker is about the dogfood matrix's `skipIf` block, already `blocked(dependency)`. None of their acceptance clauses needs a walled boot. ## Measurements (at `bfc1539fe0`) - Built under `os-verify-lock`: `turbo run build --concurrency=2 --filter='@objectstack/plugin-auth^...'` (27/27, cache), then `--filter='@objectstack/cli^...'` (60/60, 37 cached, held 101 s), then `--filter=@objectstack/cli`. VERDICT command-exit 0 each time. - Pins, under the lock, `vitest run --maxWorkers=2`, VERDICT command-exit 0: - plugin-auth `org-create-posture-gate.test.ts`: 25 passed. - plugin-auth `accept-invitation-adopt-membership.test.ts`: 6 passed. - cli `serve-walled-posture-refusal.test.ts`: 5 passed. - Live walled probes: listed in Part (a). - Proxy reads: `classifySelector` on 5 selectors, the qa-run page-2 403 and 200, and the demotiles `CONNECT` 403. ## Gates Derived with `node scripts/pm/dispatch-gates.mjs --commands --repo objectstack-ai/objectstack`: 13 families for the 6 paths, against merge base `92ce3af40`, tree `cfb070a467`. All 13 ran at `cfb070a467`, each exit captured before any pipe, all exit 0: - `node scripts/check-ci-filter-parity.mjs` - `node scripts/check-closing-keyword-parity.mjs` - `node scripts/check-closing-keyword-parity.mjs --self-test` - `node scripts/check-comment-mask-corpus.mjs` - `pnpm --filter @objectstack/lint run check:doc-formula-expressions` - `pnpm check:cross-package-test-inputs` - `pnpm check:doc-authoring` - `pnpm check:driver-memory-census` - `pnpm check:gitlink-declared` - `pnpm check:nul-bytes` - `pnpm check:platform-checklist` - `pnpm check:refd-timer-probe` - `pnpm check:watch-hint-literal` `--ran` reports: "13 derived famil(ies) accounted for — 13 run, 0 NOT-MEASURED (a DERIVED zero …)". `check:platform-checklist` runs in no per-PR CI, so this local reading is its evidence: "check-platform-checklist: OK — 16 areas, 327 items (322 active, 1 planned)". Symbol anchors are 875/885 at `bfc1539fe0` and at merged `origin/main` `92ce3af404`, and 886/896 at head: 11 added (access-security +7, platform-core +2, attachments-storage +1, automation +1), 0 lost, 10 on the #16898 residual, 18 file floors held. `RUNNER.md` carries no anchor, by design (it has no floor entry). `check:platform-checklist --changed-from` was not run: no symbol was renamed or removed. A control-character scan of the six files is empty. ## Changeset `skip-changeset`. `docs/qa/**` sits in no package, so this diff publishes nothing. ## Not in this PR - The four files of #24671 (now landed). - `integration-system.json`. - §2's "pins that cover no full clause" and "objectui live e2e" themes (next round). - §4 fixture gaps. --- _Generated by [Claude Code](https://claude.ai/code/session_01FM29ZqLTrnPNXxijioZwMp)_ --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent c663245 commit 045dae8

6 files changed

Lines changed: 213 additions & 25 deletions

File tree

‎docs/qa/platform-checklist/FOLLOW-UPS.md‎

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -612,7 +612,7 @@ card filed against it*. Recorded, not acted on — the channel question is #1173
612612
|---|---|---|---|---|
613613
| E1 | **`metadata.endpoints.items` gates four routes, three of which its declared meaning does not cover** — its `describe()` says "GET /meta/:type — List items of type", and it also gates `GET {prefix}/diagnostics`, `GET {prefix}/_drafts` and the **`POST {prefix}/_migrate-stored` write door**. An operator switching off a listing read silently disarms a migration door and the cross-type spec-validation sweep. `endpoints.item` is milder but the same shape: it also takes `{prefix}/book/:name/tree`. | `packages/rest/src/rest-server.ts#registerMetadataEndpointsInner` (four `endpoints.items` gates, four `endpoints.item` gates) vs `packages/spec/src/api/rest-server.zod.ts#MetadataEndpointsConfigSchema` (one route named per switch) | api-backend.rest-metadata-config-contract (a clause requires the run to ENUMERATE each switch's real radius) | design/docs — filed as #15542, **RULED and closed**: every `endpoints.*` switch now gates exactly the face its name states. The whole-store family (`/diagnostics`, `/_drafts`, `POST {prefix}/_migrate-stored`) moved to a new key `maintenance`; `items` is down to its one declared mount; `item` gained the per-item `PUT`/`DELETE` and the history family it never gated (the converse mismatch, filed as #15854 and landed in the same PR); all four `describe()` strings now enumerate their mounts. ⛔ The checklist clause is **kept**, not retired — the run still ENUMERATES each switch's real radius from a route-table diff, and `areas/api-backend.json` revision 2 carries the new expected sets. |
614614
| E2 | **No shipped boot path authors `RestServerConfig` at all.** `os serve` constructs the REST plugin with a fixed config (only `enableProjectScoping` / `projectResolution` are threaded) and the dev plugin calls `createRestApiPlugin()` with none, so `crud` / `metadata` / `batch` / `routes` are reachable only from embedder code — through the one real door, `createRestApiPlugin({ api })`. ⚠️ **This row used to name a second door, `createHonoServerPlugin({ restConfig })`; no such function exists** (definition probe: zero across the tree, against a positive control that finds `createRestApiPlugin` at `packages/rest/src/rest-api-plugin.ts#createRestApiPlugin`). `HonoServerPlugin` is a class whose `restConfig` option has one reader taking `api.basePath` for the SPA fallback, and it never constructs a REST server. Corrected 2026-09-08 with #15543. A deployment cannot set `batch.maxBatchSize`, move `crud.dataPrefix`, or opt out of ADR-0106 D8 masking without embedding. | `packages/cli/src/commands/serve.ts` (the fixed construction) · `packages/plugins/plugin-dev/src/dev-plugin.ts` (no config) | the three config items' `knownGaps` — every non-default clause is scored `oracle: test` in a harness, and the run record must say so instead of claiming a reconfigured deployment | capability gap — filed as #15543, **RULED 2026-09-07** (director seat, summon #17, decision batch #2, maintainer verbatim 「同意」): the keys **stay** and keep their runtime reads; threading a config through is not taken (a new authorable surface for no measured demand, and blocked on #15542) and retiring them is refused (they have an embedder consumer). ⇒ **This is a recorded posture, not an open gap**, so the harness scoring in the three items' `knownGaps` is the permanent method, not a workaround pending a fix. The reachability answer is now written down in two places instead of being re-derived per run: the schema docblocks (`packages/spec/src/api/rest-server.zod.ts` → WHO CAN WRITE THIS CONFIG, plus a per-sub-object line on `crud` / `metadata` / `batch`) and a per-key REACHABILITY row in `packages/spec/liveness/{crud,metadata,batch}_endpoints.json`. ⚠️ Re-derived while closing this, and the correction is about WHERE, not whether. The card and the ruling cite `batch.maxBatchSize`'s docblock as calling the cap *"deployment policy"* and attribute it to `packages/spec/src/api/rest-server.zod.ts`; the phrase **does not occur in that file** (control: `maxBatchSize` occurs there 3 times, so the zero is a real zero). It **does exist**, verbatim, in the REST server: *"The cap is deployment policy — `RestServerConfig.batch.maxBatchSize` (1..1000, default 200)"* at `packages/rest/src/rest-server.ts#enforceBatchSize`. ⇒ **The ruling misattributed the sentence to the wrong file; it did not invent it.** That carrier is real, is the same defect class, and is **owed to a successor card in `domain:rest`** — this PR does not touch `packages/rest`. Inside `packages/spec/src/api/rest-server.zod.ts` the population is: `metadata.maskObjectFields`'s `false` *"opts this server out"* (the ruling's one correctly-placed citation), plus a carrier the card missed entirely, `api.enableSearch` (*"Deployment-wide switch"* in its docblock and *"deployment-wide search opt-out"* in its `describe()`), which `os serve` does not forward either. Both are repaired here. |
615-
| E3 | **The MOUNT half of every sub-config switch is unpinned.** `packages/rest/src/rest-sub-config-parse-not-cast.test.ts` pins what a switch normalizes to, and `rest-batch-size-cap.test.ts` pins the cap's effect; nothing asserts that a `false` switch removes its route from the table `getRoutes()` returns. The declared-not-enforced direction — a switch that normalizes correctly and gates nothing — is exactly what no current test would catch. ⚠️ The card said **nine** switches; re-measured on `cc5b3dd0c27` the mount-gating population is **nineteen** — the twelve sub-config switches the card enumerates (its own list adds to twelve, not nine) plus the seven `api.enable*` gates in `registerRoutes`, which are the same seam and were equally unpinned. | the two test files above; the gates live in `registerCrudEndpoints` / `registerBatchEndpoints` / `registerMetadataEndpointsInner` / `registerRoutes` | the three config items (the mount clauses, each with the gap named in `knownGaps`) | test gap — filed as #15544, **closed by `packages/rest/src/rest-config-mount-table.pin.test.ts`**: all nineteen gates pinned as a set difference against the all-true baseline, each with its presence twin (**twenty** since #15542 added `metadata.endpoints.maintenance`; the pin's §0 count moves deliberately with each switch added or retired). ⚠️ The three config items' `knownGaps` still say the harness is the only observation — stale in the good direction, refresh pending (`areas/api-backend.json` was held by another branch when this landed); `rest-metadata-config-contract`'s `automated.ref` now names this pin, the other two are still owed. |
615+
| E3 | **The MOUNT half of every sub-config switch is unpinned.** `packages/rest/src/rest-sub-config-parse-not-cast.test.ts` pins what a switch normalizes to, and `rest-batch-size-cap.test.ts` pins the cap's effect; nothing asserts that a `false` switch removes its route from the table `getRoutes()` returns. The declared-not-enforced direction — a switch that normalizes correctly and gates nothing — is exactly what no current test would catch. ⚠️ The card said **nine** switches; re-measured on `cc5b3dd0c27` the mount-gating population is **nineteen** — the twelve sub-config switches the card enumerates (its own list adds to twelve, not nine) plus the seven `api.enable*` gates in `registerRoutes`, which are the same seam and were equally unpinned. | the two test files above; the gates live in `registerCrudEndpoints` / `registerBatchEndpoints` / `registerMetadataEndpointsInner` / `registerRoutes` | the three config items (the mount clauses, each with the gap named in `knownGaps`) | test gap — filed as #15544, **closed by `packages/rest/src/rest-config-mount-table.pin.test.ts`**: all nineteen gates pinned as a set difference against the all-true baseline, each with its presence twin (**twenty** since #15542 added `metadata.endpoints.maintenance`; the pin's §0 count moves deliberately with each switch added or retired). ⚠️ This row used to end that the three config items' `knownGaps` still said the harness was the only observation (refresh pending, `areas/api-backend.json` held by another branch) and that only `rest-metadata-config-contract`'s `automated.ref` named this pin, the other two still owed; **paid 2026-10-19 with #24649** (`cc95f5e63e`) — `rest-crud-config-contract` and `rest-batch-config-contract`, revision 3 each, name this pin in `automated.ref` and say in `knownGaps[1]` that the mount half is pinned, so all three config items cite it and nothing in this row is owed. |
616616

617617
### 10c. Checked and CLEAN (so the next sweep does not re-derive)
618618

0 commit comments

Comments
 (0)