Skip to content

docs: RFC-0002 — first-class authorization for Temper apps (ARN-255) - #352

Draft
nerdsane wants to merge 4 commits into
mainfrom
claude/arn-255-authz-rfc
Draft

docs: RFC-0002 — first-class authorization for Temper apps (ARN-255)#352
nerdsane wants to merge 4 commits into
mainfrom
claude/arn-255-authz-rfc

Conversation

@nerdsane

@nerdsane nerdsane commented Jul 11, 2026

Copy link
Copy Markdown
Owner

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_KEY for every caller, self-asserted x-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-only change; the full pre-push pipeline (fmt, clippy, ratchet, workspace tests) passed locally.
  • 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.
  • Coordination: rollout step 1 (header removal) intersects fix(server,authz): ingress auth edge — never trust a client-asserted principal (ARN-170) #343 / ARN-170 — the Katagami MCP adapter depends on the header path being removed there; the RFC's rollout section pins the ordering.
  • Independent review ran 3 rounds before commit (1 P1 + 10 P2 findings, all fixed, final PASS).

🤖 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.

  • Adds 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.
  • Extends ADR-0033 (Platform-Assigned Agent Identity) to humans and resolves the OAuth/JWT validation that ADR deferred.
  • Two rollout-sequencing gaps need attention before implementation begins: ARN-230 (permit-all fallback) is not a formal Step 4 prerequisite, and the atomic-deploy requirement for Steps 1+2 (tied to in-flight PR fix(server,authz): ingress auth edge — never trust a client-asserted principal (ARN-170) #343) has no specified enforcement mechanism.

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

Filename Overview
docs/rfcs/0002-first-class-authorization.md New RFC establishing platform-level authorization design; two rollout gaps flagged: ARN-230 must be a formal Step 4 prerequisite, and the Steps 1+2 atomic-deploy dependency needs a specified enforcement mechanism.

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
Loading
%%{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 403
Loading

Fix All in Claude Code Fix All in Codex Fix All in Cursor

Prompt To Fix All With AI
Fix the following 4 code review issues. Work through them one at a time, proposing concise fixes.

---

### Issue 1 of 4
docs/rfcs/0002-first-class-authorization.md:280-290
**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.

### Issue 2 of 4
docs/rfcs/0002-first-class-authorization.md:231-247
**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.

### Issue 3 of 4
docs/rfcs/0002-first-class-authorization.md:1-3
**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.

### Issue 4 of 4
docs/rfcs/0002-first-class-authorization.md:143-154
**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.

Reviews (1): Last reviewed commit: "docs: RFC-0002 — first-class authorizati..." | Re-trigger Greptile

Greptile also left 4 inline comments on this PR.

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
@nerdsane

Copy link
Copy Markdown
Owner Author

@greptile review

Comment on lines +280 to +290
### 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Fix in Claude Code Fix in Codex Fix in Cursor

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +231 to +247
## 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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 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.

Fix in Claude Code Fix in Codex Fix in Cursor

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +1 to +3
# RFC-0002: First-Class Authorization for Temper Apps

- Status: Draft

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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!

Fix in Claude Code Fix in Codex Fix in Cursor

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

Comment on lines +143 to +154
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.

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 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.

Fix in Claude Code Fix in Codex Fix in Cursor

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

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.

rita-aga and others added 2 commits July 14, 2026 09:14
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.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants