Repository navigation
Commit 166a94f
Fixes #22430
Clause-②: no (narrowing)
## What changed
`RestServer.registerOpenApiEndpoints` registers the API-description
endpoints: the document and its viewer page, on every base the server
mounts. Neither handler checked who was calling. Both now open with the
package's existing anonymous-deny floor (`enforceAuth`, which asks
`shouldDenyAnonymous` from `@objectstack/core`). The check runs first,
before the bundled artifact is loaded or a protocol is resolved, so a
refused request does no work.
- An anonymous caller gets `401` with code `UNAUTHENTICATED`. The body
is `ANONYMOUS_DENY_BODY`, byte-identical to the anonymous 401 the data
routes answer.
- A signed-in caller is served as before.
- A session held by the ADR-0069 auth-policy gate (an expired password,
enforced MFA) gets that gate's `403`, as on every other protected route.
- No new error code, no spec key: `Clause-②: no`.
This executes ruling `6074960686` on #22146, item 2, verbatim:
> 2. **The API-description endpoints** refuse an anonymous caller (401).
## Measured first: a real boot, before and after
`pnpm dev -- --fresh` (the showcase) on this branch, before the fix
(base `b9222dc70`) and after it. Statuses only. "Gated" is a member
created by the admin, still under the must-change-password policy,
probed before the password change. "Member" is the same account after
it.
| endpoint | caller | before | after |
|---|---|---|---|
| the document, unscoped base | anonymous | 200 (served) | **401
`UNAUTHENTICATED`** |
| the document, unscoped base | gated session | 200 (served) | 403
`PASSWORD_EXPIRED` |
| the document, unscoped base | signed-in member | 200 | 200 |
| the document, unscoped base | signed-in admin | 200 | 200 |
| the viewer, unscoped base | anonymous | 200 (page) | **401
`UNAUTHENTICATED`** |
| the viewer, unscoped base | gated session | 200 (page) | 403
`PASSWORD_EXPIRED` |
| the viewer, unscoped base | signed-in member | 200 | 200 |
| the viewer, unscoped base | signed-in admin | 200 | 200 |
| either, environment-scoped base | every caller | 404 (not mounted) |
404 (not mounted) |
| control: a data route | anonymous / gated / member | 401 / 403 / 200 |
401 / 403 / 200 |
The environment-scoped base is registered only under project scoping,
and the showcase does not enable it. So on this boot both bases answer
the same way for the scoped twin: it is not mounted. The scoped twin
runs the same handler closures. It is measured at the handler level in
`rest-api-description-anonymous-deny.test.ts`, for both bases. Against
the base `rest-server.ts`, all four anonymous cells answer 200, and the
test turns red.
## The viewer carries the session (H4)
The viewer is an HTML page whose script fetches the document from the
browser. I measured it in Chromium with the real viewer bundle. The CDN
the page names is unreachable from this container, so the browser was
served the same package (`@scalar/api-reference` 1.73.1) from its npm
tarball.
- Before: anonymous navigation 200; the viewer fetched the document with
no cookie and got 200.
- After, anonymous: navigation 401; the viewer never runs.
- After, signed in (cookie session): navigation 200; the viewer's fetch
of the document carried the session cookie and got 200.
A signed-in browser is served on both endpoints, as before.
## The ruling's confidence gap: an anonymous consumer in the tree?
Not found. I searched objectstack at this branch, objectui `origin/main`
`049012bf`, and the objectui pin `a58626c88` for anything that fetches
the document or the viewer.
- `@objectstack/client`, the CLI, `scripts/`, `.github/`, `examples/`,
`apps/docs` (its reference pages are generated from the Zod schemas, not
from the served document): zero hits.
- objectui `apps/**` and `packages/**`, at both commits: zero fetches.
The only `openapi.json` mentions are the `@objectstack/spec`
package-export name in build tooling and its tests. The Console's
`/docs` routes are its own book portal, not this viewer.
- One reader of the served document exists in the tree:
`showcase-declarative-endpoints.dogfood.test.ts`. It reads it with an
admin token, so it is unaffected.
- The platform checklist's route-parity item probes the document "as
admin", so it is unaffected.
- `/discovery` does not advertise the document's URL.
A deployed public API portal outside this repository is NOT MEASURED.
## Translation-flip sweep
Pins and prose that held the old meaning, all in this PR:
- `packages/rest/src/openapi-builtin-paths.ts`: the coverage note listed
these endpoints among "the routes that answer anonymously". It now names
only the discovery routes and says the inherited document-level
requirement is true of these two. A new case pins that neither operation
carries its own `security`, and that the document states a requirement.
- Four rest suites drove the document with no session and read a 200:
`rest-openapi-route`, `rest-openapi-info-overlay`,
`direct-mount-introspection` and `direct-mount-base-follows-apipath`. A
fifth, `rest-endpoint-surfaces-served-only`, was already signed in. The
four test the document's contents, so they now read it as a signed-in
caller. The refusal itself is asserted in the new suite: status, `code`,
the whole body, no document keys, no page, and no artifact load or
protocol read.
- `execctx-consumer-census.test.ts`: 66 to 68 sites, 45 to 47 bare. Both
new sites are bare, with the shared floor on the next line. Its §3
drives them: an absent context gets 401 `UNAUTHENTICATED`, an entitled
one clears the floor.
- Authorization matrix: the `openapi` REST family moves from the
shrink-only unclassified baseline (32 to 31) to a new `enforced` row,
`anonymous-deny-api-description`. Its cited proof is
`showcase-anonymous-deny-surfaces.dogfood.test.ts`, which now drives
both endpoints on the booted showcase. Anonymous gets 401, the REST flat
envelope, the whole `ANONYMOUS_DENY_BODY`, nothing served. A signed-in
member gets 200 for the document and 200 for the viewer page. Both
endpoints joined the envelope-family table. The population pin in
`authz-conformance.test.ts` lists the family as classified.
- The authz probe blind-spot census (`authz-probe-blind-spot.census.ts`
and its test), re-measured from `deriveProbeFileCensus()` on the tree,
as in the `/analytics` precedent:
- `PROBE_TABLE` moves from `{ entries: 19, files: 14, keys: 17 }` to `{
19, 14, 18 }`. The new row covers one more key; no probe and no file
joined.
- The `rest-server.ts` control `enforceAuth` moves from 56 to 58: the
two handlers' call sites, with no prose mention.
- Population, reach and blind spot re-derive unchanged at 71 / 19 / 52,
and every other control is unchanged. Guarding a route is not reaching
it with a key, so the blind spot does not shrink.
- The test's count moves from 17 to 18 `covers` keys. The ledgers still
mint 39 keys: 8 classified, 31 baselined.
- The `rest-route-ledger.ts` row's note now says 2 families classified
(metadata and openapi) and 16 baselined.
## Tests and gates (at `d1cc56281`)
- Reverse verification, unit level: the new suite against the base
`rest-server.ts`. 7 failed and 4 passed. The 4 anonymous cells read
`expected 200 to be 401`. The signed-in controls passed. Restored to the
HEAD blob, `git diff HEAD` empty. After the fix: 13 of 13.
- Ablation through `dist/` (`scripts/ablation-replace.mjs` +
`ablation-dist-preflight.mjs`): I removed only the document's floor,
rebuilt `@objectstack/rest`, and confirmed the marker in both built
files. The booted-showcase proof then failed exactly its two
document-anonymous cases (2 failed / 65 passed); the viewer cases and
the controls stayed green. Restored to the HEAD blob, rebuilt, and
confirmed the marker absent from `dist/`.
- `@objectstack/rest`, at `1d4ad614f`: the full suite gave 271 files,
5164 passed, 327 skipped; typecheck passed (tests are covered through
`tsconfig.test.json`, 6 of 6 touched files). Nothing under
`packages/rest` changed after that commit.
- `@objectstack/dogfood`, the WHOLE suite, through the verify lock: 233
files (232 passed, 1 skipped), 1847 tests (1838 passed, 9 skipped), exit
0. It ran at `0c0b1f7e5`; the next commit, `d1cc56281`, changes only a
string and a comment in the census note. At `d1cc56281`,
`authz-probe-blind-spot.test.ts` + `authz-conformance.test.ts` passed 90
of 90.
- `pnpm check:doc-authoring` exits 0 at `d1cc56281`. It was red at
`0c0b1f7e5` on a tracker id inside the census note's string prose. The
id now sits in an adjacent comment.
- `dispatch-gates.mjs --commands` derives the same 69 commands at
`d1cc56281`, and all 69 exited 0 there. Three gates read built output
and first answered `PREREQUISITE NOT MET` (exit 3) in the fresh
worktree: `check:dual-build-cjs-loads`, `check:dts-closure` and
`check:sourcemap-no-sources-content` (the last two swept 63 and 60
packages). I built the eight packages that had no `dist/` (all turbo
cache hits) and re-ran them: exit 0, sweeping 66, 71 and 68 packages.
`--ran` reconciles 69 derived, 69 run, 0 not measured.
- `pnpm lint` (the full run) exited 0 at `d1cc56281`.
`@objectstack/dogfood` typecheck exited 0 at `d1cc56281`.
## Acceptance notes
- **Operators who served the API description publicly:** readers must
now sign in. Otherwise, publish a static copy of the document. The
changeset says so.
- **Not in this change, by the ruling's scope:** `/discovery` still
answers anonymously, as before. On the environment-scoped twin, a
signed-in caller is judged by the auth service of the environment the
URL names. These handlers do not add the ownership comparison the
UI-view route makes. I did not measure that, because the showcase mounts
no scoped base. It is a question for whoever re-measures the
everything-else class for ADR-0138.
- **Observation:** the viewer page loads its script from a CDN with no
version pinned, on the API's own origin. That was true before this
change and is unchanged by it.
- **Left as is:** the ledger rows for this family in
`rest-route-ledger.ts` could carry the optional `authz:
'anonymous-deny-api-description'` field. I left that file alone because
another hold covers its notes.
---
_Generated by [Claude
Code](https://claude.ai/code/session_01BmsuLyUeuG5CNpZFMH1jzS)_
---------
Co-authored-by: Claude <noreply@anthropic.com>
1 parent c512c25 commit 166a94f
15 files changed
Lines changed: 444 additions & 46 deletions
File tree
- .changeset
- packages
- qa/dogfood/test
- rest/src
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
| 1 | + | |
| 2 | + | |
| 3 | + | |
| 4 | + | |
| 5 | + | |
| 6 | + | |
| 7 | + | |
| 8 | + | |
| 9 | + | |
| 10 | + | |
| 11 | + | |
| 12 | + | |
| 13 | + | |
| 14 | + | |
| 15 | + | |
| 16 | + | |
| 17 | + | |
| 18 | + | |
| 19 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
284 | 284 | | |
285 | 285 | | |
286 | 286 | | |
| 287 | + | |
| 288 | + | |
| 289 | + | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
| 294 | + | |
| 295 | + | |
| 296 | + | |
287 | 297 | | |
288 | 298 | | |
289 | 299 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
977 | 977 | | |
978 | 978 | | |
979 | 979 | | |
| 980 | + | |
| 981 | + | |
980 | 982 | | |
981 | 983 | | |
982 | 984 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
70 | 70 | | |
71 | 71 | | |
72 | 72 | | |
73 | | - | |
| 73 | + | |
| 74 | + | |
| 75 | + | |
74 | 76 | | |
75 | 77 | | |
76 | 78 | | |
| |||
80 | 82 | | |
81 | 83 | | |
82 | 84 | | |
83 | | - | |
84 | 85 | | |
85 | 86 | | |
86 | 87 | | |
| |||
126 | 127 | | |
127 | 128 | | |
128 | 129 | | |
129 | | - | |
| 130 | + | |
| 131 | + | |
| 132 | + | |
130 | 133 | | |
131 | | - | |
| 134 | + | |
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
287 | 287 | | |
288 | 288 | | |
289 | 289 | | |
290 | | - | |
| 290 | + | |
| 291 | + | |
| 292 | + | |
| 293 | + | |
291 | 294 | | |
292 | 295 | | |
293 | 296 | | |
| |||
342 | 345 | | |
343 | 346 | | |
344 | 347 | | |
| 348 | + | |
| 349 | + | |
345 | 350 | | |
346 | 351 | | |
347 | 352 | | |
348 | 353 | | |
349 | 354 | | |
350 | | - | |
351 | | - | |
| 355 | + | |
| 356 | + | |
352 | 357 | | |
353 | 358 | | |
354 | 359 | | |
| |||
499 | 504 | | |
500 | 505 | | |
501 | 506 | | |
| 507 | + | |
| 508 | + | |
| 509 | + | |
| 510 | + | |
| 511 | + | |
| 512 | + | |
| 513 | + | |
502 | 514 | | |
503 | 515 | | |
504 | 516 | | |
| |||
509 | 521 | | |
510 | 522 | | |
511 | 523 | | |
512 | | - | |
| 524 | + | |
513 | 525 | | |
514 | 526 | | |
515 | 527 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
43 | 43 | | |
44 | 44 | | |
45 | 45 | | |
46 | | - | |
47 | | - | |
| 46 | + | |
| 47 | + | |
48 | 48 | | |
49 | 49 | | |
50 | | - | |
| 50 | + | |
| 51 | + | |
51 | 52 | | |
52 | 53 | | |
53 | 54 | | |
54 | 55 | | |
55 | | - | |
56 | | - | |
| 56 | + | |
| 57 | + | |
57 | 58 | | |
58 | | - | |
| 59 | + | |
59 | 60 | | |
60 | 61 | | |
61 | 62 | | |
| |||
| Original file line number | Diff line number | Diff line change | |
|---|---|---|---|
| |||
50 | 50 | | |
51 | 51 | | |
52 | 52 | | |
| 53 | + | |
| 54 | + | |
| 55 | + | |
| 56 | + | |
| 57 | + | |
| 58 | + | |
| 59 | + | |
| 60 | + | |
| 61 | + | |
| 62 | + | |
| 63 | + | |
53 | 64 | | |
54 | 65 | | |
55 | 66 | | |
| |||
137 | 148 | | |
138 | 149 | | |
139 | 150 | | |
| 151 | + | |
| 152 | + | |
| 153 | + | |
| 154 | + | |
| 155 | + | |
| 156 | + | |
| 157 | + | |
| 158 | + | |
| 159 | + | |
| 160 | + | |
| 161 | + | |
| 162 | + | |
| 163 | + | |
| 164 | + | |
| 165 | + | |
140 | 166 | | |
141 | 167 | | |
142 | 168 | | |
| |||
636 | 662 | | |
637 | 663 | | |
638 | 664 | | |
| 665 | + | |
| 666 | + | |
| 667 | + | |
| 668 | + | |
| 669 | + | |
| 670 | + | |
| 671 | + | |
| 672 | + | |
| 673 | + | |
| 674 | + | |
| 675 | + | |
| 676 | + | |
| 677 | + | |
| 678 | + | |
| 679 | + | |
| 680 | + | |
| 681 | + | |
| 682 | + | |
| 683 | + | |
| 684 | + | |
| 685 | + | |
| 686 | + | |
| 687 | + | |
| 688 | + | |
| 689 | + | |
| 690 | + | |
| 691 | + | |
| 692 | + | |
| 693 | + | |
| 694 | + | |
| 695 | + | |
| 696 | + | |
| 697 | + | |
| 698 | + | |
639 | 699 | | |
640 | 700 | | |
641 | 701 | | |
| |||
740 | 800 | | |
741 | 801 | | |
742 | 802 | | |
| 803 | + | |
| 804 | + | |
| 805 | + | |
| 806 | + | |
| 807 | + | |
| 808 | + | |
| 809 | + | |
743 | 810 | | |
744 | 811 | | |
745 | 812 | | |
| |||
0 commit comments