Demo companies: three staged clients, 30-day histories, runbook, daily warm pulse - #186
Conversation
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
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
…g spec 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
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbjvLUvHRKVDKbYgZEHWMQ
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
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
|
No actionable comments were generated in the recent review. 🎉 ℹ️ Recent review info⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: ⛔ Files ignored due to path filters (1)
📒 Files selected for processing (8)
🚧 Files skipped from review as they are similar to previous changes (7)
📝 WalkthroughWalkthroughThe demo system now models three companies with generated histories, staged outcomes, expired seats, frozen pools, and pulse expectations. The runner adds HQ key persistence, priming, history, status, and multi-persona support, with scheduled GitHub Actions execution and supporting operational documentation. ChangesDemo fleet lifecycle
Estimated code review effort: 4 (Complex) | ~60 minutes Sequence Diagram(s)sequenceDiagram
participant GitHubActions
participant DemoRunner
participant KeyStore
participant SanctionAPI
GitHubActions->>KeyStore: restore DEMO_KEYS_JSON files
GitHubActions->>DemoRunner: run pulse for meridian, coastline, and harborwren
DemoRunner->>SanctionAPI: stage and verify tokens, spends, outcomes, and injections
SanctionAPI-->>DemoRunner: return decisions and outcomes
DemoRunner-->>GitHubActions: report expectation mismatches
Possibly related PRs
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
Comment |
There was a problem hiding this comment.
Actionable comments posted: 7
🧹 Nitpick comments (3)
scripts/demo/personas/meridian.ts (1)
86-113: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winExtract the duplicated "rough weekday" formula into a shared helper.
(day + 3) % 7 < 5is copy-pasted identically incoastline.tsandharborwren.ts. Consider exporting a singleisWeekday(day: number)fromlib.tsso the three personas can't silently drift.♻️ Proposed refactor
// lib.ts +export function isWeekday(dayIndex: number): boolean { + return (dayIndex + 3) % 7 < 5 +}// meridian.ts / coastline.ts / harborwren.ts - const weekday = (day + 3) % 7 < 5 + const weekday = isWeekday(day)🤖 Prompt for 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. In `@scripts/demo/personas/meridian.ts` around lines 86 - 113, Extract the shared rough-weekday calculation into an exported isWeekday(day: number) helper in lib.ts, then update history in meridian.ts and the corresponding coastline.ts and harborwren.ts logic to call it instead of evaluating (day + 3) % 7 < 5 directly. Preserve the existing weekday behavior.scripts/demo/run.ts (1)
42-56: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick winReuse
createWalletfor pool provisioning too.The pool block (unchanged lines 105-115) duplicates the same
POST /wallets+ response-shape handling thatcreateWalletnow encapsulates. Since both paths must stay contract-compatible with the same API, keeping them in sync manually risks silent drift.♻️ Proposed refactor
- const { status, json } = await call<{ id: string; management_key: string }>("/wallets", { - auth: { mgmt: company.mgmtKey }, - body: { name: pool.name, owner_email: demoEmail(`${persona.key}-${pool.name.split("/").pop()}`), parent_id: company.walletId }, - }) - if (status !== 201 && status !== 200) fail(`create pool "${pool.name}" → ${status} ${JSON.stringify(json)}`) - keys.pools[pool.name] = { walletId: json.id, mgmtKey: json.management_key } + keys.pools[pool.name] = await createWallet( + pool.name, + demoEmail(`${persona.key}-${pool.name.split("/").pop()}`), + company, + )🤖 Prompt for 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. In `@scripts/demo/run.ts` around lines 42 - 56, The pool provisioning block should reuse the existing createWallet function instead of duplicating the POST /wallets request and response handling. Replace its manual wallet creation logic with a createWallet call, passing the pool name, email, and parent credentials as appropriate, while preserving the current provisioning behavior.scripts/demo/backdate.ts (1)
22-78: 🚀 Performance & Scalability | 🔵 TrivialSolid transactional design; consider index coverage for the scan predicates.
The transaction wrapping, rollback-on-error, and the
OutcomeEvent.createdAt = occurredAtalignment plusWalletBudgetCounterreset are all sound given the stated fabrication-lite constraints. One operational note: everyUPDATEhere filters on"agentId" = ANY($2) AND "createdAt" >= $3(or"walletId" = ANY($1) AND "createdAt" >= $2) againstAuthorizationRequest,TokenLog,PendingApproval,Grant, andOutcomeEvent— worth confirming these columns are indexed (composite(agentId, createdAt)/(walletId, createdAt)) so repeated backdate passes stay cheap as these production tables grow.🤖 Prompt for 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. In `@scripts/demo/backdate.ts` around lines 22 - 78, Confirm and add composite indexes covering the backdate scan predicates used by backdateWindow: (agentId, createdAt) on AuthorizationRequest, TokenLog, PendingApproval, and Grant, and (walletId, createdAt) on OutcomeEvent. Use the project’s existing schema or migration conventions and avoid changing the transactional update behavior.
🤖 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 @.github/workflows/demo-pulse.yml:
- Line 28: Update the workflow step using actions/checkout@v4 to disable
credential persistence with persist-credentials: false, and add job-level
permissions granting only contents: read.
- Around line 18-21: Add a workflow-level concurrency group to the
scheduled/manual workflow so same-day runs cannot queue or overlap. In
scripts/demo/run.ts, check the date-suffixed outcome dedupe state before
processing and skip execution when that date was already processed, preserving
normal processing for new dates.
- Line 56: Update the demo pulse workflow around the `npx -y tsx` invocation to
install dependencies with `npm ci` first and execute the lockfile-pinned local
`tsx` dependency via the offline npm execution path. Add `tsx` as a direct
dependency and ensure it is recorded in the lockfile, replacing the runtime
registry fetch.
In `@docs/DEMO-RUNBOOK.md`:
- Around line 15-18: Update the bootstrap instructions in the demo runbook to
document the persona-specific required order: run seed, then history for
database-backed targets or prime for DB-less targets, and only then pulse.
Ensure the sequence explicitly includes prime before pulse for Coastline and
related DB-less personas.
In `@scripts/demo/personas/coastline.ts`:
- Around line 150-167: The Bluebird seed must retain at least three qualifying
bookings as the 30-day window advances, and the prime comment must use the
correct $180 spend total. Update the Bluebird `prime` outcomes to provide
ongoing in-window coverage without requiring `prime` to be rerun, and correct
the explanatory math from approximately $204 to $180.
In `@scripts/demo/run.ts`:
- Around line 2-20: Document the dispatched prime command in the header command
list and the CLI usage/error string near the command dispatcher. Describe its
expected persona argument and API-only behavior consistently with the existing
command descriptions, while preserving the current prime dispatch
implementation.
- Around line 353-377: The history() loop needs resumable progress handling
before backdateWindow() so failed runs do not replay completed days or leave
current-time rows stranded. Add a persistent completion marker or equivalent
idempotency/cleanup guard keyed by persona and day, consult it before processing
each day, and record completion only after backdateWindow() succeeds; preserve
the existing mismatch failure behavior and outcome deduplication.
---
Nitpick comments:
In `@scripts/demo/backdate.ts`:
- Around line 22-78: Confirm and add composite indexes covering the backdate
scan predicates used by backdateWindow: (agentId, createdAt) on
AuthorizationRequest, TokenLog, PendingApproval, and Grant, and (walletId,
createdAt) on OutcomeEvent. Use the project’s existing schema or migration
conventions and avoid changing the transactional update behavior.
In `@scripts/demo/personas/meridian.ts`:
- Around line 86-113: Extract the shared rough-weekday calculation into an
exported isWeekday(day: number) helper in lib.ts, then update history in
meridian.ts and the corresponding coastline.ts and harborwren.ts logic to call
it instead of evaluating (day + 3) % 7 < 5 directly. Preserve the existing
weekday behavior.
In `@scripts/demo/run.ts`:
- Around line 42-56: The pool provisioning block should reuse the existing
createWallet function instead of duplicating the POST /wallets request and
response handling. Replace its manual wallet creation logic with a createWallet
call, passing the pool name, email, and parent credentials as appropriate, while
preserving the current provisioning behavior.
🪄 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: f60bc4d2-5e63-4c16-ab24-33ddbbbc7e25
📒 Files selected for processing (10)
.github/workflows/demo-pulse.yml.gitignoredocs/BACKLOG.mddocs/DEMO-RUNBOOK.mdscripts/demo/backdate.tsscripts/demo/lib.tsscripts/demo/personas/coastline.tsscripts/demo/personas/harborwren.tsscripts/demo/personas/meridian.tsscripts/demo/run.ts
| on: | ||
| workflow_dispatch: | ||
| schedule: | ||
| - cron: "0 13 * * *" # daily, morning US |
There was a problem hiding this comment.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== workflow file ==\n'
nl -ba .github/workflows/demo-pulse.yml | sed -n '1,220p'
printf '\n== search for demo pulse references ==\n'
rg -n "demo[- ]pulse|production pulse|same-day|budget expectations|concurrency|workflow_dispatch|schedule" . -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**'
printf '\n== workflow-aware structural summary ==\n'
python3 - <<'PY'
from pathlib import Path
p = Path('.github/workflows/demo-pulse.yml')
text = p.read_text()
print('contains concurrency:', 'concurrency:' in text)
print('contains workflow_dispatch:', 'workflow_dispatch:' in text)
print('contains schedule:', 'schedule:' in text)
PYRepository: ericlovold/sanction
Length of output: 218
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== workflow file ==\n'
cat -n .github/workflows/demo-pulse.yml | sed -n '1,220p'
printf '\n== search for demo pulse references ==\n'
rg -n "demo[- ]pulse|production pulse|same-day|budget expectations|concurrency|workflow_dispatch|schedule" . -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**'
printf '\n== workflow-aware structural summary ==\n'
python3 - <<'PY'
from pathlib import Path
p = Path('.github/workflows/demo-pulse.yml')
text = p.read_text()
print('contains concurrency:', 'concurrency:' in text)
print('contains workflow_dispatch:', 'workflow_dispatch:' in text)
print('contains schedule:', 'schedule:' in text)
PYRepository: ericlovold/sanction
Length of output: 6458
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== demo runbook ==\n'
cat -n docs/DEMO-RUNBOOK.md | sed -n '1,220p'
printf '\n== demo plans ==\n'
cat -n docs/plans/demo-companies.md | sed -n '70,120p'
printf '\n== demo pulse script references ==\n'
rg -n "processed date|already-processed|same-day|date|concurrency|pulse|workflow_dispatch|schedule" scripts docs .github/workflows/demo-pulse.yml -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**'
printf '\n== pulse script outline ==\n'
fd -a "run.ts" scripts/demoRepository: ericlovold/sanction
Length of output: 26288
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== pulse implementation ==\n'
cat -n scripts/demo/run.ts | sed -n '230,330p'
printf '\n== any date/state guard in demo scripts ==\n'
rg -n "processed date|already-processed|dedupeSuffix|slice\(0, 10\)|re-pulses|same day|manual pulse|skip" scripts/demo scripts -g '!**/node_modules/**' -g '!**/dist/**' -g '!**/build/**'Repository: ericlovold/sanction
Length of output: 6760
Prevent duplicate same-day pulses.
This workflow is both scheduled and manually dispatchable, but it has no concurrency lock, and scripts/demo/run.ts only date-suffixes outcome dedupe keys. A queued rerun can still replay tokens, spends, and tools for the same day and trip the budget-denial expectations called out in the runbook. Add workflow concurrency and a processed-date skip in the runner.
🤖 Prompt for 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.
In @.github/workflows/demo-pulse.yml around lines 18 - 21, Add a workflow-level
concurrency group to the scheduled/manual workflow so same-day runs cannot queue
or overlap. In scripts/demo/run.ts, check the date-suffixed outcome dedupe state
before processing and skip execution when that date was already processed,
preserving normal processing for new dates.
| // For DB-less targets (prod): arm Bluebird's ceiling through the API alone — | ||
| // today's approved spend supplies the ratio's numerator, three backdated | ||
| // bookings (occurred_at) satisfy min_outcomes. Windowed: ~$204 / 3 ≈ $68 > | ||
| // the $60 ceiling, so the next spend escalates. `history` supersedes this | ||
| // when direct DB access exists. | ||
| prime: { | ||
| spends: [ | ||
| { seat: "bluebird-media-agent", action: "purchase", amount_usd: 45, merchant: "Google Ads", category: "marketing", description: "Search defense — week 1 batch", tags: { channel: "bluebird", play: "branded-search" }, expect: "approved" }, | ||
| { seat: "bluebird-media-agent", action: "purchase", amount_usd: 45, merchant: "Google Ads", category: "marketing", description: "Search defense — week 2 batch", tags: { channel: "bluebird", play: "branded-search" }, expect: "approved" }, | ||
| { seat: "bluebird-media-agent", action: "purchase", amount_usd: 45, merchant: "Google Ads", category: "marketing", description: "Search defense — week 3 batch", tags: { channel: "bluebird", play: "branded-search" }, expect: "approved" }, | ||
| { seat: "bluebird-media-agent", action: "purchase", amount_usd: 45, merchant: "Google Ads", category: "marketing", description: "Search defense — week 4 batch", tags: { channel: "bluebird", play: "branded-search" }, expect: "approved" }, | ||
| ], | ||
| outcomes: [ | ||
| { seat: "bluebird-media-agent", kind: "booking", value_usd: 180, play: "branded-search", dedupe_key: "bluebird-booking-p20", days_ago: 20 }, | ||
| { seat: "bluebird-media-agent", kind: "booking", value_usd: 180, play: "branded-search", dedupe_key: "bluebird-booking-p10", days_ago: 10 }, | ||
| { seat: "bluebird-media-agent", kind: "booking", value_usd: 180, play: "branded-search", dedupe_key: "bluebird-booking-p1", days_ago: 1 }, | ||
| ], | ||
| }, |
There was a problem hiding this comment.
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
# Check whether the scheduled workflow / runbook re-runs `prime` periodically
fd -e yml -e md . .github docs 2>/dev/null | xargs -I{} sh -c 'echo "== {} =="; grep -n -i "prime" {}'Repository: ericlovold/sanction
Length of output: 1244
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '== coastline.ts ==\n'
wc -l scripts/demo/personas/coastline.ts
sed -n '130,190p' scripts/demo/personas/coastline.ts
printf '\n== demo-pulse.yml ==\n'
wc -l .github/workflows/demo-pulse.yml
sed -n '1,220p' .github/workflows/demo-pulse.yml
printf '\n== DEMO-RUNBOOK.md ==\n'
wc -l docs/DEMO-RUNBOOK.md
sed -n '1,120p' docs/DEMO-RUNBOOK.mdRepository: ericlovold/sanction
Length of output: 11845
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '== coastline.ts excerpt ==\n'
sed -n '1,260p' scripts/demo/personas/coastline.ts
printf '\n== demo-pulse workflow ==\n'
sed -n '1,240p' .github/workflows/demo-pulse.yml
printf '\n== runbook mentions ==\n'
grep -RniE 'prime|pulse|coastline|bluebird' docs/DEMO-RUNBOOK.md docs 2>/dev/null | sed -n '1,120p'Repository: ericlovold/sanction
Length of output: 15312
🌐 Web query:
ericlovold/sanction scripts/demo/personas/coastline.ts demo-pulse.yml DEMO-RUNBOOK prime bluebird
💡 Result:
The references provided in your query correspond to a development environment using the Coasts tool [1][2] and the Sanction MCP server [3][4][5], which is authored by Eric Lovold [6][7]. Based on the structure of the paths and files provided: 1. ericlovold/sanction: This refers to the Sanction project, an open-source MCP (Model Context Protocol) server designed to act as an agent wallet, credential vault, and governance layer for autonomous AI agents [3][4][5]. 2. demo/personas/coastline.ts: This is a TypeScript file likely located within the demo folder of the Sanction repository or a related project. Given the context of "coastline," it is highly probable that this script is used to define or simulate agent personas or interaction patterns within a Coasts-based development environment [1][2]. 3. demo-pulse.yml: This is a configuration file, likely used for pulse-based monitoring or demonstration setups, often found in projects integrating with PulseMCP or similar monitoring/orchestration systems [5][8]. 4. DEMO-RUNBOOK: This typically refers to a markdown or text document (e.g., RUNBOOK.md) used to guide a live demonstration, detailing the sequence of commands, check-lists, and recovery steps [9]. 5. prime bluebird: These appear to be specific labels, environment identifiers, or internal names used for the demo scenario (e.g., "prime" might indicate a primary environment or persona, and "bluebird" could be the codename for a specific service or simulation branch) [9][10]. To run this demonstration, you would typically follow the procedures outlined in the DEMO-RUNBOOK and utilize the coast CLI tools (e.g., coast run, coast assign) to provision the necessary containers for the Sanction server and its demo components [1][10][2].
Citations:
- 1: https://github.com/coast-guard/coasts-demo
- 2: https://raw.githubusercontent.com/coast-guard/coasts/main/docs/skills_prompt.txt
- 3: https://github.com/ericlovold/sanction
- 4: https://registry.modelcontextprotocol.io/
- 5: https://www.pulsemcp.com/servers/sanction
- 6: https://linkedin.com/in/ericlovold
- 7: https://github.com/ericlovold
- 8: https://github.com/gurshaan17/runbook
- 9: Add presentation site + live-demo runbook camharris93/sediment#4
- 10: https://coasts.dev/docs/concepts-and-terminology/run
🏁 Script executed:
#!/bin/bash
set -euo pipefail
rg -n "cost_per_outcome_(ceiling|window_days|min_outcomes)|outcome.*ceiling|min_outcomes|pending" scripts docs .github -g '!**/*.json'Repository: ericlovold/sanction
Length of output: 9842
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '== spend rule ==\n'
sed -n '1,260p' scripts/lib.ts 2>/dev/null || true
sed -n '1,260p' lib/rules/spend.ts 2>/dev/null || true
printf '\n== outcomes context ==\n'
sed -n '1,260p' lib/outcomes.ts 2>/dev/null || true
printf '\n== coastline usage ==\n'
rg -n "bluebird-booking|prime:|pulse:" scripts/demo/personas/coastline.ts scripts/demo/run.ts docs/DEMO-RUNBOOK.md docs/plans/demo-companies.mdRepository: ericlovold/sanction
Length of output: 11536
Keep Bluebird’s CPO seed from expiring
The seeded Bluebird bookings are added once, and the daily pulse only replays spend. After ~10 days, the oldest booking drops out of the 30-day window, min_outcomes falls below 3, and Bluebird stops escalating again until prime is rerun. The comment’s math is also off: 4 × $45 = $180, not $204.
🤖 Prompt for 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.
In `@scripts/demo/personas/coastline.ts` around lines 150 - 167, The Bluebird seed
must retain at least three qualifying bookings as the 30-day window advances,
and the prime comment must use the correct $180 spend total. Update the Bluebird
`prime` outcomes to provide ongoing in-window coverage without requiring `prime`
to be rerun, and correct the explanatory math from approximately $204 to $180.
| async function history(persona: Persona, days: number) { | ||
| if (!persona.history) fail(`persona "${persona.key}" has no history generator`) | ||
| const keys = loadKeys(persona.key) | ||
| if (!keys.company) fail(`no keys for "${persona.key}" — run seed first`) | ||
| const { backdateWindow } = await import("./backdate") | ||
| const agentIds = Object.values(keys.seats).map((s) => s.agentId) | ||
| const walletIds = [keys.company.walletId, ...Object.values(keys.pools).map((p) => p.walletId)] | ||
|
|
||
| console.log(`Generating ${days - 1} backdated days for ${persona.company} (oldest first)…`) | ||
| for (let day = days - 1; day >= 1; day--) { | ||
| const plan = persona.history(day) | ||
| const nothing = !plan.tokens.length && !plan.spends.length && !(plan.outcomes?.length ?? 0) | ||
| if (nothing) continue | ||
| const batchStart = new Date(Date.now() - 2000) | ||
| const occurredAt = new Date(Date.now() - day * 24 * 3600 * 1000).toISOString() | ||
| const c = makeChecker() | ||
| await runTokens(keys, plan.tokens, c) | ||
| await runSpends(keys, plan.spends, c, { stagePending: false }) | ||
| if (plan.outcomes?.length) await runOutcomes(keys, plan.outcomes, c, { occurredAt }) | ||
| if (c.mismatches()) fail(`day -${day}: ${c.mismatches()} expectation mismatch(es) — stopping before backdate`) | ||
| const shifted = await backdateWindow({ since: batchStart, days: day, agentIds, walletIds }) | ||
| console.log(` day -${day}: ${shifted.authRows} decisions + ${shifted.tokenRows} token logs shifted`) | ||
| } | ||
| console.log(`History complete. Run pulse for today's staged state.`) | ||
| } |
There was a problem hiding this comment.
🗄️ Data Integrity & Integration | 🟠 Major | 🏗️ Heavy lift
🧩 Analysis chain
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== files ==\n'
git ls-files | rg '^(scripts/demo/run\.ts|scripts/demo/.*backdate|scripts/demo/.*keys|scripts/demo/.*history|scripts/demo/.*token|scripts/demo/.*spend|scripts/demo/.*outcome)'
printf '\n== outline run.ts ==\n'
ast-grep outline scripts/demo/run.ts --view expanded
printf '\n== targeted lines run.ts ==\n'
sed -n '300,430p' scripts/demo/run.ts
printf '\n== search helper defs/usages ==\n'
rg -n 'function (runTokens|runSpends|runOutcomes|history|backdateWindow|loadKeys|saveKeys|dedupe|stagePending|mismatches|fail\()' scripts/demo -SRepository: ericlovold/sanction
Length of output: 8322
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== backdate.ts ==\n'
wc -l scripts/demo/backdate.ts scripts/demo/lib.ts scripts/demo/run.ts
printf '\n-- backdate.ts outline --\n'
ast-grep outline scripts/demo/backdate.ts --view expanded
printf '\n-- lib.ts outline --\n'
ast-grep outline scripts/demo/lib.ts --view expanded
printf '\n== backdate.ts contents ==\n'
sed -n '1,220p' scripts/demo/backdate.ts
printf '\n== lib.ts contents (relevant) ==\n'
sed -n '1,240p' scripts/demo/lib.ts
printf '\n== helper functions in run.ts ==\n'
sed -n '180,280p' scripts/demo/run.ts
printf '\n== search for progress/dedupe/history state ==\n'
rg -n 'pending|progress|resume|dedupe|dedup|history|last-completed|completed day|sidecar|backdateWindow|occurredAt' scripts/demo -SRepository: ericlovold/sanction
Length of output: 23548
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== search for models and endpoints ==\n'
rg -n 'TokenLog|AuthorizationRequest|OutcomeEvent|dedupe_key|dedupeKey|PendingApproval|Grant|/tokens|/authorize|/outcomes' . -S --glob '!scripts/demo/**'
printf '\n== possible schema files ==\n'
git ls-files | rg '(^prisma/|schema\.prisma$|drizzle|migrations|packages/.*/schema|src/.*/schema|server/.*/schema)'
printf '\n== targeted unique constraint search ==\n'
rg -n 'unique|@@unique|`@unique`|dedupe' . -S --glob '!scripts/demo/**'Repository: ericlovold/sanction
Length of output: 50376
🏁 Script executed:
#!/bin/bash
set -euo pipefail
printf '\n== tokens route outline + relevant lines ==\n'
ast-grep outline app/api/v1/tokens/route.ts --view expanded
sed -n '1,220p' app/api/v1/tokens/route.ts
printf '\n== authorize route outline + relevant lines ==\n'
ast-grep outline app/api/v1/authorize/route.ts --view expanded
sed -n '1,260p' app/api/v1/authorize/route.ts
printf '\n== persona history generators ==\n'
for f in scripts/demo/personas/*.ts; do
echo "--- $f"
rg -n 'history:\s*\(|dedupe_key|tokens:|spends:|outcomes:' "$f" -n -A40 -B8
doneRepository: ericlovold/sanction
Length of output: 35644
Make history() resumable before backdating scripts/demo/run.ts:353-377
A failure before backdateWindow() leaves that day’s token logs and authorization rows at “now”, and reruns start from days - 1 again with no progress marker. outcomes are already idempotent via dedupe_key, but the failed run still leaves the original row stranded if the shift never happens. Persist the last completed day or add a cleanup/idempotency guard so retries don’t replay earlier days and leave mixed timestamps.
🤖 Prompt for 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.
In `@scripts/demo/run.ts` around lines 353 - 377, The history() loop needs
resumable progress handling before backdateWindow() so failed runs do not replay
completed days or leave current-time rows stranded. Add a persistent completion
marker or equivalent idempotency/cleanup guard keyed by persona and day, consult
it before processing each day, and record completion only after backdateWindow()
succeeds; preserve the existing mismatch failure behavior and outcome
deduplication.
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
Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01KbjvLUvHRKVDKbYgZEHWMQ
What this is
The demo-companies arc (
docs/plans/demo-companies.md), PR2+PR3+prod-hardening — the driver's PR1 landed in #184. Three fictional clients now live in production under one Demo HQ, built entirely through the public REST API:In this PR
scripts/demo/:history --days N(API-driven days + quarantined direct-DB backdate that keepsWalletBudgetCountercoherent; requires explicitDATABASE_URL),prime(API-only ceiling arming for DB-less targets — how prod got staged), Coastline + Harbor & Wren manifests, HQ keys in a shared store, date-suffixed outcome dedupe for daily re-pulses.docs/DEMO-RUNBOOK.md: per-client click-paths with wow moments, the admin subtree tour, caveats learned by running it..github/workflows/demo-pulse.yml: daily warm pulse against prod from theDEMO_KEYS_JSONsecret. Every pulse asserts the engine's decisions, so a red run doubles as a production smoke alarm. Scheduled workflows fire from the default branch — merging this is what activates the cron.Verification
valid: true, 32 entries); sequential simulation over the real month flips 4/10 decisions under a $50 draft capCOST_PER_OUTCOME_CEILINGpost-prime; the audit page (the Prod patch: audit-page crash fix + red-CI fix, sanction-mcp 0.6.0, demo-company driver, MCP vision specs #184 fix) renders vault-injection rows at HTTP 200npm run checkgreen🤖 Generated with Claude Code
https://claude.ai/code/session_01KbjvLUvHRKVDKbYgZEHWMQ
Generated by Claude Code
Summary by CodeRabbit
history,status, andprimecommands.tsxfor development.