docs: RFC-0002 — first-class authorization for Temper apps (ARN-255) - #352
docs: RFC-0002 — first-class authorization for Temper apps (ARN-255)#352nerdsane wants to merge 4 commits into
Conversation
Records the 2026-07-11 decision (Option B): the platform runs the authorization server; the kernel verifies every principal (humans via token exchange, agents via platform-issued JWTs, services via registered credentials); app authorization is declared in the IOA spec and compiled to Cedar. Grounded in the Katagami authorization audit; extends ADR-0033 (Platform-Assigned Agent Identity) to humans and OAuth/JWT. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0125RD5byAmmMuvhLaK2jsZR
|
@greptile review |
| ### Risks | ||
|
|
||
| - **Migration breakage.** Katagami is live; each rollout step changes a live | ||
| credential path. Every step needs the local end-to-end run against a seeded | ||
| server before deploy, per the standing definition of done. | ||
| - **Spec sugar scope creep.** `requires_role` / `requires = "creator"` covers | ||
| the audited needs; resisting a policy-language-in-TOML is deliberate. | ||
| Anything more complex is a hand-written Cedar file. | ||
| - **Fail-open regressions.** ARN-230's finding (missing tenant policies fall | ||
| back to permit-all) must be fixed before default-deny generation can be | ||
| trusted; otherwise a failed policy load reopens everything. |
There was a problem hiding this comment.
ARN-230 fix is unsequenced as a Step 4 dependency
The Risks section correctly identifies that the permit-all fallback (ARN-230) must be fixed before default-deny generation "can be trusted," but the rollout plan doesn't enforce this ordering — Step 4 has no listed prerequisite. If Step 4 ships before ARN-230 is resolved, a failed policy load at tenant startup silently reopens every action the spec intended to deny. The ARN-230 fix should appear as an explicit dependency line in Step 4, similar to how Step 3 calls out that it "unblocks ARN-248." Without that, the rollout checklist provides no gate to catch a premature Step 4 deploy.
Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/rfcs/0002-first-class-authorization.md
Line: 280-290
Comment:
**ARN-230 fix is unsequenced as a Step 4 dependency**
The Risks section correctly identifies that the permit-all fallback (ARN-230) must be fixed before default-deny generation "can be trusted," but the rollout plan doesn't enforce this ordering — Step 4 has no listed prerequisite. If Step 4 ships before ARN-230 is resolved, a failed policy load at tenant startup silently reopens every action the spec intended to deny. The ARN-230 fix should appear as an explicit dependency line in Step 4, similar to how Step 3 calls out that it "unblocks ARN-248." Without that, the rollout checklist provides no gate to catch a premature Step 4 deploy.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Fixed in 6b6a16c — step 4 now carries an explicit prerequisite line: ARN-230's fail-open fallback must be fixed and verified (failed/missing tenant policy loads fail closed) before default-deny generation ships, with the silent-reopen consequence spelled out.
| ## Rollout | ||
|
|
||
| Ordered by dependency; step numbers match ARN-255. Steps 1 and 2 ship | ||
| together (see the coordination note); steps 3 and 4 each land value on their | ||
| own. | ||
|
|
||
| 1. **Kernel verifies tokens.** Extend the resolver to validate JWTs from | ||
| allowlisted per-tenant issuers (interim Option A: katagami.ai's existing | ||
| authorization server is the first allowlisted issuer). Self-declared | ||
| principal headers are dropped in the same change. | ||
| *Coordination:* ARN-170 (PR #343) closes the header-trust path the | ||
| Katagami MCP adapter currently depends on for its contributor stamp, so | ||
| step 2 must land together with the header removal — otherwise contributor | ||
| enforcement silently disappears. | ||
| 2. **Katagami MCP adapter forwards tokens.** The adapter passes the caller's | ||
| JWT through instead of swapping to the shared key. The contributor boundary | ||
| becomes kernel-enforced. |
There was a problem hiding this comment.
Steps 1 and 2 atomicity mechanism is unspecified
The rollout preamble says "Steps 1 and 2 ship together," and the coordination note makes clear why: if header removal (Step 1, tied to PR #343 / ARN-170) lands before the adapter forwards tokens (Step 2), the Katagami MCP adapter's contributor enforcement silently disappears for any window between the two deploys. The RFC states the ordering requirement but doesn't specify the enforcement mechanism — is Step 1 gated on Step 2 being merged? Does PR #343 carry an explicit merge-block? Since PR #343 is already in-flight and independent, without a concrete gate the "ship together" requirement depends on manual coordination that can fail silently.
Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/rfcs/0002-first-class-authorization.md
Line: 231-247
Comment:
**Steps 1 and 2 atomicity mechanism is unspecified**
The rollout preamble says "Steps 1 and 2 ship together," and the coordination note makes clear why: if header removal (Step 1, tied to PR #343 / ARN-170) lands before the adapter forwards tokens (Step 2), the Katagami MCP adapter's contributor enforcement silently disappears for any window between the two deploys. The RFC states the ordering requirement but doesn't specify the enforcement mechanism — is Step 1 gated on Step 2 being merged? Does PR #343 carry an explicit merge-block? Since PR #343 is already in-flight and independent, without a concrete gate the "ship together" requirement depends on manual coordination that can fail silently.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Fixed in 6b6a16c by removing the atomicity requirement instead of specifying a coordination mechanism: the rollout is now additive-first. Step 1 only adds JWT verification (headers keep working); step 2 migrates the adapter while both paths are live; header removal is a separately gated change that merges only after step 2 is deployed AND telemetry shows zero header-authenticated traffic, recorded as a blocking checklist item on the removal PR. The same gate is stated for the ARN-170/#343 edge before it drops x-temper-agent-type handling.
| # RFC-0002: First-Class Authorization for Temper Apps | ||
|
|
||
| - Status: Draft |
There was a problem hiding this comment.
Status field does not reflect the recorded decision
The PR description states "Decision recorded (2026-07-11, Option B)" and the RFC body itself documents the final decision. The header still reads Status: Draft, which will cause confusion for anyone consulting it later. Consider updating to Accepted before merging.
Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/rfcs/0002-first-class-authorization.md
Line: 1-3
Comment:
**Status field does not reflect the recorded decision**
The PR description states "Decision recorded (2026-07-11, Option B)" and the RFC body itself documents the final decision. The header still reads `Status: Draft`, which will cause confusion for anyone consulting it later. Consider updating to `Accepted` before merging.
How can I resolve this? If you propose a fix, please make it concise.Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!
There was a problem hiding this comment.
Fixed in 6b6a16c. Kept it precise rather than jumping straight to Accepted: the header now reads 'Status: Proposed — the Option B direction was decided 2026-07-11 (ARN-255); this document becomes Accepted when it merges after review.' The direction decision is recorded; document acceptance is the merge.
| With these in place, the self-declared `x-temper-principal-*` / | ||
| `x-temper-agent-type` headers are removed from the protocol, completing what | ||
| ADR-0033 specified and ARN-170's ingress work began. A request with no | ||
| verified credential resolves to an anonymous principal that can reach only | ||
| whatever policies explicitly permit anonymously. This deliberately revises | ||
| ADR-0033's "no token = 401" posture: once headers are gone the kernel needs a | ||
| defined identity for credential-less requests, and apps with public read | ||
| surfaces (a catalog page, a published design language) need a way to serve | ||
| them without handing the front end a privileged key. An anonymous principal | ||
| governed by explicit Cedar permits is one way to provide that; whether | ||
| generated policies include anonymous read permits per entity, or apps instead | ||
| mint a public-reader service credential, is an open question below. |
There was a problem hiding this comment.
Anonymous principal behavior should be resolved before Step 1 ships
The RFC commits to anonymous principals (revising ADR-0033's "no token = 401") but leaves the concrete behavior — anonymous read permits in generated policies vs. a public-reader service credential — as Open Question 2. Step 1 drops the self-declared headers, so after Step 1 any credential-less request hits the anonymous path. If anonymous Cedar permits for public resources haven't been decided and provisioned, public catalog pages will start returning 403s on a live system immediately after Step 1.
Prompt To Fix With AI
This is a comment left during a code review.
Path: docs/rfcs/0002-first-class-authorization.md
Line: 143-154
Comment:
**Anonymous principal behavior should be resolved before Step 1 ships**
The RFC commits to anonymous principals (revising ADR-0033's "no token = 401") but leaves the concrete behavior — anonymous read permits in generated policies vs. a public-reader service credential — as Open Question 2. Step 1 drops the self-declared headers, so after Step 1 any credential-less request hits the anonymous path. If anonymous Cedar permits for public resources haven't been decided and provisioned, public catalog pages will start returning 403s on a live system immediately after Step 1.
How can I resolve this? If you propose a fix, please make it concise.There was a problem hiding this comment.
Fixed in 6b6a16c. Two corrections: (1) credential-less requests now explicitly keep returning 401 until Open Question 2 is resolved — no rollout step changes credential-less behavior, so there is no 403 window to hit; (2) the 403-after-step-1 scenario also rested on a wrong premise — public catalog pages authenticate with the shared key (a valid credential through step 4), so they never traverse the anonymous path during rollout. The RFC now states both.
Rollout is now additive-first: header removal rides behind an explicit gate (step 2 deployed + zero header-authenticated traffic observed) instead of a ship-together promise; step 4 gains the ARN-230 fail-closed prerequisite; credential-less requests stay 401 until Open Question 2 resolves; Status header reflects the recorded decision. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0125RD5byAmmMuvhLaK2jsZR
Humans get sign-out-everywhere from day one (Member auth_generation counter checked at token verification, decided 2026-07-14); the ADR numbering collisions move to a separate renumbering change. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_0125RD5byAmmMuvhLaK2jsZR
…pec sugar) The design doc had drifted from the implementation on two decisions: - Revocation: the generation counter lives in a kernel-owned PrincipalGeneration entity keyed by subject, not on the app's Member entity — the kernel is generic and cannot read an app's entities, and keeping it in the kernel makes revocation a capability every app inherits. Records what shipped alongside it: missing claim counts as generation 0, sign-out also revokes the human's live grants, the session cookie re-checks the same counter, and the counter keyed by grant_id carries per-agent revocation. - Spec-declared authorization (requires_role / requires) was built and then backed out. Section 4 now explains why: the sugar was bound to a spec format that is being replaced, its forbid semantics excluded role-less service principals (so it could not express this RFC's own Publish example) and compared principal.id, which for an agent is its client id rather than the human it acts for; and it created a second home for rules that already live in Cedar. Authorization has one home today: hand-written per-entity .cedar. Rollout step 4 and the risks section follow suit.
What
RFC-0002 (
docs/rfcs/0002-first-class-authorization.md) — the design record for ARN-255: making authorization a platform capability.Decision recorded (2026-07-11, Option B): the platform runs the authorization server; apps handle sign-in and token exchange only; the kernel verifies every principal (humans via token exchange, agents via platform-issued JWTs, services via registered credentials); app authorization is declared in the IOA spec and compiled to Cedar. Extends ADR-0033 (Platform-Assigned Agent Identity) to humans and to the OAuth/JWT validation it deferred.
Why
The 2026-07-11 authorization audit of Katagami (the first Temper app with a human front end and agent clients) found the platform makes no per-caller access decisions: one shared
TEMPER_API_KEYfor every caller, self-assertedx-temper-*principal headers trusted verbatim, permit-all Cedar with contributor-only forbids, and human identity appearing in zero policies. Full evidence is in the RFC and on ARN-255.Notes for review
docs/rfcs/was untracked until this PR — RFC-0001 (directed evolution) exists only as a local file in the primary checkout. This PR does not adopt it; numbering starts committed history at 0002 to match the existing local file's claim on 0001.🤖 Generated with Claude Code
https://claude.ai/code/session_0125RD5byAmmMuvhLaK2jsZR
Greptile Summary
RFC-0002 records the platform decision (Option B, 2026-07-11) to make authorization a first-class Temper kernel capability: the platform runs the authorization server, the kernel verifies every principal (humans via token exchange, agents via platform-issued JWTs, services via registered credentials), and Cedar policies compiled from the IOA spec govern access. This is a docs-only change; no production code is modified.
docs/rfcs/0002-first-class-authorization.md— a 316-line design record covering the motivation (Katagami audit findings), the four design components (platform AS, verified principals, Member roles, spec-compiled Cedar), a four-step rollout, consequences, risks, and open questions.Confidence Score: 4/5
Safe to merge as a design record; no production code is touched, and the issues found are gaps in the rollout plan rather than problems with the current codebase.
The RFC accurately diagnoses the authorization gaps in the current platform and proposes a coherent design. The two substantive issues — ARN-230 not being a formal Step 4 gate, and the Steps 1+2 atomicity mechanism being unspecified — are rollout-plan gaps that could cause a silent security regression or live-traffic outage if implementation proceeds without addressing them.
docs/rfcs/0002-first-class-authorization.md — specifically the Rollout section (Steps 1–2 coordination and Step 4 ARN-230 dependency) and the anonymous principal discussion before Open Question 2.
Important Files Changed
Sequence Diagram
%%{init: {'theme': 'neutral'}}%% sequenceDiagram participant FE as App Front End participant KE as Temper Kernel participant AS as Platform Auth Server participant Cedar as Cedar Engine Note over FE,Cedar: Human path (post-RFC) FE->>KE: Exchange Google ID token KE->>KE: Verify issuer, validate OIDC token KE->>KE: Load Member entity, inject role attribute KE-->>FE: Short-lived platform token (Customer, role owner) FE->>KE: OData API call with Bearer token KE->>KE: Resolve token to Customer principal KE->>Cedar: Evaluate Publish action Cedar-->>KE: "Permit (role == owner)" KE-->>FE: 200 OK Note over FE,Cedar: Agent path (post-RFC) participant AG as Agent AG->>AS: Auth-code + PKCE or pre-authorized grant AS-->>AG: Platform-issued JWT (Agent, acting_for human) AG->>KE: MCP call with Bearer JWT KE->>KE: Verify signature + grant liveness KE->>Cedar: Evaluate Publish (Agent principal) Cedar-->>KE: Deny KE->>Cedar: Evaluate SubmitForReview Cedar-->>KE: Permit KE-->>AG: 200 OK Note over FE,Cedar: Anonymous path (behavior TBD, Open Q2) FE->>KE: Request with no credential KE->>KE: Resolve to anonymous principal KE->>Cedar: Evaluate against generated policies Cedar-->>KE: Permit only if explicit anonymous policy exists KE-->>FE: 200 or 403%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%% sequenceDiagram participant FE as App Front End participant KE as Temper Kernel participant AS as Platform Auth Server participant Cedar as Cedar Engine Note over FE,Cedar: Human path (post-RFC) FE->>KE: Exchange Google ID token KE->>KE: Verify issuer, validate OIDC token KE->>KE: Load Member entity, inject role attribute KE-->>FE: Short-lived platform token (Customer, role owner) FE->>KE: OData API call with Bearer token KE->>KE: Resolve token to Customer principal KE->>Cedar: Evaluate Publish action Cedar-->>KE: "Permit (role == owner)" KE-->>FE: 200 OK Note over FE,Cedar: Agent path (post-RFC) participant AG as Agent AG->>AS: Auth-code + PKCE or pre-authorized grant AS-->>AG: Platform-issued JWT (Agent, acting_for human) AG->>KE: MCP call with Bearer JWT KE->>KE: Verify signature + grant liveness KE->>Cedar: Evaluate Publish (Agent principal) Cedar-->>KE: Deny KE->>Cedar: Evaluate SubmitForReview Cedar-->>KE: Permit KE-->>AG: 200 OK Note over FE,Cedar: Anonymous path (behavior TBD, Open Q2) FE->>KE: Request with no credential KE->>KE: Resolve to anonymous principal KE->>Cedar: Evaluate against generated policies Cedar-->>KE: Permit only if explicit anonymous policy exists KE-->>FE: 200 or 403Prompt To Fix All With AI
Reviews (1): Last reviewed commit: "docs: RFC-0002 — first-class authorizati..." | Re-trigger Greptile