Skip to content

test(client): resolve every AUTH_ROUTE_LEDGER client name against a real client - #11403

Merged
os-sam merged 1 commit into
mainfrom
claude/issue-11359-auth-ledger-client-resolution
Aug 23, 2026
Merged

test(client): resolve every AUTH_ROUTE_LEDGER client name against a real client#11403
os-sam merged 1 commit into
mainfrom
claude/issue-11359-auth-ledger-client-resolution

Conversation

@claude

@claudeclaudeBot commented Aug 23, 2026

Copy link
Copy Markdown
Contributor

Fixes#11359

AUTH_ROUTE_LEDGER is the largest of the six route ledgers (56 rows, 54 sdk, 55 carrying a client name) and it was the only one whose client names were never resolved against a real client. The server-side test that reads as if it did — auth-route-ledger.conformance.test.ts, "every sdk entry names its client method" — filters on e.disposition === 'sdk' && !e.client, and !e.client is falsy-on-absent, so it checks presence, not existence. A row spelled client: 'auth.setInitialPasword' passed it unchanged.

This adds the missing client half as packages/client/src/auth-route-ledger-coverage.test.ts, alongside the three that were already there.

Not a live wire defect. All 55 client-naming rows resolve today, so this file was green on its first run. That is the expected result and it is also the reason the ablation below is the substance of this PR rather than a formality: a guard that passes because the invariant holds and a guard that passes because it asserts nothing are indistinguishable from outside, and that indistinguishability is the defect being closed.

Where it lands, and why the card beat the triage note

The card said packages/client; triage said packages/plugins/plugin-auth. Measured rather than picked:

measurementresult
files repo-wide containing a typeof resolve(...) === 'function' ledger guard3 — route-ledger-coverage.test.ts, rest-route-ledger-coverage.test.ts, service-route-ledger-coverage.test.ts; all in packages/client/src, 0 elsewhere
does @objectstack/plugin-auth depend on @objectstack/client?no — not in dependencies, devDependencies or peerDependencies
does packages/client already read auth-route-ledger.ts?yesclient-url-conformance.test.ts:52 and route-ledger-response-schema.test.ts:48, both by relative source specifier

So the split is: each ledger's server half lives next to the registrar that mounts the routes, and every client half lives in packages/client, because resolving a name needs an ObjectStackClient. Landing it in plugin-auth would have created a plugin-auth → client package edge — the "backwards" direction all three sibling headers name, and unbuildable in CI's per-package closure. The auth ledger's own header already anticipates this file: "This module is package-internal … It must stay import-free — the client-side guard imports it as a relative SOURCE file."

A sibling file rather than a fourth leg inside service-route-ledger-coverage.test.ts (the card allowed either): that file's title and docblock are a statement about the #3636service tranche — "all THREE server surfaces" — and auth is a plugin, not a service. Filing auth under it would have made the file's own title false, which is the class of defect this card exists to remove.

