release: v0.9.0 — the engine stops asking permission to enforce - #266
Merged
Conversation
Context-conditional tool rules, the second approved engine arc. A tool rule can carry a `when` — a CLOSED vocabulary, exactly one predicate per rule, restrictive effects only (an allow is never conditional, so allow-list semantics never depend on a clock or a counter). No DSL, no expressions: composition is more rules, which stays simulable and explainable. Two predicates ship: - outside_hours_utc: [start, end) UTC, wrapping midnight — "deploys escalate outside business hours". - after_model_calls_today: N — the runaway breaker: once the agent's persisted model calls pass N today, block (or escalate) matching tools. One honest correction from the design doc, recorded there: the counting signal is MODEL CALLS, not "decisions" — allowed tool calls are decision-only and never persisted, so a decisions counter would be blind to exactly the runaway loops this exists to stop. Determinism (ADR-0009): signals are captured ONCE per request by the enforcement shell — the UTC hour, and the token-log count fetched ONLY when some layer actually conditions on it — passed into the pure ladder, and persisted in the decision's evidence. Replay reads the stored snapshot, never the live clock. A rule whose signal is absent from a context is deterministically inactive, so pre-COND rows replay unchanged. Precedence: block → conditional block → allow-list → escalate → conditional escalate. Conditions ride the INHERIT-1 layers, so an org-level breaker binds every team below; parity on the AuthZEN PDP with the same signal contract. New Policy.toolConditions Json column (migration included), validated in lib/policy.ts (one predicate, hour bounds, start != end), writable through the existing PATCH /wallets/policy and console actions via policyInputSchema. OpenAPI documents the shape. Console: tool-posture card counts conditionals; the inherited card names them. Simulate boundary, stated not hidden: the tool ladder predates SIM-1's simulatable set; candidate tool_conditions echo back in ignored_fields. Verified: 13 new unit tests (window boundaries + midnight wrap, threshold edges with counts in the reason, absent-signal inactivity, precedence, prefix-glob), 4 route tests (threshold deny with signals persisted in evidence; the counter queried only when conditioned on; a parent's conditional rule binding the child), and both predicates live-fired in both directions against the seeded org — including catching and redoing a false positive where an unconditional escalate masked the conditional one. Policy page render-checked. Truth surfaces: TRACEABILITY COND-1 row + suite map, DOMAIN, roadmap Now, changelog, design doc stamped with the correction, backlog checked off. Gate: tsc clean, eslint 0 errors, 1130 passing. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_013Eb6zTfLyE42dYS8tgWfvn
/mcp/broker/<upstream> fronts registered MCP servers: every tools/call is authorized through the tool ladder (inheritance + conditions included) before it forwards; refusals come back as MCP isError results the model can read and replan on; escalations name the request_id and the _meta["sanction/grant_id"] retry path, and a redeemed grant is stripped before forwarding. Batched requests containing tools/call are refused — fail closed, never around. For brokered traffic, "a hijacked agent cannot act" is now enforcement, not cooperation. - lib/broker.ts: upstream registry (SEC-1-vaulted under mcp:<name>), request classification, forwarding with fresh outbound headers — the agent's Sanction key never reaches an upstream, and vaulted upstream credentials never reach the agent. - app/mcp/broker/[upstream]/route.ts: authorization runs as an in-process call to the /authorize/tool route handler — one enforcement shell by construction, no drift between broker and REST decisions. - app/api/v1/broker/upstreams: owner-plane register/list/revoke. - Found and fixed shipped bug: the gateway's provider-key vault read ran outside withTenant, so FORCE RLS silently returned null and connected providers 401'd PROVIDER_NOT_CONNECTED. Now tenant-scoped (and the gateway test mock now exercises withTenant). - Honesty flip: Wallet Card now says enforcement cooperative+broker, interception gateway+mcp-broker, with the scope stated plainly — traffic that bypasses the broker is not governed. - Version literals: walletCard/mcp.json/server.json now 0.8.0 (walletCard reads MCP_SERVER_VERSION instead of a hardcoded string). - Truth surfaces: README, roadmap, changelog, DOMAIN, AGENT-WALLET, TRACEABILITY, OpenAPI, backlog annotation. Tests: 17 new (broker classification/refusal + route: blocked never forwards, escalation carries the grant path, allowed forwards with upstream auth only, grant redeemed+stripped, batch refused). Full suite 1147 passing. Co-Authored-By: Claude <noreply@anthropic.com>
Eric's direction: agents' authorized spend settles in regulated stablecoins (USDC most likely), not card-rail USD — build the path. Position: stablecoin settlement is irreversible, so pre-transaction authorization becomes the only control point; the rail shift makes the engine more necessary. Slices STABLE-0..3 (settlement metadata → x402 spend gate → wallet-provider co-signer → onchain mirror), custody explicitly rejected. Market state verified 2026-08-22 (x402 under the Linux Foundation w/ Stripe/Visa/Mastercard, AP2 v0.2 mandates, GENIUS Act). Co-Authored-By: Claude <noreply@anthropic.com>
…itecture Market-structure bill (House-passed, Senate cloture 2026-09-15): its Section 604 codifies that non-custodial, non-controlling developers are not money transmitters. Adopt the design constraint now: Sanction's co-signing (STABLE-2/3) is veto-only — it can refuse a transfer the wallet initiates, never initiate or effectuate one. Co-Authored-By: Claude <noreply@anthropic.com>
Companion to stablecoin-rails.md, from a UX / financial / growth / brand / competitive research fan-out. Position: if Sanction meters, the unit is the decision (bps on spend rejected — wrong economics, wrong optics); Zapier-style bundles; self-serve tier payable via x402/USDC itself. Trust surfaces (fee meter, operator-set fee cap, per-decision receipts, grace mode) precede any invoice. Distribution ranked: MCP registries, /govern pages, Slack App Directory, x402/AP2 ecosystem listing (no policy layer listed there today), n8n before Zapier. First slices MONO-0 / DIST-0. Co-Authored-By: Claude <noreply@anthropic.com>
STABLE-0 (docs/plans/stablecoin-rails.md): POST /v1/authorize accepts
optional settlement {rail, asset, network} — a closed vocabulary
(lib/settlement.ts; a chain network is refused on a non-x402 rail so
evidence cannot lie). Inert to the decision like attribution tags, rides
detailsJson, surfaces in the audit feed and CSV, documented in OpenAPI.
Stablecoin settlement is irreversible, so pre-transaction authorization
is the only control point — the ledger is rail-aware before any
rail-specific enforcement exists.
MONO-0 (docs/plans/monetization-and-distribution.md): per-wallet,
per-UTC-month WalletDecisionCounter incremented once per FRESH engine
decision across all four authorize shells (approve/escalate/deny alike,
observe included). Idempotent replays, grant redemptions, simulate runs,
and frozen short-circuits never count; increments run via after() and
swallow errors — metering can never fail or slow a decision. Exposed as
month.decisions on /v1/wallets/stats and a Decisions (month) card on the
spend console. Instrumentation only: no fee attaches — the unit is
measured before it is ever priced.
Found and fixed while wiring: the provision route caught a subtree-cap
breach INSIDE its transaction, committing the ancestor counter increments
the reservation loop had already written — phantom pool spend on a denied
provision (the same defect the spend route's D1 fix removed). It now lets
the transaction roll back and persists the denial outside, evidence
attached, mirroring the spend route.
Tests: 14 new (closed-vocab settlement accept/reject, persistence, meter
counts fresh approve+deny once, replay/simulate never count, meter
failure swallowed, month key UTC, D1-parity provision regression).
Full suite 1161 passing. Live-fired: x402/usdc/base persisted on an
approved row, off-vocab rail 400s, counter=2 after one spend + one
allowed tool call, console card render-checked.
Co-Authored-By: Claude <noreply@anthropic.com>
Eric's call 2026-08-22: the model gains a metered middle — Pro $20/mo, metered in decisions, early access only until real billing ships (no checkout; trust surfaces precede any invoice). Free and enterprise framing unchanged. GTM launch arc captured in the backlog. Co-Authored-By: Claude <noreply@anthropic.com>
…the unit New /blog (index + slug pages, changelog conventions: typed array in lib/blog.ts, shared Markdown rendering, static params + per-post metadata). Post #1 announces the August arc honestly: broker interception, the settlement-aware ledger, the decision meter, the GENIUS/CLARITY ground and the veto-only architecture, with the x402 spend gate and Pro tier explicitly marked next-not-shipped. Co-Authored-By: Claude <noreply@anthropic.com>
New #rails section under the canonical line ("Sanction authorizes the
spend; any rail settles it"): the irreversibility argument, the
settlement-aware ledger marked live, the x402 spend gate marked next
(never overclaimed), and the non-custodial/veto-only regulatory posture
(GENIUS as instrument law, CLARITY as pending). Pricing becomes three
cards: Free (unchanged) / Pro $20/mo early access (metered in decisions,
lead-capture CTA, no checkout) / Enterprise Agreement. Disambiguation
line: sanction (v.) — to give official authorization.
Co-Authored-By: Claude <noreply@anthropic.com>
Two self-contained HTML documents under docs/gtm/: the 14-day launch playbook (positioning, sequence, the CLARITY-vote play, content engine, Pro funnel, metrics) and "Where Sanction sits" — seven integration architectures with pre-rendered sequence diagrams (static SVG, no runtime dependencies; open in any browser). Co-Authored-By: Claude <noreply@anthropic.com>
Strategy/GTM material does not belong in the public repo (same call as the 2026-07-06 move) — the launch playbook and use-case catalog now live in ericlovold/sanction-strategy under docs/gtm/. Co-Authored-By: Claude <noreply@anthropic.com>
…allet signs An x402 402 is a demand for money, and stablecoin settlement is irreversible: the moment between receiving a payment challenge and signing it is the only control point there is. STABLE-1 takes that moment. Two surfaces, one shell (both call POST /v1/authorize in-process, so a quote can never drift from ordinary spend): - Cooperative: POST /v1/authorize/quote — post the 402 body you just received; Sanction prices it and runs the same ladder (budgets, escalation, grants, cascade caps, evidence, decision meter), with STABLE-0 settlement metadata derived from the quote itself. - Intercepting: the MCP broker gates upstream 402s and, on anything but an approval, WITHHOLDS the challenge. The refusal type structurally cannot carry payTo/maxAmountRequired — an agent cannot sign payment requirements it never received. Two rules keep pricing honest and deterministic: - No oracle, no FX. A quote is priced only when its asset is a USD-pegged stablecoin with known decimals (native USDC on Base; address + 6 decimals verified against Circle). Anything else denies as QUOTE_NOT_PRICEABLE rather than being guessed — a decision must never depend on a rate looked up at decision time (ADR-0009). - Worst case wins. A challenge offering several ways to pay is authorized at its most expensive option, and one unpriceable option poisons the whole challenge: the client picks, and we cannot know which. Still non-custodial and veto-only — no keys, no signing, refusal only, which is the shape CLARITY §604's non-controlling test describes. Tests: 28 new (pricing, worst-case selection, poisoned challenges, the gate's withholding contract, quote route, broker interception). Full suite 1189 passing. Live-fired against Postgres: $0.05 and $15 approved, $900 denied PER_TXN_LIMIT, a multi-option challenge authorized at its worst case, every row carrying settlement metadata. Truth surfaces: /platform now says the gate is live (was "next"), changelog, roadmap, TRACEABILITY, OpenAPI, README, backlog, design doc. Co-Authored-By: Claude <noreply@anthropic.com>
… hero fix Icons were still the old shield-and-checkmark mark. The whole icon set is now generated from the $ glyph in Paul's wordmark (the three green paths of sanction-wordmark-green.svg, bounds measured with getBBox rather than eyeballed), on the brand's own pine ground with the wordmark's signal green: app/icon.svg 512, rounded square (browser tab) app/favicon.ico 16 + 32, PNG-in-ICO app/apple-icon.png 180 public/icons/icon-192|512.png PWA "any" public/icons/icon-maskable-512.png glyph inside the 80% safe zone manifest theme/background move to pine so the PWA splash frames the icon in the same dark it uses. Also re-lands the hero fix from #256, which was stranded: it merged into the marketing branch after that branch had already been squashed into main, so main still read "AVAILABLE $2,500.00". Sanction is non-custodial and veto-only, so the card states the limit — "DAILY CAP / ENFORCED". Verified: icon legible at 16px on light and dark tab strips; /, /icon.svg, /favicon.ico and /apple-icon.png all 200 with the right content types. Caught in render-check and fixed: Next decodes favicon.ico and requires RGBA, and an RGB one 500s every page — noted in AGENTS.md along with the production-deploy-path and stacked-PR lessons. Co-Authored-By: Claude <noreply@anthropic.com>
The "ANTHROPIC CERTIFIED" trust strip (and the "Also certified: Clio Legal AI Fundamentals" line under it) belonged to the services-firm framing. On the reframed page it reads as personal credentials on what is now a product homepage, and it sat between the hero and the argument it leads into. Removes the section and deletes components/anthropic-certs.tsx — its own header said it served the homepage and the consulting page, and the consulting page has since split out, so nothing else referenced it. Git history keeps it if it wants a home on the services site. Verified: rendered homepage contains no ANTHROPIC CERTIFIED / Clio / cert-pill markers; hero, wallet card, and the "Why authorization comes first" section all intact. Co-Authored-By: Claude <noreply@anthropic.com>
The Moral Intention Analyst has its own space now, so it comes off this site entirely — one product, one story. - deletes app/moral-intention/ (page + reveal component) - homepage: drops the /moral-intention footer link - /about: removes the "Moral Intention" nav and footer links, the MIA sentence from the thesis paragraph, MIA from the metadata description and from Eric's bio, and Dr. A.C. Ping's team card together with the ac-ping photo placeholder — the ethics authorship belongs with MIA wherever MIA now lives, not on the authorization product's site No references remain anywhere in app/, components/, lib/ or public/. /moral-intention now 404s; if the new home has a public URL a permanent redirect there would be better than a dead end for anything already linking to it. Verified: /, /about, /platform, /blog all 200 and carry no MIA, A.C. Ping, acping, ethics-framework or brain-trust residue. tsc 0, eslint 0, 1189 tests passing. Co-Authored-By: Claude <noreply@anthropic.com>
moralintention.com is live, so the old page becomes a permanent cross-origin redirect rather than a 404 — anything already linking to it lands on the real thing, and the link equity follows. Sub-paths are preserved too (/moral-intention/:path* -> the same path on moralintention.com), so any deeper link that ever existed still resolves. Verified locally: /moral-intention -> 308 https://moralintention.com/, /moral-intention/analyzer -> 308 https://moralintention.com/analyzer, and /consulting's existing redirect plus /, /about, /platform, /blog are all unaffected. Note the destination itself could not be fetched from this sandbox — its proxy 403s that host — but DNS resolves and the domain is confirmed live by its owner. Co-Authored-By: Claude <noreply@anthropic.com>
The wordmark alone says nothing about what Sanction does — and to finance readers the name reads as OFAC first. The nav lockup now carries a descriptor: "Sanction | AGENT AUTHORIZATION", inline right with a hairline divider, on the homepage, /about and /platform. That created a second problem worth fixing in the same pass: the hero eyebrow said "Spend governance for autonomous systems" thirty pixels below it, so the page opened with two competing descriptions of the same company. The mark now owns the category and the eyebrow owns the moment — "Runaway agent spend, stopped" — which also sets up the headline instead of restating it. Mechanics: .sn-lockup-tag drops out below 1100px rather than wrapping, where the nav needs its width for links. The tagline is aria-hidden because the wordmark's alt already announces the brand — a screen reader should hear "Sanction", not "Sanction Agent authorization". Render-checked at 1440 / 1180 / 1040 / 640: visible above the breakpoint, gone below it, no wrap at any width. eslint 0 errors, 1189 tests passing. Co-Authored-By: Claude <noreply@anthropic.com>
The root site had no Open Graph image at all — layout.tsx declared
twitter card "summary_large_image" with nothing to fill it, so a link to
getsanction.com rendered a large-image card with an empty image well.
Adds:
- public/brand/sanction-lockup-{green,white}.svg — the wordmark plus the
"Agent authorization" descriptor as VECTOR OUTLINES, not <text>. Off-site
consumers (social cards, npm/registry READMEs, decks) do not load our
webfonts, so a <text> tagline would fall back to a system font and the
lockup would drift. Verified: zero <text> elements, renders identically
with no webfont loaded, legible down to 16px on light and dark.
- app/opengraph-image.png + app/twitter-image.png (1200x630) using that
lockup, with matching .alt.txt files.
Verified against a production build: og:image / twitter:image / og:image:alt
all emit, images serve 200 at the right dimensions.
…udit Cleanup sprint before handing the repo to a Codex pass. New skill `/fresh-eyes` — the inverse of `/INPUT`. Where INPUT ingests outside material INTO the project, fresh-eyes exports verified state OUT to a reader with no context, then stares at the repo the way that reader will. Two jobs, and the second is the one people skip: the export, and naming what our own fluency hides. Every claim must be verified live in the run that writes it — a handoff is read by someone who cannot check your work, so a stale claim is not caught, it is acted on. Contract is a blind-spot ledger: VERIFIED / LANDMINE / ASSUMED / UNOWNED / DECIDE / DRIFT. First run of it produced docs/handoff/CODEX-SPRINT-2026-08-23.md, and turned up one thing that matters more than the sprint: PRODUCTION IS A MERGE BEHIND MAIN. PR #259 merged at 19:23 UTC and produced no Vercel build at all. Last production deploy is #258, from the previous day. Live site confirms it — /opengraph-image.png 404s and /moral-intention still 200s instead of redirecting. Main auto-deployed three times the day before, so this is an anomaly, not configuration. Needs a human redeploy of main@5dc4c9c. Also verified and recorded: - Slack OAuth is complete end to end (SLACK-1 slice 2) — install path, interactive approve/deny, SlackInstall under RLS, fail-closed 503 when the signing secret is unset, five test files. Documented in the export. - Seven remote branches are fully absorbed into main. Each verified by locating its PR's squash commit, and for fix/hero-noncustodial (whose #256 was stranded and re-landed via #257) by content comparison. The delete command and every branch tip are recorded so it is reversible — the sandbox classifier denies the deletion, so it is queued for Eric. - Five remaining branches carry real unmerged work and are listed with what each holds, so nobody prunes them by mistake. AGENTS.md gains a dated note: a merge to main is not a deploy. Close an arc by checking production, not the merge. Gate: tsc 0, eslint 0, 1189 tests passing.
CI has been red on main for 20 consecutive runs across six days — since the v0.8.0 release (#244, 2026-08-19). Every PR since, mine included, merged straight through it. The cause is one line. `lib/mcpServer.ts` exports MCP_SERVER_VERSION, esbuild INLINES it into packages/sanction-mcp/mcp-server.js, and #244 bumped the source without rebuilding the bundle. CI regenerates the bundle and runs `git diff --exit-code packages/` as its LAST step, so every run went green through 1,191 tests and 90% coverage and then failed on: -var MCP_SERVER_VERSION = "0.7.0"; +var MCP_SERVER_VERSION = "0.8.0"; which reads like a flake at the end of a green run, and got treated as one. The published npm artifact is fine — the workflow builds fresh. What was wrong was the artifact checked into the repo. The second cost is the one worth fixing properly: a permanently-red main means nobody can distinguish a NEW failure from the standing one. The merge gate was decorative for six days. So this does three things, not one: 1. Rebuilds the bundle. `npm run build:mcp && git diff --exit-code packages/` now passes locally, which is CI's exact check. 2. /cut-release gains a step: regenerate every artifact that EMBEDS the version, not just the ones that declare it, with the reason and the verify command. The ritual is why this recurred; the ritual is where it gets fixed. 3. AGENTS.md gains a dated note: a red main is an incident, not a backlog item. Never merge into a red main assuming the red is "the known one" without reading the log. Also corrects the version-literal list in the 2026-08-23 handoff export, which omitted the generated bundle — the exact omission that caused this — and wrongly listed lib/walletCard.ts, which derives the constant rather than restating it. Gate: tsc 0, eslint 0, 1191 tests passing.
The `Dependency audit (high+)` CI step is `continue-on-error: true`, so a
green check on it means "did not block", not "clean". 16 advisories
(12 high, 4 moderate) accumulated behind that green check, several of them
request-path: Next.js SSRF in rewrites, cache confusion on bodied requests,
DoS in Server Actions; undici response desync; postcss source-map path
traversal.
Cleared in three separable steps so any regression is attributable:
1. `npm audit fix` — 13 semver-compatible: brace-expansion, fast-uri,
ip-address, js-yaml, nanoid, undici, hono, @hono/node-server, valibot,
prisma, @prisma/dev and friends. 16 -> 6.
2. next 16.2.9 -> 16.3.3, which also clears postcss and sharp. This is a
MINOR bump inside Next 16, not the major upgrade the audit output
implies — npm says "outside the stated dependency range" only because
package.json pins the version exactly, with no caret. 6 -> 3.
3. `overrides: { "deepmerge-ts": "^8.0.2" }` for the last three.
Step 3 needs explaining, because npm's own suggestion here is a trap. For
the deepmerge-ts stack-exhaustion advisory npm proposes `prisma@6.12.0` —
a MAJOR DOWNGRADE from the installed 7.10.0, which would tear out the
Prisma 7 driver adapter the entire data layer runs on. The real constraint
is that @prisma/config@7.10.0 hard-pins deepmerge-ts to exactly 7.1.5,
while the patched line is 8.0.2. An override fixes it and keeps Prisma 7.
Result: `npm audit` reports 0 vulnerabilities, Prisma stays at 7.10.0.
Verified, not assumed:
- gate: tsc 0, eslint 0, 1191 tests passing
- DB tests: 20/20 under CI's exact env, RLS isolation and e2e data plane
included (they fail locally only because the dev role cannot CREATE ROLE)
- prisma generate / validate / migrate status all load prisma.config.ts
through the overridden @prisma/config — 38 migrations, schema up to date
- next build clean; next start + Chromium on /, /platform, /slack: all
render correctly, zero page errors (only console noise is the Vercel
Analytics script, which 404s off-Vercel by design)
- the regenerated MCP bundle still reports 0.8.0 and answers `initialize`
over stdio; bundle-freshness check passes
The bundle diff is large because esbuild inlines the upgraded transitive
deps; it is committed here, per the lesson from the last PR.
Adds .github/dependabot.yml — there was none, which is why this rotted
unseen. Weekly, grouped, majors excluded from groups so each gets its own
gate. The audit step stays non-blocking on purpose: making it blocking
would let a new upstream advisory redden main with no commit behind it,
which is the exact failure that made the merge gate decorative for six days.
Vercel Connect reached GA 2026-08-25 — runtime-minted short-lived scoped credentials, 100+ connectors, replacing stored provider secrets. Verified against vercel.com/docs/connect, not the launch post. It closes a problem Sanction never claimed to solve, and leaves one open. Read the token request wire format: the requested `scopes` come from the CALLER. Connect authenticates the requester via deployment OIDC and issues a correctly-scoped, correctly-expiring credential. It is an issuer, not a decision point. An agent in a loop asking for a legitimate scope 4,000 times gets 4,000 correctly-scoped, correctly-expiring tokens. The composition needs no engine change: a Connect token request IS a capability acquisition, and POST /v1/authorize/capability already governs those against ordered namespaced rules — the same ladder as skill:install:* and api:host/path. Namespace it `connector:<connector>:<scope>`, authorize, then call getToken. Denied or escalated, nothing is minted; on approval the single-use grant replays as grant_id. Capability rules inherit down the wallet tree, so a department can tighten what the org allows. Ships with the boundary stated plainly, including two things the pattern does NOT do: it is cooperative, not enforcement at the issuer (an agent that skips the call still gets a token), and it cannot narrow a scope — Sanction answers yes or no on what the caller asked for. Also records where the vault still applies: agents that do not run on Vercel, and broker-held upstreams. Where Connect applies, prefer it — a credential that never exists at rest beats one encrypted at rest. Hold the mandate, not the rail: Connect owns issuance, Sanction owns the decision and the record. The follow-on arc is captured in the backlog — enforcement at the issuer via the existing AuthZEN PDP, which is the same veto-only posture as STABLE-2, and worth a conversation with Vercel rather than speculative code. Gate: tsc 0, eslint 0, 1191 tests passing.
The cut: version bump + changelog stamp + regenerated artifacts, nothing
else. Every version literal moves together this time:
package.json, package-lock.json, sdk/package.json,
packages/sanction-mcp/package.json, packages/sanction-mcp/server.json
(both fields), public/.well-known/mcp.json, lib/mcpServer.ts,
tests/wallet-card.route.test.ts — and the esbuild bundle
packages/sanction-mcp/mcp-server.js REGENERATED so the inlined
MCP_SERVER_VERSION says 0.9.0. That last step missing is what kept CI
red for six days after v0.8.0; /cut-release now carries it as a step
and this commit is its first exercise.
The pack since v0.8.0, verified against code not memory:
- BROKER-1: /mcp/broker/<upstream> authorizes every tools/call before
a byte reaches the upstream; upstream credentials vaulted server-side.
- STABLE-1: POST /v1/authorize/quote prices x402 challenges through the
ladder; the broker withholds refused challenges. USD-pegged only,
worst case wins.
- STABLE-0: settlement {rail, asset, network} on the ledger.
- MONO-0: per-wallet monthly decision counter, replay-safe.
- INHERIT-1: tool + capability rules inherit down the wallet tree —
tighten, never loosen.
- COND-1: outside_hours_utc and after_model_calls_today predicates,
signals snapshotted into evidence for replay.
- Slack: /slack install surface; OAuth-bound workspace + channel,
bot token under the wallet's SEC-1 envelope, interactive Approve/Deny
through resolveApproval.
- sanction-sdk on npm; Python LiteLLM logger in-repo.
- Security: all 16 advisories cleared (npm audit: 0), Next 16.3.3,
Dependabot standing guard.
Gates run for this cut: npm run check (1191 passing), sdk typecheck +
build + 55 tests, npm pack --dry-run for both packages (sanction-sdk
0.9.0, sanction-mcp 0.9.0), build:mcp + git diff --exit-code packages/.
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
Contributor
|
Important
This repository does not receive automatic reviews because it has fewer than 10 stars. ⚙️ Run configurationConfiguration used: defaults Review profile: CHILL Plan: Pro Plus Run ID: Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Bugbot couldn't run - usage limit reachedBugbot is counted against Cursor usage for this user or team, and this run hit a usage or spend limit. A user or team admin can review and increase usage limits in the Cursor dashboard. (requestId: serverGenReqId_44e3573d-7138-4142-93ad-34c6f1a2d3a9) |
ericlovold
pushed a commit
that referenced
this pull request
Aug 28, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
The cut PR: version bump + changelog stamp + regenerated artifacts, nothing else.
Version literals — all of them, this time
package.json+package-lock.jsonsdk/package.jsonpackages/sanction-mcp/package.jsonpackages/sanction-mcp/server.jsonpublic/.well-known/mcp.jsonlib/mcpServer.ts(MCP_SERVER_VERSION)tests/wallet-card.route.test.tspackages/sanction-mcp/mcp-server.js— regeneratedThat last row missing is what kept CI red for six days after v0.8.0.
/cut-releasenow carries it as an explicit step, and this PR is that step's first exercise.The pack (v0.8.0 → v0.9.0)
The theme: on governed paths, enforcement no longer depends on the agent's cooperation.
/mcp/broker/<upstream>authorizes everytools/callbefore a byte reaches the upstream; upstream credentials vaulted server-sidePOST /v1/authorize/quoteprices x402 challenges through the ladder; the broker withholds refused challenges/slackinstall surface, OAuth-bound, interactive Approve/Deny throughresolveApprovalGates run for this cut
npm run check— tsc 0, eslint 0, 1191 passingnpm pack --dry-run—sanction-sdk-0.9.0.tgz(21.1 kB),sanction-mcp-0.9.0.tgz(200.7 kB)npm run build:mcp && git diff --exit-code packages/— cleanAfter merge: dispatch Publish SDK and Publish MCP, tag
v0.9.0, then verify npmlatestand a cleannpx sanction-mcpfrom a temp directory before claiming it live.🤖 Generated with Claude Code
https://claude.ai/code/session_013Eb6zTfLyE42dYS8tgWfvn
Generated by Claude Code
Note
Low Risk
Metadata and generated-artifact sync only; no authorization, broker, or API behavior changes in the diff.
Overview
Release cut for v0.9.0 — bumps every published version literal from 0.8.0 → 0.9.0 across the root app,
sanction-sdk,sanction-mcp(package.json,server.json),public/.well-known/mcp.json, andMCP_SERVER_VERSIONinlib/mcpServer.ts.Adds a new top-of-list changelog entry for v0.9.0 (enforcement/broker, x402 quote gate, inheritance, decision meter, Slack approvals, dependency hygiene — narrative only; no feature code in this diff).
Regenerates
packages/sanction-mcp/mcp-server.jsso the bundled server reports 0.9.0, matching source (fixes the v0.8.0 drift called out in release process). Updatestests/wallet-card.route.test.tsto expect 0.9.0 onmcp.json.package-lock.jsonreflects the app version bump plus lockfile resolution churn under@tailwindcss/oxide-wasm32-wasi.Reviewed by Cursor Bugbot for commit 6b4fe83. Bugbot is set up for automated code reviews on this repo. Configure here.