Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9dbd7ff
docs(005): implementation plan — Option B seams-first (ratified 2026-…
dzuluaga Jul 9, 2026
09e34fe
docs(005): record maintainer ratifications — Group-A D1-D3 + sequenci…
dzuluaga Jul 9, 2026
0ea98d4
governance: constitution v1.1.0 — HNP amendment (Decision 13, ratifie…
dzuluaga Jul 9, 2026
81ae70c
docs(005): tasks.md — seams-first task breakdown (T001-T009)
dzuluaga Jul 9, 2026
9fbfce1
feat(gate,005): HNP shared seams — Intent Mandate bounds + checkDraw …
dzuluaga Jul 9, 2026
646410e
fix(gate): CI typecheck — webcrypto.CryptoKey type alias (lib ES2022,…
dzuluaga Jul 9, 2026
7c9bae1
docs(gate): README — Delegated draws (HNP seams) section, honesty-fen…
dzuluaga Jul 9, 2026
8ab2f35
docs(examples): organize with a grouped index; move HNP demo into exa…
dzuluaga Jul 9, 2026
ff956fa
fix(gate,005): close two Codex P1s — content-address integrity + atom…
dzuluaga Jul 10, 2026
bd30369
fix(gate): CI typecheck — contentAddressId accepts object, not Record
dzuluaga Jul 10, 2026
e1d56eb
chore: re-trigger CI (dropped synchronize event on bd30369)
dzuluaga Jul 10, 2026
c2e6d70
docs(examples): clarify hnp-draws demo — one intent, many draws; cano…
dzuluaga Jul 10, 2026
ef637b4
build: add build:packages script alias (the name ~20 docs + examples …
dzuluaga Jul 10, 2026
78f2c4f
docs(examples): show the mandate's journey — agent HOLDS it, gate re-…
dzuluaga Jul 10, 2026
ce625f2
docs(examples): simplify hnp-draws demo to the 'huh, it's easy' bar (…
dzuluaga Jul 10, 2026
90ea929
docs(examples): buy() takes named parameters, not positional string
dzuluaga Jul 10, 2026
5b80968
docs(examples): reduce hnp-draws demo 133→99 lines, reviewed before c…
dzuluaga Jul 10, 2026
f7252c7
docs(examples): print the shop's unit prices above the purchases
dzuluaga Jul 10, 2026
b12a24c
docs(examples): story states quantities + rules, never prices (gate p…
dzuluaga Jul 10, 2026
0c628d5
docs(examples): make the code self-explanatory on READ (prices above …
dzuluaga Jul 10, 2026
5c88094
feat(gate,005): DelegatedGate — Stripe-grade facade over the HNP seams
dzuluaga Jul 10, 2026
e84f215
docs(examples): rename demo helper show → attempt (it spends, not jus…
dzuluaga Jul 10, 2026
cb2589c
feat(gate,005): SpendResult.remaining — the grant's headroom after ea…
dzuluaga Jul 10, 2026
8ba6d4b
merge: main (DX gate #43, incl. constitution 1.1.0-DX) into 005-hnp-s…
dzuluaga Jul 10, 2026
c1624d9
docs: STATUS refresh — DX gate merged (#43, constitution 1.2.0), 005 …
dzuluaga Jul 10, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 51 additions & 29 deletions .specify/memory/constitution.md
Original file line number Diff line number Diff line change
@@ -1,28 +1,26 @@
<!--
Sync Impact Report
- Version change: 1.0.0 → 1.1.0 (2026-07-10) — DX-gate amendment
- Bump rationale: MINOR — materially expanded guidance on Principle I (Stripe-grade API): the example is
the acceptance test for ergonomics ("fix the API, not the example"), and the DX rubric
(docs/reference/architecture-principles.md) is now a BINDING review lens at the Security-Requirements tier.
No principle removed or redefined. Reconciliation note: a CONCURRENT branch (005-hnp-seams, PR #41) also
bumps 1.0.0 → 1.1.0 for the HNP Principles-II/III/VII amendment. Whichever merges second must renumber to
1.2.0 and fold both sync-impact entries — the two amendments are independent and additive.
- Version change: 1.1.0 → 1.2.0 (2026-07-10) — folds two independent, additive MINOR amendments developed
concurrently off 1.0.0 (the reconciliation both branches anticipated): the HNP amendment (via PR #41) and
the DX-gate amendment (via PR #43). No principle removed or redefined; every pre-existing rule holds.
- HNP amendment (Principles II, III, VII — 005 Decision 13, ratified 2026-07-08): II (Context 1 may VERIFY a
pre-existing delegation grant; redeeming is not a ceremony) · III (HNP consolidation = one delegate ceremony
+ full server-side gate chain on every redemption) · VII (orthogonal `presence` axis; `trust_level` gains
`server-issued-demo`, weaker than `presence-only-demo`; `enforcedAt` gains `"intent"`). Added: Terminology &
Retained-Primitive Rulings (envelope vs grant; Mode-B gated() retained).
- DX-gate amendment (Principle I, ratified 2026-07-10): the example IS the acceptance test for ergonomics
("fix the API, not the example"); the DX rubric (docs/reference/architecture-principles.md) is a BINDING
review lens at the Security-Requirements tier.
- ── prior entry ──
- Version change: (unratified template) → 1.0.0
- Bump rationale: initial ratification of the CredentAgent SDK constitution.
- Principles: initial set — I. Stripe-grade, MCP-idiomatic API · II. The three execution contexts are
sacred · III. Consolidated checkout flow · IV. One ordered, conditional policy array · V. Extensible to
any credential · VI. structuredContent is data, not policy · VII. Honesty in the types; prefer simplicity
- Added sections: Security Requirements; Development Workflow & Quality Gates; Governance
- Removed sections: none
- Version change: (unratified template) → 1.0.0 — initial ratification (Principles I–VII; Security
Requirements; Development Workflow & Quality Gates; Governance).
- Templates checked for alignment:
✅ .specify/memory/constitution.md (filled from template)
⚠ .specify/templates/plan-template.md — its "Constitution Check" gate is generic; first /speckit-plan
MUST instantiate it against Principles I–VII + Security Requirements (no edit needed now)
✅ .specify/templates/spec-template.md — no change required (spec-grounding already practiced; see
specs/001-attesto-sdk/spec.md)
⚠ .specify/templates/tasks-template.md — ensure generated tasks include security-bypass tests
(Security Requirements) and DCO sign-off (Workflow); enforce at /speckit-tasks time
✅ plan-template.md — Constitution Check instantiated per-plan; 005's plan already reflects the
amended II/III/VII rows
✅ spec-template.md — no change required
⚠ tasks-template.md — generated tasks MUST include security-bypass tests + DCO (unchanged obligation)
⚠ CLAUDE.md — honesty-fencing section gains the presence axis AT 005 IMPLEMENTATION TIME (the axis
does not exist in code until then)
- Deferred TODOs: none
-->

Expand Down Expand Up @@ -51,15 +49,23 @@ DX regression is **blocking, at the same tier as the Security Requirements**.
### II. The three execution contexts are sacred
Every example and design decision MUST respect the split (spec §0): (1) the MCP tool handler runs ONCE when
checkout is requested and only mints the link + reports requirements — there is no phone in the loop, so it
MUST NOT perform a credential ceremony; (2) the checkout page/phone is where the gates actually run; (3) a
poll reports completion. Conflating these contexts is the documented root cause of confusion and is
forbidden.
MUST NOT perform a credential ceremony. It MAY, however, **verify a pre-existing delegation grant** minted
in an earlier, separate live ceremony: redeeming a grant is verification of consent already given, not a
new ceremony, and MUST run entirely server-side against the full gate chain. (2) the checkout page/phone is
where ceremonies actually run — including the **delegate ceremony** that mints a grant; (3) a poll reports
completion. Conflating these contexts is the documented root cause of confusion and is forbidden.

### III. Consolidated checkout flow
Checkout MUST be one handoff: the buyer opens the link once and completes all verifications and payment in
a single browser session. The agent orchestrates URLs and polls; it MUST NOT perform the ceremony. A
blocking-tool mode (for page-less tools) is roadmap, not v0.1.

These rules govern **human-present** flows. A human-not-present redemption has, by definition, no browser
session and no live buyer; its consolidation invariant is instead: **one delegate ceremony** (itself a
single consolidated handoff, per this principle) authorizes a bounded set of later redemptions, and **every
redemption runs the full server-side gate chain on every completion path** (Security Requirements). The
agent still only orchestrates and polls; it MUST NOT hold or perform authorization itself.

### IV. One ordered, conditional policy array
Gates MUST be expressed as a single ordered array: array position is run order, payment MUST settle last,
and the payment amount MUST be derived server-side from the order — never passed as a field. Gates are
Expand All @@ -80,10 +86,17 @@ a flat data manifest (`[{ credential, required, effect, label, minAge? }]`). Fun
wire; `requirements()` is the code→data boundary.

### VII. Honesty in the types; prefer simplicity
Status MUST be carried in types, not prose: `enforcedAt: "tool" | "checkout"` and
`trust_level: "presence-only-demo" | "issuer-verified"`. v0.1 is presence-only (disclosure + nonce
binding, NOT issuer/device signatures) and MUST be fenced as a demonstration, never sold as a real safety
control. Prefer simplicity — defer complexity (e.g. real mdoc-verifier integration) rather than overbuild.
Status MUST be carried in types, not prose: `enforcedAt: "tool" | "checkout" | "intent"`, an orthogonal
**`presence`** axis (`"live" | "delegated-demo" | "delegated"`) carrying *when consent happened*, and
`trust_level` (`"presence-only-demo" | "server-issued-demo" | "issuer-verified"`) carrying *how strongly
the authorization is bound* — the live-ceremony/nonce connotation lives on the presence axis, not in
`trust_level`. `"server-issued-demo"` is WEAKER than `"presence-only-demo"`: it proves issuance only, not
user authorization. Every `presence: "delegated-demo"` surface MUST carry a non-empty `disclaimer`, MUST
NOT expose any `authorizedByUser`-style field, and MUST NOT settle real value. A real HNP control requires
`presence: "delegated"` AND `trust_level: "issuer-verified"`. v0.1 human-present rails remain
presence-only (disclosure + nonce binding, NOT issuer/device signatures) and MUST be fenced as a
demonstration, never sold as a real safety control. Prefer simplicity — defer complexity (e.g. real
mdoc-verifier integration) rather than overbuild.

## Security Requirements

Expand All @@ -105,6 +118,15 @@ Load-bearing controls; a change that breaks one is blocking even in demo code (m
Until cryptographic mdoc trust verification (issuer/device signatures) lands, any gate relying on it MUST
be fenced behind a demo-only mode and MUST NOT be presented as a real safety control.

## Terminology & Retained-Primitive Rulings

- **"envelope"** refers ONLY to the Mode-B `verification_required` wire envelope (`envelope.ts`). A
standing HNP authorization is a **grant** (typed `ap2.IntentMandate`); the merchant's standing
delegation config is the **delegation policy**. The phrase "spending envelope" MUST NOT be used.
- **Mode-B `gated()`** and the wire envelope are **retained unchanged** as the page-less blocking
primitive, decoupled from HNP. Async step-up (an HNP redeem answering with a blocking envelope) remains
roadmap — explicitly out of 005's scope.

## Development Workflow & Quality Gates

- **Spec-grounded.** The spec and docs MUST cite real code (file/line). Claims that drift from the code are
Expand All @@ -126,4 +148,4 @@ Versioning (semantic): **MAJOR** — backward-incompatible principle removal or
new principle/section or materially expanded guidance; **PATCH** — clarifications and wording. Runtime
guidance for agents lives in `CLAUDE.md` and `specs/001-attesto-sdk/spec.md`.

**Version**: 1.1.0 | **Ratified**: 2026-06-25 | **Last Amended**: 2026-07-10
**Version**: 1.2.0 | **Ratified**: 2026-06-25 | **Last Amended**: 2026-07-10
25 changes: 14 additions & 11 deletions STATUS.md
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Project Status — CredentAgent

_Single source of truth for what's done, what's next, and what's waiting on you._
_Updated **2026-07-08** · rename shipped (#37): repos + packages live as CredentAgent · CI green · 247 tests pass._
_Updated **2026-07-10** · rename shipped · 0.2.0 published · 005 seams + `DelegatedGate` facade (#41, awaiting merge) · DX gate merged (#43) · constitution 1.2.0 · 288 tests pass._

> **How this file works.** Read it at the start of every working session and update it at the end. It is
> decisions-first: "Decisions for you" (each a checkbox + recommendation), then In flight / next, a rolling
Expand All @@ -19,21 +19,20 @@ _Updated **2026-07-08** · rename shipped (#37): repos + packages live as Creden
us; that window is the accepted risk.
- [ ] **Add the `CLAUDE_CODE_OAUTH_TOKEN` secret** + a `claude-code-review.yml` workflow if you want the
automated PR review (the org-managed review also covers it).
- [ ] **005 sequencing fork — decision memo ready to ratify (2026-07-03).** Ship merchant-side v0.1
(server-HMAC) first, or re-scope 005 to wallet-custody directly? Full analysis +
recommendation in [`sequencing-fork-memo.md`](specs/005-human-not-present/sequencing-fork-memo.md).
**Recommendation: Option B (wallet-custody directly), seams-first** — the spike (Runs 1–5, incl. a
green settlement) retired Option A's de-risking rationale, so A now only ships a merchant-side minting
rail the wallet model obsoletes. Build the shared gate seams first (envelope, completeOrder branch,
revocation store, typed refusals), then the wallet server (the one new backend). Not urgent to
*execute* (005 builds on 004 → publish → rename, deferred a week), but ratifying unblocks the 005 plan.
- [ ] **Confirm the 005 Group-A decisions** (D1–D3, still *tentative* per the 2026-07-01 discussion) + the
Decision-13 constitution amendment — both gate `/speckit-plan` → `/speckit-implement` for 005.
- [ ] **Approve + merge [PR #41](https://github.com/openmobilehub/credentagent/pull/41)** — 005 seams +
`DelegatedGate` facade. All checks green (DCO · build-test · claude-review), MERGEABLE, reconciled with
`main` (constitution folded to 1.2.0). Blocked only on your approving review.
- [x] **005 Group-A (D1–D3) + sequencing (Option B) + Decision-13 — RATIFIED/DONE 2026-07-08** (see Done).

---

## 🔨 In flight / next

- **005 NEXT INCREMENT (after #41 merges): the HTTP intent rail → the wallet server.** The seams + facade
land in #41; the next pieces turn them into the live "delegate on your phone → agent buys while you sleep"
demo: a `ceremony/intent/` rail (mirroring the `dcql`/`request`/`verify`/`page`/`routes` split), then the
wallet server (`credentagent-wallet`, Kotlin/JVM — the one new backend, likely a sibling repo). Wants a
short `/speckit-plan` pass. Both were explicitly out of #41's scope.
- **AP2 v2 alignment — captured as [#39](https://github.com/openmobilehub/credentagent/issues/39) +
[#40](https://github.com/openmobilehub/credentagent/issues/40)** (2026-07-08), prompted by AP2-team feedback
(Yanhe Chen, Google, Discord 2026-06-02: v1-shaped mandate, unsigned amount, unverified issuer/deviceAuth).
Expand Down Expand Up @@ -119,6 +118,9 @@ _Updated **2026-07-08** · rename shipped (#37): repos + packages live as Creden

| What | Where |
| :-- | :-- |
| **DX rubric is now a BINDING review gate + constitution 1.2.0** — `architecture-principles.md` gains Principle 12 ("the example IS the DX test") + the `DelegatedGate` golden before/after; CLAUDE.md DX section at the security-invariant tier; automated review checks it. Folds with the HNP amendment (II/III/VII) into constitution v1.2.0. | [#43](https://github.com/openmobilehub/credentagent/pull/43) |
| **005 ratified + first increment built** — Group-A D1–D3 + sequencing **Option B** + Decision-13 (constitution amendment) all ratified 2026-07-08; the shared gate seams (`checkDraw`, `RevocationStore`, `completeOrder` draw branch, typed refusals) + the Stripe-grade **`DelegatedGate`** facade + a 36-line example, **288 tests** | [PR #41](https://github.com/openmobilehub/credentagent/pull/41) (awaiting merge) |
| **HNP §12 spikes COMPLETE** — on-device (Runs 1–5, incl. a green settlement) + headless-auth (PASS: claude.ai routines carry the unattended leg; two setup gates found) | `specs/005-…` |
| **Rename EXECUTED: AttestoMCP → CredentAgent (2026-07-08)** — library ([PR #38](https://github.com/openmobilehub/credentagent/pull/38), 132 files, verified live both custody modes), GitHub repos renamed (`credentagent`, `credentagent-website`), website content ([credentagent-website#8](https://github.com/openmobilehub/credentagent-website/pull/8), Pages live), #31 retrofitted via the committed rename script | [#37](https://github.com/openmobilehub/credentagent/issues/37) |
| **Published `0.2.0` as `@openmobilehub/credentagent-*`** (release `v0.2.0-credentagent` → CI publish with provenance; `NPM_TOKEN` secret set). Full deprecation chain: `attesto-*` + `attestomcp-*` all point at `credentagent-*` | npm |
| `AttestoMcp` → `AttestoMCP` brand-casing rename (class, options type, ~171 sites across code + docs), version bumped `0.1.0` → `0.2.0` *(historical — pre-CredentAgent)* | [#26](https://github.com/openmobilehub/credentagent/issues/26) |
Expand All @@ -134,3 +136,4 @@ _Updated **2026-07-08** · rename shipped (#37): repos + packages live as Creden
- **Honesty:** `trust_level` stays `presence-only-demo` for the OpenID4VP rails (real wire crypto, no issuer
trust anchor yet) — never sold as a real safety control. A pro trademark search is advised before publish.
- **DCO** `git commit -s` on every commit; bypass tests must fail with their control removed.
- **DX is Stripe-grade or it's a request-changes** (constitution 1.2.0 Principle I + `docs/reference/architecture-principles.md`) — the example IS the DX test: if it needs a plumbing block, fix the API, not the example. Same blocking tier as the security invariants.
21 changes: 21 additions & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,26 @@
# CredentAgent examples

Each is runnable against the two `@openmobilehub/credentagent-*` packages (build them first:
`npm run build:packages`). Grouped by what they show:

**Storefront + gate** (connect to Goose / any MCP host)
- [`storefront.mjs`](#storefrontmjs--a-credential-gated-storefront-in-8-lines) — a credential-gated storefront in ~8 lines
- [`custom-credential.mjs`](#custom-credentialmjs--gate-any-action-with-any-credential) — gate any action with **any** credential (Principle V)
- [`with-x402-settlement.mjs`](#with-x402-settlementmjs--settle-payment-on-chain-via-the-settle-seam) — settle payment on-chain via the `settle` seam
- [`storefront-redis.mjs`](storefront-redis.mjs) / [`storefront-firestore.mjs`](storefront-firestore.mjs) — injectable persistence (Upstash Redis stores / Firestore catalog source)
- [`run-storefront/`](run-storefront/) — run THIS repo's storefront directly (stateful + stateless side by side)

**Gating patterns** (identity-first, beyond commerce)
- [`gate-any-action.mjs`](#gate-any-actionmjs--gate-a-non-commerce-action-identity-first-no-checkout) — gate a non-commerce action, no checkout

**Cart Mandate / stateless** (004)
- [`stateless-orders/`](stateless-orders/) — the created order rides in a signed Cart Mandate on the link

**Human-not-present** (005, preview)
- [`hnp-draws/`](hnp-draws/) — the delegated-draw "doorman": one pre-approval, good + bad draws, all decided server-side

---

## `storefront.mjs` — a credential-gated storefront in ~8 lines

A minimal, runnable agentic storefront you add to **Goose** (or any MCP host) as an HTTP connector and
Expand Down
45 changes: 45 additions & 0 deletions examples/hnp-draws/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
# `hnp-draws/` — the human-not-present "doorman" in action (005 seams, preview)

Watch the [PR #41](https://github.com/openmobilehub/credentagent/pull/41) HNP seams decide, with **no web
page and no phone** — a self-contained script that mints a **pre-approval** (an Intent Mandate) and throws
good + bad purchases (**draws**) at `completeOrder`, printing what the gate allows and refuses.

## Run it

```bash
npm run build:packages # build the two @openmobilehub/credentagent-* packages
node examples/hnp-draws/demo.mjs
```

## What you see

```
🎫 Pre-approval: "Reorder Blue Bottle coffee, up to $40, this month"
✅ 1 bag of coffee ($18) → COMPLETED (no real money moved)
⛔ reuse same transaction → REFUSED: replay
⛔ $54 cart, over the cap → REFUSED: over-cap, over-total
⛔ different store (Starbucks) → REFUSED: out-of-scope
⛔ wine on a coffee approval → REFUSED: step-up (age is never delegable)
🔴 you revoke from your phone…
⛔ another reorder → REFUSED: revoked
```

## What it proves

- **The gate is the doorman, not the agent.** Every draw is re-checked server-side in `completeOrder` —
bounds (`checkDraw`), revocation, and an **atomic single-use** consume — so a producer reaching the seam
directly is still fully checked (invariant 1). Refusals are **typed data** (a `code` per failure), not prose.
- **One pre-approval = one purchase.** Reusing a transaction id is refused; two racing draws yield exactly one
completion.
- **Age is non-delegable.** An age-restricted line always steps up to a live ceremony — a grant can never
complete it.
- **Revocation is immediate + fail-closed.** Revoke, and the next draw is refused; an unreachable revocation
store refuses too (never fail-open).

## Honest limits

The wire crypto is **real** (ES256 over the canonical draw; content-addressed `intentId`), but v0.1 has **no
issuer/DeviceKey trust anchor and no per-draw proof-of-possession** — the grant is a bearer instrument,
demo-fenced (`presence: "delegated-demo"`, `trust_level: "server-issued-demo"`), and **no real money moves**.
The user ceremony that seals the bounds on a phone, the HTTP intent rail, and the wallet server that provide a
*real* control are later increments (spec.md Out of Scope). This script is that flow's logic, minus the web.
Loading
Loading