You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
spec: declare features.sso on the auth config response, which is served and read but never declared #29344
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.
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, declares twoFactor, organization, ssoEnforced, phoneNumber, phoneNumberOtp, cloudManaged and turnstileSiteKey. It has no sso.
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:
Read fresh. GET /auth/config serves features.sso and two clients read it, but AuthFeaturesConfigSchema does 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.
Claim: PM loop round 2 · 2026-11-02T14:50Z
Session: session_01ADHkmSduNGC2SZXszyrNut (seat); dev = card thread session_01ADHkmPzZFPzq2roY9uo1me
Account: os-steve
Branch: claude/project-thread-ntfxyn
Worktree: the card thread's own checkout of objectstack-ai/objectstack
Domain: domain:spec
Seat: domain:spec#2
File surface: packages/spec/src/api/auth-endpoints.zod.ts (AuthFeaturesConfigSchema gains sso) and its test; packages/spec/src/kernel/public-auth-features.ts only 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: high
Clause-②: 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/config
Thread-read: 6577724749
Premise: holds (on origin/main 96fb7b5d, AuthFeaturesConfigSchema declares ssoEnforced and no sso); the dev measures first and stops with premise_still_valid: false if it does not reproduce
Serial constraints cleared: no in-flight claim names auth-endpoints.zod.ts or public-auth-features.ts
The key name sso is the producer's and its readers'; as a new key on a published contract it goes to the maintainer as needs-user-decision on the PR (Steve's rule, 2026-11-02 14:39Z).
{
"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'"
]
}
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).