Skip to content

Email lands the human ON the decision — approval deep links + honest landing states#190

Merged
ericlovold merged 23 commits into
mainfrom
claude/sanction-mcp-vision-vyn7iy
Jul 12, 2026
Merged

Email lands the human ON the decision — approval deep links + honest landing states#190
ericlovold merged 23 commits into
mainfrom
claude/sanction-mcp-vision-vyn7iy

Conversation

@ericlovold

@ericlovold ericlovold commented Jul 12, 2026

Copy link
Copy Markdown
Owner

The bug (found live by Eric)

Clicking Review & decide in an escalation email landed on the approvals inbox with no context: without the right session it silently fell back to the public demo view — an empty inbox that reads as "already resolved" while the real decision waits in another wallet. The human dropped out of the loop at exactly the moment the product asked them into it.

The fix

  • Every escalation notification deep-links its own decision. approveUrlFor(requestId)/dashboard/approvals?review=<id>, threaded through all five escalation fan-out sites (spend, tool, provision, capability, AARP access-request), the email template, webhook payloads, the Slack "Review in Sanction" button, and the AARP task links.review.
  • /login learns next — validated local-path redirect (must start with /, never //), threaded through the page and key form. Sign in, land back on the decision.
  • The approvals page resolves a ?review click to one of four honest states:
    • Waiting on you — banner + the decision card highlighted and auto-scrolled, showing who's asking, what, why it paused, the deadline, and what approving mints (the one-use grant sentence).
    • Already decided — status, when, the grant's state (issued/consumed), and the resolution note.
    • Waiting in a pool below you — named, with the each-pool's-owner-decides rule explained and a sign-in path.
    • Not visible from this wallet — a sign-in link that returns to the decision.
  • The demo fallback (or a bare no-wallet screen) can never answer a decision click again?review with no session always routes through login and back.

Verification

  • Live end-to-end in a real browser: logged-out email click → login with destination preserved → highlighted decision card with Approve/Reject → approved via the owner API → revisiting the same link shows the truthful terminal state with the note.
  • Dev-mode email output carries the deep link (Approve or reject: …?review=<id>).
  • npm run check green (852 tests, incl. new approveUrlFor cases with URL-encoding of hostile ids).

🤖 Generated with Claude Code

https://claude.ai/code/session_01KbjvLUvHRKVDKbYgZEHWMQ


Generated by Claude Code

Summary by CodeRabbit

  • New Features
    • Added deep links to open a specific approval for review and highlight the matching item in the approvals list.
    • Updated approval and escalation notifications (webhooks, Slack, and email) to use request-specific review URLs.
    • Preserved the review destination through sign-in with focused UI for linked approvals.
  • Bug Fixes
    • Hardened login redirects by sanitizing the next parameter to prevent unsafe navigation.
    • Ensured review URLs are properly encoded and fall back to the base approve page when missing.
  • Tests
    • Extended unit tests and mocks for request-specific review link generation and deep-link behavior.

claude added 20 commits July 11, 2026 23:03
Captures Eric's 2026-07-11 direction — Apollo-grade connector enable,
org scan for shadow AI / seats, autofill seat-plan recommendations,
manual path over the same roster — as a phased build spec, verified
against shipped primitives (SEATS-1, provision ladder, subtree caps,
PACK-1, KILL-1, the promoted hosted-remote-MCP item).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbjvLUvHRKVDKbYgZEHWMQ
…rding spec

Eric's 2026-07-11 strategy addition: after connect + scan, the org gets
monitoring + recommendations with zero governance assigned. Track C adds
Policy.enforcementMode (enforce|observe, shadow decisions logged, nothing
blocked) and a monitoring digest whose upgrade recommendations replay the
org's own observed contexts via the shipped simulation engine. Migration
flips pool-by-pool from observe to enforce; readiness ladder becomes the
migration UI.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbjvLUvHRKVDKbYgZEHWMQ
… on TS2554

sanctionTool<T> returns T, so a zero-param inner execute types the wrapped
execute zero-param too; calling it with {} failed tsc. Every main CI run
since #178 was failing on this line.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbjvLUvHRKVDKbYgZEHWMQ
…lease

npm still serves 0.4.0; the announced 0.5.0 was never published (and
server.json had stayed at 0.4.0, so the MCP registry would have been
mispinned even if it had been). Jump straight to 0.6.0 per Eric: bump
package.json, server.json (both fields), the serverInfo version, rebuild
the committed bundle, and update the changelog's two version claims so
the public record names the version that will actually exist.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbjvLUvHRKVDKbYgZEHWMQ
Eric's 2026-07-12 direction: fully-loaded mock client accounts he can
click through and demo — data passed A→B through the real engine, not
fixtures. Three personas mapped to the three buyer stories; driver =
seed/pulse/history over pure REST; admin oversight via the shipped
subtree views; /login-by-mgmt-key as the per-company demo door.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbjvLUvHRKVDKbYgZEHWMQ
…feed

Outside a tenant transaction the joined CredentialVault row is hidden by
RLS and Prisma returns null for the required relation — a child pool's
injection crashed the org owner's Audit page. Found live by the demo-
company driver's first full run. Regression test pins it.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbjvLUvHRKVDKbYgZEHWMQ
PR1 of docs/plans/demo-companies.md. REST-only (the customer's own
surface): seed builds HQ → company → pools → seats → vault with per-
wallet plus-addressed owner emails and wide HQ guardrails (ancestor
policies fold into every decision); pulse drives a self-verifying
day-in-the-life — token burn to ~87% of Engineering's pooled cap, a
seat denied over its own line, the spend ladder end to end, an owner
approval redeemed through the grant loop, exec→inject — and leaves two
escalations pending so every demo opens with something to approve.
pulse --watch polls the staged escalations and completes them live as
the owner clicks approve. Keys land in a gitignored key store.

Verified end-to-end against local Postgres: all expectations green,
audit export verifies (signed, chain-valid), dashboard render-checked
via headless Chromium.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbjvLUvHRKVDKbYgZEHWMQ
…ision-vyn7iy

# Conflicts:
#	docs/BACKLOG.md
#	sdk/src/adapters.test.ts
history <persona> --days N drives each backdated day through the real
engine, then shifts timestamps with a quarantined direct-DB pass
(scripts/demo/backdate.ts) that also resets WalletBudgetCounter so
enforcement state stays coherent; requires an explicit DATABASE_URL so
it can never silently touch an API-only target.

Coastline Digital stages the agency story: channels as pools with
attribution tags, bookings as outcomes, one channel throttled live by
its cost-per-outcome ceiling (min_outcomes cold-start guard holds it
silent until the 3rd booking lands), one channel frozen on stage
(WALLET_FROZEN). Harbor & Wren LLP stages the regulated story: an
already-expired contractor seat failing closed 401, clearance-3 vault
injection, tight ladders, a $450 retainer + docusign.send left pending.
HQ keys move to a shared store so all personas nest under one root.

Verified against a fresh local stack: 3 seeds + 3 backdated months +
3 pulses with zero expectation mismatches; sequential simulation over
the real month flips 4/10 decisions under a $50 cap; the audit chain
verifies across 32 decisions including backdated ones.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbjvLUvHRKVDKbYgZEHWMQ
docs/DEMO-RUNBOOK.md: bootstrap commands, the three client click-paths
with the wow moments marked (live approve→grant→complete, the CPO
throttle, the fail-closed contractor, the verifiable evidence export),
the HQ admin tour, and the caveats learned by running it (freeze
denials don't persist as rows; open on Pools, not Overview).

.github/workflows/demo-pulse.yml: daily cron re-pulses all three
companies against prod from the DEMO_KEYS_JSON secret — dashboards stay
warm and, because every pulse asserts the engine's decisions, a red run
is a daily production smoke alarm. Outcome dedupe keys now carry a date
suffix so daily re-pulses record new bookings instead of deduping into
day one (validated with a same-day re-pulse).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbjvLUvHRKVDKbYgZEHWMQ
Production has no DATABASE_URL access from the driver, so the CPO
ceiling's cold-start guard (min_outcomes) kept Bluebird silent. prime
stages it through the API alone: today's approved spends supply the
ratio's numerator and occurred_at-backdated bookings satisfy the
outcome floor. Verified live: post-prime, Bluebird's $12 charge
escalates COST_PER_OUTCOME_CEILING on getsanction.com.

Also widens the key-store gitignore to the .local-keys/ stash directory
(the previous tip briefly committed local-container-only dev keys —
worthless outside this sandbox, and rewritten out of the branch).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbjvLUvHRKVDKbYgZEHWMQ
Applied: checkout persist-credentials:false + contents:read permissions
(prod keys sit next to executed code); tsx pinned in the lockfile and
run via npm ci + npx --no-install (no registry fetch-and-execute);
workflow concurrency group (no overlapping pulses); prime documented in
the CLI header/usage; pool creation reuses createWallet; shared
isWeekday helper across personas; runbook states the per-target order
(seed → history|prime → pulse).

The real find: Bluebird's CPO arming decays — a throttled channel
approves nothing, so its windowed spend only ages out and the throttle
disarms in ~10 days (seeded bookings left the 30-day window even
sooner). Tightened prime bookings to 5/3/1 days ago (~25-day hold),
fixed the ratio math for fresh targets (4×$48=$192 → $64 > $60),
date-scoped the prime dedupe keys so re-priming mints fresh rows, and
documented the red-run→re-prime maintenance beat. The decay itself is
honest engine behavior, so it's operated around, not patched over.

Skipped with reason: history() resumability (fresh-target staging tool;
documented recovery is wipe/reseed — the mismatch guard already stops
before any backdate) and backdate index additions (AuthorizationRequest,
TokenLog, PendingApproval, Grant already carry (agentId, createdAt)
indexes; OutcomeEvent has (walletId, kind, occurredAt) — demo-scale).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbjvLUvHRKVDKbYgZEHWMQ
Policy.enforcementMode: enforce | observe. Observe runs the identical
rule fold and persists the truthful would-be decision — marked observed
in detailsJson, evidence-replayable (verified: replay.matches on a live
observed denial) — but blocks nothing: the response reads approved with
the real decision in would_be (existing SDK/MCP clients proceed
untouched), escalations log without paging anyone, and no enforcement
state is written: no cascade-counter or exec-token debits, so an
observed pool can never trip a shared cap and cause real denials for an
enforcing sibling. Freeze (KILL-1) and no-policy still enforce — the
kill switch is an owner action, and observe is a property of a policy.

Slice 1 covers the wedge routes (/authorize, /authorize/tool); the
provision/capability routes, gateway token budgets, and an
observe-everything pack ride the monitoring-digest slice (C2,
docs/plans/connector-onboarding.md Track C). Verified live on the local
stack: flip a pool to observe → a blocked spend returns authorized with
would_be.CATEGORY_BLOCKED → flip back → it denies again. Migration
between modes is a one-field policy PATCH, pool by pool.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbjvLUvHRKVDKbYgZEHWMQ
CodeRabbit caught a real one: observe mode skips the cascade counter
reserve (correct — zero enforcement writes), but that reserve was the
only subtree-cap check, so an observed row could claim would-be-approved
when enforcement would deny on a parent's cap. The would_be must be
truthful: observe now runs the read-only cascadeDailyWouldExceed check —
the same answer FUND-1's simulate path gives — and persists the
would-be SUBTREE_CAP_EXCEEDED denial, still writing no counters. Test
pins it.

Also consolidates isObserved into lib/decisions.ts (shared by both
routes; provision/capability join in C2) and gives the tool route the
same httpFor helper as spend.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbjvLUvHRKVDKbYgZEHWMQ
The escalation email's Review button was a bare link to the approvals
inbox: clicked without the right session it silently fell back to the
public demo view — an empty inbox that reads as 'already resolved' while
the real decision waits in another wallet. The human dropped out of the
loop at the exact moment we asked them in.

Now: every escalation notification (email, webhook payload, Slack
button, AARP task link) deep-links its own decision via
approveUrlFor(requestId) → /dashboard/approvals?review=<id>. A ?review
click with no session redirects to /login?next=… (validated local-path
redirect) and returns to the decision after sign-in. The approvals page
resolves the deep link to one of four honest states: waiting on you
(banner + highlighted, auto-scrolled card with why-it-paused and
what-approving-mints), already decided (status, when, grant state,
note), waiting in a descendant pool (named, with the read-only rule
explained), or not visible from this wallet (sign-in link that returns
here). The demo fallback can never answer a decision click again.

Verified live end-to-end in a real browser: logged-out email click →
login with destination preserved → highlighted decision card →
approve via API → revisit shows the truthful terminal state. Dev-mode
email output carries the deep link.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbjvLUvHRKVDKbYgZEHWMQ
@vercel

vercel Bot commented Jul 12, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
sanction Ready Ready Preview, Comment Jul 12, 2026 6:32pm

@coderabbitai

coderabbitai Bot commented Jul 12, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 56193987-a8c9-4693-a3fd-99d5e86a30f4

📥 Commits

Reviewing files that changed from the base of the PR and between f9e3139 and c8d0b79.

📒 Files selected for processing (11)
  • tests/approvals-resolution.test.ts
  • tests/authorize.route.test.ts
  • tests/authzen-aarp.route.test.ts
  • tests/authzen-hardening.test.ts
  • tests/capability.route.test.ts
  • tests/dataplane.route.test.ts
  • tests/digests.route.test.ts
  • tests/observe.route.test.ts
  • tests/provision.route.test.ts
  • tests/redteam.test.ts
  • tests/richDenials.route.test.ts
🚧 Files skipped from review as they are similar to previous changes (9)
  • tests/redteam.test.ts
  • tests/approvals-resolution.test.ts
  • tests/digests.route.test.ts
  • tests/authorize.route.test.ts
  • tests/authzen-aarp.route.test.ts
  • tests/provision.route.test.ts
  • tests/observe.route.test.ts
  • tests/dataplane.route.test.ts
  • tests/capability.route.test.ts

📝 Walkthrough

Walkthrough

Approval notifications now include request-specific review links. The approvals dashboard resolves review deep links, focuses matching decisions, and preserves unauthenticated review targets through a validated login redirect.

Changes

Approval deep-link flow

Layer / File(s) Summary
Generate and deliver request-specific URLs
lib/webhooks.ts, app/api/.../route.ts, tests/...
approveUrlFor generates encoded review links, escalation notifications use them across authorization routes, Slack renders supplied links, and webhook mocks/tests support the helper.
Resolve and focus dashboard reviews
app/dashboard/approvals/page.tsx, components/approval-queue.tsx
The approvals page classifies the requested decision and passes its identifier to the queue, which scrolls to and highlights the matching approval.
Preserve review targets through login
app/login/page.tsx, components/login-form.tsx, app/login/actions.ts
Login carries the review destination through a hidden form field and redirects only to sanitized local paths after authentication.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Notification
  participant ApprovalsPage
  participant LoginPage
  participant loginAction
  participant ApprovalQueue
  Notification->>ApprovalsPage: open ?review=requestId
  ApprovalsPage->>LoginPage: redirect with next review URL when unauthenticated
  LoginPage->>loginAction: submit credentials and next
  loginAction-->>ApprovalsPage: redirect to validated review URL
  ApprovalsPage->>ApprovalQueue: pass focusId
  ApprovalQueue->>ApprovalQueue: scroll to and highlight matching decision
Loading

Possibly related PRs

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 20.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title captures the main change set: approval deep links plus improved landing states after login.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch claude/sanction-mcp-vision-vyn7iy

Comment @coderabbitai help to get the list of available commands.

CI caught what my local gate hid: eleven route-test files mock
@/lib/webhooks with a factory returning only {deliverEvent, APPROVE_URL},
so routes now calling approveUrlFor threw 'no export defined on mock'.
The factories now provide a test-shaped approveUrlFor mirroring the real
deep-link format. (The local miss was a piped gate swallowing the exit
code — verified with pipefail this time: 870 passing.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbjvLUvHRKVDKbYgZEHWMQ

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@app/api/access/v1/access-request/route.ts`:
- Line 21: Update the vi.mock("`@/lib/webhooks`", ...) factories in the
access-request and sibling authorize, capability, and provision test suites to
expose approveUrlFor alongside their existing mocked exports. Preserve the
current deliverEvent and APPROVE_URL mocks, either by spreading the real module
or explicitly adding the approveUrlFor mock so success/replay route tests can
call it.

In `@app/dashboard/approvals/page.tsx`:
- Around line 140-179: Update the descendant branch in the focus-resolution
logic to inspect row.status before assigning kind "descendant". Preserve the
pool/title details only for approvals still pending; route already-resolved
descendant rows through the resolved outcome with their status and resolution
metadata, so stale links do not report them as waiting.

In `@app/login/actions.ts`:
- Around line 15-21: Update safeNext to reject local-looking paths containing a
backslash, including values such as `/\evil.com`, before returning them for
redirect. Preserve acceptance of valid single-slash local absolute paths and
fall back to /dashboard for protocol-relative or backslash-prefixed redirect
targets.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c6e26315-975f-4234-9869-20dba7a9e85b

📥 Commits

Reviewing files that changed from the base of the PR and between 9a29a2d and 9741f2f.

📒 Files selected for processing (12)
  • app/api/access/v1/access-request/route.ts
  • app/api/v1/authorize/capability/route.ts
  • app/api/v1/authorize/provision/route.ts
  • app/api/v1/authorize/route.ts
  • app/api/v1/authorize/tool/route.ts
  • app/dashboard/approvals/page.tsx
  • app/login/actions.ts
  • app/login/page.tsx
  • components/approval-queue.tsx
  • components/login-form.tsx
  • lib/webhooks.ts
  • tests/webhooks.test.ts

Comment thread app/api/access/v1/access-request/route.ts
Comment thread app/dashboard/approvals/page.tsx
Comment thread app/login/actions.ts Outdated
… descendant state

safeNext now rejects any next carrying a backslash (raw or %5C-encoded):
browsers normalize \ to /, so '/\evil.com' becomes the off-site
'//evil.com' protocol-relative redirect. And a deep link to an
already-settled descendant approval now says 'was already decided in
<pool>' instead of claiming it's still waiting — stale escalation
emails get the same honesty as everything else on the landing.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbjvLUvHRKVDKbYgZEHWMQ

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 11

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tests/approvals-resolution.test.ts`:
- Line 22: Update the mocked approveUrlFor implementation in the webhooks mock
to encode the provided id with encodeURIComponent before interpolating it into
the review query parameter. Preserve the existing URL and behavior when no id is
supplied, matching the production contract in approveUrlFor.

In `@tests/authorize.route.test.ts`:
- Line 25: Update the mocked approveUrlFor implementation in the `@/lib/webhooks`
mock to wrap the review id with encodeURIComponent before interpolating it into
the query string, matching the production contract while preserving the existing
behavior when no id is provided.

In `@tests/authzen-aarp.route.test.ts`:
- Line 30: Update the mocked approveUrlFor function in the webhook mock to apply
encodeURIComponent to the optional id before embedding it in the review query
parameter, while preserving the existing URL format and behavior when no id is
provided.

In `@tests/authzen-hardening.test.ts`:
- Line 29: Update the mocked approveUrlFor helper in the vi.mock definition to
wrap the interpolated id with encodeURIComponent, matching the production
URL-encoding behavior while preserving the existing optional review query
parameter format.

In `@tests/capability.route.test.ts`:
- Line 26: Update the mocked approveUrlFor function in the `@/lib/webhooks` mock
to apply encodeURIComponent to the optional id before appending it as the review
query parameter, matching the production URL encoding behavior while preserving
the existing URL when no id is provided.

In `@tests/dataplane.route.test.ts`:
- Line 29: Update the approveUrlFor mock in the `@/lib/webhooks` vi.mock to apply
encodeURIComponent to the optional review ID before interpolating it into the
URL, matching the production helper’s contract while preserving the existing
behavior when no ID is provided.

In `@tests/digests.route.test.ts`:
- Line 18: Update the mocked approveUrlFor implementation in the webhooks mock
to apply encodeURIComponent to the optional id before appending it as the review
query value, while preserving the existing URL and no-id behavior.

In `@tests/observe.route.test.ts`:
- Line 27: Update the mocked approveUrlFor implementation in the `@/lib/webhooks`
mock to encode the optional id with encodeURIComponent before appending it as
the review query parameter, matching the production implementation while
preserving the existing URL format.

In `@tests/provision.route.test.ts`:
- Line 27: Update the mocked approveUrlFor function in the `@/lib/webhooks` mock
to apply encodeURIComponent to the optional id before appending it as the review
query parameter, matching the production implementation while preserving the URL
without a query parameter when id is absent.

In `@tests/redteam.test.ts`:
- Line 48: Update the mocked approveUrlFor function in the `@/lib/webhooks` mock
to apply encodeURIComponent to the optional request ID before constructing the
review query parameter, matching production URL encoding while preserving the
existing URL when no ID is provided.

In `@tests/richDenials.route.test.ts`:
- Line 28: Update the mocked approveUrlFor helper in the `@/lib/webhooks` test
double to wrap the request id with encodeURIComponent when constructing the
review query value, while preserving the existing URL format and behavior when
no id is provided.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: c8e23c5e-e235-4658-a38d-2efd225d72bd

📥 Commits

Reviewing files that changed from the base of the PR and between 9741f2f and f9e3139.

📒 Files selected for processing (13)
  • app/dashboard/approvals/page.tsx
  • app/login/actions.ts
  • tests/approvals-resolution.test.ts
  • tests/authorize.route.test.ts
  • tests/authzen-aarp.route.test.ts
  • tests/authzen-hardening.test.ts
  • tests/capability.route.test.ts
  • tests/dataplane.route.test.ts
  • tests/digests.route.test.ts
  • tests/observe.route.test.ts
  • tests/provision.route.test.ts
  • tests/redteam.test.ts
  • tests/richDenials.route.test.ts
🚧 Files skipped from review as they are similar to previous changes (2)
  • app/login/actions.ts
  • app/dashboard/approvals/page.tsx

Comment thread tests/approvals-resolution.test.ts Outdated
Comment thread tests/authorize.route.test.ts Outdated
Comment thread tests/authzen-aarp.route.test.ts Outdated
Comment thread tests/authzen-hardening.test.ts Outdated
Comment thread tests/capability.route.test.ts Outdated
Comment thread tests/digests.route.test.ts Outdated
Comment thread tests/observe.route.test.ts Outdated
Comment thread tests/provision.route.test.ts Outdated
Comment thread tests/redteam.test.ts Outdated
Comment thread tests/richDenials.route.test.ts Outdated
CodeRabbit round 3: the test doubles interpolated review ids raw while
the real helper encodeURIComponents them — reserved-character ids could
pass in tests and misbehave in production links. All eleven mocks now
mirror the contract.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01KbjvLUvHRKVDKbYgZEHWMQ
@ericlovold
ericlovold merged commit 8e7aaaf into main Jul 12, 2026
8 checks passed
ericlovold added a commit that referenced this pull request Jul 12, 2026
…#193)

Bump 0.6.0 → 0.7.0 and drain the truth surfaces (truthsync folded into the
cut). The pack since v0.6.0: pay-per-crawl sanctionedFetch (#183),
@sanction/sdk publish-ready (#169), Sanction Local install package (#168,
#184), observe mode (#187) + observe digest & subtree rollup (#192),
email-lands-on-the-decision (#190).

Changelog gaps filled (verified against code, not the draft):
- v0.7.0 release header (theme: adoption — watch-first, install, drop-in).
- Observe mode entry (OBS-1 + OBS-2): the do-nothing on-ramp — real engine
  records the would-be outcome, blocks nothing, flip-to-enforce per pool with
  a revisioned timestamp; org roots read the whole subtree.
- Email-on-decision entry (#190): escalation notices deep-link to the exact
  pending request (approveUrlFor), not the generic inbox.

Roadmap rotated: Local install → shipped phrasing; observe-mode adoption
added to Now as shipped; arc comment updated. (Demo companies, consulting
page, and the skills work are infra/site/tooling — no product-changelog
entry, by design.)

Diff is package.json + changelog + roadmap only. Isolated branch — does not
touch the concurrent MCP-demo work.


Claude-Session: https://claude.ai/code/session_01UEw1RdHk5bmphKxr6N5mJY

Co-authored-by: Claude <noreply@anthropic.com>
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