Repository navigation
Description
repo:mobile#1 · os-justin · session_01QSeKYnW1Xf6HDSWz1eGAqv
Filing category: ① product defect with a named producer.
What is wrong
GET /api/v1/auth/config serves features.sso, and two clients read it. The spec's response contract does not declare the key. A client written from the contract cannot see the SSO sign-in capability, and a zod parse through GetAuthConfigResponseSchema (a plain z.object) strips it silently.
Readings, objectstack main at 27715752:
- Producer:
packages/plugins/plugin-auth/src/auth-manager.ts:4594setssso: this.isSsoWired().packages/plugins/plugin-auth/src/auth-plugin.ts:3109setsconfig.features.sso = await this.authManager!.isSsoUsable().
- Classified as a served login feature:
packages/spec/src/kernel/public-auth-features.ts:188,sso: { surface: 'login', … }. - Contract:
packages/spec/src/api/auth-endpoints.zod.ts,AuthFeaturesConfigSchema, declarestwoFactor,organization,ssoEnforced,phoneNumber,phoneNumberOtp,cloudManagedandturnstileSiteKey. It has nosso. - Consumers:
ui/packages/app-shell/src/runtime-config.ts:475readsbody.features.sso === true.- feat(server-picker): probe ObjectOS discovery and confirm the server before connecting mobile#165 (in review) reads
data.features.ssofor the server-confirm screen's sign-in methods row.
reach: named real producer (above). Precedent with the same shape: #25292 declared features.cloudManaged after it was served but undeclared.
Business reason
- Who benefits: every client that renders a login entry from this payload. That is the Console and the mobile shell today.
- Scenario: a deployment with SSO configured. A client built from the contract does not know the SSO button exists. A future client that parses through the schema drops the flag and hides SSO sign-in.
- If not done: the published contract under-states a live, consumed capability. That is the declared-vs-enforced drift the contract-first rule exists to stop.
Who acts
The domain:spec seat. It adds sso: z.boolean().optional().describe(…) beside ssoEnforced, describing it as "usable (≥1 provider configured)", the refinement isSsoUsable() applies. The narrowing or widening class (Clause-②) is the spec seat's call.
Duplicate check
Three searches covered open and closed cards in objectstack-ai/objectstack, run on 2026-11-02:
| Query | Hits | Exact duplicates |
|---|---|---|
is:issue AuthFeaturesConfigSchema sso |
5 | 0 (#25292 is the cloudManaged precedent) |
is:issue "features.sso" |
12 | 0 |
is:issue "auth/config" sso schema |
66 | 0 |
Found by the os-dev review of objectstack-ai/mobile#154.
Activity
objectstack-fleet commentedon Nov 2, 2026
triage#1 · os-zhuang · session_01AavokzJ5DndAwitDXvKy4U
Path: none
Triage: p1 ·
domain:spec·pm:queue· 2026-11-02T12:58Z⛔ Not a claim.
GET /auth/configservesfeatures.ssoand two clients read it, butAuthFeaturesConfigSchemadoes not declare it, so a contract parse strips it. Declaring it is a protocol addition, and protocol additions are p1 by standing ruling. The name is already fixed by the producer and its readers; the maintainer's nod on the key is recorded before merge.Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut
Claim: PM loop round 2 · 2026-11-02T14:50Z
Session:
session_01ADHkmSduNGC2SZXszyrNut(seat); dev = card threadsession_01ADHkmPzZFPzq2roY9uo1meAccount:
os-steveBranch:
claude/project-thread-ntfxynWorktree: the card thread's own checkout of
objectstack-ai/objectstackDomain:
domain:specSeat:
domain:spec#2File surface:
packages/spec/src/api/auth-endpoints.zod.ts(AuthFeaturesConfigSchemagainssso) and its test;packages/spec/src/kernel/public-auth-features.tsonly if a pin there ties the classification to the schema keys; the liveness row for the key if the auth config response is ledgered; regenerated references; root.changeset/(stop on breach; explain in the report)Container & model:
S,mode:thread,model: default — the tier is the PM's per-card call (floor sonnet · default opus · ceiling fable); a widening of a served response schema, one CONTRACT_REVIEW_TIER review by the seat before the queue,effort: highClause-②: yes (widening)
Metadata-protocol: added
Responsibility:
packages/spec auth-endpoints.zod.ts does not declare features.sso, which plugin-auth serves and the Console and the mobile shell read | none: a contract parse strips it | every client that renders a login entry from GET /api/v1/auth/configThread-read: 6577724749
Premise: holds (on origin/main
96fb7b5d,AuthFeaturesConfigSchemadeclaresssoEnforcedand nosso); the dev measures first and stops withpremise_still_valid: falseif it does not reproduceSerial constraints cleared: no in-flight claim names
auth-endpoints.zod.tsorpublic-auth-features.tsThe key name
ssois the producer's and its readers'; as a new key on a published contract it goes to the maintainer asneeds-user-decisionon the PR (Steve's rule, 2026-11-02 14:39Z).objectstack-fleet commentedon Nov 2, 2026
domain:spec#2 · os-steve · session_01ADHkmPzZFPzq2roY9uo1me
os-dev-report
{ "issue": 29344, "status": "done", "branch": "claude/project-thread-ntfxyn", "pr": "https://github.com/objectstack-ai/objectstack/pull/29455", "session": "session_01ADHkmPzZFPzq2roY9uo1me", "premise_still_valid": true, "summary": "AuthFeaturesConfigSchema gains optional boolean `sso` beside ssoEnforced, no default; the describe says it is the usable answer (isSsoUsable: plugin wired AND >=1 provider), names that both GET /auth/config doors (plugin-auth /config route, hono adapter) refine the coarse wired flag before answering, and contrasts ssoEnforced. Regenerated authorable-surface/api.json and the auth-endpoints reference; spec minor changeset. Premise holds (on 96fb7b5d the schema has no sso). One premise detail disproved: ui/packages/app-shell/src/runtime-config.ts reads GET /api/v1/runtime/config (cloud-connection plan flag), not /auth/config; the real ui reader is ui/packages/auth/src/LoginForm.tsx. No producer disagreement a client can see (both HTTP doors refine; the unrefined value is read only server-internally), so no domain:server finding.", "tests": "Head 0d814466. spec test VERDICT 0: 791/791 files, 23474 passed, 1 todo. spec typecheck VERDICT 0 (131 pinned held). client typecheck VERDICT 0 + reverse probe ({sso:true} as AuthFeaturesConfig compiles exit 0; {sso:'yes'} TS2322 exit 2; probe deleted). plugin-auth full suite VERDICT 0: 211/211 files, 3643 passed, 10 skipped. hono adapter 3 auth-config files: 33 passed (lock VERDICT batch-last-exit 0, the vitest was the last part). ui from ui/: LoginForm.test.tsx + app-shell runtime-config.test.ts 2/2 files, 85 passed. New block in auth-endpoints.test.ts: 8 cases (keep true/false, full response parse keeps it, CONTROL absent stays absent, non-boolean yes/1/null refused with one invalid_type at sso, describe names isSsoUsable+ssoEnforced, no tracker id). Ablation: sso renamed ssoAblated via ablation-replace (anchor 1->0, blob fb278750->8c4aae77): 7 failed of 31, CONTROL green; restored blob = HEAD fb278750, git diff HEAD empty. Scoped eslint on the 2 changed .ts files exit 0.", "gates": "dispatch-gates --commands at 0d814466: 103 derived, 103 exit 0 (stdin closed, exit before pipe); check-plugin-teardown-shape --self-test first exit 3 (fixture commit outside shallow clone), 0 after git fetch --depth=1 origin 621a4876. --ran: 103 derived, 103 run, 0 NOT-MEASURED (derived zero), 0 UNRUN. Prior: turbo build packages/** VERDICT 0 (78 tasks); spec build + check:generated exit 1 naming check:docs only, gen:docs VERDICT 0. Offline body checks: check-changeset-no-major --event exit 0 (yes (widening) + added, spec minor); check-partof-closing-keyword exit 0. Local-only: check:skill-frame-freshness 0; check:dev-prereqs 1 = 35/106 packages (ui/ tree) unbuilt here, machine state, spec dist fresh. CI check-runs: in_progress at report time, the seat reads them.", "dogfood_battery": "n/a — no `!` in the PR title and no (narrowing) arm", "mcp_calls": "7 — all reads: issue_read x4 (get #29344, get_comments #29344 x2, get #25292), pull_request_read x3 (get_files #25309 precedent, get #29401 body shape, get_files #29455 read-back)", "api_writes": "2 — relay pr_create (#29455, run 41474194165, 10681 bytes sent = stored, no labels, no assignees) and this os-dev-report comment via post-stamped; plus git push (not REST)", "line_budget": "+73 / -1 over 5 files: zod +9, test +44, changeset +16, reference mdx +3/-1, authorable-surface +1", "files_changed": [".changeset/29344-auth-config-features-sso.md", "content/docs/references/api/auth-endpoints.mdx", "packages/spec/authorable-surface/api.json", "packages/spec/src/api/auth-endpoints.test.ts", "packages/spec/src/api/auth-endpoints.zod.ts"], "deviations": [ "Consumer test set differs from the delta: ran ui/packages/auth LoginForm.test.tsx (the real /auth/config reader) beside the delta's app-shell runtime-config.test.ts, which reads a different response (/api/v1/runtime/config); also ran the hono adapter's 3 /auth/config door files, the second producer.", "public-auth-features.ts not touched: its sso entry (login, opt-in, exempt: refined to usable via isSsoUsable) agrees, and no pin ties its keys to AuthFeaturesConfigSchema's keys.", "No liveness row: packages/spec/liveness governs metadata types, no ledger names this response (same as the cloudManaged precedent).", "PR body starts with the harness attribution block, then Fixes #29344 (os-dev exception for the harness first-line comment).", "PR assignee/reviewer not set: the standard order forbids MCP writes and os-dev forbids assignees at pr_create." ], "open_questions": [ { "question": "Maintainer nod on the new public key name `sso` (fixed by the producer and its readers).", "options": ["A keep `sso` as served", "B rename (would need a producer + reader change and a migration)"], "recommendation": "A, because the producer, the console LoginForm and the mobile shell already use it; the seat puts it as needs-user-decision on the PR." }, { "question": "Close the whole drift class with a guard?", "options": ["A family card: declare the 9 other served-undeclared feature keys and add a pin that PUBLIC_AUTH_FEATURES + PUBLIC_AUTH_CONFIG_NON_FLAG_KEYS equal AuthFeaturesConfigSchema's live keys", "B per-key cards as readers appear"], "recommendation": "A, one guard ends the class (os-dev: prefer a guard over single-point fixes)." } ], "security_findings": "none", "out_of_scope_findings": [ "class: b · reach: named real producer packages/plugins/plugin-auth/src/auth-manager.ts getPublicConfig() features, served on GET /api/v1/auth/config · evidence: it serves multiOrgEnabled, tenancyPosture, audiencePosture, degradedTenancy, oidcProvider, deviceAuthorization, admin, termsUrl, privacyUrl, none declared in AuthFeaturesConfigSchema (on 96fb7b5d it declares twoFactor, organization, ssoEnforced, phoneNumber, phoneNumberOtp, cloudManaged, turnstileSiteKey + 2 retired), so a contract parse strips them; no pin ties PUBLIC_AUTH_FEATURES to the schema keys · Seam: spec:AuthFeaturesConfigSchema → runtime:auth-manager.ts getPublicConfig | renderer:ui/packages/auth types.ts hand mirror · family card for the seat to file · dedupe words: AuthFeaturesConfigSchema undeclared served keys, features.deviceAuthorization schema, auth config contract drift guard", "carrier: none · noted, not filed: ui/packages/auth/src/types.ts hand-typed features mirror documents sso as 'wired' while the served value is 'usable'" ] }Stamped 2026-11-02T15:43Z.
Generated by Claude Code
objectstack-fleet commentedon Nov 2, 2026
domain:spec#2 · os-steve · session_01ADHkmSduNGC2SZXszyrNut
Landed: PR #29455, merged as
aee9c471at 2026-11-02T18:20Z.ACCEPT → PR #29455 comment 6582030426 · contract review PASS 6581701011 · name kept by the seat 6583305638.
Follow-up filed: #29473 (nine undeclared auth feature keys and the registry pin).