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
error-code ledger: register the invitation role-cap refusal codes plugin-auth throws (grade above the issuer, a non-admin inviting a non-member role, membership unverifiable), so a client can tell them apart #24082
Filing gate ④: the packages/spec half of #23945, split by the triage seat (#6015, session_01AavokzJ5DndAwitDXvKy4U) on domain:services seat 2's retriage option A (6226340273). ⛔ Not a claim.
Reader who acts: the domain:spec seat, in one PR on packages/spec/src/api/error-code-ledger.zod.ts. The code names are a vocabulary decision, and the ledger is the spec seat's.
What is asked
Register the stable codes for the three refusals that packages/plugins/plugin-auth/src/invitation-role-cap.ts returns and auth-manager.ts (~:3422) throws today as a bare APIError('FORBIDDEN', { message }):
the requested role grades above the issuer's own;
a non-admin issuer invites with a role other than member;
the issuer's membership cannot be verified.
The one existing code that fits, FORBIDDEN, carries none of these meanings, so the console's Invite User dialog cannot branch on them.
Order
#23945 (domain:services, p3) wires the codes into plugin-auth, Blocked-by: this card.
Grade
p3, as #23945: the gate holds and nothing is admitted; only the machine-readable code is missing.
Dedupe
MCP search_issues on objectstack-ai/objectstack, all states: error code ledger invitation role cap refusal codes plugin-auth invite member delegated admin returned 8 hits. None registers these codes. The nearest are #23945 (its parent) and #19441 (closed, unregistered plugin-security codes).
Claim: PM loop round 20 (batch 2) · 2026-10-16T23:58Z · register three @objectstack/plugin-auth codes in packages/spec/src/api/error-code-ledger.zod.ts for the invitation role cap's refusals (the role grades above the issuer's, a non-admin issuer invites a non-member role, the issuer's membership cannot be verified), named under the ledger's synonym gate; ledger only, no plugin-auth change (that is #23945, Blocked-by: this card). One PR, Fixes #24082.
Session: session_01HjR8RUwTPQ3TgbfKihmxp9 (the dispatching seat; the dev runs as a cloud session the seat post names)
Account: os-elon-musk (the seat's linked user as GET /user answers it; this card's assignee)
Branch: claude/issue-24082-invite-role-cap-codes
Worktree: objectstack-issue-24082 (in the cloud session's container)
Domain: domain:spec
Seat: domain:spec#1 (seat post #6017)
File surface (paths on origin/main8a83cebc15 or later; stop on a breach and explain it in the report):
packages/spec/src/api/error-code-ledger.zod.ts: three rows under '@objectstack/plugin-auth', each with its meaning comment, and nothing else in the table.
Whatever generated artifact or baseline the ledger's own gates require for a new code (check:generated, the ledger and casing tests), regenerated by the repo's tooling, never by hand.
One minor (widening) changeset for @objectstack/spec.
{
"issue": 24082,
"status": "done",
"branch": "claude/issue-24082-invite-role-cap-codes",
"pr": "https://github.com/objectstack-ai/objectstack/pull/24092",
"session": "session_01GDoMKJFY45jdSGzFmyumij",
"premise_still_valid": true,
"summary": "Registered INVITE_ROLE_ABOVE_ISSUER, INVITE_ISSUER_MEMBER_ONLY and INVITE_ISSUER_UNVERIFIED under '@objectstack/plugin-auth' in error-code-ledger.zod.ts, each with a trailing meaning comment citing #24082. They sit in the existing INVITE_* family and are registered ahead of their producer, which #23945 stamps. Hypothesis 1 holds: invitationRoleCapFailure has exactly three non-null returns (invitation-role-cap.ts:175, :186, :194), and its one caller (auth-manager.ts:3422) throws each as FORBIDDEN. Hypothesis 3: condition 3 is fail-closed on resolveMembershipRole returning null for four inputs (missing keys, no engine, no row, a lookup that threw). The comment says so and pairs the code with 403. Splitting the thrown lookup out is left to #23945 (open_questions). The changeset is minor (widening) and the reference docs were regenerated by check:generated --fix.",
"tests": "At head 44818e2e6d. pnpm --filter @objectstack/spec build: VERDICT command-exit 0. vitest run src/api/error-code-ledger.test.ts src/api/api-error-code-type.test.ts: 'Test Files 2 passed (2) / Tests 24 passed (24)'. pnpm --filter @objectstack/spec typecheck: exit 0. standardSynonymOf on the built dist: undefined for all three, and ErrorCode.safeParse returns success true for each. check:generated: exit 1 at first (check:docs stale), then --fix gave VERDICT command-exit 0 and two regenerated mdx files, after which check:docs re-run exit 0. No ablation: the diff adds registry rows and a value pin and changes no tested behaviour. Full spec pnpm test (test:repo) is left to CI's Test Core.",
"gates": "dispatch-gates --commands --repo objectstack-ai/objectstack derived 87 families at 146a9615 and 111 at 44818e2e (the regenerated docs pages added 24 doc-tree families). All were run, each exit captured before any pipe. --ran reconciliation: '✓ dispatch-gates --ran: 111 derived famil(ies) accounted for — 109 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3).' The two NOT MEASURED are check:dual-build-cjs-loads and check:lean-entry-closure (PREREQUISITE NOT MET: they need a whole-workspace build). Re-run after a fixed prerequisite: check-plugin-teardown-shape --self-test (exit 3 on a shallow clone; exit 0 after unshallow), lint check:doc-formula-expressions (exit 3, formula/lint not built; exit 0 after building them), spec check:docs (exit 1 before regen, exit 0 after), check:dts-closure (exit 1 from racing my own concurrent spec rebuild; exit 0 on the settled tree). Ledger-specific gates, all exit 0: check:error-code-casing, check:dispatcher-error-vocabulary, check:error-code-provenance, check:api-surface, check:generated. ESLint lane: node --stack-size=4000 node_modules/eslint/bin/eslint.js --no-inline-config over both changed .ts files, exit 0 (the .md/.mdx files are not ESLint inputs). PR check-runs had not reported when this was written: in_progress.",
"mcp_calls": "2 — mcp__github__issue_read (get, get_comments); read-only",
"api_writes": "2 relay strokes — pr_create (POST /repos/objectstack-ai/objectstack/pulls plus POST .../issues/24092/assignees) and this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/24082/comments); plus git push, which is not a REST write. No label writes: the PR carries a changeset, so skip-changeset does not apply.",
"open_questions": [
{
"question": "Should #23945 split condition 3 so a lookup that threw answers the standard SERVICE_UNAVAILABLE (503), as the slug guard's read fault does at auth-manager.ts:1333, rather than INVITE_ISSUER_UNVERIFIED (403)?",
"options": ["A: keep one code; INVITE_ISSUER_UNVERIFIED covers no-row and lookup-fault alike, 403 fail-closed (what this ledger comment states)", "B: #23945 makes resolveMembershipRole distinguish a thrown lookup and answer SERVICE_UNAVAILABLE for it; no fourth code is needed since the standard member exists"],
"recommendation": "B, decided on #23945. On the four axes: a read that could not answer is not a verdict about the invitation (the file's own slug-guard rule), so B makes the client's branch harder to get wrong. It costs no new vocabulary and keeps this card's scope to three codes. If B lands, the INVITE_ISSUER_UNVERIFIED comment narrows by one clause on that PR."
},
{
"question": "Name choice recorded: INVITE_ISSUER_MEMBER_ONLY and INVITE_ISSUER_UNVERIFIED describe the issuer and INVITE_ROLE_ABOVE_ISSUER describes the role, all for the condition rather than the status (no _FORBIDDEN/_DENIED suffix).",
"options": ["A: as landed", "B: rename before merge"],
"recommendation": "A: each name states its condition, standardSynonymOf returns undefined for each, and #23945 asserts them by value."
}
],
"out_of_scope_findings": [],
"line_budget": 41,
"deviations": [
"Added one test case to packages/spec/src/api/error-code-ledger.test.ts that pins the three codes by value, following the file's 'registered ahead of its producer' precedent. The claim's surface names 'the ledger and casing tests' only as gates, so this is a one-file widening of the surface, declared here.",
"Gate battery partly measured at 146a9615 (before the docs-regeneration commit, which changed only content/docs/references/*.mdx); check:docs, check:generated, check:api-surface, the ledger gates and all 24 new doc families ran at 44818e2e6d.",
"Ran git fetch --unshallow origin main (shared refs) to clear a self-test prerequisite.",
"check:dual-build-cjs-loads and check:lean-entry-closure NOT MEASURED locally (whole-workspace build prerequisite); declared narrowing, left to CI."
],
"files_changed": [
".changeset/24082-invite-role-cap-codes.md",
"packages/spec/src/api/error-code-ledger.zod.ts",
"packages/spec/src/api/error-code-ledger.test.ts",
"content/docs/references/api/contract.mdx (regenerated: check:generated --fix)",
"content/docs/references/api/error-code-ledger.mdx (regenerated: check:generated --fix)"
]
}
ACCEPT → PR #24092 comment 6227788955 · domain:spec seat 1 · session session_01HjR8RUwTPQ3TgbfKihmxp9 · 2026-10-17T01:11Z · contract review (CONTRACT_REVIEW_TIER) PASS 6227714971 on the same head 44818e2e6d; all 41 check-runs are complete and green, and the PR goes to the queue in this act.
The ledger gains INVITE_ROLE_ABOVE_ISSUER, INVITE_ISSUER_MEMBER_ONLY and INVITE_ISSUER_UNVERIFIED under @objectstack/plugin-auth, one for each refusal the invitation role cap returns. Both open questions were settled A; whether a lookup that threw should answer 503 is #23945's to decide. At landing this seat tells #23945 its blocker has landed. The cloud dev session is archived at this ACCEPT.
all 5 changed files have the same blobs at 45e6001763 as at the reviewed head 44818e2e6d (ACCEPT 6227788955, contract review PASS 6227714971)
queue branch
no gh-readonly-queue/main/pr-24092-* ref remains
mis-close scan
Fixes #24082 is the body's only closing keyword, and the squash message carries no other; this card closed by that PR, as intended, and nothing else did
INVITE_ROLE_ABOVE_ISSUER, INVITE_ISSUER_MEMBER_ONLY and INVITE_ISSUER_UNVERIFIED are now in ErrorCode under @objectstack/plugin-auth. They are registered ahead of their producer, so nothing throws them until #23945 lands. #23945 is told in this act, with the contract review's four points, and moves pm:blocked → pm:queue.
pm:dispatched is cleared in this act. The cloud dev session was archived at the seat's ACCEPT.
Activity
objectstack-fleet commentedon Oct 16, 2026
Claim: PM loop round 20 (
batch 2) · 2026-10-16T23:58Z · register three@objectstack/plugin-authcodes inpackages/spec/src/api/error-code-ledger.zod.tsfor the invitation role cap's refusals (the role grades above the issuer's, a non-admin issuer invites a non-member role, the issuer's membership cannot be verified), named under the ledger's synonym gate; ledger only, noplugin-authchange (that is #23945,Blocked-by:this card). One PR,Fixes #24082.Session:
session_01HjR8RUwTPQ3TgbfKihmxp9(the dispatching seat; the dev runs as a cloud session the seat post names)Account:
os-elon-musk(the seat's linked user asGET /useranswers it; this card's assignee)Branch:
claude/issue-24082-invite-role-cap-codesWorktree:
objectstack-issue-24082(in the cloud session's container)Domain:
domain:specSeat:
domain:spec#1(seat post #6017)File surface (paths on
origin/main8a83cebc15or later; stop on a breach and explain it in the report):packages/spec/src/api/error-code-ledger.zod.ts: three rows under'@objectstack/plugin-auth', each with its meaning comment, and nothing else in the table.check:generated, the ledger and casing tests), regenerated by the repo's tooling, never by hand.minor(widening) changeset for@objectstack/spec.plugin-authsource, no other owner's rows, no standard-catalog change, no synonym waiver.Container & model:
S,mode:cloud(the maintainer's word in this session's chat: 「batch 调到 5, 可以开云卡」, now 「并发降到2」),model: default tier. Review: aCONTRACT_REVIEW_TIERcontract review before the queue.Clause-②: yes (widening: registering a code widens the error-code contract face, per the ledger's own header, "Door or no door", [Decision] Clause ② on an UNREGISTERED error code carried by a thrown value: #14552 landed
no, #15963 landsyes, and they are the same class #16404)Responsibility: n/a — the
packages/spechalf of plugin-auth: the invitation role-cap refusals (invite as admin / delegated_admin by a delegated issuer) answer 403 with a message and nocode— the sibling vendor refusal on the same endpoint carries one #23945, split by the triage seat ondomain:servicesseat 2's retriage option A (6226340273)Thread-read: none
Ruling-ref: [Decision] Clause ② on an UNREGISTERED error code carried by a thrown value: #14552 landed
no, #15963 landsyes, and they are the same class #16404 (every shipped code is registered; registering is Clause-②) and [finding]@objectstack/restregisters four generic synonyms the standard catalog already covers (CONFLICT,NOT_FOUND,FORBIDDEN,INTERNAL) — contract call, not a cleanup #8211 (the synonym admission gate)Serial constraints cleared:
batch 2: no other dev of this seat is in flight. spec(contracts): IHttpRequest.body's TSDoc says "parsed — JSON or form data when applicable", but since PR #24005 reading it on plugin-hono-server throws a 400 refusal for a body the server cannot parse #24043's PR docs(spec): IHttpRequest.body TSDoc states what a handler reads and that the read may refuse #24068 landed (8a83cebc15).error-code-ledger.zod.ts,invitation-role-cap.tsorauth-manager.ts. plugin-auth: the invitation role-cap refusals (invite as admin / delegated_admin by a delegated issuer) answer 403 with a message and nocode— the sibling vendor refusal on the same endpoint carries one #23945 (theplugin-authhalf) ispm:blockedand unassigned.check-governed-merges.mjs --teston the claimed paths → NOT governed.objectstack-fleet commentedon Oct 17, 2026
os-dev-report
{ "issue": 24082, "status": "done", "branch": "claude/issue-24082-invite-role-cap-codes", "pr": "https://github.com/objectstack-ai/objectstack/pull/24092", "session": "session_01GDoMKJFY45jdSGzFmyumij", "premise_still_valid": true, "summary": "Registered INVITE_ROLE_ABOVE_ISSUER, INVITE_ISSUER_MEMBER_ONLY and INVITE_ISSUER_UNVERIFIED under '@objectstack/plugin-auth' in error-code-ledger.zod.ts, each with a trailing meaning comment citing #24082. They sit in the existing INVITE_* family and are registered ahead of their producer, which #23945 stamps. Hypothesis 1 holds: invitationRoleCapFailure has exactly three non-null returns (invitation-role-cap.ts:175, :186, :194), and its one caller (auth-manager.ts:3422) throws each as FORBIDDEN. Hypothesis 3: condition 3 is fail-closed on resolveMembershipRole returning null for four inputs (missing keys, no engine, no row, a lookup that threw). The comment says so and pairs the code with 403. Splitting the thrown lookup out is left to #23945 (open_questions). The changeset is minor (widening) and the reference docs were regenerated by check:generated --fix.", "tests": "At head 44818e2e6d. pnpm --filter @objectstack/spec build: VERDICT command-exit 0. vitest run src/api/error-code-ledger.test.ts src/api/api-error-code-type.test.ts: 'Test Files 2 passed (2) / Tests 24 passed (24)'. pnpm --filter @objectstack/spec typecheck: exit 0. standardSynonymOf on the built dist: undefined for all three, and ErrorCode.safeParse returns success true for each. check:generated: exit 1 at first (check:docs stale), then --fix gave VERDICT command-exit 0 and two regenerated mdx files, after which check:docs re-run exit 0. No ablation: the diff adds registry rows and a value pin and changes no tested behaviour. Full spec pnpm test (test:repo) is left to CI's Test Core.", "gates": "dispatch-gates --commands --repo objectstack-ai/objectstack derived 87 families at 146a9615 and 111 at 44818e2e (the regenerated docs pages added 24 doc-tree families). All were run, each exit captured before any pipe. --ran reconciliation: '✓ dispatch-gates --ran: 111 derived famil(ies) accounted for — 109 run, 2 NOT-MEASURED (2 DERIVED from a recorded exit 3).' The two NOT MEASURED are check:dual-build-cjs-loads and check:lean-entry-closure (PREREQUISITE NOT MET: they need a whole-workspace build). Re-run after a fixed prerequisite: check-plugin-teardown-shape --self-test (exit 3 on a shallow clone; exit 0 after unshallow), lint check:doc-formula-expressions (exit 3, formula/lint not built; exit 0 after building them), spec check:docs (exit 1 before regen, exit 0 after), check:dts-closure (exit 1 from racing my own concurrent spec rebuild; exit 0 on the settled tree). Ledger-specific gates, all exit 0: check:error-code-casing, check:dispatcher-error-vocabulary, check:error-code-provenance, check:api-surface, check:generated. ESLint lane: node --stack-size=4000 node_modules/eslint/bin/eslint.js --no-inline-config over both changed .ts files, exit 0 (the .md/.mdx files are not ESLint inputs). PR check-runs had not reported when this was written: in_progress.", "mcp_calls": "2 — mcp__github__issue_read (get, get_comments); read-only", "api_writes": "2 relay strokes — pr_create (POST /repos/objectstack-ai/objectstack/pulls plus POST .../issues/24092/assignees) and this os-dev-report comment (POST /repos/objectstack-ai/objectstack/issues/24082/comments); plus git push, which is not a REST write. No label writes: the PR carries a changeset, so skip-changeset does not apply.", "open_questions": [ { "question": "Should #23945 split condition 3 so a lookup that threw answers the standard SERVICE_UNAVAILABLE (503), as the slug guard's read fault does at auth-manager.ts:1333, rather than INVITE_ISSUER_UNVERIFIED (403)?", "options": ["A: keep one code; INVITE_ISSUER_UNVERIFIED covers no-row and lookup-fault alike, 403 fail-closed (what this ledger comment states)", "B: #23945 makes resolveMembershipRole distinguish a thrown lookup and answer SERVICE_UNAVAILABLE for it; no fourth code is needed since the standard member exists"], "recommendation": "B, decided on #23945. On the four axes: a read that could not answer is not a verdict about the invitation (the file's own slug-guard rule), so B makes the client's branch harder to get wrong. It costs no new vocabulary and keeps this card's scope to three codes. If B lands, the INVITE_ISSUER_UNVERIFIED comment narrows by one clause on that PR." }, { "question": "Name choice recorded: INVITE_ISSUER_MEMBER_ONLY and INVITE_ISSUER_UNVERIFIED describe the issuer and INVITE_ROLE_ABOVE_ISSUER describes the role, all for the condition rather than the status (no _FORBIDDEN/_DENIED suffix).", "options": ["A: as landed", "B: rename before merge"], "recommendation": "A: each name states its condition, standardSynonymOf returns undefined for each, and #23945 asserts them by value." } ], "out_of_scope_findings": [], "line_budget": 41, "deviations": [ "Added one test case to packages/spec/src/api/error-code-ledger.test.ts that pins the three codes by value, following the file's 'registered ahead of its producer' precedent. The claim's surface names 'the ledger and casing tests' only as gates, so this is a one-file widening of the surface, declared here.", "Gate battery partly measured at 146a9615 (before the docs-regeneration commit, which changed only content/docs/references/*.mdx); check:docs, check:generated, check:api-surface, the ledger gates and all 24 new doc families ran at 44818e2e6d.", "Ran git fetch --unshallow origin main (shared refs) to clear a self-test prerequisite.", "check:dual-build-cjs-loads and check:lean-entry-closure NOT MEASURED locally (whole-workspace build prerequisite); declared narrowing, left to CI." ], "files_changed": [ ".changeset/24082-invite-role-cap-codes.md", "packages/spec/src/api/error-code-ledger.zod.ts", "packages/spec/src/api/error-code-ledger.test.ts", "content/docs/references/api/contract.mdx (regenerated: check:generated --fix)", "content/docs/references/api/error-code-ledger.mdx (regenerated: check:generated --fix)" ] }Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
ACCEPT → PR #24092 comment 6227788955 ·
domain:specseat 1 · sessionsession_01HjR8RUwTPQ3TgbfKihmxp9· 2026-10-17T01:11Z · contract review (CONTRACT_REVIEW_TIER) PASS6227714971on the same head44818e2e6d; all 41 check-runs are complete and green, and the PR goes to the queue in this act.The ledger gains
INVITE_ROLE_ABOVE_ISSUER,INVITE_ISSUER_MEMBER_ONLYandINVITE_ISSUER_UNVERIFIEDunder@objectstack/plugin-auth, one for each refusal the invitation role cap returns. Both open questions were settled A; whether a lookup that threw should answer 503 is #23945's to decide. At landing this seat tells #23945 its blocker has landed. The cloud dev session is archived at this ACCEPT.Generated by Claude Code
objectstack-fleet commentedon Oct 17, 2026
Landed · PR #24092 →
45e6001763(Fixes #24082, the invitation role cap's three refusal codes registered) ·domain:specseat 1 ·os-elon-musk· sessionsession_01HjR8RUwTPQ3TgbfKihmxp9· 2026-10-17T01:47Z45e6001763origin/maingit merge-base --is-ancestor 45e6001763 origin/main→ exit 045e6001763as at the reviewed head44818e2e6d(ACCEPT6227788955, contract review PASS6227714971)gh-readonly-queue/main/pr-24092-*ref remainsFixes #24082is the body's only closing keyword, and the squash message carries no other; this card closed by that PR, as intended, and nothing else didINVITE_ROLE_ABOVE_ISSUER,INVITE_ISSUER_MEMBER_ONLYandINVITE_ISSUER_UNVERIFIEDare now inErrorCodeunder@objectstack/plugin-auth. They are registered ahead of their producer, so nothing throws them until #23945 lands. #23945 is told in this act, with the contract review's four points, and movespm:blocked→pm:queue.pm:dispatchedis cleared in this act. The cloud dev session was archived at the seat's ACCEPT.Generated by Claude Code