Skip to content

Slice 0: per-user subject for device-key upload claims - #152

Merged
zmanian merged 6 commits into
mainfrom
slice0-per-user-subject
Jun 25, 2026
Merged

zmanian merged 6 commits into
mainfrom
slice0-per-user-subject

Conversation

@zmanian

@zmanian zmanian commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

Lets one enrolled device key mint per-user contributor claims by accepting an optional, opaque subject in the upload-claim request. This enables a single IronClaw instance to fan out to many per-user Trace Commons accounts within its own tenant (the client-side integration lives in the ironclaw repo).

  • TraceUploadClaimRequest gains optional subject: Option<String> + a normalize_subject validator (≤128 bytes, charset [A-Za-z0-9:_-], non-empty).
  • issue_claim_for_device_key: when subject is present, derives sub/principal_ref = instance:{tenant}:{device_key_id}:user:{subject}; absent → unchanged (raw device_key_id). The tenant-access-grant principal stays device-scoped in both cases.
  • Integration test mints a per-user bearer through the real issuer path and feeds the real mint_login_link_handler, proving distinct subjects under one device key resolve to distinct accounts (DB-gated, runs in CI).

Properties

  • Additive / backward-compatible: no subject → byte-identical to today.
  • Tenant-bounded: the principal embeds the server-verified tenant_id + device_key_id; a compromised instance can only mis-attribute within its own tenant (RLS). Derivation is injective in subject.
  • No audience gap: the issuer-minted contributor bearer is accepted by /v1/account/login-links as the design assumed.

Non-blocking follow-ups

  • Add a grant_principal_ref device-scoping regression assertion.
  • Add a normalize_subject 128-byte accept-edge test.
  • Set the role claim explicitly in the integration test.
  • Doc note: the workload-claim path ignores subject.

🤖 Generated with Claude Code

zmanian and others added 6 commits June 25, 2026 10:53
…ce keys

When a `subject` field is present in the upload-claim request, the issued
JWT's `sub` and `principal_ref` become
`instance:{tenant_id}:{device_key_id}:user:{normalized_subject}`.
When absent, behavior is unchanged (principal equals the raw device_key_id).
The `grant_principal_ref` used for policy lookup remains device-scoped so
tenant access grant checks are not affected by per-user subjects.

Also removes the three `#[allow(dead_code)]` placeholder attributes added
in Task 1 now that `subject`, `MAX_SUBJECT_LEN`, and `normalize_subject`
are all actively consumed by the new derivation path.

Includes TDD: StubDeviceKeyDb + two new unit tests verified RED then GREEN.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…ipals

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…esolution test

Replace the hand-built JWT in `mint_login_link_account_for_subject` with a
call to the real `issue_claim_for_device_key` path via
`trace_upload_claim_issuer_router`.  A local `PerUserTestDeviceKeyDb` stub
(in-memory RwLock map, `get_device_key` only) registers the test keypair so
the issuer can verify the Ed25519 request-body signature.  The issuer signs
the returned JWT with `TEST_EDDSA_PRIVATE_KEY_PEM`, which the ingest state's
existing `test_eddsa_signed_token_verifier` already trusts.

The test now proves:
- `issue_claim_for_device_key` derives the correct namespaced principal
  (`instance:{tenant}:{device_key_id}:user:{subject}` vs raw `device_key_id`)
- the `aud` claim is present and round-trips through the token
- the four account-distinctness / idempotency assertions still hold

Self-skips (early return, exit ok) when `TRACE_COMMONS_PG_TEST_DATABASE_URL`
/ `DATABASE_URL` are unset; the issuer stub and router exercise the full
non-DB path regardless.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@zmanian
zmanian merged commit 266d414 into main Jun 25, 2026
8 checks passed
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.

1 participant