Zone-2 assumptions, as measured

  1. Placement — resolved above, in the card's favour.
  2. No new package edge — confirmed, and stronger than assumed: no new cross-package radius either. packages/plugins/plugin-auth/src/auth-route-ledger.ts was already declared in CROSS_PACKAGE_TEST_INPUTS['@objectstack/client'] and already listed in turbo.json's @objectstack/client#test inputs, because two existing tests read it the same way.
  3. All rows resolve — confirmed. 55 rows carry a client name (54 sdk + one disabled row that deliberately keeps its name, auth-method-matrix: change-email and delete-user are booked as mounted in auth-route-ledger.ts but plugin-auth never configures better-auth's user.changeEmail / user.deleteUser — no switch to enable them #7735auth.deleteUser), and all 55 resolve to a function. No live defect found, so the clause-② grading of no still holds and needed no re-grade.

The ablation

Predicted in writing before running: typo one row's client (auth.changePasswordauth.changePasword); the new leg fails naming that row, while the existing server-side check stays green.

Mutation subject is packages/plugins/plugin-auth/src/auth-route-ledger.ts, which the suite imports as a relative source path — no exports resolution, no vitest alias, so dist is not on the path for it and no rebuild is required for a mutation to reach the test. (check-test-source-alias's registry agrees: @objectstack/client's unaliased dist imports are six package specifiers, and @objectstack/plugin-auth is not among them.) The dependency closure was built first regardless — pnpm --filter '@objectstack/client^...' build, BUILD_EXIT=0 — so nothing measured here ran against a stale dist. Mutation confirmed on disk by anchored grep in both directions plus an untouched-sibling control; restore verified byte-identical with git hash-object; the whole script ran under trap restore EXIT INT TERM.

### LEG 0 — baseline (unmutated), new guard
LEG0_EXIT=0 Test Files 1 passed (1) Tests 2 passed (2)
### MUTATE
removed text "client: 'auth.changePassword'" count = 0 (expect 0)
injected text "client: 'auth.changePasword'" count = 1 (expect 1)
control: untouched sibling row still present = 1 (expect 1)
blob 123e1a87dced → 2390fa5efc84
### LEG 1 — the NEW guard under mutation (predicted: RED)
LEG1_EXIT=1
AssertionError: auth ledger entries claiming a client method that does not exist:
POST /api/v1/auth/change-password → client.auth.changePasword: expected [ Array(1) ] to deeply equal []
Test Files 1 failed (1) Tests 1 failed | 1 passed (2)
### LEG 2 — the OLD server-side check, SAME mutation (predicted: GREEN — this is the hole)
LEG2_EXIT=0 Test Files 1 passed (1) Tests 1 passed | 9 skipped (10)
### RESTORE
restored blob 123e1a87dced = original RESTORE=BYTE-IDENTICAL
### LEG 3 — restore leg, new guard green again
LEG3_EXIT=0 Test Files 1 passed (1) Tests 2 passed (2)

LEG 2 is the finding the card was filed on, measured rather than argued: the same typo that the new guard catches is waved through by the test whose name claims to check it.

The second leg in the new file is that ablation made permanent — a population floor plus a positive and a negative control derived from the ledger itself, so the guard cannot decay into one that passes while measuring nothing.

Serial, not folded — and a claim in #10974 that does not hold

#10974 is in flight on the same auth surface. Its PR #11360 changes exactly two files, packages/client/src/index.ts and a changeset — disjoint from everything here, so this proceeded independently.

Reported rather than fixed here, because it is another card's reasoning: #10974's ordering argument says a sdk row naming a nonexistent method is "the exact failure packages/client/src/route-ledger-coverage.test.ts was written for". That test reads ROUTE_LEDGER, the dispatcher's — not AUTH_ROUTE_LEDGER — so it would not have caught an auth row, and until this PR nothing would have. The conclusion of that argument survives and is in fact strengthened (with no guard at all, a premature auth row would have sat there silently), but the authority cited for it was wrong. Left for the PM to route.

Deliberately not added: a reverse-direction leg ("every client.auth.* method is backed by a ledger row"), which the service-ledger file carries for storage and i18n. Adding it here would go red the moment #11360 merges and stay red until #10975 lands — inverting the maintainer-ruled ordering of option C. That is a separate decision, not a rider on a guard-hole card.

Verification

Gate union derived with node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack, no hand-supplied paths, re-derived after the final commit. All at 2de8c3ef71.

Green, each read from the gate's own verdict line with the exit code captured before any pipe: check:cross-package-test-inputs · check:entry-guard · check:parse-guard · check:pnpm-filter-targets · check:published-files · check:slot-lookup · check:test-source-alias · check:type-source-resolution · check-ci-filter-parity · check-plugin-teardown-shape · docs-audit/check-affected-docs · and the convention-triggered check:query-options-erasure · check:engine-double-contract · check:where-matcher · check:type-check-coverage.

Suites: pnpm --filter @objectstack/client test24 files / 316 tests passed. pnpm --filter @objectstack/client typecheck → green, and check:test-typecheck reports "@objectstack/client's test layer compiles under packages/client/tsconfig.test.json; 0 file(s) / 0 error(s) held in test-typecheck-debt.json", so the new test file adds no debt. pnpm --filter @objectstack/plugin-auth typecheck → green.

A second reverse control, on the one declaration this PR adds: removing the new glob from CROSS_PACKAGE_TEST_INPUTS and its $TURBO_ROOT$ twin from turbo.json turns check:cross-package-test-inputs red with "@objectstack/client names path(s) no declared glob covers … packages/plugins/plugin-auth/src/auth-route-ledger.conformance.test.ts (named in packages/client/src/auth-route-ledger-coverage.test.ts)" — so the declaration is load-bearing, not decorative. Restored byte-identically.

Narrowed, and declared as narrowed:pnpm check:type-check-debt --re-measure needs the whole workspace built and was not run locally. It cannot move here: plugin-auth's tsconfig.json excludes **/*.test.ts, its ledger entry is the hidden test layer (109 errors), and this PR's only change to that package is comment lines inside an existing test body — no identifiers, no imports. pnpm lint was likewise narrowed to the changed files: eslint reports 3 of the 4 linted (it ignores turbo.json"File ignored because no matching configuration was supplied", identical on base), 0 errors, 0 warnings, counts read from --format json; and no untouched file's verdict can move, because eslint.config.mjs states in its own words that this repo "runs one eslint.config.mjs, which never enables type-aware linting (no parserOptions.project, no typed @typescript-eslint rules) for ANY file, test or not". CI runs both in full regardless.

No changeset

skip-changeset, and the label is on this PR. The diff is one new test file, comment lines in another test file, one entry in a gate's declaration table and its matching turbo.json input — nothing published, no user-visible behaviour, no public surface. Nothing here belongs in release notes.

Generated by Claude Code


Generated by Claude Code

@claudeclaudeBot added the skip-changeset PR has no user-facing published change; bypasses the changeset gate label Aug 23, 2026
@github-actions

Copy link
Copy Markdown
Contributor

📓 Docs Drift Check

Nothing in this diff resolved to a documentable surface (no symbol, route or SDK anchor derived from 0 changed package(s)), so this run has no opinion about the docs.

@os-sam
os-sam marked this pull request as ready for review August 23, 2026 16:04
@os-sam
os-sam added this pull request to the merge queueAug 23, 2026
Merged via the queue into main with commit bb00644Aug 23, 2026
34 checks passed
@os-sam
os-sam deleted the claude/issue-11359-auth-ledger-client-resolution branch August 23, 2026 16:18
Sign up for freeto join this conversation on GitHub. Already have an account? Sign in to comment

Labels

size/mskip-changesetPR has no user-facing published change; bypasses the changeset gatetests

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AUTH_ROUTE_LEDGER's client names are never resolved against a real client — the #3528 guard has an auth-shaped hole

2 participants

@os-sam@claude