Skip to content

Commit 6a04a50

Browse files
committed
test(runtime): pin that a domain registered at /authx is REACHED, not merely that auth stops answering
The nine cases in this pin all read the auth service spy: "not called" is how they conclude the `/auth` domain did not claim a path. The contract review measured what that cannot see — a repair which KEEPS the wide `startsWith('/auth')` claim and moves the refusal INSIDE `handleAuthRequest` passes all nine green, because the service is still never called and the `ROUTE_NOT_FOUND` envelope is still what comes back. Under that shape `/authx` is still SHADOWED: a domain mounted there never runs, which is the harm the card names and the changeset says is gone. The new case observes the REGISTRY instead. `registerDomainHandler` appends to a first-match-wins table, so a probe domain registered at `/authx` AFTER construction sits BEHIND the auth route — exactly where a package mounting that namespace later would sit — and is reachable only if the auth route declines the path. The evidence asserted is the probe's OWN response coming back out of `dispatch()` for `/authx` and `/authx/foo`, not the absence of a call. Test-only: no production file changes, and the existing `patch` changeset on `@objectstack/runtime` is unchanged. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01D47qPfEWVPmhguWgBZCi5N
1 parent 59b1001 commit 6a04a50

1 file changed

Lines changed: 59 additions & 0 deletions

File tree

packages/runtime/src/domains/auth-claim-segment-boundary.test.ts

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,12 @@
4848
* `/aut/foo` and `/zzz/foo` are the card's own clean rows, carried so a run
4949
* where EVERYTHING 404s is distinguishable from the fix.
5050
*
51+
* ⚠️ That spy is not sufficient on its own, and the LAST case in this file is
52+
* why: "not called" cannot separate a RELEASED namespace from a still-wide
53+
* claim that refuses inside `handleAuthRequest`. That case observes the
54+
* registry resolving `/authx` to a domain registered there after construction
55+
* — its own docblock carries the argument.
56+
*
5157
* ## ⛔ Not covered here
5258
*
5359
* The `200 {}` those rows carried. It is manufactured one layer out, in the
@@ -166,4 +172,57 @@ describe('#16026: the /auth claim stops at a segment boundary', () => {
166172
expect(result.result).toBeInstanceOf(Response);
167173
expect(result.response?.body?.error?.code).toBeUndefined();
168174
});
175+
176+
/**
177+
* ⭐ The registry-resolution case — what every case above is blind to.
178+
*
179+
* The nine cases above read the auth service spy: "not called" is how they
180+
* conclude "the domain did not claim this path". That observation cannot
181+
* tell the delivered fix apart from a repair which KEEPS the wide
182+
* `startsWith('/auth')` claim and moves the refusal INSIDE
183+
* `handleAuthRequest`. Under that shape the service is still never called
184+
* and the `ROUTE_NOT_FOUND` envelope is still what comes back, so all nine
185+
* stay green — while `/authx` is still SHADOWED and a domain someone mounts
186+
* there never runs. That shadowing is the harm the card names and the
187+
* changeset says is gone, so it needs an observation of its own.
188+
*
189+
* This case observes the REGISTRY instead of the spy.
190+
* `registerDomainHandler` appends to a first-match-wins table
191+
* (`DomainHandlerRegistry.register` → `resolve` walks in registration
192+
* order), so a probe registered AFTER construction sits BEHIND the auth
193+
* route — exactly where a package that mounts `/authx` later would sit. It
194+
* is reachable only if the auth route declines the path, and the evidence
195+
* is the probe's OWN response coming back out of `dispatch()`, not the
196+
* absence of a call.
197+
*
198+
* ⛔ What falsifies it: the registry resolving `/authx` or `/authx/foo` to
199+
* anything other than the probe. In this fixture the auth route is the only
200+
* other claimant, so red here means the claim did not stop at the segment
201+
* boundary — stated about the registry, which is where the shadowing lives.
202+
*/
203+
it('⭐ a domain registered at /authx AFTER construction is REACHED — the claim released the namespace, it did not merely stop answering', async () => {
204+
const { dispatcher, handleRequest } = makeFixture();
205+
const probe = vi.fn(async (req: any) => ({
206+
handled: true as const,
207+
response: { status: 200, body: { success: true, data: { probe: '/authx', path: req.path } } },
208+
}));
209+
dispatcher.registerDomainHandler({ prefix: '/authx', match: 'segment', handler: probe });
210+
211+
for (const path of ['/authx', '/authx/foo']) {
212+
const result = await dispatcher.dispatch('GET', path, undefined, {}, { request: new Request(`http://localhost${path}`) } as any);
213+
214+
// The registry resolved to the PROBE: its own response is what the
215+
// dispatcher handed back, for this exact path.
216+
expect(result.handled).toBe(true);
217+
expect(result.response?.status).toBe(200);
218+
expect(result.response?.body?.data?.probe).toBe('/authx');
219+
expect(result.response?.body?.data?.path).toBe(path);
220+
// …and specifically NOT the terminal refusal, which is what a claim
221+
// that is still wide but refuses in the handler would produce.
222+
expect(result.response?.body?.error?.code).toBeUndefined();
223+
}
224+
225+
expect(probe).toHaveBeenCalledTimes(2);
226+
expect(handleRequest).not.toHaveBeenCalled();
227+
});
169228
});

0 commit comments

Comments
 (0)