Skip to content

feat(authz): verify human proofs for offline account linking - #2401

Merged
justinhelmer merged 1 commit into
mainfrom
plan/implement-issue-2387-as-f0ed6f/u1
Sep 26, 2026
Merged

justinhelmer merged 1 commit into
mainfrom
plan/implement-issue-2387-as-f0ed6f/u1

Conversation

@coreplane-switchboard

@coreplane-switchboard coreplane-switchboard Bot commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor

Adds replaceable Access and Slack human-proof adapters with a replay-safe, bounded handoff to the atomic link transaction. Signed-proof and adversarial tests establish the offline ceremony without enabling account linking for users.

Why: U2c #2387 needs verified human subjects before U2b #2389's transaction can safely bind them. Email and bot credentials cannot establish control; record 0081 remains proposed and grants no live authority.

Where to look

  1. Bounded ceremony initiation Mints independent browser, state and nonce secrets; stores only hashes and caps intent and result-read lifetimes. ⚠ Refreshing Access must not extend an existing intent.
  2. Access human-only projection Reuses application JWT verification before retaining metadata, with stricter human, key and time admission isolated from the live dashboard.
  3. Slack signed identity admission Checks issuer, audience/authorized party, nonce, issuance, signed team and matching human user subject after confidential-client exchange. ⚠ A workspace hint or email must never replace signed identity.
  4. Durable callback claim Reads authenticated stored policy and claims the one-time state before any code exchange; only validated metadata reaches proof staging. ⚠ No replay or concurrent callback may exchange twice.
  5. Crash and restart contract Runs failure and durable-claim recovery scenarios against memory and real SQLite, without replaying a code or creating bindings.
  6. Real signed-proof handoff Exercises both real adapters through explicit consent and verifies that directory commands contain no credentials or raw browser/state/nonce secrets.
  7. Offline scope and release gates Specifies proofs and disabled boundaries; edge logging, secure cookies, CSRF, rate limits and deployed acceptance remain later ingress gates.

Feedback wanted: Challenge the claim-before-exchange/crash boundary and the signed human-subject checks. Confirm that no live linking or person-derived authority becomes reachable.

Risk: 1,219 changed lines; considered splitting adapters from ceremony, kept together to prove the handoff. Incorrect proof admission could misbind future users, but no live caller exists. Revert this additive slice before enablement; existing ingress and email bridge are unchanged.

Verified: 110 focused tests pass; scoped types, lint, formatting, hygiene and spec/docs/decision gates pass. Exact-head CI and independent review remain pending; a person merges.

Decisions (5)
  • Reuse Access verification without changing the live gate. The existing verifier authenticates the exact JWT; a separate adapter retains signed metadata and narrows human/key/time admission. Tightening dashboard behavior in this offline slice would expand its blast radius.
  • Read durable proof context, not callback assertions. Added authenticated internal inspect to the existing transaction engine. It returns detached active metadata under the owner/deadline fence, so nonce, creation time and Slack policy survive restart without trusting callback-supplied copies.
  • One exchange claim and explicit crash recovery. A callback first wins the durable claim. Failures interrupt it; process loss leaves a non-replayable claim for authenticated interruption. Duplicate concurrent callbacks do not interrupt the winning exchange.
  • Bound result reads independently. The intent ends at the earlier of ten minutes and Access expiry, then can only shorten to Slack expiry. Result reads expire ten minutes after the original intent deadline; fresh Access cannot extend either window.
  • Pin query transport and keep activation separate. Live discovery still advertises query/code, RS256 and confidential-client authentication. Protocol drift fails closed. No startup, HTTP route, UI, production OAuth configuration, authorization consumer or email-bridge change is included.
Validation (14 criteria)
Criterion Proof
Signed proofs, memory/SQLite ceremonies and nearest regressions: 110 tests passed EXIT 0: npx vitest run src/channels/linkProofs.test.ts src/core/identity/linkCeremony.test.ts src/core/identity/link.test.ts src/core/identity/directory.test.ts src/channels/accessAuth.test.ts
Changed-set TypeScript, including identity contracts and tests EXIT 0: env NODE_OPTIONS=--max-old-space-size=6144 npx tsc --noEmit -p /tmp/tsconfig-u2c.json
Changed-file formatting EXIT 0: npx prettier --check $(git diff --name-only origin/main...HEAD)
Changed TypeScript lint EXIT 0: npx eslint $(git diff --name-only origin/main...HEAD -- '*.ts')
Public-tree hygiene EXIT 0: npm run hygiene:check
All spec proof references and header paths resolve EXIT 0: npm run specs:check
Changed sources covered; no test guard weakened EXIT 0: npm run specs:coverage -- --changed origin/main...HEAD --test-guard --require
Generated documentation remains current EXIT 0: npm run docs:check
Decision status and accepted-body guards unchanged EXIT 0: npm run decisions:check
PR changelog title EXIT 0: npm run check:pr-title -- feat(authz): verify human proofs for offline account linking
Clock and duration ratchets clock exit=0 — npm run clock:check
Red-green development New Access, Slack and ceremony test files each first failed with missing implementation (exit=1), then passed after implementation.
Current-base push and draft isolation Rebased onto origin/main 358c21a before gates; pushed 68c14f3. No changed path overlaps draft #1625's 164 inspected paths.
Exact-head CI and independent review Not yet proven: GitHub returned no check runs or PR for the pushed head at handoff. Parent Ship must publish and obtain CI plus independent exact-head review; no self-approval or merge.
For agents

Bound unit branch retained as contracted. Initial rebase was caa2887; final pre-push rebase advanced to 358c21a without conflicts. No generated artifacts or decision records changed. Scoped /tmp/tsconfig-u2c.json extends the repository tsconfig, sets absolute rootDir and node_modules/@types typeRoots, and includes src/channels/linkProof*.ts, src/core/identity/.ts and src/core/identity/testing/.ts. Initial external config lacked typeRoots and could not resolve Node types; corrected before the passing gate. Full suite/typecheck/verification are CI-only and were not run locally. Independent fresh-context review is unavailable to this coding child; parent Ship owns it. Cross-model review skipped in this non-interactive run. Live transport logging/redaction and cookie/CSRF/rate-limit acceptance remain deliberately unproven because no ingress is installed.

🤖 Generated with Claude Code

Co-Authored-By: coreplane-switchboard[bot] <318072483+coreplane-switchboard[bot]@users.noreply.github.com>

@coreplane-switchboard coreplane-switchboard Bot left a comment

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

LGTM: No actionable defects found in the offline proof adapters and intent handoff.

Note

Approved · head 68c14f3 · no findings

Full review

No actionable findings at 68c14f343bbcc3f14ac1827b38c4100ff3cb755e.

@github-actions github-actions Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Auto-approved: coreplane-switchboard[bot] reviewed this PR and posted an LGTM verdict (see its review). This repository opted in through its REVIEW_BOT_LOGIN and REVIEW_BOT_ID variables.

@justinhelmer
justinhelmer merged commit 68e523a into main Sep 26, 2026
30 checks passed
@justinhelmer
justinhelmer deleted the plan/implement-issue-2387-as-f0ed6f/u1 branch September 26, 2026 02:45
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