Skip to content

Commit 4b61cf3

Browse files
baozhoutaoclaude
andauthored
docs(runtime,metadata,rest): retire the expired "structurally unreachable until the E7 flip" comment family + cover the multi-tenant decline branch (#5399) (#5404)
The #5040 E7 publish flip landed (packages/spec/src/api/endpoint-publish-gate.ts opens with "This module is that flip"), and E8 moved endpoints back into the OpenAPI document. Thirteen comments across runtime/metadata/rest still asserted the pre-flip world -- "Structurally unreachable today", "Nothing calls this yet", "Today it emits nothing" -- which is exactly the #5078 defect: a comment that contradicts the code in front of it. Each site is rewritten to describe current reality and cite the authority (the publish gate, or the real-boot probe in packages/qa/dogfood/test/showcase-declarative-endpoints.dogfood.test.ts, whose /openapi.json case disproves "emits nothing" directly). Surrounding still-true prose is preserved. Pure comment change: the only non-comment line touched is a descriptive `note:` string in the route ledger. Also adds dispatcher-plugin.multi-tenant-endpoint.integration.test.ts, covering the E5b branch "multi-tenant resolution finds no environment -> decline + warn", which had zero test references repo-wide because the kernel-resolver PROVIDER ships in the cloud distribution. A stub resolver drives a real boot over a real socket: a placed request executes on its own tenant kernel; an unplaced one gets the transport's bare 404, the warn, and no probe of any declaration. The host kernel deliberately declares the same path too. Without that, the decline cases pass vacuously -- verified by deleting the branch and re-running: with tenant-only declarations only the warn assertion moved, because the step declined a second time for an unrelated reason. With the host copy in place, deleting the branch turns both decline cases red with "expected 200 to be 404", the cross-tenant answer the branch exists to prevent. Out of scope, filed as #5400: the sibling `else` branch still logs `debug` when an adapter exposes no setFallbackHandler. Its own comment scheduled a move to `warn` "when that flip lands" -- it has landed, so declared endpoints are now silently unservable there -- but changing the level is a behavior change, so this commit only makes the comment truthful and names the tracking issue. Refs #5231, #5040, #5078, #5230, #5385 Claude-Session: https://claude.ai/code/session_01VkPSGsX9o17MsGv3Lbxu2w Co-authored-by: Claude <noreply@anthropic.com>
1 parent 235b94a commit 4b61cf3

14 files changed

Lines changed: 395 additions & 60 deletions

packages/metadata/src/metadata-manager.ts

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2161,9 +2161,14 @@ export class MetadataManager implements IMetadataService {
21612161
* already resolve the `metadata` service for the environment they serve —
21622162
* adding one here would create a second scoping mechanism.
21632163
*
2164-
* Nothing reaches this method over HTTP in 17.x: the dispatcher seam is
2165-
* #5090's, and publish still rejects a non-empty `apis:` (#4936), so the
2166-
* whole path is structurally unreachable until the #5040 E7 flip.
2164+
* This method is reached over HTTP on a real boot. The dispatcher seam
2165+
* landed as #5090 (`packages/runtime/src/api-endpoint-step.ts`, called from
2166+
* the `setFallbackHandler` the dispatcher plugin installs), and #4936's
2167+
* wholesale publish refusal of a non-empty `apis:` was replaced by the
2168+
* #5040 E7 per-shape gates (`packages/spec/src/api/endpoint-publish-gate.ts`)
2169+
* — so declarations exist and requests arrive here. The showcase's two
2170+
* declared endpoints are matched and executed through this path in
2171+
* `packages/qa/dogfood/test/showcase-declarative-endpoints.dogfood.test.ts`.
21672172
*
21682173
* @throws when the metadata store cannot be read — an outage must never be
21692174
* reported as a miss, because a miss becomes a 404.

packages/rest/src/openapi-endpoints.test.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,17 @@
33
/**
44
* #5040 E6 — declared endpoints in the OpenAPI document.
55
*
6-
* Two jobs, and the second one is the load-bearing one TODAY:
6+
* Two jobs:
77
*
88
* 1. the positive shapes, driven straight through the pure enrichment with
9-
* parsed declarations (publish still refuses to let any of them exist, so
10-
* there is no boot that could exercise them end to end yet);
9+
* parsed declarations. Since the #5040 E7 publish flip these are the LIVE
10+
* path — a real showcase boot serves an `/openapi.json` carrying its two
11+
* declared endpoints
12+
* (`packages/qa/dogfood/test/showcase-declarative-endpoints.dogfood.test.ts`);
13+
* the cases here pin the projection itself, apart from any boot;
1114
* 2. the empty-set invariant — with no declarations the document must come
12-
* back not merely equivalent but IDENTICAL, because that is the entire
13-
* live-behaviour claim this change makes until the E7 flip.
15+
* back not merely equivalent but IDENTICAL, which is what keeps a
16+
* deployment that declares nothing byte-for-byte unchanged.
1417
*/
1518

1619
import { describe, it, expect, vi } from 'vitest';

packages/rest/src/openapi-endpoints.ts

Lines changed: 17 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -55,12 +55,20 @@
5555
* the mirror; it is not built here because Prime Directive #2 keeps logic out
5656
* of `packages/spec` and the frozen vocabulary is not this unit's to widen.
5757
*
58-
* ## Today it emits nothing
58+
* ## What it emits today
5959
*
60-
* Publish/validate still rejects a non-empty `apis:` until the E7 flip, so the
61-
* enumeration yields an empty set and {@link enrichOpenApiWithEndpoints}
62-
* returns its input document BY REFERENCE — the served bytes are identical to
63-
* before this change. That invariant is pinned by a test rather than argued.
60+
* Real documents. The #5040 E7 publish flip
61+
* (`packages/spec/src/api/endpoint-publish-gate.ts`) ended the wholesale
62+
* refusal of a non-empty `apis:`, so the enumeration is no longer empty on a
63+
* deployment that declares endpoints: a real showcase boot serves an
64+
* `/openapi.json` describing its two declared endpoints
65+
* (`packages/qa/dogfood/test/showcase-declarative-endpoints.dogfood.test.ts`).
66+
*
67+
* The empty-set case is still exact rather than approximate — with nothing to
68+
* add, {@link enrichOpenApiWithEndpoints} returns its input document BY
69+
* REFERENCE — which is what keeps a deployment that declares no endpoint
70+
* byte-identical to one built before this module existed. That invariant is
71+
* pinned by a test rather than argued.
6472
*/
6573

6674
import { ApiEndpointSchema, type ApiEndpoint } from '@objectstack/spec/api';
@@ -328,8 +336,10 @@ export function selectDocumentableEndpoints(
328336
* Fold declared endpoints into an OpenAPI document's `paths`.
329337
*
330338
* Returns `doc` ITSELF when there is nothing to add — that is what makes the
331-
* empty-set case byte-identical rather than merely equivalent, and it is the
332-
* state of the world until the E7 flip lets a non-empty `apis:` publish.
339+
* empty-set case byte-identical rather than merely equivalent, which is the
340+
* state a deployment declaring no endpoint stays in. Since the E7 flip let a
341+
* non-empty `apis:` publish, the other branch is the live one wherever
342+
* endpoints are declared.
333343
*
334344
* A declaration never displaces a built-in: if the document already describes
335345
* the same path+method, the built-in keeps it and the declaration is reported.

packages/rest/src/rest-openapi-route.test.ts

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -77,9 +77,11 @@ describe('GET /api/v1/openapi.json — endpoint enrichment', () => {
7777
});
7878

7979
it('serves a document identical to the pre-#5093 one while no endpoint is declared', async () => {
80-
// The load-bearing invariant: publish rejects a non-empty `apis:` until the
81-
// E7 flip, so this is the ONLY state that exists in production today, and
82-
// the change is required to be invisible in it. Compared against the same
80+
// The load-bearing invariant: a deployment that declares no endpoint must
81+
// not be able to tell the enrichment step exists. Since the #5040 E7
82+
// publish flip this is no longer the only state in production — endpoints
83+
// do publish — which is exactly why the no-declaration state needs pinning
84+
// rather than assuming. Compared against the same
8385
// handler fed a protocol with no `api` capability at all — i.e. the world
8486
// exactly as it was before the enrichment step existed.
8587
const withEmptyApis = await serveOpenApi(makeProtocol({ object: [], api: [] }).protocol);

packages/rest/src/rest-server.ts

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2862,10 +2862,12 @@ export class RestServer {
28622862
// ADR-0076): declared endpoints join it here rather than
28632863
// growing a second generator somewhere else.
28642864
//
2865-
// Until the E7 flip a non-empty `apis:` cannot publish, so
2866-
// the enumeration is empty and `enrichOpenApiWithEndpoints`
2867-
// hands `enriched` straight back — the served bytes today
2868-
// are exactly the ones served before this change.
2865+
// Since the E7 flip a non-empty `apis:` publishes, so this
2866+
// enumeration returns real declarations on a deployment that
2867+
// has them and the document grows a path entry per endpoint.
2868+
// Where nothing is declared the enumeration is empty and
2869+
// `enrichOpenApiWithEndpoints` hands `enriched` straight
2870+
// back, byte for byte.
28692871
try {
28702872
const apiResult = await protocol?.getMetaItems?.({ type: 'api' });
28712873
const apiItems: unknown[] = Array.isArray((apiResult as any)?.items)

packages/runtime/src/api-endpoint-step.test.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,10 @@
55
*
66
* Every case here is about ONE question: when does this step answer, and when
77
* does it write nothing so the transport's existing unmatched answer stands?
8-
* Getting that wrong in either direction is a live behavior change on a surface
9-
* that is supposed to be inert until the #5040 E7 flip.
8+
* Getting that wrong in either direction is a live behavior change — and since
9+
* the #5040 E7 publish flip it is live for real traffic, not just in principle:
10+
* endpoints can be declared, so a step that answers when it should stay silent
11+
* now shadows the transport's 404 on a deployment.
1012
*
1113
* `matchEndpoint` is driven by a stub implementing the contract in
1214
* `@objectstack/spec/contracts` — deliberately, not by the real matcher: that

packages/runtime/src/api-endpoint-step.ts

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -20,11 +20,14 @@
2020
* ## What it does today, and what it does not
2121
*
2222
* On a match it runs the WHOLE chain: policies (#5040 E4) and then target
23-
* execution (#5040 E5), wired together here by E5b. It is still structurally
24-
* unreachable — a non-empty `apis:` is rejected at publish / validate until the
25-
* E7 flip — so no deployment can observe it; the tests drive `matchEndpoint`
26-
* through a stub, exactly as #5040 §5 prescribes for every E-series unit that
27-
* lands before the flip.
23+
* execution (#5040 E5), wired together here by E5b. Since the E7 publish flip
24+
* (`packages/spec/src/api/endpoint-publish-gate.ts`) a non-empty `apis:` is no
25+
* longer refused wholesale — only shape by shape — so a deployment CAN observe
26+
* this step: the showcase declares two endpoints and a real boot serves them
27+
* (`packages/qa/dogfood/test/showcase-declarative-endpoints.dogfood.test.ts`).
28+
* The tests here still drive `matchEndpoint` through a stub, which is what
29+
* keeps the decision (when to answer, when to write nothing) testable apart
30+
* from the wiring.
2831
*
2932
* ## The chain, in the one order it can run in
3033
*

packages/runtime/src/api-mapping.test.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,9 +14,10 @@
1414
* applied. `transform` is the case #5137 was filed over, but an unusable
1515
* path and colliding targets are the same category and answer identically.
1616
*
17-
* And the property that makes both safe to land before the E7 flip: with no
18-
* declaration, the value that goes in is the value that comes out, by
19-
* reference.
17+
* And the identity property both rest on: with no declaration, the value that
18+
* goes in is the value that comes out, by reference. That is what made these
19+
* keys safe to land ahead of the #5040 E7 publish flip, and it is still what
20+
* keeps an endpoint declaring no mapping free of any projection cost.
2021
*/
2122

2223
import { describe, it, expect } from 'vitest';

packages/runtime/src/dispatcher-plugin.endpoint-fallback.integration.test.ts

Lines changed: 6 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,9 +18,12 @@
1818
* rides a success and never an error.
1919
*
2020
* The load-bearing assertion in most of these is a NEGATIVE one: that adding
21-
* this seam changed nothing for anybody. Today's unmatched answers — the bare
22-
* 404 and the 405 + `Allow` — must come back byte for byte, since a stack
23-
* cannot declare an endpoint at all until the E7 flip.
21+
* this seam changed nothing for anybody who did not ask for it. The unmatched
22+
* answers — the bare 404 and the 405 + `Allow` — must come back byte for byte
23+
* for every path no declaration owns. Since the #5040 E7 publish flip that is
24+
* the assertion's whole weight: stacks CAN declare endpoints now, so "the
25+
* fallback stays silent unless a declaration matches" is a promise to live
26+
* deployments rather than a property of a surface nothing could reach.
2427
*
2528
* NOTE on the body guarantee: that the fallback receives a READABLE `req.body`
2629
* (the difference from the `use()` middleware seam) is a transport promise, and

0 commit comments

Comments
 (0)