Skip to content

feat: add signed-audit-trails teaching plugin (third governance-category skill)#496

Merged
wshobson merged 2 commits intowshobson:mainfrom
tomjwxf:feat/signed-audit-trails-skill
Apr 18, 2026
Merged

feat: add signed-audit-trails teaching plugin (third governance-category skill)#496
wshobson merged 2 commits intowshobson:mainfrom
tomjwxf:feat/signed-audit-trails-skill

Conversation

@tomjwxf
Copy link
Copy Markdown
Contributor

@tomjwxf tomjwxf commented Apr 17, 2026

Hi @wshobson, third plugin in the governance category, this one is a
teaching skill rather than a runtime hook. Pairs naturally with the existing
protect-mcp (runtime) and review-agent-governance (runtime + human
approval) plugins without overlapping them.

What this is

A single skill file, ~1,600 words, walking through signed-audit-trail setup
end-to-end: Cedar policy authoring, hook configuration, first receipt,
offline verification with @veritasacta/verify, live tamper-detection
demonstration, and CI/CD integration. Plus: the three cryptographic
invariants (JCS + Ed25519 + hash chain), a cross-implementation interop
table, SLSA composition via ResourceDescriptor byproduct, and common
pitfalls.

Why a teaching plugin alongside the two runtime ones

  • protect-mcp is the production runtime. Install it, hooks fire, receipts
    write.
  • review-agent-governance is a focused runtime for review-surface gating.
  • signed-audit-trails (this one) is for a different user: someone who has
    not yet decided to commit to the infrastructure and wants to understand
    the pattern in-session before proceeding.

A user invokes the skill via Skill in Claude Code, gets the walkthrough,
reproduces the demo locally, then either installs protect-mcp for
production use or decides receipts are not right for them. Education before
commitment.

Files in the PR

plugins/signed-audit-trails/
├── .claude-plugin/plugin.json
├── README.md
└── skills/signed-audit-trails-recipe/SKILL.md   (~1600 words)

Plus one entry added to .claude-plugin/marketplace.json, right after
protect-mcp, keeping the governance category ordered by install-then-learn
for protect-mcp and learn-then-install for signed-audit-trails.

Verification

  • python3 -m json.tool validates plugin.json and marketplace.json
  • SKILL.md frontmatter matches the existing convention in this repo
    (name, description, YAML delimiters)
  • README.md mirrors the structure of protect-mcp/README.md and
    review-agent-governance/README.md
  • All external links resolve (Cedar docs, RFCs, IETF draft, npm packages)
  • Keyword list distinguishes teaching content from runtime
    (tutorial, skill, recipe)

Notes

  • This is additive. No changes to protect-mcp or review-agent-governance.
  • The SKILL is self-contained. It references the runtime plugins but does
    not require them to be installed; a user can read the skill, run the
    demo with npx, and never touch the runtime plugin if they choose not
    to.
  • Keywords include both governance (category match) and tutorial-class
    terms for discovery.

Thanks for the governance category in #484 and #495. This rounds out the
shelf with a learn-before-commit entry point for users evaluating the
pattern.

cc @wshobson

Companion to the existing protect-mcp and review-agent-governance plugins
in the governance category. This one is a teaching skill, not a runtime
hook: a cookbook-style walkthrough for explaining, evaluating, or
demonstrating the signed-audit-trails pattern before committing to the
protect-mcp infrastructure.

Contents
────────
- plugin.json, README.md (explains this is a teaching skill, not runtime)
- skills/signed-audit-trails-recipe/SKILL.md (~1600 words) walking through:
    1. Hook configuration in .claude/settings.json
    2. Cedar policy authoring (four permit/forbid rules)
    3. Using Claude Code normally with hooks active
    4. Inspecting a produced receipt
    5. Verifying the chain with @veritasacta/verify
    6. Demonstrating tamper detection live
  Plus: three-invariant cryptographic model (JCS + Ed25519 + hash chain),
  cross-implementation interop table (4 implementations), CI/CD YAML
  snippet, SLSA composition via ResourceDescriptor byproduct, common
  pitfalls, references.

Marketplace entry
─────────────────
Added under category: "governance" immediately after protect-mcp. Keywords
emphasize the tutorial/cookbook nature ("tutorial", "skill", "recipe") to
distinguish from the runtime plugins.

Why this is useful alongside the two existing plugins
─────────────────────────────────────────────────────
protect-mcp is runtime. review-agent-governance is runtime (with human-
approval pattern). signed-audit-trails is education. A user evaluating
whether to adopt receipts can invoke the skill to get the concept and a
reproducible demo in-session, then install the runtime plugin if they
decide to proceed. Pairs naturally with /audit-chain and /verify-receipt
slash commands shipped in protect-mcp.

No new dependencies. Same IETF draft + @veritasacta/verify references as
the other governance plugins.
Copy link
Copy Markdown
Owner

@wshobson wshobson left a comment

Choose a reason for hiding this comment

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

Thanks @tomjwxf — the SKILL reads well and the three-invariants explanation (JCS + Ed25519 + hash chain) plus the cross-implementation interop table are good teaching content. One blocker before merge:

marketplace.json unicode regression (blocking). Same issue as #495: the diff rewrites literal UTF-8 characters on other, unrelated plugin entries as ASCII escapes (\u2014, \u2192, \u00e1). Please only append the new signed-audit-trails entry and leave the surrounding descriptions byte-for-byte intact. Likely a json.dump round-trip without ensure_ascii=False.

Also: this PR and #495 both modify the same section of marketplace.json and will conflict. Once the unicode issue is fixed on both, one will need to rebase on the other.

Non-blocking thoughts:

  • The refs.arewm.com/agent-commit/v0.2 link should probably be verified before merge.
  • Worth deciding with the maintainer whether a teaching plugin with no agents/commands/hooks — just one SKILL — is better as a standalone plugin or as an additional skill under plugins/protect-mcp/skills/. Both are defensible; the current decoupling has the advantage that a user evaluating receipts before committing to infrastructure can install this one alone. Your call to propose, maintainer's call to decide.
  • The walkthrough shows --fail-on-missing-policy false and then warns against it for production — good. Showing the strict form end-to-end as a second example would tighten the "production-ready" pathway.

Happy to re-review once the marketplace.json diff is clean.

tomjwxf pushed a commit to tomjwxf/wshobson-agents that referenced this pull request Apr 17, 2026
Three blocking items + one non-blocking clarification from the review:

1. marketplace.json unicode regression FIXED. Reset the file to upstream
   HEAD, then inserted ONLY the review-agent-governance entry with a
   string-based append that preserves every existing UTF-8 character
   byte-for-byte. No entries other than the new one are modified.
   `grep -c '\\u' marketplace.json` returns 0 escape sequences.

2. approve-review.md $1 → $ARGUMENTS. The marketplace slash-command
   convention (per plugins/codebase-cleanup/commands/deps-audit.md from
   PR wshobson#490) is $ARGUMENTS, which captures the full argument including
   spaces. $1 only captured the first word. Also JSON-escape the reason
   before embedding in the approval record (via python3 json.dumps) so
   quotes, backslashes, and newlines do not break the JSON body. This
   resolves the non-blocking JSON-escape note too.

3. README honesty on the approval log. Previously claimed the chain
   "records exactly which actions were human-gated and when," which was
   overstating: approval log entries under ./review-receipts/approvals/
   are plain JSON, not signed. Rewrote that paragraph to explicitly
   separate the signed PostToolUse chain (covered by
   @veritasacta/verify) from the operator-trust approval log. Points
   users at protect-mcp sign directly if they need signed approval
   records for regulated environments.

4. Added an explicit note on what the signed chain covers when the
   approval flag is present: PreToolUse short-circuits without calling
   Cedar, so the downstream PostToolUse receipt has decision:allow but
   no policy_digest. Auditors walking the chain should expect this.
   Resolves the "document the short-circuit" non-blocking item.

Not addressed (pending Seth's follow-up):
- marketplace.json conflict with wshobson#496 will be resolved by rebase order
  (whichever merges first; the other rebases)

Tests:
- python3 -m json.tool validates marketplace.json, plugin.json, hooks.json
- grep -c '\\u' on marketplace.json = 0
1. marketplace.json unicode regression FIXED. Same fix as wshobson#495: reset
   the file to upstream HEAD and did a string-based append of only the
   new signed-audit-trails entry. No other entries are touched. Verified
   with grep on \\u escape sequences returning 0.

2. refs.arewm.com/agent-commit/v0.2 link verified. curl -sIL returns
   HTTP 200; the page is live and correct.

Non-blocking thought (standalone plugin vs skill under protect-mcp)
acknowledged; keeping as standalone for v0.1 since the user-evaluation
path ('read before commit to infrastructure') works better without
requiring protect-mcp install first. Happy to restructure if the
maintainer prefers the nested-skill shape.

Strict-form --fail-on-missing-policy true production example deferred
to a follow-up SKILL revision. Not in this fix since it would be new
content rather than a bug fix.
@tomjwxf
Copy link
Copy Markdown
Contributor Author

tomjwxf commented Apr 17, 2026

Thanks @wshobson, pushed 34b0c54 addressing the blocker plus the non-blocking notes:

1. marketplace.json unicode regression — fixed. Same fix as #495: reset the file to upstream HEAD and did a string-based append of only the new signed-audit-trails entry. No other entries modified. grep -c "\\u" marketplace.json = 0. Root cause was json.dump without ensure_ascii=False; the string-based approach sidesteps that entirely so it does not regress on a future edit either.

2. refs.arewm.com/agent-commit/v0.2 — verified live. curl -sIL returns HTTP 200. Arewm pushed v0.2 a few hours ago, and my companion PR at arewm/refs.arewm.com#2 builds on v0.2 specifically. Link is correct.

3. On the standalone-plugin vs nested-skill question — your call, but here is my read. The user I had in mind for this plugin is someone evaluating the receipt pattern BEFORE they install protect-mcp. If the teaching skill lives under plugins/protect-mcp/skills/, the user has to install the runtime to read the skill, which is the opposite of the "learn before commit" path. Keeping it as a standalone plugin lets the user invoke the skill via Skill, reproduce the demo with npx (no runtime install), and only THEN install protect-mcp if they decide to proceed. Happy to restructure if you prefer the nested shape, but that is the UX argument for standalone.

4. Strict-form --fail-on-missing-policy true example — deferred. Agreed this would tighten the production-ready pathway. Not in this fix because it is new content, not a bug fix. Will add it as a follow-up SKILL revision once this merges, alongside the pinned-protect-mcp@x.y.z change I mentioned on #495.

On the #495 conflict: whichever you want to land first is fine. The string-based insertion approach in both PRs means the rebase of whichever-is-second is mechanical (just re-insert after the new first entry).

Re-requesting review.

tomjwxf pushed a commit to tomjwxf/wshobson-agents that referenced this pull request Apr 18, 2026
Closes the version-pinning suggestion from @wshobson on wshobson#494. The
tamper-detection test in plugins/protect-mcp/test/run-tests.sh
previously called `npx protect-mcp@latest` and `npx @veritasacta/verify`
with no version constraint, meaning an upstream npm publish could
flip the test green or red without any repo-side signal. Pinning
eliminates that.

Changes
───────
- plugins/protect-mcp/hooks/hooks.json: 2 x
    protect-mcp@latest -> protect-mcp@0.5.5
  (PreToolUse evaluate + PostToolUse sign)

- plugins/protect-mcp/test/run-tests.sh:
    6 x protect-mcp@latest -> protect-mcp@0.5.5
    3 x @veritasacta/verify    -> @veritasacta/verify@0.3.0
  (the four PreToolUse test invocations, keygen, sign, plus the two
  verify calls in tests 7 and 8)

- Header comment at the top of run-tests.sh now mentions the pinned
  @veritasacta/verify version for clarity.

What is NOT pinned
──────────────────
README.md and SKILL.md references remain as `npx protect-mcp@latest`
and `npx @veritasacta/verify`. Those are documentation of the pattern
a user should use in their own project, and "latest" is the right
advice for that audience. The test infrastructure is the only
executed path where pinning matters for reproducibility.

How to bump
───────────
When you want to update (e.g., protect-mcp publishes 0.6.0 with a
breaking change to --input handling), update both files together:

  perl -i -pe 's/protect-mcp\@0\.5\.5/protect-mcp\@0.6.0/g' \
    plugins/protect-mcp/hooks/hooks.json \
    plugins/protect-mcp/test/run-tests.sh

Then re-run ./plugins/protect-mcp/test/run-tests.sh to confirm the
tamper-detection guard still passes before merging.

This PR does NOT touch review-agent-governance or signed-audit-trails
because those plugins are still in review (wshobson#495, wshobson#496). Once they
land, a follow-up PR will pin them too.

Tests
─────
- python3 -m json.tool hooks.json passes
- bash -n run-tests.sh passes
- No other files touched; no marketplace.json changes (avoids conflict
  with wshobson#495 and wshobson#496)
@tomjwxf tomjwxf requested a review from wshobson April 18, 2026 01:09
@wshobson wshobson merged commit 27a7ed9 into wshobson:main Apr 18, 2026
2 checks passed
tomjwxf pushed a commit to tomjwxf/wshobson-agents that referenced this pull request Apr 19, 2026
Three blocking items + one non-blocking clarification from the review:

1. marketplace.json unicode regression FIXED. Reset the file to upstream
   HEAD, then inserted ONLY the review-agent-governance entry with a
   string-based append that preserves every existing UTF-8 character
   byte-for-byte. No entries other than the new one are modified.
   `grep -c '\\u' marketplace.json` returns 0 escape sequences.

2. approve-review.md $1 → $ARGUMENTS. The marketplace slash-command
   convention (per plugins/codebase-cleanup/commands/deps-audit.md from
   PR wshobson#490) is $ARGUMENTS, which captures the full argument including
   spaces. $1 only captured the first word. Also JSON-escape the reason
   before embedding in the approval record (via python3 json.dumps) so
   quotes, backslashes, and newlines do not break the JSON body. This
   resolves the non-blocking JSON-escape note too.

3. README honesty on the approval log. Previously claimed the chain
   "records exactly which actions were human-gated and when," which was
   overstating: approval log entries under ./review-receipts/approvals/
   are plain JSON, not signed. Rewrote that paragraph to explicitly
   separate the signed PostToolUse chain (covered by
   @veritasacta/verify) from the operator-trust approval log. Points
   users at protect-mcp sign directly if they need signed approval
   records for regulated environments.

4. Added an explicit note on what the signed chain covers when the
   approval flag is present: PreToolUse short-circuits without calling
   Cedar, so the downstream PostToolUse receipt has decision:allow but
   no policy_digest. Auditors walking the chain should expect this.
   Resolves the "document the short-circuit" non-blocking item.

Not addressed (pending Seth's follow-up):
- marketplace.json conflict with wshobson#496 will be resolved by rebase order
  (whichever merges first; the other rebases)

Tests:
- python3 -m json.tool validates marketplace.json, plugin.json, hooks.json
- grep -c '\\u' on marketplace.json = 0
tomjwxf pushed a commit to tomjwxf/wshobson-agents that referenced this pull request Apr 20, 2026
Three blocking items + one non-blocking clarification from the review:

1. marketplace.json unicode regression FIXED. Reset the file to upstream
   HEAD, then inserted ONLY the review-agent-governance entry with a
   string-based append that preserves every existing UTF-8 character
   byte-for-byte. No entries other than the new one are modified.
   `grep -c '\\u' marketplace.json` returns 0 escape sequences.

2. approve-review.md $1 → $ARGUMENTS. The marketplace slash-command
   convention (per plugins/codebase-cleanup/commands/deps-audit.md from
   PR wshobson#490) is $ARGUMENTS, which captures the full argument including
   spaces. $1 only captured the first word. Also JSON-escape the reason
   before embedding in the approval record (via python3 json.dumps) so
   quotes, backslashes, and newlines do not break the JSON body. This
   resolves the non-blocking JSON-escape note too.

3. README honesty on the approval log. Previously claimed the chain
   "records exactly which actions were human-gated and when," which was
   overstating: approval log entries under ./review-receipts/approvals/
   are plain JSON, not signed. Rewrote that paragraph to explicitly
   separate the signed PostToolUse chain (covered by
   @veritasacta/verify) from the operator-trust approval log. Points
   users at protect-mcp sign directly if they need signed approval
   records for regulated environments.

4. Added an explicit note on what the signed chain covers when the
   approval flag is present: PreToolUse short-circuits without calling
   Cedar, so the downstream PostToolUse receipt has decision:allow but
   no policy_digest. Auditors walking the chain should expect this.
   Resolves the "document the short-circuit" non-blocking item.

Not addressed (pending Seth's follow-up):
- marketplace.json conflict with wshobson#496 will be resolved by rebase order
  (whichever merges first; the other rebases)

Tests:
- python3 -m json.tool validates marketplace.json, plugin.json, hooks.json
- grep -c '\\u' on marketplace.json = 0
tomjwxf pushed a commit to tomjwxf/wshobson-agents that referenced this pull request Apr 26, 2026
Three blocking items + one non-blocking clarification from the review:

1. marketplace.json unicode regression FIXED. Reset the file to upstream
   HEAD, then inserted ONLY the review-agent-governance entry with a
   string-based append that preserves every existing UTF-8 character
   byte-for-byte. No entries other than the new one are modified.
   `grep -c '\\u' marketplace.json` returns 0 escape sequences.

2. approve-review.md $1 → $ARGUMENTS. The marketplace slash-command
   convention (per plugins/codebase-cleanup/commands/deps-audit.md from
   PR wshobson#490) is $ARGUMENTS, which captures the full argument including
   spaces. $1 only captured the first word. Also JSON-escape the reason
   before embedding in the approval record (via python3 json.dumps) so
   quotes, backslashes, and newlines do not break the JSON body. This
   resolves the non-blocking JSON-escape note too.

3. README honesty on the approval log. Previously claimed the chain
   "records exactly which actions were human-gated and when," which was
   overstating: approval log entries under ./review-receipts/approvals/
   are plain JSON, not signed. Rewrote that paragraph to explicitly
   separate the signed PostToolUse chain (covered by
   @veritasacta/verify) from the operator-trust approval log. Points
   users at protect-mcp sign directly if they need signed approval
   records for regulated environments.

4. Added an explicit note on what the signed chain covers when the
   approval flag is present: PreToolUse short-circuits without calling
   Cedar, so the downstream PostToolUse receipt has decision:allow but
   no policy_digest. Auditors walking the chain should expect this.
   Resolves the "document the short-circuit" non-blocking item.

Not addressed (pending Seth's follow-up):
- marketplace.json conflict with wshobson#496 will be resolved by rebase order
  (whichever merges first; the other rebases)

Tests:
- python3 -m json.tool validates marketplace.json, plugin.json, hooks.json
- grep -c '\\u' on marketplace.json = 0
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants