Gate a single tool in one call — credentagent.gate() + the gate-my-tool skill (#17) - #94
Gate a single tool in one call — credentagent.gate() + the gate-my-tool skill (#17)#94dzuluaga wants to merge 6 commits into
Conversation
Design settled in brainstorming: Mode-B page-less gateTool() facade (core of #23), placeholder age.over(21) policy + TODO, a minimal release-records sample server, load-bearing bypass test, writing-skills TDD. Mandate exposure and the AP2 mandate-chain DX are explicitly out of scope (separate design). Refs #17 #23 Signed-off-by: Diego Zuluaga <dfzuluaga@gmail.com>
) Wrap any MCP tool handler in one call: unproven -> the typed verification_required envelope (approve link + action-agnostic agent instruction, isError unset); proven -> the handler runs unchanged. Built on the requirements() resolver (enforcedAt: "tool"), proof keyed per provenBy subject on the client's store. DX (cold-reader validated over 3 rounds; spec's shape revised on maintainer feedback): gate(handler, { require, provenBy, name? }) — no required() wrapper noise, no order/total/currency on identity actions. Fail-fast at wrap time on payment/discount steps (no silent no-op); fail-closed on an empty provenBy (no shared proof bucket); warns once when no ceremony is mounted in-process. Additive envelope changes: custom credential ids in present.credential, resumePoll override. Bypass tests proven load-bearing by mutation (write-bypass-test): (A) deleting the proven-check entries.find in gate.ts -> 6/9 red; (B) ageVerified === true weakened to record != null -> negative-claim red; (C) provenBy(args) replaced with a shared constant -> cross-subject red. Signed-off-by: Diego Zuluaga <dfzuluaga@gmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R9tR5vZy7jPw8pZHzSFkV4 Signed-off-by: Diego Zuluaga <dfzuluaga@gmail.com>
…he facade (#92) examples/gate-my-tool-sample/ is a tiny runnable MCP server with ONE deliberately ungated release-records tool — the gate-my-tool skill's verification target and the before/after demo (README states the intent). examples/gate-any-action.mjs now uses credentagent.gate() instead of hand-rolling the envelope with buildVerificationRequired — the plumbing the old example carried (and its 'action-agnostic instruction helper is a follow-up' note) moved into the library. Signed-off-by: Diego Zuluaga <dfzuluaga@gmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R9tR5vZy7jPw8pZHzSFkV4 Signed-off-by: Diego Zuluaga <dfzuluaga@gmail.com>
) The gate package README gains 'Gate a single tool' — the cold-reader- validated quickstart (full CallToolResult refusal shown, isError semantics, the provenBy footgun warning, the no-outputSchema rule, the honest approve-page note) and the one-policy-three-surfaces table. Root README gets the short teaser; api.md documents credentagent.gate(). Honest-status wording updated: enforcedAt "tool" ships via gate(); gated() is the deprecated predecessor. Signed-off-by: Diego Zuluaga <dfzuluaga@gmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R9tR5vZy7jPw8pZHzSFkV4 Signed-off-by: Diego Zuluaga <dfzuluaga@gmail.com>
…DX fixes (#92) provenBy is now (args, extra?) => string — the SDK's per-request extra rides through, so a multi-user server keys proofs by the CALLER ((_args, extra) => extra.sessionId) instead of the requested resource. Round-4 cold readers caught that keying by the record's subject re-creates the shared-bucket bleed per record; the docs/skill/example now state the self-service assumption explicitly. New regression test pins the passthrough. Doc fixes from the same round: the stdio/approve-page paragraph hoisted and answered plainly, outputSchema rule strengthened, resume.tool default named, MCP result vocabulary + wire-JSON note, presence-only honesty line. ROADMAP Mode-B entry updated: gate() ships; the standalone proving-page mount is the next build. Sample README: stale pre-rename 'subject' option corrected. Signed-off-by: Diego Zuluaga <dfzuluaga@gmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R9tR5vZy7jPw8pZHzSFkV4 Signed-off-by: Diego Zuluaga <dfzuluaga@gmail.com>
.claude/skills/gate-my-tool/SKILL.md — wrap a named tool in credentagent.gate() with a load-bearing bypass test, in one shot. Authored with the writing-skills TDD loop: a no-skill RED baseline (which, honestly, SUCCEEDED on every core behavior thanks to this branch's fresh docs + CLAUDE.md — recorded as such; the skill's measured value is discovery compression, ~a-dozen source reads down to 2, plus trap insurance), a GREEN run with the skill (full pass incl. the wrap-removed red proof), and a REFACTOR pass folding back the GREEN auditor's five friction points. docs/superpowers/specs/2026-07-20-gate-my-tool-implementation-notes.md — the spec-deviation record (maintainer-directed cold-reader DX loop, 4 rounds), bypass-mutation evidence, skill TDD evidence, and the follow-up list for needs-decision issues (standalone proving-page mount, walletOrigin naming, ?order= param, envelope field polish, CredentAgent naming). Also notes #17's stale 'discovery surface ships in v0.1' premise. Signed-off-by: Diego Zuluaga <dfzuluaga@gmail.com> Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01R9tR5vZy7jPw8pZHzSFkV4 Signed-off-by: Diego Zuluaga <dfzuluaga@gmail.com>
|
The latest updates on your projects. Learn more about Vercel for GitHub.
|
There was a problem hiding this comment.
💡 Codex Review
Here are some automated review suggestions for this pull request.
Reviewed commit: 470b298cf8
ℹ️ About Codex in GitHub
Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you
- Open a pull request for review
- Mark a draft as ready
- Comment "@codex review".
If Codex has suggestions, it will comment; otherwise it will react with 👍.
Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".
| ); | ||
| } | ||
| // A $0 ACTION, not a sale — the gate doesn't care that there's no money. | ||
| const order: GateOrder = { id: subject, total: 0, currency: "USD", lines: [] }; |
There was a problem hiding this comment.
Make gate-only approve URLs usable
When gate() is used with the documented mounted-ceremony flow, this fabricated zero-line order is never persisted in the ceremony's orderStore. The generated /credentagent/credential?order=<subject> link is therefore rejected as “Order not found” by credential-gate/routes.ts because that route resolves every ID through resolveOrder before serving or verifying a credential. As a result, mounting removes the warning but does not let a normal gate-only tool obtain proof; the handler remains permanently refused unless application code writes the verification store directly.
Useful? React with 👍 / 👎.
Deep review —
|
Closes #17. Draft — will be split into two smaller PRs (the function, then the skill).
In plain terms
This adds one function a developer wraps around any AI-agent tool to make it require the user's consent before it runs — plus a skill that adds that wrapper automatically. Concrete example: a tool that releases someone's medical records won't hand them over until the user has proven they're allowed to see them. If they haven't, the tool asks them to prove it first, then runs.
What you're approving
credentagent.gate(handler, …)— a new library function that wraps a tool so an unproven call is refused (the tool returns "prove this first" instead of running).gate-my-tool) that installs that wrapper — you tell a coding agent "gate my release-records tool" and it does it in one step, and writes the test that proves it worked.It's opt-in — existing tools are unchanged unless a developer wraps them. Worst case if the code is wrong: a tool that should refuse an unproven caller runs anyway — so there's a test that fails the instant that protection breaks (step 2 below).
How to test
1 · Run the skill (the real test — "one command to gate a tool"):
The skill is built into this repo, so Claude Code picks it up automatically on this branch. Point it at the ungated example (
examples/gate-my-tool-sample/server.mjs) and say "gate my release-records tool." It wraps the handler incredentagent.gate(...)and writes a test. Then confirm:git checkout examples/ packages/to undo the demo edits afterward.2 · Prove the protection is real (~30s):
In
packages/credentagent-gate/src/gate.ts, addreturn true;as the first line ofproven()(this fakes "everyone is proven"), re-run the gate tests → the test named "REFUSES an unproven gated action" goes red. Restore the line → green. That's proof the gate actually blocks, not just decorates.3 · The deterministic tests:
For reviewers — the detail
credentagent.gate()replaces ~60 lines of hand-rolled refusal-envelope plumbing (and the deprecatedgated()shim) with one wrap. It's action-agnostic — the example gates a records release, not a purchase, because identity leads and payment is just one credential; that's why it's a top-level method, not under a commerce resource (the naming was settled in favour ofcredentagent.gate()overorders.gate; see #95 / feature 009).The three security-critical tests in
gate.test.ts: "REFUSES an unproven gated action" (the core control) · "cross-subject bleed — casey's proof never unlocks riley" (per-user scoping) · "a negative/absent claim is not proof" (must be an explicit yes).Files: the function + tests (
gate.ts,gate.test.ts, small edits toclient.ts/index.ts/envelope.ts/gated.ts), thegate-my-toolskill, the sample server, and README/docs. Rebased onmainafter #91 and #84 landed (kept both seams inmount.ts, kept your reader-identity README section).Note: to be split — PR 1 = the
credentagent.gate()function + its tests (careful review), PR 2 = the skill + sample (lighter), stacked on PR 1.