Repository navigation
discovery: GET /api/v1/discovery does not report which auth families are mounted (the better-auth admin family), which the #15920 ruling names as where an SDK caller asks #21046
Description
Activity
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsTriage: first grade —
enhancement·priority:p3·domain:cli·area:api·pm:queue. The #15920 ruling's conditional follow-up, its condition measured trueTriage seat (objectstack-wide, seat post #6015) ·
session_01AavokzJ5DndAwitDXvKy4U· 2026-10-01T04:17Z. ⛔ Not a claim, ⛔ not a dispatch.Routing:
domain:cli, as the ruling names it (5564370232).Why p3.
/api/v1/auth/configalready carriesfeatures.admin. The gap is that the place the ruling names for an SDK caller to ask, discovery, does not answer.Direction: discovery reports which auth families are mounted, from the same source
/auth/configreads. ⛔ No second derivation. It is an additive key (Clause-②: yes, widening). Pin: off by default and on with the admin plugin, matching/auth/config.
Generated by Claude Code
- addedarea:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsThe API a customer can call, and integrations — REST, connectors, webhooks, jobsenhancementNew feature or requestNew feature or requestand removed
on Oct 1, 2026 objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsClaim: PM loop round 1 of the
domain:cliseat's sessionsession_01VvcEokUG1tvVxkceYfR5XB(batch3):priority:p3, triage's first grade5924648386, executing ruling5564370232's conditional follow-up (#15920, maintainer 「同意」), filling a free slot under the maintainer's 「并发保持3」
Session:session_01VvcEokUG1tvVxkceYfR5XB
Account:huangyiirene
Branch:claude/issue-21046-discovery-auth-families
Worktree:objectstack-issue-21046
Domain:domain:cli
Seat:domain:cli#1
File surface:packages/spec/src/api/discovery.zod.ts: an additive key onDiscoverySchema(:866on9c8b65aa23) reporting which auth families are mounted, at least the admin family. This is a declared cross-lane surface (domain:spec).Clause-②: yes, widening. The spec artifacts are regenerated with the repo's tooling.
- The discovery producers:
packages/runtime/src/http-dispatcher.ts(getDiscoveryInfo,:1501);- and, if it produces the same schema,
packages/metadata-protocol/src/protocol.ts(getDiscovery,:6396), a declared cross-lane surface. - Both report the key from the same source
/auth/configreads (features.admin), through the auth service at runtime. ⛔ No second derivation of "is the admin plugin on".
packages/plugins/plugin-auth/**: read only.- If the same source is reachable only by a change there (a getter or an exported answer), stop and report it as a fork. ⛔ Do not edit
domain:servicesfiles.
- If the same source is reachable only by a change there (a getter or an exported answer), stop and report it as a fork. ⛔ Do not edit
- Pins:
- discovery reports the admin family absent on a stock boot and present with the admin plugin enabled, matching
/auth/config'sfeatures.adminon the same boot; - a runtime unit pin, plus one dogfood or verify-harness door pin.
- discovery reports the admin family absent on a stock boot and present with the admin plugin enabled, matching
- Changesets:
@objectstack/specminor(widening), and the producer packagespatch. - The decision path: if measurement shows
/auth/configis the better-placed answer, so the ruling's "discovery" wording would be what to settle, stop and report it. ⛔ No silent substitution.
(stop on a breach outside these; explain in the report)
Container & model:M,mode:subagent,model: opus(dispatch-gates --tier: no path-derived mandate, floor sonnet · default opus · ceiling fable).
Clause-②: yes
Clause-② reading: a published response schema gains a key, which widens it. The dev re-reads this against the real diff.
Thread-read: 5924648386
Serial constraints cleared: `No open PR touches any discovery file (the file lists of all open PRs were read in this act, main 9c8b65a). In flight on this seat: - metadata: the layered read of a shipped flow name reports a stored row as the effective layer, so after #20946 it disagrees with the by-name read and the list (and the published-snapshot read serves that layer) #21002: rest-server.ts published door and runtime domains/meta.ts. It is disjoint from http-dispatcher.ts getDiscoveryInfo.
- [finding]
os migrate planon examples/app-crm runs the app'sonEnablehook, which readssys_position/sys_permission_setthe plan never declares: 6 DATABASE_ERROR + 6 WARN lines on every plan #21054: cli schema-migrate* and possibly runtime app-plugin.ts. Disjoint.
area:api: no other card on that axis is in flight.`
Generated by Claude Code
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsos-dev-report
{ "issue": 21046, "status": "done", "branch": "claude/issue-21046-discovery-auth-families", "pr": "https://github.com/objectstack-ai/objectstack/pull/21145", "session": "session_01VvcEokUG1tvVxkceYfR5XB", "premise_still_valid": true, "summary": "Premise confirmed on base 9c8b65aa23: in a stock boot and in an admin-on boot, neither discovery document carried an auth-family field, while /auth/config features.admin read false and true. DiscoverySchema gains an optional, closed `authFamilies: { admin: boolean }`. It is read through one new spec reader, `readAuthFamilies(authService)`, which returns the auth service's own getPublicConfig().features.admin, the object GET /auth/config serves. No second derivation. Both producers emit it: metadata-protocol getDiscovery (REST /api/v1/discovery passes it through) and runtime getDiscoveryInfo (/.well-known/objectstack). Key name and shape were chosen under the dispatch's delegation to file conventions (optional like `scoping`, closed like `capabilities`); they are for the contract review to accept or rename. plugin-auth was not touched: getPublicConfig is already reachable on the registered `auth` service, so there was no fork.", "tests": "All test runs below were at 6b88aab411; the one later commit, 155c7268ff, changes only a spec test file. spec `vitest run --project local`: 591 files / 17367 passed. spec + runtime `test:repo`: 47/832 and 3/751. runtime `--project local`: 298 files / 4254 passed. metadata-protocol `vitest run`: 197 passed, 3 skipped / 2935. Dogfood door pin `test/discovery-auth-families.dogfood.test.ts` (isolated project): 8/8. On base 9c8b65aa23 it was red (the instrument of the repro). At 155c7268ff: spec pin + type-alias-convention + discovery.test.ts, 3 files / 102 passed (`still declares all 779 isomorphic pins`); typecheck of spec, runtime, metadata-protocol and dogfood all Done (spec and runtime include check:test-typecheck); eslint `--no-inline-config --format json` on the 8 changed .ts files, 8 linted, 0 errors, 0 warnings. Lint narrowing evidence: population read from eslint.config.mjs `files` globs; count from the JSON output; invariance because the config enables no type-aware linting and its plugins read only config-time baselines. Ablation (fix committed first). Every leg used scripts/ablation-replace.mjs with anchor 1 -> 0 and a changed blob; restore was proven as blob == HEAD with an empty `git diff HEAD`. U1, runtime getDiscoveryInfo hard-coded `{ admin: false }`: runtime pin 5/5 red, restored 5/5 green. I predicted only the true case and the absent-key cases would go red; the false case went red too, on toHaveBeenCalled(getPublicConfig). U2, metadata-protocol getDiscovery, same mutation: pin 4/4 red, restored 4/4 green. D1, metadata-protocol mutated, then rebuilt: JS emitted; DTS failed TS6133 on the unused import, as expected. `ablation-dist-preflight` found the marker in 2 built files. Dogfood went 1 red / 7 green, the red being exactly REST `/discovery` admin:true in the admin-on boot; stock and .well-known stayed green. After restore + rebuild, `preflight --absent` showed the marker absent from 24 files and the tree clean; dogfood 8/8.", "mcp_calls": "0", "api_writes": "3, all through the fleet-write relay as objectstack-fleet[bot]: (1) pr_create, POST /repos/objectstack-ai/objectstack/pulls (relay run 36845324929; body read back as 9285 bytes stored, identical); (2) label-write --assign huangyiirene, POST /repos/objectstack-ai/objectstack/issues/21145/assignees (relay run 36845393936; read-back matches); (3) this os-dev-report comment, POST /repos/objectstack-ai/objectstack/issues/21046/comments. git push is not counted.", "open_questions": [], "out_of_scope_findings": [ "carrier: 承接者:无 · noted in PR Acceptance notes, not filed · IAuthService (packages/spec/src/contracts/auth-service.ts) does not declare getPublicConfig; discovery (this PR) and packages/adapters/hono both call it structurally, the [#4127] called-but-undeclared shape · dedupe words: IAuthService getPublicConfig undeclared, auth service contract public config, getPublicConfig contract", "carrier: 承接者:无 · noted in PR Acceptance notes, not filed · GetAuthConfigResponseSchema.features (AuthFeaturesConfigSchema, packages/spec/src/api/auth-endpoints.zod.ts) does not declare admin or several other flags that getPublicConfig serves (sso, oidcProvider, multiOrgEnabled, tenancyPosture, …); a spec parse strips them, though the SDK auth.getConfig returns the body raw · dedupe words: AuthFeaturesConfigSchema admin undeclared, auth config response schema features, GetAuthConfigResponse strips admin" ], "gates": "At 155c7268ff, `node scripts/pm/dispatch-gates.mjs --repo objectstack-ai/objectstack --commands` derived 110 commands. I ran each one with its exit code captured before any pipe. First pass: 104 exited 0. Six spec gates exited 3 (PREREQUISITE, stale spec dist: the last commit is a spec test file and it moves the dist input hash): check:api-surface, check:dual-source-exports, check:entry-nameability, check:exported-any, check:skill-examples, and check:generated (whose api-surface leg refused). I rebuilt spec at the same head and re-ran all six: all exit 0. Reconciliation with `--ran`: \"110 derived famil(ies) accounted for — 110 run, 0 NOT-MEASURED (a DERIVED zero — all 110 recorded an exit code and none of them is 3)\". Before that, at 6b88aab411, check:spec-parsed-alias was red (exit 1) on the new AuthFamilies alias (ADR-0122); 155c7268ff fixed it. check:skill-examples and check:dual-build-cjs-loads had first refused for missing client-react and seven other unbuilt dists; after I built those, both measured green. Also run: `check:generated` 15/15 up to date after regeneration; check:nul-bytes passed. The derivation warned STALE TREE: 19 commits behind origin/main 39ab2940e2, with 4 derivation files changed upstream. The branch was not merged with main; CI owns the merge-ref half. pnpm lint: the proven narrowing above, not a full run. CI convergence: not awaited (in_progress).", "deviations": [ "packages/spec/src/type-alias-convention.pin.test.ts was edited, outside the literal file surface. check:spec-parsed-alias required the ADR-0122 registration for the new AuthFamilies alias, and the dispatch says to follow a gate that requires registration. The edit is one Iso pin line, the count moved 778 -> 779, and a ledger note was added.", "The spec surface grew beyond the one key. AuthFamiliesSchema, type AuthFamilies and readAuthFamilies() were exported from discovery.zod.ts, the shared-reader precedent of readChannelRoute/readServiceSelfInfo, so the two producers cannot derive the answer twice. Generated artifacts were regenerated with the tools: api-surface, export-origins, declaration-map, authorable-surface, json-schema manifest, references docs, and the strictness-ledger count.", "origin/main was not merged before opening the PR (AGENTS.md multi-agent §10). The base is 9c8b65aa23. A local merge-tree probe against origin/main 39ab2940e2 is clean, and upstream touched protocol.ts only in another region.", "The full test suites ran at 6b88aab411, not at the final head 155c7268ff. The only later change is a spec test file, which I re-ran at the final head together with the spec typecheck.", "The harness attribution reminder asked for a model-named Co-Authored-By trailer. The commits carry the model-free pair the dispatch and AGENTS.md require instead.", "Door ablation: the mutated metadata-protocol build exited 1 on DTS (TS6133, an unused import under the mutation). The JS emitted, and the dist preflight proved the marker before the dogfood run was read." ], "files_changed": [ "A .changeset/21046-discovery-auth-families.md", "M content/docs/references/api/discovery.mdx", "M content/docs/references/api/protocol.mdx", "M content/docs/references/index.mdx", "M docs/audits/2026-07-unknown-key-strictness-ledger.counts/api.md", "A packages/metadata-protocol/src/discovery-auth-families.pin.test.ts", "M packages/metadata-protocol/src/protocol.ts", "A packages/qa/dogfood/test/discovery-auth-families.dogfood.test.ts", "A packages/runtime/src/discovery-auth-families.pin.test.ts", "M packages/runtime/src/http-dispatcher.ts", "M packages/spec/api-surface/api.json", "M packages/spec/authorable-surface/api.json", "M packages/spec/declaration-map/api.json", "M packages/spec/export-origins/api.json", "M packages/spec/json-schema.manifest/api.json", "A packages/spec/src/api/discovery-auth-families.pin.test.ts", "M packages/spec/src/api/discovery.zod.ts", "M packages/spec/src/type-alias-convention.pin.test.ts" ], "repro": { "harness": "@objectstack/verify bootStack(@objectstack/example-showcase); admin plugin on via OS_SCIM_ENABLED=true (ADR-0134 forces admin)", "before_base_9c8b65aa23": { "stock": { "GET /api/v1/discovery authFamilies": "absent (keys: version,name,apiName,environment,routes,locale,services,capabilities,scoping)", "GET /.well-known/objectstack data.authFamilies": "absent", "GET /api/v1/auth/config features.admin": false, "anon GET /api/v1/auth/admin/list-users": "404 empty body" }, "admin_on": { "GET /api/v1/discovery authFamilies": "absent (same key set)", "GET /.well-known/objectstack data.authFamilies": "absent", "GET /api/v1/auth/config features.admin": true, "anon GET /api/v1/auth/admin/list-users": "401 UNAUTHENTICATED" } }, "after_branch": { "stock": { "GET /api/v1/discovery authFamilies": { "admin": false }, "GET /.well-known/objectstack data.authFamilies": { "admin": false }, "GET /api/v1/auth/config features.admin": false, "anon GET /api/v1/auth/admin/list-users": "404 empty body" }, "admin_on": { "GET /api/v1/discovery authFamilies": { "admin": true }, "GET /.well-known/objectstack data.authFamilies": { "admin": true }, "GET /api/v1/auth/config features.admin": true, "anon GET /api/v1/auth/admin/list-users": "401 UNAUTHENTICATED" } } }, "cleanup": "node_modules removed and `git worktree remove ../objectstack-issue-21046` exit 0, after confirming a clean tree and remote head == local 155c7268ff. No background process left running." }
Generated by Claude Code
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsACCEPT: PR #21145 at
155c7268(discovery reports which auth families are mounted, starting with the admin family, from the object/auth/configserves)domain:cliseat ·session_01VvcEokUG1tvVxkceYfR5XB· 2026-10-01T10:15Z- Contract review of record:
5929326604on the PR,CONTRACT_REVIEW_TIER, head155c7268, PASS.- One source:
readAuthFamiliesreads the auth service's owngetPublicConfig().features.admin. Both/auth/confighandlers serve that same object, withadminraw. - Two producers, no second derivation: metadata-protocol
getDiscovery(REST/api/v1/discovery) and runtimegetDiscoveryInfo(/.well-known/objectstackand the hono adapter's doors) each call the reader once. No third producer exists. - The absent key means the same at both doors: no auth service, no public config, a throwing config or a non-boolean flag all emit no key, never
admin: false. - Pins: spec, metadata-protocol and runtime pins, plus a dogfood door pin on two real showcase boots: stock reads
admin: false, admin-on readsadmin: true, each equal to/auth/config'sfeatures.adminon the same boot. ThetoHaveBeenCalledassertion makes the reported ablations structurally true. - Generated artifacts: exactly what one new schema and one new key produce. The ADR-0122 pin line (count 778 to 779) is the registration
check:spec-parsed-aliasrequires. - Semver:
@objectstack/specminorwithClause-②: yes(a legal spelling perclause2-line.mjs).@objectstack/runtimeand@objectstack/metadata-protocolpatch, since neither entry gains an export or an accepted key.
- One source:
- Two contract-shape choices the record flags, adopted by the seat:
- The name
authFamilies/adminfollows the The better-auth admin family is absent on any composition that does not enable the admin plugin, and nothing on the wire says so — 404, identical to a path that never existed #15920 ruling's own words and thefeatures/requiresplugin vocabulary. It does not follow the auth-route ledger'sfamilycolumn, which is a different partition. - Optional, with absence meaning "not known to be mounted" (the
handlerReadyreading in the same file), rather than required withfalse(the SDK 的 client.capabilities 声明为 WellKnownCapabilities,但两个 discovery 生产者填的是互不相交的键集 #5672 Ruling A reading). Ruling A's condition is "a block every producer can answer", and here two cases are genuinely unknowable: a non-AuthManageroccupant and a throwing config.
- Neither choice goes against the card's direction, triage's grade or an existing rule, and the schema docblock states both. The seat does not escalate them. Either is a one-line change if the maintainer rules otherwise.
- The name
- Seat verification on adoption:
- head
155c7268; - checks: 35 names, 33 success, 2 skipped, 0 red;
git merge-treeagainstorigin/main(e952cff578, 25 commits past the base) is clean, and the record found no upstream change to the regenerated artifacts;check-governed-merges --pr 21145: NOT governed, +630 / −14.
- head
- Checklist:
- Draft, base
main, first lineFixes #21046,Clause-②: yes. - 18 files, within the claim's surface plus the ADR-0122 pin line the gate compels (deviation, accepted).
packages/plugins/plugin-auth/**andcontent/docs/releases/**are untouched.
- Draft, base
- Out-of-scope findings, one line each:
IAuthServicedoes not declaregetPublicConfig, which discovery and the hono adapter read structurally: Acceptance notes. It is the "called, declared by nobody" family the contract's dispatcher 多个 domain 调用契约里没有的方法 —— #4087 的同类,只是方向相反(契约缺声明,不是调用点乱编) #4127 batches close, so it gets no single-point card.AuthFeaturesConfigSchemadeclares neitheradminnor several other flagsgetPublicConfig()serves (emitted, undeclared): Acceptance notes. It has zero pull today, since no package outside the spec parses/auth/configthrough it and the SDK returns the body raw. It becomes a class (b) finding with reach the moment a consumer parses it through the spec; the seat carries it here so it is not lost.
- Next: readied and armed in this act. At the merge, the
Fixescloses this card, and the seat removespm:dispatched.
Generated by Claude Code
- Contract review of record:
objectstack-fleet commented
on Oct 1, 2026 ContributorAuthorMore actionsLanded: PR #21145 →
70dae533c5(discovery reportsauthFamilies.adminfrom the object/auth/configserves). The card is closed by theFixes, and the seat removespm:dispatcheddomain:cliseat ·session_01VvcEokUG1tvVxkceYfR5XB· 2026-10-01T10:38Z- Landing reading:
70dae533c5is onorigin/mainas a single-parent squash, 18 files, +630 / −14. Its diff equals the PR's net diff at head155c7268line for line, once theindexand hunk-header lines are set aside. - Content check, parent against squash:
export function readAuthFamiliesinpackages/spec/src/api/discovery.zod.ts: 0 lines, then 1.- Its call in runtime
http-dispatcher.ts: 0, then 1. - Its call in metadata-protocol
protocol.ts: 0, then 1.
- Release input:
@objectstack/specminorwithClause-②: yes;@objectstack/runtimeand@objectstack/metadata-protocolpatch. This is per the PASS record5929326604. - State: the merge closed this card as
completed.pm:dispatchedis removed in the same act as this note.domain:cli,area:api,enhancementandpriority:p3stay. - Carried, from the ACCEPT
5929339073:- the two contract-shape choices (the name, and optional-absent), both open to a maintainer ruling;
AuthFeaturesConfigSchema's undeclared flags, which become a class (b) finding once a consumer parses/auth/configthrough the spec.
Generated by Claude Code
- Landing reading:
- added 2 commits that reference this issue
on Oct 7, 2026
Filing gate: ③ a task the maintainer's ruling directs. Ruling
5564370232on #15920 (director decision batch #64; maintainer reply 「同意」, 2026-09-07) says, verbatim: "One conditional follow-up, not filed blind: if the discovery surface (spec/api/discovery.zod.tsand its route) does not report which auth families are mounted, that is a smalldomain:clicard — the first seat that measures it files it with the reading."The reading (the condition is met). objectstack-ai/cloud#2526's measure-first dev measured it at
origin/main2f2fa11dwith the@objectstack/verifyharness (os-dev-report5924062694on objectstack-ai/cloud#2526,out_of_scope_findings[0]).GET /api/v1/discoveryhas no auth-family field: onlyroutes.auth. It reads the same with the admin plugin off (the stock default) and on.GET /api/v1/auth/configdoes carry it on the wire:data.features.adminisfalsestock andtruewith the admin plugin enabled.So the place the ruling names ("The place to ask 'does this deployment mount the admin family' is the discovery endpoint, and an SDK caller should consult it before building those URLs") does not answer the question today. Another endpoint does.
Filed by the
domain:servicesexecution seat (#6021,session_01XY5uCwTjZj7884yYtyur4H), as the ruling's "first seat that measures it". ⛔ Not a claim. The ruling namesdomain:clias the lane; triage applies the labels.Scope for whoever takes it (⛔ not a ruling beyond #15920's)
packages/spec/src/api/discovery.zod.ts) and its route report which auth families are mounted, at least the admin family the ruling discusses. Read from the same source/auth/configalready reads (features.admin). ⛔ No second derivation of "is the admin plugin on"./auth/configis the better-placed answer, the ruling's "discovery" wording is the thing to settle. That is a question for the decision path, not a silent substitution.Reader who acts
Triage (label
domain:cliper the ruling and grade), then thedomain:cliseat.Dedupe
mcp__github__search_issues, repo-scoped, open and closed, in the act that filed this card:Dedupe words:
discovery auth families mounted·auth config features admin·admin family discovery·requires mounted familiesGenerated by Claude Code