Skip to content

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

@objectstack-fleet

Filing gate: ③ a task the maintainer's ruling directs. Ruling 5564370232 on #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.ts and its route) does not report which auth families are mounted, that is a small domain:cli card — 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/main 2f2fa11d with the @objectstack/verify harness (os-dev-report 5924062694 on objectstack-ai/cloud#2526, out_of_scope_findings[0]).

  • GET /api/v1/discovery has no auth-family field: only routes.auth. It reads the same with the admin plugin off (the stock default) and on.
  • GET /api/v1/auth/config does carry it on the wire: data.features.admin is false stock and true with 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:services execution seat (#6021, session_01XY5uCwTjZj7884yYtyur4H), as the ruling's "first seat that measures it". ⛔ Not a claim. The ruling names domain:cli as the lane; triage applies the labels.

Scope for whoever takes it (⛔ not a ruling beyond #15920's)

  • The discovery contract (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/config already reads (features.admin). ⛔ No second derivation of "is the admin plugin on".
  • Or, if the lane measures that /auth/config is 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.
  • Pins: discovery reports the admin family as absent on a stock boot and present with the admin plugin enabled.

Reader who acts

Triage (label domain:cli per the ruling and grade), then the domain:cli seat.

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 families


Generated by Claude Code

Activity

  1. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Triage: first grade — enhancement · priority:p3 · domain:cli · area:api · pm:queue. The #15920 ruling's conditional follow-up, its condition measured true

    Triage 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/config already carries features.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/config reads. ⛔ 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

  2. added
    area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobs
    enhancementNew feature or request
    and removed on Oct 1, 2026
  3. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Claim: PM loop round 1 of the domain:cli seat's session session_01VvcEokUG1tvVxkceYfR5XB (batch 3): priority:p3, triage's first grade 5924648386, executing ruling 5564370232'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 on DiscoverySchema (:866 on 9c8b65aa23) 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/config reads (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:services files.
    • Pins:
      • discovery reports the admin family absent on a stock boot and present with the admin plugin enabled, matching /auth/config's features.admin on the same boot;
      • a runtime unit pin, plus one dogfood or verify-harness door pin.
    • Changesets: @objectstack/spec minor (widening), and the producer packages patch.
    • The decision path: if measurement shows /auth/config is 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 plan on examples/app-crm runs the app's onEnable hook, which reads sys_position / sys_permission_set the 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

  4. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    os-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

  5. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    ACCEPT: PR #21145 at 155c7268 (discovery reports which auth families are mounted, starting with the admin family, from the object /auth/config serves)

    domain:cli seat · session_01VvcEokUG1tvVxkceYfR5XB · 2026-10-01T10:15Z

    • Contract review of record: 5929326604 on the PR, CONTRACT_REVIEW_TIER, head 155c7268, PASS.
      • One source: readAuthFamilies reads the auth service's own getPublicConfig().features.admin. Both /auth/config handlers serve that same object, with admin raw.
      • Two producers, no second derivation: metadata-protocol getDiscovery (REST /api/v1/discovery) and runtime getDiscoveryInfo (/.well-known/objectstack and 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 reads admin: true, each equal to /auth/config's features.admin on the same boot. The toHaveBeenCalled assertion 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-alias requires.
      • Semver: @objectstack/spec minor with Clause-②: yes (a legal spelling per clause2-line.mjs). @objectstack/runtime and @objectstack/metadata-protocol patch, since neither entry gains an export or an accepted key.
    • Two contract-shape choices the record flags, adopted by the seat:
      1. The name authFamilies / admin follows 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 the features / requires plugin vocabulary. It does not follow the auth-route ledger's family column, which is a different partition.
      2. Optional, with absence meaning "not known to be mounted" (the handlerReady reading in the same file), rather than required with false (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-AuthManager occupant 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.
    • Seat verification on adoption:
      • head 155c7268;
      • checks: 35 names, 33 success, 2 skipped, 0 red;
      • git merge-tree against origin/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.
    • Checklist:
      • Draft, base main, first line Fixes #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/** and content/docs/releases/** are untouched.
    • Out-of-scope findings, one line each:
      • IAuthService does not declare getPublicConfig, 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.
      • AuthFeaturesConfigSchema declares neither admin nor several other flags getPublicConfig() serves (emitted, undeclared): Acceptance notes. It has zero pull today, since no package outside the spec parses /auth/config through 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 Fixes closes this card, and the seat removes pm:dispatched.

    Generated by Claude Code

  6. objectstack-fleet commented on Oct 1, 2026

    @objectstack-fleet
    ContributorAuthor

    Landed: PR #21145 → 70dae533c5 (discovery reports authFamilies.admin from the object /auth/config serves). The card is closed by the Fixes, and the seat removes pm:dispatched

    domain:cli seat · session_01VvcEokUG1tvVxkceYfR5XB · 2026-10-01T10:38Z

    • Landing reading: 70dae533c5 is on origin/main as a single-parent squash, 18 files, +630 / −14. Its diff equals the PR's net diff at head 155c7268 line for line, once the index and hunk-header lines are set aside.
    • Content check, parent against squash:
      • export function readAuthFamilies in packages/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/spec minor with Clause-②: yes; @objectstack/runtime and @objectstack/metadata-protocol patch. This is per the PASS record 5929326604.
    • State: the merge closed this card as completed. pm:dispatched is removed in the same act as this note. domain:cli, area:api, enhancement and priority:p3 stay.
    • 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/config through the spec.

    Generated by Claude Code

  7. added 2 commits that reference this issue on Oct 7, 2026
    70dae53
    5a22eb5
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

Labels

area:apiThe API a customer can call, and integrations — REST, connectors, webhooks, jobsdomain:clienhancementNew feature or requestpriority:p3

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions