@raishin/vanguard-frontier-agentic 3.15.0 → 3.17.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (374) hide show
  1. package/.claude-plugin/marketplace.json +2 -2
  2. package/.claude-plugin/plugin.json +3 -1
  3. package/.cursor-plugin/plugin.json +3 -1
  4. package/.github/plugin/marketplace.json +1 -1
  5. package/README.md +11 -11
  6. package/agents/AGENTS.md +1 -1
  7. package/agents/README.md +1 -1
  8. package/agents/cross-functional/agentic-security-review-agent/AGENT.md +168 -0
  9. package/agents/cross-functional/agentic-security-review-agent/harnesses/claude-code.agent.md +158 -0
  10. package/agents/cross-functional/agentic-security-review-agent/harnesses/codex.toml +165 -0
  11. package/agents/cross-functional/agentic-security-review-agent/harnesses/copilot.agent.md +163 -0
  12. package/agents/cross-functional/agentic-security-review-agent/harnesses/cursor.agent.md +158 -0
  13. package/agents/cross-functional/agentic-security-review-agent/harnesses/gemini.agent.md +164 -0
  14. package/agents/cross-functional/agentic-security-review-agent/harnesses/kiro-cli.agent.json +5 -0
  15. package/agents/cross-functional/agentic-security-review-agent/harnesses/kiro-ide.agent.md +157 -0
  16. package/agents/cross-functional/agentic-security-review-agent/metadata.json +45 -0
  17. package/agents/cross-functional/agentic-system-architecture-review-agent/AGENT.md +157 -0
  18. package/agents/cross-functional/agentic-system-architecture-review-agent/harnesses/claude-code.agent.md +147 -0
  19. package/agents/cross-functional/agentic-system-architecture-review-agent/harnesses/codex.toml +154 -0
  20. package/agents/cross-functional/agentic-system-architecture-review-agent/harnesses/copilot.agent.md +152 -0
  21. package/agents/cross-functional/agentic-system-architecture-review-agent/harnesses/cursor.agent.md +147 -0
  22. package/agents/cross-functional/agentic-system-architecture-review-agent/harnesses/gemini.agent.md +153 -0
  23. package/agents/cross-functional/agentic-system-architecture-review-agent/harnesses/kiro-cli.agent.json +5 -0
  24. package/agents/cross-functional/agentic-system-architecture-review-agent/harnesses/kiro-ide.agent.md +146 -0
  25. package/agents/cross-functional/agentic-system-architecture-review-agent/metadata.json +46 -0
  26. package/agents/typescript/typescript-maestro-agent/AGENT.md +1 -1
  27. package/agents/typescript/typescript-maestro-agent/harnesses/claude-code.agent.md +1 -1
  28. package/agents/typescript/typescript-maestro-agent/harnesses/codex.toml +1 -1
  29. package/agents/typescript/typescript-maestro-agent/harnesses/copilot.agent.md +1 -1
  30. package/agents/typescript/typescript-maestro-agent/harnesses/cursor.agent.md +1 -1
  31. package/agents/typescript/typescript-maestro-agent/harnesses/gemini.agent.md +1 -1
  32. package/agents/typescript/typescript-maestro-agent/harnesses/kiro-cli.agent.json +1 -1
  33. package/agents/typescript/typescript-maestro-agent/harnesses/kiro-ide.agent.md +1 -1
  34. package/agents/typescript/typescript-mcp-tool-contract-agent/AGENT.md +1 -1
  35. package/agents/typescript/typescript-mcp-tool-contract-agent/harnesses/claude-code.agent.md +1 -1
  36. package/agents/typescript/typescript-mcp-tool-contract-agent/harnesses/copilot.agent.md +1 -1
  37. package/agents/typescript/typescript-mcp-tool-contract-agent/harnesses/cursor.agent.md +1 -1
  38. package/agents/typescript/typescript-mcp-tool-contract-agent/harnesses/gemini.agent.md +1 -1
  39. package/agents/typescript/typescript-mcp-tool-contract-agent/harnesses/kiro-cli.agent.json +1 -1
  40. package/agents/typescript/typescript-mcp-tool-contract-agent/harnesses/kiro-ide.agent.md +1 -1
  41. package/catalog/AGENTS.md +2 -1
  42. package/catalog/agents.json +71 -0
  43. package/catalog/asset-integrity.json +1639 -92
  44. package/catalog/index.json +3 -2
  45. package/catalog/install-roles.json +24 -3
  46. package/catalog/model-assignments.json +66 -0
  47. package/catalog/mods.json +131 -0
  48. package/catalog/skill-manifest.json +134 -8
  49. package/catalog/skills.json +89 -0
  50. package/mods/README.md +220 -0
  51. package/mods/audit-trail/README.md +70 -0
  52. package/mods/audit-trail/bin/hook.mjs +65 -0
  53. package/mods/audit-trail/claude-code/UNSUPPORTED.md +11 -0
  54. package/mods/audit-trail/claude-code/settings.hooks.json +14 -0
  55. package/mods/audit-trail/codex/hooks.json +14 -0
  56. package/mods/audit-trail/kiro/UNSUPPORTED.md +7 -0
  57. package/mods/audit-trail/kiro/audit-trail-post-tool-use-hook.json +14 -0
  58. package/mods/audit-trail/lib/common.mjs +213 -0
  59. package/mods/audit-trail/lib/core.mjs +50 -0
  60. package/mods/audit-trail/lib/runtime.mjs +140 -0
  61. package/mods/audit-trail/mod.toml +37 -0
  62. package/mods/blast-radius-guard/.claude-plugin/plugin.json +5 -0
  63. package/mods/blast-radius-guard/README.md +53 -0
  64. package/mods/blast-radius-guard/bin/hook.mjs +18 -0
  65. package/mods/blast-radius-guard/claude-code/settings.hooks.json +14 -0
  66. package/mods/blast-radius-guard/codex/UNSUPPORTED.md +7 -0
  67. package/mods/blast-radius-guard/codex/hooks.json +14 -0
  68. package/mods/blast-radius-guard/hooks/hooks.json +5 -0
  69. package/mods/blast-radius-guard/hooks/register.test.ts +97 -0
  70. package/mods/blast-radius-guard/hooks/register.ts +13 -0
  71. package/mods/blast-radius-guard/kiro/UNSUPPORTED.md +7 -0
  72. package/mods/blast-radius-guard/kiro/blast-radius-guard-pre-tool-use-hook.json +14 -0
  73. package/mods/blast-radius-guard/lib/common.mjs +213 -0
  74. package/mods/blast-radius-guard/lib/core.mjs +66 -0
  75. package/mods/blast-radius-guard/lib/patterns.mjs +102 -0
  76. package/mods/blast-radius-guard/lib/runtime.mjs +140 -0
  77. package/mods/blast-radius-guard/mod.toml +34 -0
  78. package/mods/blast-radius-guard/patterns.json +101 -0
  79. package/mods/context-burn/.claude-plugin/plugin.json +5 -0
  80. package/mods/context-burn/README.md +66 -0
  81. package/mods/context-burn/bin/hook.mjs +38 -0
  82. package/mods/context-burn/claude-code/UNSUPPORTED.md +7 -0
  83. package/mods/context-burn/claude-code/settings.hooks.json +24 -0
  84. package/mods/context-burn/codex/UNSUPPORTED.md +7 -0
  85. package/mods/context-burn/codex/hooks.json +24 -0
  86. package/mods/context-burn/hooks/hooks.json +5 -0
  87. package/mods/context-burn/hooks/register.test.ts +151 -0
  88. package/mods/context-burn/hooks/register.ts +35 -0
  89. package/mods/context-burn/kiro/UNSUPPORTED.md +7 -0
  90. package/mods/context-burn/kiro/context-burn-post-tool-use-hook.json +14 -0
  91. package/mods/context-burn/kiro/context-burn-session-start-hook.json +14 -0
  92. package/mods/context-burn/lib/common.mjs +213 -0
  93. package/mods/context-burn/lib/core.mjs +79 -0
  94. package/mods/context-burn/lib/runtime.mjs +140 -0
  95. package/mods/context-burn/mod.toml +37 -0
  96. package/mods/live-guard-gate/.claude-plugin/plugin.json +5 -0
  97. package/mods/live-guard-gate/README.md +43 -0
  98. package/mods/live-guard-gate/bin/hook.mjs +12 -0
  99. package/mods/live-guard-gate/claude-code/settings.hooks.json +14 -0
  100. package/mods/live-guard-gate/codex/UNSUPPORTED.md +7 -0
  101. package/mods/live-guard-gate/codex/hooks.json +14 -0
  102. package/mods/live-guard-gate/hooks/hooks.json +5 -0
  103. package/mods/live-guard-gate/hooks/register.test.ts +59 -0
  104. package/mods/live-guard-gate/hooks/register.ts +11 -0
  105. package/mods/live-guard-gate/kiro/UNSUPPORTED.md +7 -0
  106. package/mods/live-guard-gate/kiro/live-guard-gate-pre-tool-use-hook.json +14 -0
  107. package/mods/live-guard-gate/lib/common.mjs +213 -0
  108. package/mods/live-guard-gate/lib/core.mjs +24 -0
  109. package/mods/live-guard-gate/lib/live-agents.mjs +65 -0
  110. package/mods/live-guard-gate/lib/patterns.mjs +523 -0
  111. package/mods/live-guard-gate/lib/runtime.mjs +140 -0
  112. package/mods/live-guard-gate/mod.toml +34 -0
  113. package/mods/live-guard-gate/patterns.json +522 -0
  114. package/mods/secret-redactor/.claude-plugin/plugin.json +5 -0
  115. package/mods/secret-redactor/README.md +86 -0
  116. package/mods/secret-redactor/bin/hook.mjs +35 -0
  117. package/mods/secret-redactor/claude-code/UNSUPPORTED.md +7 -0
  118. package/mods/secret-redactor/claude-code/settings.hooks.json +24 -0
  119. package/mods/secret-redactor/codex/UNSUPPORTED.md +11 -0
  120. package/mods/secret-redactor/codex/hooks.json +14 -0
  121. package/mods/secret-redactor/hooks/hooks.json +5 -0
  122. package/mods/secret-redactor/hooks/register.test.ts +150 -0
  123. package/mods/secret-redactor/hooks/register.ts +19 -0
  124. package/mods/secret-redactor/kiro/UNSUPPORTED.md +11 -0
  125. package/mods/secret-redactor/kiro/secret-redactor-pre-tool-use-hook.json +14 -0
  126. package/mods/secret-redactor/lib/common.mjs +213 -0
  127. package/mods/secret-redactor/lib/core.mjs +61 -0
  128. package/mods/secret-redactor/lib/patterns.mjs +75 -0
  129. package/mods/secret-redactor/lib/runtime.mjs +140 -0
  130. package/mods/secret-redactor/mod.toml +43 -0
  131. package/mods/secret-redactor/patterns.json +74 -0
  132. package/package.json +9 -4
  133. package/plugins/vanguard-frontier-agentic/.codex-plugin/plugin.json +1 -1
  134. package/powers/vanguard-generic/POWER.md +6 -6
  135. package/powers/vanguard-kubernetes/POWER.md +1 -1
  136. package/powers/vanguard-multi-cloud/POWER.md +1 -1
  137. package/schemas/agentic-handoff.schema.json +443 -0
  138. package/scripts/generate-kiro-powers.mjs +17 -6
  139. package/scripts/generate-mods.mjs +784 -0
  140. package/scripts/mods/common.mjs +213 -0
  141. package/scripts/mods/runtime.mjs +140 -0
  142. package/scripts/typescript_data/agents/00-typescript-maestro-agent.json +2 -2
  143. package/scripts/typescript_data/agents/11-typescript-mcp-tool-contract-agent.json +4 -4
  144. package/skills/cross-functional/agentic-board-protocol/SKILL.md +85 -0
  145. package/skills/cross-functional/agentic-board-protocol/metadata.json +28 -0
  146. package/skills/cross-functional/agentic-board-protocol/references/decision-record.md +134 -0
  147. package/skills/cross-functional/agentic-board-protocol/references/handoff-envelope.md +208 -0
  148. package/skills/cross-functional/agentic-board-protocol/references/risk-tier-crosswalk.md +88 -0
  149. package/skills/cross-functional/agentic-board-protocol/references/stop-conditions.md +122 -0
  150. package/skills/cross-functional/agentic-board-protocol/references/value-case.md +159 -0
  151. package/skills/cross-functional/agentic-security-review/SKILL.md +78 -0
  152. package/skills/cross-functional/agentic-security-review/metadata.json +30 -0
  153. package/skills/cross-functional/agentic-security-review/references/identity-and-delegated-authorization.md +144 -0
  154. package/skills/cross-functional/agentic-security-review/references/supply-chain-and-provenance.md +111 -0
  155. package/skills/cross-functional/agentic-security-review/references/threat-model-owasp-asi.md +55 -0
  156. package/skills/cross-functional/agentic-security-review/references/tools-memory-and-data-reach.md +125 -0
  157. package/skills/cross-functional/agentic-security-review/references/workflow-and-output.md +210 -0
  158. package/skills/cross-functional/agentic-system-architecture-review/SKILL.md +74 -0
  159. package/skills/cross-functional/agentic-system-architecture-review/metadata.json +31 -0
  160. package/skills/cross-functional/agentic-system-architecture-review/references/delegation-and-handoff-review.md +107 -0
  161. package/skills/cross-functional/agentic-system-architecture-review/references/pattern-selection.md +81 -0
  162. package/skills/cross-functional/agentic-system-architecture-review/references/reliability-and-containment.md +86 -0
  163. package/skills/cross-functional/agentic-system-architecture-review/references/state-memory-and-durability.md +102 -0
  164. package/skills/cross-functional/agentic-system-architecture-review/references/workflow-and-output.md +135 -0
  165. package/skills/typescript/typescript-maestro/SKILL.md +1 -1
  166. package/skills/typescript/typescript-maestro/references/routing-taxonomy.md +1 -1
  167. package/skills/typescript/typescript-mcp-tool-contract/SKILL.md +1 -1
  168. package/tests/agentic-board-suite/_build_fixtures.py +1751 -0
  169. package/tests/agentic-board-suite/_build_harnesses.py +123 -0
  170. package/tests/agentic-board-suite/agentic_board_rules.py +857 -0
  171. package/tests/agentic-board-suite/fixtures/invalid/CH-PROPOSAL-LINK--challenge-names-a-later-envelope.json +244 -0
  172. package/tests/agentic-board-suite/fixtures/invalid/CH-PROPOSAL-LINK--dangling-challenge-beside-a-request.json +410 -0
  173. package/tests/agentic-board-suite/fixtures/invalid/CH-PROPOSAL-LINK--dangling-proposal.json +187 -0
  174. package/tests/agentic-board-suite/fixtures/invalid/CH-ROUND-COUNTED--round-two-declared-as-one.json +481 -0
  175. package/tests/agentic-board-suite/fixtures/invalid/CH-ROUND-COUNTED--self-declared-round.json +187 -0
  176. package/tests/agentic-board-suite/fixtures/invalid/DR-ALTERNATIVES--no-alternatives.json +340 -0
  177. package/tests/agentic-board-suite/fixtures/invalid/DR-CHALLENGE-COMPLETE--hard-gate-left-behind-by-proposer-swap.json +532 -0
  178. package/tests/agentic-board-suite/fixtures/invalid/DR-CHALLENGE-COMPLETE--hard-gate-left-unattached.json +480 -0
  179. package/tests/agentic-board-suite/fixtures/invalid/DR-CHALLENGE-COMPLETE--hard-gate-on-root-left-unattached.json +410 -0
  180. package/tests/agentic-board-suite/fixtures/invalid/DR-CHALLENGE-COMPLETE--late-challenge-before-record.json +410 -0
  181. package/tests/agentic-board-suite/fixtures/invalid/DR-CHALLENGE-COMPLETE--superseded-request-missed-earlier-challenge.json +565 -0
  182. package/tests/agentic-board-suite/fixtures/invalid/DR-CHALLENGE-PRECEDES--withdrawal-attached-before-it-was-sent.json +483 -0
  183. package/tests/agentic-board-suite/fixtures/invalid/DR-CHALLENGE-PRECEDES--withdrawal-descends-from-the-record.json +547 -0
  184. package/tests/agentic-board-suite/fixtures/invalid/DR-CHALLENGE-REQUIRED--r1-flag-without-challenge.json +349 -0
  185. package/tests/agentic-board-suite/fixtures/invalid/DR-CHALLENGE-REQUIRED--r2-without-challenge.json +339 -0
  186. package/tests/agentic-board-suite/fixtures/invalid/DR-CHALLENGE-REQUIRED--r3-no-flags-without-challenge.json +339 -0
  187. package/tests/agentic-board-suite/fixtures/invalid/DR-CHALLENGE-RESOLVES--dangling-challenge.json +347 -0
  188. package/tests/agentic-board-suite/fixtures/invalid/DR-CHALLENGE-TARGETS-PROPOSAL--challenge-of-requester-message.json +415 -0
  189. package/tests/agentic-board-suite/fixtures/invalid/DR-CLASS-FROM-ROOT--root-declares-no-class.json +345 -0
  190. package/tests/agentic-board-suite/fixtures/invalid/DR-CLASS-FROM-ROOT--root-declares-other-class.json +346 -0
  191. package/tests/agentic-board-suite/fixtures/invalid/DR-DESIGNATED-CHALLENGER--designated-challenger-only-on-the-framing.json +346 -0
  192. package/tests/agentic-board-suite/fixtures/invalid/DR-DESIGNATED-CHALLENGER--investment-without-finance.json +436 -0
  193. package/tests/agentic-board-suite/fixtures/invalid/DR-DESIGNATED-CHALLENGER--stopped-designated-challenge.json +348 -0
  194. package/tests/agentic-board-suite/fixtures/invalid/DR-HARD-GATE--conditional-over-hard-gate.json +357 -0
  195. package/tests/agentic-board-suite/fixtures/invalid/DR-HARD-GATE--unresolved-hard-gate.json +357 -0
  196. package/tests/agentic-board-suite/fixtures/invalid/DR-NO-APPROVE-OVER-DISSENT--approve-over-a-stopped-challenge.json +413 -0
  197. package/tests/agentic-board-suite/fixtures/invalid/DR-NO-APPROVE-OVER-DISSENT--approve-over-dissent.json +346 -0
  198. package/tests/agentic-board-suite/fixtures/invalid/DR-ONE-REQUEST--re-ask-without-supersedes.json +426 -0
  199. package/tests/agentic-board-suite/fixtures/invalid/DR-ONE-REQUEST--request-superseded-twice.json +581 -0
  200. package/tests/agentic-board-suite/fixtures/invalid/DR-ONE-REQUEST--supersedes-a-challenge.json +427 -0
  201. package/tests/agentic-board-suite/fixtures/invalid/DR-ONE-REQUEST--supersedes-a-dangling-id.json +427 -0
  202. package/tests/agentic-board-suite/fixtures/invalid/DR-ONE-REQUEST--supersedes-a-later-request.json +500 -0
  203. package/tests/agentic-board-suite/fixtures/invalid/DR-ONE-REQUEST--superseding-request-from-another-proposer.json +621 -0
  204. package/tests/agentic-board-suite/fixtures/invalid/DR-ONE-REQUEST--supersession-cycle.json +501 -0
  205. package/tests/agentic-board-suite/fixtures/invalid/DR-OWNER-FROM-ROOT--agent-opens-decision-path.json +480 -0
  206. package/tests/agentic-board-suite/fixtures/invalid/DR-OWNER-FROM-ROOT--owner-is-the-requester.json +273 -0
  207. package/tests/agentic-board-suite/fixtures/invalid/DR-OWNER-FROM-ROOT--request-to-later-sorting-non-owner.json +273 -0
  208. package/tests/agentic-board-suite/fixtures/invalid/DR-OWNER-FROM-ROOT--request-to-non-owner.json +273 -0
  209. package/tests/agentic-board-suite/fixtures/invalid/DR-OWNER-FROM-ROOT--root-names-no-owner.json +341 -0
  210. package/tests/agentic-board-suite/fixtures/invalid/DR-PROPOSER-IS-SENDER--challenger-assembles-request.json +337 -0
  211. package/tests/agentic-board-suite/fixtures/invalid/DR-PROPOSER-NOT-CHALLENGER--self-challenge.json +411 -0
  212. package/tests/agentic-board-suite/fixtures/invalid/DR-RISK-CARRIED--concurrent-higher-tier-envelope.json +403 -0
  213. package/tests/agentic-board-suite/fixtures/invalid/DR-RISK-CARRIED--r5-decided-through-lower-tier-request.json +433 -0
  214. package/tests/agentic-board-suite/fixtures/invalid/DR-RISK-CARRIED--request-drops-materiality-flags.json +479 -0
  215. package/tests/agentic-board-suite/fixtures/invalid/DR-RISK-CARRIED--risk-raised-after-request-before-decision.json +403 -0
  216. package/tests/agentic-board-suite/fixtures/invalid/DR-TO-PRINCIPAL--request-to-agent.json +272 -0
  217. package/tests/agentic-board-suite/fixtures/invalid/DR-UNRESOLVED-DISSENT--dissent-dropped.json +341 -0
  218. package/tests/agentic-board-suite/fixtures/invalid/DR-UNRESOLVED-DISSENT--stopped-challenge-recorded-withdrawn.json +408 -0
  219. package/tests/agentic-board-suite/fixtures/invalid/DR-UNRESOLVED-DISSENT--unresolved-without-dissent.json +341 -0
  220. package/tests/agentic-board-suite/fixtures/invalid/DR-VALUE-CASE-REQUIRED--investment-without-value-case.json +353 -0
  221. package/tests/agentic-board-suite/fixtures/invalid/DRC-AFTER-REQUEST--decided-before-request.json +408 -0
  222. package/tests/agentic-board-suite/fixtures/invalid/DRC-AFTER-REQUEST--request-descends-from-its-record.json +408 -0
  223. package/tests/agentic-board-suite/fixtures/invalid/DRC-APPROVAL-STATE--state-contradicts-outcome.json +346 -0
  224. package/tests/agentic-board-suite/fixtures/invalid/DRC-CO-SIGN-BINDING--co-sign-approves-a-redesign.json +433 -0
  225. package/tests/agentic-board-suite/fixtures/invalid/DRC-CO-SIGN-BINDING--co-sign-contradicts-outcome.json +433 -0
  226. package/tests/agentic-board-suite/fixtures/invalid/DRC-CO-SIGN-BINDING--co-sign-does-not-descend-from-its-record.json +499 -0
  227. package/tests/agentic-board-suite/fixtures/invalid/DRC-CO-SIGN-BINDING--co-sign-is-the-parent-of-its-record.json +500 -0
  228. package/tests/agentic-board-suite/fixtures/invalid/DRC-CO-SIGN-BINDING--co-sign-names-another-approver.json +438 -0
  229. package/tests/agentic-board-suite/fixtures/invalid/DRC-CO-SIGN-BINDING--co-sign-nobody-asked-for.json +421 -0
  230. package/tests/agentic-board-suite/fixtures/invalid/DRC-CO-SIGN-BINDING--co-sign-sent-before-the-record.json +499 -0
  231. package/tests/agentic-board-suite/fixtures/invalid/DRC-CO-SIGNED--decision-in-force-not-co-signed.json +591 -0
  232. package/tests/agentic-board-suite/fixtures/invalid/DRC-CO-SIGNED--later-sorting-second-approver-never-signs.json +359 -0
  233. package/tests/agentic-board-suite/fixtures/invalid/DRC-CO-SIGNED--named-second-approver-never-signs.json +359 -0
  234. package/tests/agentic-board-suite/fixtures/invalid/DRC-COMPLETED--record-left-dispatched.json +346 -0
  235. package/tests/agentic-board-suite/fixtures/invalid/DRC-DECIDER-IS-ADDRESSEE--decided-by-non-addressee.json +408 -0
  236. package/tests/agentic-board-suite/fixtures/invalid/DRC-DECIDER-IS-ADDRESSEE--earlier-sorting-non-addressee-decides.json +408 -0
  237. package/tests/agentic-board-suite/fixtures/invalid/DRC-DECIDER-IS-SENDER--relayed-decision.json +408 -0
  238. package/tests/agentic-board-suite/fixtures/invalid/DRC-HARD-GATE-ACCEPTANCE--override-without-acceptance.json +356 -0
  239. package/tests/agentic-board-suite/fixtures/invalid/DRC-HARD-GATE-ACCEPTANCE--same-round-withdrawal.json +423 -0
  240. package/tests/agentic-board-suite/fixtures/invalid/DRC-HARD-GATE-ACCEPTANCE--stopped-over-cap-withdrawal.json +623 -0
  241. package/tests/agentic-board-suite/fixtures/invalid/DRC-HARD-GATE-ACCEPTANCE--unrelated-withdrawal-does-not-clear-gate.json +482 -0
  242. package/tests/agentic-board-suite/fixtures/invalid/DRC-HARD-GATE-ACCEPTANCE--withdrawal-names-an-undelivered-envelope.json +482 -0
  243. package/tests/agentic-board-suite/fixtures/invalid/DRC-HARD-GATE-ACCEPTANCE--withdrawal-on-another-proposers-revision.json +600 -0
  244. package/tests/agentic-board-suite/fixtures/invalid/DRC-NOT-REQUESTER--second-approver-is-requester.json +433 -0
  245. package/tests/agentic-board-suite/fixtures/invalid/DRC-ONE-RECORD--contradicting-records.json +416 -0
  246. package/tests/agentic-board-suite/fixtures/invalid/DRC-ONE-RECORD--duplicate-co-signature.json +507 -0
  247. package/tests/agentic-board-suite/fixtures/invalid/DRC-PRINCIPAL-SOURCE--agent-writes-decision.json +345 -0
  248. package/tests/agentic-board-suite/fixtures/invalid/DRC-REQUEST-BINDING--points-at-challenge.json +346 -0
  249. package/tests/agentic-board-suite/fixtures/invalid/DRC-REQUEST-BINDING--record-names-a-missing-request.json +346 -0
  250. package/tests/agentic-board-suite/fixtures/invalid/DRC-REQUEST-BINDING--request-edited-after-decision.json +346 -0
  251. package/tests/agentic-board-suite/fixtures/invalid/DRC-SECOND-APPROVER--r5-request-decided-on-lower-tier-branch.json +418 -0
  252. package/tests/agentic-board-suite/fixtures/invalid/DRC-SECOND-APPROVER--r5-single-approver.json +354 -0
  253. package/tests/agentic-board-suite/fixtures/invalid/DRC-SECOND-APPROVER--second-approver-is-decider.json +359 -0
  254. package/tests/agentic-board-suite/fixtures/invalid/HO-AGENT-NO-APPROVAL--agent-claims-approval.json +132 -0
  255. package/tests/agentic-board-suite/fixtures/invalid/HO-APPROVAL-ONLY-ON-RECORD--principal-delegate-claims-approval.json +132 -0
  256. package/tests/agentic-board-suite/fixtures/invalid/HO-ATTEN-ACTIONS--child-widens-actions.json +345 -0
  257. package/tests/agentic-board-suite/fixtures/invalid/HO-ATTEN-BUDGET--child-raises-limit.json +132 -0
  258. package/tests/agentic-board-suite/fixtures/invalid/HO-ATTEN-BUDGET--currency-changed.json +132 -0
  259. package/tests/agentic-board-suite/fixtures/invalid/HO-BLOCK-AS-CHALLENGE--block-sent-as-return.json +184 -0
  260. package/tests/agentic-board-suite/fixtures/invalid/HO-CHAIN-CUSTODY--parent-sender-continues-unstopped.json +187 -0
  261. package/tests/agentic-board-suite/fixtures/invalid/HO-CHAIN-CUSTODY--reply-from-outside-chain.json +132 -0
  262. package/tests/agentic-board-suite/fixtures/invalid/HO-CHAIN-PARENT--hop-skips.json +132 -0
  263. package/tests/agentic-board-suite/fixtures/invalid/HO-CHAIN-TIME--sent-before-parent.json +132 -0
  264. package/tests/agentic-board-suite/fixtures/invalid/HO-DESTINATION-TIER--delegate-to-live-guard.json +132 -0
  265. package/tests/agentic-board-suite/fixtures/invalid/HO-DESTINATION-TIER--delegate-to-untiered-agent.json +132 -0
  266. package/tests/agentic-board-suite/fixtures/invalid/HO-ESCALATE-TO-PRINCIPAL--escalate-to-agent.json +131 -0
  267. package/tests/agentic-board-suite/fixtures/invalid/HO-ESCALATE-TO-PRINCIPAL--escalated-delegate-to-agent.json +132 -0
  268. package/tests/agentic-board-suite/fixtures/invalid/HO-MONO-CLASS--classification-lowered.json +346 -0
  269. package/tests/agentic-board-suite/fixtures/invalid/HO-MONO-DEADLINE--deadline-extended.json +132 -0
  270. package/tests/agentic-board-suite/fixtures/invalid/HO-MONO-MATERIALITY--flag-dropped.json +434 -0
  271. package/tests/agentic-board-suite/fixtures/invalid/HO-MONO-MAX-HOPS--max-hops-raised.json +132 -0
  272. package/tests/agentic-board-suite/fixtures/invalid/HO-MONO-PROHIBITIONS--prohibition-dropped.json +131 -0
  273. package/tests/agentic-board-suite/fixtures/invalid/HO-MONO-SPENT--one-counter-decreases.json +346 -0
  274. package/tests/agentic-board-suite/fixtures/invalid/HO-MONO-SPENT--spent-counter-reset.json +346 -0
  275. package/tests/agentic-board-suite/fixtures/invalid/HO-MONO-TIER--tier-lowered.json +346 -0
  276. package/tests/agentic-board-suite/fixtures/invalid/HO-NO-EXECUTION--deploy-authorized.json +133 -0
  277. package/tests/agentic-board-suite/fixtures/invalid/HO-NO-SELF-ADDRESS--challenge-to-self.json +187 -0
  278. package/tests/agentic-board-suite/fixtures/invalid/HO-ONE-CORRELATION--two-correlations.json +132 -0
  279. package/tests/agentic-board-suite/fixtures/invalid/HO-ONE-ROOT--second-root-beside-a-decision.json +410 -0
  280. package/tests/agentic-board-suite/fixtures/invalid/HO-ONE-ROOT--two-roots.json +190 -0
  281. package/tests/agentic-board-suite/fixtures/invalid/HO-PAYLOAD-CHALLENGE--challenge-without-payload.json +180 -0
  282. package/tests/agentic-board-suite/fixtures/invalid/HO-PAYLOAD-DECISION-RECORD--record-without-payload.json +331 -0
  283. package/tests/agentic-board-suite/fixtures/invalid/HO-PAYLOAD-DECISION-REQUEST--request-without-payload.json +324 -0
  284. package/tests/agentic-board-suite/fixtures/invalid/HO-PAYLOAD-ESCALATE--escalate-without-payload.json +128 -0
  285. package/tests/agentic-board-suite/fixtures/invalid/HO-PAYLOAD-EXCLUSIVE--return-with-escalation.json +136 -0
  286. package/tests/agentic-board-suite/fixtures/invalid/HO-PAYLOAD-RETURN--return-without-verdict.json +124 -0
  287. package/tests/agentic-board-suite/fixtures/invalid/HO-REASON-HAS-STATUS--reason-on-completed.json +134 -0
  288. package/tests/agentic-board-suite/fixtures/invalid/HO-RETURN-EVIDENCE--pass-on-inference-only.json +132 -0
  289. package/tests/agentic-board-suite/fixtures/invalid/HO-RETURN-EVIDENCE--pass-over-blocking-gap.json +137 -0
  290. package/tests/agentic-board-suite/fixtures/invalid/HO-RETURN-EVIDENCE--pass-with-no-evidence.json +127 -0
  291. package/tests/agentic-board-suite/fixtures/invalid/HO-RETURN-TO-SENDER--return-skips-the-delegator.json +185 -0
  292. package/tests/agentic-board-suite/fixtures/invalid/HO-ROOT-PARENT--root-with-parent.json +132 -0
  293. package/tests/agentic-board-suite/fixtures/invalid/HO-SCHEMA--unknown-credential-field.json +133 -0
  294. package/tests/agentic-board-suite/fixtures/invalid/HO-STOP-NOTE--conflict-without-note.json +133 -0
  295. package/tests/agentic-board-suite/fixtures/invalid/HO-STOPPED-HAS-REASON--stopped-without-reason.json +132 -0
  296. package/tests/agentic-board-suite/fixtures/invalid/HO-UNIQUE-TASK-ID--duplicate-task-id.json +198 -0
  297. package/tests/agentic-board-suite/fixtures/invalid/STOP-BUDGET--budget-undeclared.json +123 -0
  298. package/tests/agentic-board-suite/fixtures/invalid/STOP-BUDGET--return-overspent.json +132 -0
  299. package/tests/agentic-board-suite/fixtures/invalid/STOP-CHALLENGE-ROUNDS--revisions-carried-in-rebuttal-challenges.json +461 -0
  300. package/tests/agentic-board-suite/fixtures/invalid/STOP-CHALLENGE-ROUNDS--third-round.json +454 -0
  301. package/tests/agentic-board-suite/fixtures/invalid/STOP-CHALLENGE-ROUNDS--third-version-by-return-naming-the-original.json +461 -0
  302. package/tests/agentic-board-suite/fixtures/invalid/STOP-DEADLINE--sent-after-deadline.json +132 -0
  303. package/tests/agentic-board-suite/fixtures/invalid/STOP-DEPTH--depth-undeclared.json +182 -0
  304. package/tests/agentic-board-suite/fixtures/invalid/STOP-INSTRUCTION-BAIT--claimed-approval-dispatched.json +132 -0
  305. package/tests/agentic-board-suite/fixtures/invalid/STOP-LIVE-GUARD--mutation-dispatched.json +130 -0
  306. package/tests/agentic-board-suite/fixtures/invalid/STOP-MISSING-EVIDENCE--blocking-gap-dispatched.json +137 -0
  307. package/tests/agentic-board-suite/fixtures/invalid/STOP-MISSING-EVIDENCE--decision-request-blocking-gap.json +351 -0
  308. package/tests/agentic-board-suite/fixtures/invalid/STOP-ROUTING-LOOP--exchanges-on-the-original-envelope.json +381 -0
  309. package/tests/agentic-board-suite/fixtures/invalid/STOP-ROUTING-LOOP--loop-undeclared.json +182 -0
  310. package/tests/agentic-board-suite/fixtures/invalid/STOP-ROUTING-LOOP--revisit-to-challenger-of-another-party.json +256 -0
  311. package/tests/agentic-board-suite/fixtures/invalid/STOP-ROUTING-LOOP--second-revisit-to-challenger.json +540 -0
  312. package/tests/agentic-board-suite/fixtures/invalid/STOP-TERMINAL--challenge-answers-stopped-delegate.json +247 -0
  313. package/tests/agentic-board-suite/fixtures/invalid/STOP-TERMINAL--decision-on-escalated-request.json +353 -0
  314. package/tests/agentic-board-suite/fixtures/invalid/STOP-TERMINAL--decision-on-stopped-request.json +422 -0
  315. package/tests/agentic-board-suite/fixtures/invalid/STOP-TERMINAL--reply-to-stopped-delegate.json +244 -0
  316. package/tests/agentic-board-suite/fixtures/invalid/VC-BENEFIT-CEILING--benefit-above-baseline.json +436 -0
  317. package/tests/agentic-board-suite/fixtures/invalid/VC-COST-SIGN--negative-risk-loss.json +436 -0
  318. package/tests/agentic-board-suite/fixtures/invalid/VC-COST-SIGN--risk-loss-low-minus-one.json +436 -0
  319. package/tests/agentic-board-suite/fixtures/invalid/VC-COUNTERFACTUALS--single-agent-counterfactual-missing.json +427 -0
  320. package/tests/agentic-board-suite/fixtures/invalid/VC-COUNTERFACTUALS--workflow-counterfactual-missing.json +427 -0
  321. package/tests/agentic-board-suite/fixtures/invalid/VC-NAV-ARITHMETIC--nav-off-by-one.json +436 -0
  322. package/tests/agentic-board-suite/fixtures/invalid/VC-ORDER--inverted-range.json +436 -0
  323. package/tests/agentic-board-suite/fixtures/invalid/VC-OVERSIGHT--free-oversight.json +436 -0
  324. package/tests/agentic-board-suite/fixtures/invalid/VC-PROMOTION--measured-but-not-incremental.json +436 -0
  325. package/tests/agentic-board-suite/fixtures/invalid/VC-PROMOTION--promoted-on-estimate.json +436 -0
  326. package/tests/agentic-board-suite/fixtures/invalid/VC-RECOMMEND-INCREMENTAL--recommend-non-incremental.json +436 -0
  327. package/tests/agentic-board-suite/fixtures/invalid/VC-RUNS-BOUND--more-successes-than-runs.json +436 -0
  328. package/tests/agentic-board-suite/fixtures/invalid/VC-SYNTHETIC--synthetic-claims-measured.json +436 -0
  329. package/tests/agentic-board-suite/fixtures/invalid/VC-UNIT-COST--no-successes-nonzero-cost.json +436 -0
  330. package/tests/agentic-board-suite/fixtures/invalid/VC-UNIT-COST--unit-cost-mismatch.json +436 -0
  331. package/tests/agentic-board-suite/fixtures/valid/v01-r0-single-review.json +129 -0
  332. package/tests/agentic-board-suite/fixtures/valid/v01x-block-return-on-inference.json +125 -0
  333. package/tests/agentic-board-suite/fixtures/valid/v01x-block-return-to-principal.json +125 -0
  334. package/tests/agentic-board-suite/fixtures/valid/v01x-pass-return-documentation-evidence.json +129 -0
  335. package/tests/agentic-board-suite/fixtures/valid/v01x-pass-return-mixed-evidence.json +133 -0
  336. package/tests/agentic-board-suite/fixtures/valid/v01x-pass-return-nonblocking-gap.json +134 -0
  337. package/tests/agentic-board-suite/fixtures/valid/v02-r2-architecture-dissent.json +343 -0
  338. package/tests/agentic-board-suite/fixtures/valid/v02x-challenge-after-decision.json +407 -0
  339. package/tests/agentic-board-suite/fixtures/valid/v02x-challenge-after-record-same-second.json +464 -0
  340. package/tests/agentic-board-suite/fixtures/valid/v02x-redispatch-after-cured-stop.json +534 -0
  341. package/tests/agentic-board-suite/fixtures/valid/v02x-security-proposal-returned-to-its-delegator.json +407 -0
  342. package/tests/agentic-board-suite/fixtures/valid/v03-investment-loses-to-workflow.json +433 -0
  343. package/tests/agentic-board-suite/fixtures/valid/v04-r3-hard-gate-risk-acceptance.json +354 -0
  344. package/tests/agentic-board-suite/fixtures/valid/v04x-live-hard-gate-recommend-escalate.json +354 -0
  345. package/tests/agentic-board-suite/fixtures/valid/v04x-live-hard-gate-recommend-reject.json +354 -0
  346. package/tests/agentic-board-suite/fixtures/valid/v05-live-guard-escalation.json +129 -0
  347. package/tests/agentic-board-suite/fixtures/valid/v06-r5-dual-approval.json +430 -0
  348. package/tests/agentic-board-suite/fixtures/valid/v06x-co-sign-same-second.json +430 -0
  349. package/tests/agentic-board-suite/fixtures/valid/v06x-re-asked-r5-decision-co-signed-again.json +662 -0
  350. package/tests/agentic-board-suite/fixtures/valid/v06x-unsigned-approve-superseded-by-co-signed-reject.json +588 -0
  351. package/tests/agentic-board-suite/fixtures/valid/v07-depth-and-routing-loop.json +180 -0
  352. package/tests/agentic-board-suite/fixtures/valid/v08-budget-exhausted.json +186 -0
  353. package/tests/agentic-board-suite/fixtures/valid/v09-instruction-bait.json +131 -0
  354. package/tests/agentic-board-suite/fixtures/valid/v09x-requester-restates-after-bait.json +251 -0
  355. package/tests/agentic-board-suite/fixtures/valid/v10-r1-optional-challenge.json +184 -0
  356. package/tests/agentic-board-suite/fixtures/valid/v10x-report-up-after-challenge.json +246 -0
  357. package/tests/agentic-board-suite/fixtures/valid/v11-two-rounds-hard-gate-withdrawn.json +478 -0
  358. package/tests/agentic-board-suite/fixtures/valid/v11x-designated-challenge-first-answer-over-cap.json +598 -0
  359. package/tests/agentic-board-suite/fixtures/valid/v11x-interleaved-challenger-round-two.json +604 -0
  360. package/tests/agentic-board-suite/fixtures/valid/v11x-proposal-handed-to-the-challenger-capped.json +860 -0
  361. package/tests/agentic-board-suite/fixtures/valid/v11x-round-three-escalation-path.json +747 -0
  362. package/tests/agentic-board-suite/fixtures/valid/v11x-same-second-round-two.json +478 -0
  363. package/tests/agentic-board-suite/fixtures/valid/v11x-two-findings-one-round.json +549 -0
  364. package/tests/agentic-board-suite/fixtures/valid/v12-superseded-request.json +497 -0
  365. package/tests/agentic-board-suite/fixtures/valid/v12x-challenge-after-the-first-decision.json +566 -0
  366. package/tests/agentic-board-suite/fixtures/valid/v12x-late-challenge-cured-by-supersede.json +566 -0
  367. package/tests/agentic-board-suite/fixtures/valid/v12x-supersede-in-the-same-second.json +497 -0
  368. package/tests/agentic-board-suite/test_agentic_board.py +502 -0
  369. package/tests/fixtures/README.md +1 -1
  370. package/tests/fuzz-properties.test.mjs +254 -0
  371. package/tests/mods.test.mjs +768 -0
  372. package/tests/validate-agent-tool-tiers.py +7 -5
  373. package/tests/validate-asset-integrity.py +1 -0
  374. package/tests/validate-mods.py +821 -0
package/mods/README.md ADDED
@@ -0,0 +1,220 @@
1
+ # Vanguard Mods
2
+
3
+ <div align="center">
4
+
5
+ <img src="../assets/logos/vanguard-frontier-agentic-logo.png" alt="Vanguard Frontier Agentic" width="140" />
6
+
7
+ <p><strong>Less “oops.” More visibility.</strong></p>
8
+ <p>Tool-call guardrails and observers for Claude Code, Kiro and Codex.</p>
9
+
10
+ <p>
11
+ <a href="#pick-a-mod">Pick a mod</a> &nbsp;·&nbsp;
12
+ <a href="#get-started">Get started</a> &nbsp;·&nbsp;
13
+ <a href="#know-the-limits">Know the limits</a> &nbsp;·&nbsp;
14
+ <a href="#under-the-hood">Under the hood</a>
15
+ </p>
16
+
17
+ </div>
18
+
19
+ ---
20
+
21
+ Skills guide the agent. **Mods inspect its tool calls.** Ask before risky actions,
22
+ redact supported output, or record what happened. Pick what you need—not everything at once.
23
+
24
+ <a id="pick-a-mod"></a>
25
+
26
+ ## 🧰 Pick your guardrails
27
+
28
+ | Mod | The job | Reach |
29
+ | --- | --- | --- |
30
+ | [Secret Redactor](secret-redactor/README.md) | Spot credential-shaped input; redact supported output. | L0 |
31
+ | [Blast Radius Guard](blast-radius-guard/README.md) | Ask or deny on destructive commands and system-path writes. | L0 |
32
+ | [Live Guard Gate](live-guard-gate/README.md) | Flag cloud mutations and name guard agents to consult. | L0 |
33
+ | [Context Burn](context-burn/README.md) | Count tool calls; warn on reported context use where supported. | L1 |
34
+ | [Audit Trail](audit-trail/README.md) | Append call hashes and per-session sequence numbers. | L1 |
35
+
36
+ **Reach is a label, not a sandbox:** L0 = no declared writes/spawns/network;
37
+ L1 = file writes; L2 = subprocesses; L3 = network (warned, not blocked).
38
+ Reads are allowed at L0; starting the Node adapter is not a mod-spawned subprocess.
39
+
40
+ **Support differs:** Kiro/Codex deny instead of asking and cannot redact output.
41
+ Claude Code Audit Trail needs classic. Classic Post hooks miss failed calls.
42
+
43
+ <details>
44
+ <summary><strong>🔎 Same mod, different harness? Check the fine print.</strong></summary>
45
+
46
+ | Mod | Claude Code native | Claude Code classic | Kiro / Codex |
47
+ | --- | --- | --- | --- |
48
+ | Secret Redactor | Ask on input; redact or withhold output | Ask on input; replace successful output only | Deny matching input; no output redaction |
49
+ | Blast Radius Guard | Ask in confirm mode; deny in block mode | Same | Deny in either mode |
50
+ | Live Guard Gate | Ask and name guard agents | Same | Deny and name guard agents |
51
+ | Context Burn | Session context usage and threshold toasts | Tool-call counter only | Tool-call counter only |
52
+ | Audit Trail | Unsupported—use classic | Append after successful calls | Append; Kiro payload interpretation is degraded |
53
+
54
+ Guards use `PreToolUse`; supported redaction and observers use `PostToolUse`;
55
+ Context Burn also uses `SessionStart`.
56
+ Native equivalents are `tool.check`, `tool.call` and `session.start`.
57
+
58
+ Kiro's stdin fields are undocumented: guards scan the whole payload if input is
59
+ unrecognized; Audit Trail hashes that payload as arguments and `null` as the result.
60
+ Set `VANGUARD_AUDIT_TRAIL_SESSION_ID` to avoid its shared `unsessioned` fallback.
61
+ See generated `<harness>/UNSUPPORTED.md` for remaining limits.
62
+
63
+ </details>
64
+
65
+ ---
66
+
67
+ <a id="get-started"></a>
68
+
69
+ ## 🚀 Get started
70
+
71
+ Use a local checkout. Command adapters need **Node 22+**; native modules use the
72
+ harness runtime. No service needed.
73
+
74
+ ```bash
75
+ source=/absolute/path/to/vanguard-frontier-agentic
76
+ target=/absolute/path/to/your/project
77
+ mod=blast-radius-guard
78
+ ```
79
+
80
+ > [!IMPORTANT]
81
+ > **Native or classic. Not both for the same mod.** They duplicate checks and can
82
+ > double-count Context Burn. Audit Trail has no native adapter—use classic.
83
+
84
+ <details>
85
+ <summary><strong>🤖 Claude Code native</strong> — load a plugin root</summary>
86
+
87
+ ```bash
88
+ cd "$target"
89
+ claude --plugin-dir "$source/mods/$mod"
90
+ ```
91
+
92
+ Not for `audit-trail`. Native API: early access. Check the verified build in the
93
+ [implementation notes](#under-the-hood) before upgrading.
94
+
95
+ </details>
96
+
97
+ <details>
98
+ <summary><strong>🤖 Claude Code classic</strong> — copy, then merge hooks</summary>
99
+
100
+ ```bash
101
+ mkdir -p "$target/.claude/vanguard-mods/$mod"
102
+ cp -R "$source/mods/$mod/bin" "$source/mods/$mod/lib" "$target/.claude/vanguard-mods/$mod/"
103
+ ```
104
+
105
+ Append `mods/<id>/claude-code/settings.hooks.json` entries under `hooks` in the
106
+ target's `.claude/settings.json`. **Keep existing hooks/settings.** Generated command:
107
+
108
+ ```text
109
+ node "$CLAUDE_PROJECT_DIR/.claude/vanguard-mods/<id>/bin/hook.mjs" --harness claude-code --event <event>
110
+ ```
111
+
112
+ `CLAUDE_PROJECT_DIR` stays at the initial project root, even across worktree changes.
113
+ Observers resolve relative writes from the installed adapter location.
114
+
115
+ </details>
116
+
117
+ <details>
118
+ <summary><strong>🔮 Kiro</strong> — copy the adapters and hook JSON</summary>
119
+
120
+ ```bash
121
+ mkdir -p "$target/.kiro/hooks" "$target/.kiro/vanguard-mods/$mod"
122
+ cp -R "$source/mods/$mod/bin" "$source/mods/$mod/lib" "$target/.kiro/vanguard-mods/$mod/"
123
+ cp "$source/mods/$mod/kiro/"*-hook.json "$target/.kiro/hooks/"
124
+ ```
125
+
126
+ Copy only `*-hook.json`, not `UNSUPPORTED.md`. Replace the relative script path
127
+ inside each hook with its **absolute installed path**: hook cwd is undocumented.
128
+ Commands use `--harness kiro --event <event>`; confirmation requests deny with exit 2.
129
+
130
+ </details>
131
+
132
+ <details>
133
+ <summary><strong>⚡ Codex</strong> — copy, merge, then review trust</summary>
134
+
135
+ ```bash
136
+ mkdir -p "$target/.codex/vanguard-mods/$mod"
137
+ cp -R "$source/mods/$mod/bin" "$source/mods/$mod/lib" "$target/.codex/vanguard-mods/$mod/"
138
+ ```
139
+
140
+ Append event entries from `mods/<id>/codex/hooks.json` to the target's
141
+ `.codex/hooks.json`, preserving existing hooks. Replace relative script paths with
142
+ **absolute installed paths**; hook cwd is undocumented. Complete Codex's hook trust review.
143
+
144
+ Commands use `--harness codex --event <event>`; asks deny with exit 2.
145
+ Redactor Post is unsupported; the narrower MCP output field is not substituted.
146
+
147
+ </details>
148
+
149
+ **Try it before trusting it:** run ordinary and harmless pattern-matching calls in
150
+ the harness. Inspect the decision, output or observer file—not just exit codes.
151
+ Repeat after path, permission, Node or harness changes.
152
+
153
+ ---
154
+
155
+ <a id="know-the-limits"></a>
156
+
157
+ ## ⚠️ Guardrails, not guarantees
158
+
159
+ - **A hook must run to help.** Guards deny handler/input errors; missing Node,
160
+ moved scripts, startup crashes and timeouts can leave calls unguarded.
161
+ CLI errors may not block; Kiro IDE handling differs.
162
+ - **Regex is not complete DLP.** Pre checks never rewrite arguments; approved input
163
+ still reaches the tool. Post redaction does not scrub other logs or network traffic.
164
+ Redactor errors attempt withholding; classic shape mismatches may be ignored.
165
+ [Withholding has limits, too](secret-redactor/README.md#limitations).
166
+ - **A review prompt is not authorization.** Live Guard Gate names agents—it never
167
+ spawns them or verifies production accounts. Guards scan strings, not shell semantics;
168
+ relative paths stay unresolved. Bypass modes can auto-approve an ask.
169
+ - **Observers are not compliance proof.** Node counters track calls, not tokens or
170
+ spend; only native Context Burn uses reported context data. Unlocked updates can
171
+ lose increments. Audit logs are mutable, unsigned and not tamper-evident.
172
+ Observer failures do not block tools.
173
+
174
+ <details>
175
+ <summary><strong>🗂️ State files and custom patterns</strong></summary>
176
+
177
+ - **Context Burn:** `.<harness>/context-burn-status/<session-hash>.json`
178
+ (`.claude` for Claude Code). Filenames use the full lowercase SHA-256 of the UTF-8
179
+ session ID, never raw IDs. Missing/empty Node IDs share `unsessioned` state.
180
+ `VANGUARD_CONTEXT_BURN_STATUS_PATH` is an exact override; sharing it opts out of
181
+ isolation. Old single-file state is not migrated automatically.
182
+ - **Audit Trail:** `.audit/<UTC-date>/<session>.jsonl`; an earlier file is reused
183
+ across dates. `VANGUARD_AUDIT_TRAIL_LOG_DIR` overrides the directory.
184
+ - **Footprints disclose defaults, not containment.** Overrides can write outside
185
+ declared globs; overlap checks do not constrain operator overrides.
186
+ - **Patterns append, never replace.** `VANGUARD_SECRET_REDACTOR_PATTERNS` and
187
+ `VANGUARD_BLAST_RADIUS_GUARD_PATTERNS` accept extra pattern files. Use absolute paths.
188
+ Unreadable/invalid configured files deny guards or withhold redactor output.
189
+ Each mod README documents schemas and environment names.
190
+
191
+ </details>
192
+
193
+ <a id="under-the-hood"></a>
194
+
195
+ ## 🔧 Under the hood
196
+
197
+ `mod.toml` declares support and footprint; `lib/core.mjs` holds shared behavior.
198
+ Edit those sources, `patterns.json`, entries and native modules—not generated adapters.
199
+ The generator produces manifests, shared runtime copies, pattern modules and `catalog/mods.json`.
200
+
201
+ <details>
202
+ <summary><strong>🛠️ Contributing? Run the gates.</strong></summary>
203
+
204
+ ```bash
205
+ npm run mods:write
206
+ npm run validate:mods
207
+ npm run test:fuzz
208
+ npm run asset-integrity:write # last generator, on its own
209
+ npm run validate
210
+ ```
211
+
212
+ The Python gate needs **Python 3.11+**. For each supported native mod, also run
213
+ `claude plugin validate mods/<id>` and `claude plugin test mods/<id>` locally;
214
+ CI has no native CLI. Spelling and Markdown lint are separate gates.
215
+ Static checks are not proof that an installed hook runs.
216
+
217
+ </details>
218
+
219
+ [Implementation plan](https://github.com/VincentChuWaiChow/vanguard-frontier-agentic/blob/dc8b29c823fb89dc4246413f38a7b82fdd34aa18/.claude/plan/cross-harness-mods.md)
220
+ · [Spec deviations](https://github.com/VincentChuWaiChow/vanguard-frontier-agentic/blob/dc8b29c823fb89dc4246413f38a7b82fdd34aa18/.kiro/specs/cross-harness-mods/deviations.md)
@@ -0,0 +1,70 @@
1
+ # Audit Trail
2
+
3
+ Appends one JSON line per tool call to a per-session log. It is an honest append log: it makes
4
+ no claim of immutability, carries no signatures, and any process with write access can edit or
5
+ delete the file.
6
+
7
+ ## Reach and declared footprint
8
+
9
+ **L1 (file writes).** The manifest declares default writes to `.audit/**`, reads
10
+ `VANGUARD_AUDIT_TRAIL_LOG_DIR`, `VANGUARD_AUDIT_TRAIL_SESSION_ID` and
11
+ `VANGUARD_AUDIT_TRAIL_AGENT_ID`, and declares no subprocess spawns or network egress.
12
+ This is capability disclosure, not isolation; the log-directory override can write
13
+ outside the declared default footprint.
14
+
15
+ ## What it records
16
+
17
+ | Field | Value |
18
+ |---|---|
19
+ | `timestamp` | UTC ISO 8601 time of the hook run |
20
+ | `session_id` | The safe session id (see below) |
21
+ | `agent_id` | `VANGUARD_AUDIT_TRAIL_AGENT_ID`, or `null` |
22
+ | `tool_name` | The payload `tool_name`, or `null` |
23
+ | `args_hash` | SHA-256 of the canonical JSON of `tool_input` (`null` when absent) |
24
+ | `result_hash` | SHA-256 of the canonical JSON of `tool_response` (`null` when absent) |
25
+ | `sequence_number` | 1, 2, 3 ... per session file |
26
+
27
+ Only hashes are stored, never the arguments or results themselves.
28
+
29
+ ## File layout
30
+
31
+ `<dir>/<YYYY-MM-DD>/<session>.jsonl`, where `<dir>` defaults to `.audit` and dates are UTC. If
32
+ the session already has a file under an earlier date directory, that file is reused, so the
33
+ sequence does not reset at midnight. A relative `<dir>` resolves against the project root when
34
+ the hook runs from its installed location, otherwise against the working directory.
35
+
36
+ ## Session ids
37
+
38
+ 1. `VANGUARD_AUDIT_TRAIL_SESSION_ID`, when set and non-empty.
39
+ 2. The payload `session_id`, when a non-empty string.
40
+ 3. Otherwise `unsessioned`.
41
+
42
+ An id that does not match `[A-Za-z0-9._-]{1,128}` is replaced by the first 32 hex characters of
43
+ its SHA-256, so it can never escape the date directory.
44
+
45
+ ## Environment
46
+
47
+ | Variable | Purpose |
48
+ |---|---|
49
+ | `VANGUARD_AUDIT_TRAIL_LOG_DIR` | Log directory (default `.audit`). It can point outside the declared `.audit/**` write footprint; that is the operator's choice. |
50
+ | `VANGUARD_AUDIT_TRAIL_SESSION_ID` | Session id override |
51
+ | `VANGUARD_AUDIT_TRAIL_AGENT_ID` | Value for `agent_id` |
52
+
53
+ ## Harness support
54
+
55
+ | Harness | Status |
56
+ |---|---|
57
+ | Claude Code native | Unsupported: the native API has no file append. Install the classic hook instead. |
58
+ | Claude Code classic | Supported. `PostToolUse` fires only after a tool succeeds, so failed calls are not logged. |
59
+ | Kiro | Degraded: stdin field names are undocumented, so `args_hash` covers the whole payload, `result_hash` hashes `null`, and without a session id env var every session shares the `unsessioned` log. |
60
+ | Codex | Supported. |
61
+
62
+ ## Locking and failure
63
+
64
+ Each append takes an exclusive `<dir>/<session>.lock` before selecting the session file
65
+ (retrying for up to 2 s). Locks are never automatically stolen or removed based on age.
66
+ After a crash, confirm no writer remains before manually removing its orphaned lock.
67
+ A corrupt or unterminated last line, a lock timeout, or any I/O error exits 1 with a message on stderr and
68
+ appends nothing. A failure is loud but never blocks the tool call.
69
+
70
+ Requires Node 22+. Install: see `mods/README.md`.
@@ -0,0 +1,65 @@
1
+ // Node hook for audit-trail: appends one JSON line per tool call to a per-session log.
2
+ // An honest append log, not tamper-evident: anything with write access can edit it.
3
+ import { appendFileSync, closeSync, existsSync, mkdirSync, openSync, readFileSync, readdirSync, unlinkSync } from 'node:fs'
4
+ import { dirname, join } from 'node:path'
5
+ import { runHook, projectPath } from '../lib/runtime.mjs'
6
+ import { buildRecord, lastSequence, rawSessionId, safeSessionId } from '../lib/core.mjs'
7
+
8
+ const sleep = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms)
9
+
10
+ // Reuses the earliest date dir that already holds this session, so the sequence never
11
+ // resets at midnight.
12
+ // ponytail: scans every date dir, O(days); index it if slow.
13
+ function logFile(base, sid, today) {
14
+ const days = existsSync(base) ? readdirSync(base).filter((d) => /^\d{4}-\d{2}-\d{2}$/.test(d)).sort() : []
15
+ const hit = days.find((d) => existsSync(join(base, d, `${sid}.jsonl`)))
16
+ return join(base, hit ?? today, `${sid}.jsonl`)
17
+ }
18
+
19
+ // Never steal a lock: its age does not prove the owner is dead. Explicit recovery is safer
20
+ // than racing two writers after an automatic stale-lock takeover.
21
+ function acquire(lock, file) {
22
+ const deadline = Date.now() + 2000
23
+ for (;;) {
24
+ try {
25
+ return openSync(lock, 'wx')
26
+ } catch (err) {
27
+ if (err.code !== 'EEXIST') throw err
28
+ }
29
+ if (Date.now() > deadline) throw new Error(`audit-trail: could not lock ${file} within 2s`)
30
+ sleep(20)
31
+ }
32
+ }
33
+
34
+ // Read directly instead of exists-then-read, so there is no check/use gap.
35
+ function readIfExists(file) {
36
+ try {
37
+ return readFileSync(file, 'utf8')
38
+ } catch (err) {
39
+ if (err.code === 'ENOENT') return ''
40
+ throw err
41
+ }
42
+ }
43
+
44
+ async function PostToolUse(payload, ctx) {
45
+ const base = projectPath(process.env.VANGUARD_AUDIT_TRAIL_LOG_DIR || '.audit', ctx.harness, import.meta.dirname)
46
+ const sid = await safeSessionId(rawSessionId(process.env.VANGUARD_AUDIT_TRAIL_SESSION_ID, payload))
47
+ const timestamp = new Date().toISOString()
48
+ mkdirSync(base, { recursive: true })
49
+ // Serialize session-file selection as well as appends, including across midnight.
50
+ const lock = join(base, `${sid}.lock`)
51
+ const fd = acquire(lock, lock)
52
+ try {
53
+ const file = logFile(base, sid, timestamp.slice(0, 10))
54
+ mkdirSync(dirname(file), { recursive: true })
55
+ const sequence = lastSequence(readIfExists(file), file) + 1
56
+ const agentId = process.env.VANGUARD_AUDIT_TRAIL_AGENT_ID
57
+ const record = await buildRecord({ timestamp, sessionId: sid, agentId, payload, harness: ctx.harness, sequence })
58
+ appendFileSync(file, JSON.stringify(record) + '\n')
59
+ } finally {
60
+ closeSync(fd)
61
+ unlinkSync(lock)
62
+ }
63
+ }
64
+
65
+ process.exitCode = await runHook({ name: 'audit-trail', handlers: { PostToolUse } })
@@ -0,0 +1,11 @@
1
+ # Audit Trail on Claude Code
2
+
3
+ <!-- Generated by scripts/generate-mods.mjs from mod.toml. Do not edit. -->
4
+
5
+ ## Unsupported
6
+
7
+ - `native`: The native mod API has no file append; rewriting the whole log would break append-only writes. Install the classic hook instead.
8
+
9
+ ## Degraded
10
+
11
+ - `classic`: Classic PostToolUse fires only after a tool succeeds, so failed tool calls are not logged.
@@ -0,0 +1,14 @@
1
+ {
2
+ "hooks": {
3
+ "PostToolUse": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "node \"$CLAUDE_PROJECT_DIR/.claude/vanguard-mods/audit-trail/bin/hook.mjs\" --harness claude-code --event PostToolUse"
9
+ }
10
+ ]
11
+ }
12
+ ]
13
+ }
14
+ }
@@ -0,0 +1,14 @@
1
+ {
2
+ "hooks": {
3
+ "PostToolUse": [
4
+ {
5
+ "hooks": [
6
+ {
7
+ "type": "command",
8
+ "command": "node .codex/vanguard-mods/audit-trail/bin/hook.mjs --harness codex --event PostToolUse"
9
+ }
10
+ ]
11
+ }
12
+ ]
13
+ }
14
+ }
@@ -0,0 +1,7 @@
1
+ # Audit Trail on Kiro
2
+
3
+ <!-- Generated by scripts/generate-mods.mjs from mod.toml. Do not edit. -->
4
+
5
+ ## Degraded
6
+
7
+ - `PostToolUse`: Kiro does not document stdin field names; args_hash covers the whole payload, result_hash hashes null, and without VANGUARD_AUDIT_TRAIL_SESSION_ID every session shares the unsessioned log.
@@ -0,0 +1,14 @@
1
+ {
2
+ "version": "v1",
3
+ "hooks": [
4
+ {
5
+ "name": "audit-trail:PostToolUse",
6
+ "description": "Appends one JSON line per tool call to a per-session log with sha256 hashes of the arguments and result.",
7
+ "trigger": "PostToolUse",
8
+ "action": {
9
+ "type": "command",
10
+ "command": "node .kiro/vanguard-mods/audit-trail/bin/hook.mjs --harness kiro --event PostToolUse"
11
+ }
12
+ }
13
+ ]
14
+ }
@@ -0,0 +1,213 @@
1
+ // Shared helpers for the mods under mods/. scripts/generate-mods.mjs copies this file
2
+ // byte-for-byte to mods/<id>/lib/common.mjs, so edit it here and run `npm run mods:write`.
3
+ //
4
+ // Pure on purpose: no imports, no environment, no I/O. Claude Code's native mod runtime
5
+ // imports it and has no Node, and every harness must reach the same verdict on the same
6
+ // input. Regexes compile without the `u` flag so Node and Python `re.ASCII`
7
+ // (tests/validate-mods.py) read them the same way.
8
+
9
+ export const LIMITS = Object.freeze({
10
+ // A redaction that still changes after this many passes is withheld as a whole.
11
+ maxPasses: 8,
12
+ // A guard candidate longer than this is not evaluated and counts as a hit.
13
+ maxCandidateChars: 65536,
14
+ // Guard candidates are not evaluated past this combined length; the call counts as a hit.
15
+ maxTotalChars: 262144,
16
+ // Only the tail of a leaf's context (key or previous sibling) is prefixed to it.
17
+ maxContextChars: 256,
18
+ })
19
+
20
+ const MARKER_PREFIX = '[REDACTED:'
21
+ export const OVERFLOW_MARKER = '[REDACTED:overflow]'
22
+ const OVERFLOW_HIT = Object.freeze({ id: 'overflow', name: 'redaction did not converge' })
23
+ export const TOO_LARGE_HIT = Object.freeze({ id: 'too-large', name: 'input too large to evaluate' })
24
+
25
+ const ID_RE = /^[a-z0-9]+(-[a-z0-9]+)*$/
26
+ const PATTERN_KEYS = new Set(['id', 'name', 'description', 'regex', 'flags', 'context', 'severity', 'agents'])
27
+ const isStringArray = (v) => Array.isArray(v) && v.every((s) => typeof s === 'string' && s !== '')
28
+
29
+ // Parser diagnostics can quote credential-bearing data; never expose it in hook logs.
30
+ export function parseJson(text, source) {
31
+ try {
32
+ return JSON.parse(text)
33
+ } catch {
34
+ throw new Error(`${source}: invalid JSON`)
35
+ }
36
+ }
37
+
38
+ // Validates a patterns.json-shaped list and compiles it. Throws on anything malformed so a
39
+ // bad extension file fails closed in the caller instead of silently matching nothing.
40
+ export function compilePatterns(list, source = 'patterns') {
41
+ if (!Array.isArray(list)) throw new Error(`${source}: expected an array of patterns`)
42
+ const seen = new Set()
43
+ return list.map((p, i) => {
44
+ const where = `${source}[${i}]`
45
+ if (p === null || typeof p !== 'object' || Array.isArray(p)) throw new Error(`${where}: expected an object`)
46
+ for (const k of Object.keys(p)) if (!PATTERN_KEYS.has(k)) throw new Error(`${where}: unknown key ${k}`)
47
+ if (typeof p.id !== 'string' || !ID_RE.test(p.id)) throw new Error(`${where}: id must be kebab-case`)
48
+ if (seen.has(p.id)) throw new Error(`${where}: duplicate id ${p.id}`)
49
+ seen.add(p.id)
50
+ if (typeof p.name !== 'string' || p.name === '') throw new Error(`${where}: name must be a non-empty string`)
51
+ if (typeof p.regex !== 'string' || p.regex === '') throw new Error(`${where}: regex must be a non-empty string`)
52
+ if (p.flags !== '' && p.flags !== 'i') throw new Error(`${where}: flags must be "" or "i"`)
53
+ if (!isStringArray(p.context) || p.context.length === 0) throw new Error(`${where}: context must be a non-empty string array`)
54
+ for (const k of ['description', 'severity']) {
55
+ if (p[k] !== undefined && typeof p[k] !== 'string') throw new Error(`${where}: ${k} must be a string`)
56
+ }
57
+ if (p.agents !== undefined && !isStringArray(p.agents)) throw new Error(`${where}: agents must be a string array`)
58
+ let rx, rxg
59
+ try {
60
+ rx = new RegExp(p.regex, p.flags)
61
+ rxg = new RegExp(p.regex, 'g' + p.flags)
62
+ } catch {
63
+ throw new Error(`${where}: regex does not compile`)
64
+ }
65
+ return Object.freeze({
66
+ id: p.id,
67
+ name: p.name,
68
+ severity: p.severity,
69
+ context: Object.freeze([...p.context]),
70
+ agents: p.agents ? Object.freeze([...p.agents]) : undefined,
71
+ rx,
72
+ rxg,
73
+ marker: `${MARKER_PREFIX}${p.id}]`,
74
+ })
75
+ })
76
+ }
77
+
78
+ // Every string value (not key) in depth-first order. The order follows the input, so the
79
+ // same payload always yields the same list.
80
+ export function stringLeaves(value, out = []) {
81
+ if (typeof value === 'string') out.push(value)
82
+ else if (Array.isArray(value)) for (const v of value) stringLeaves(v, out)
83
+ else if (value !== null && typeof value === 'object') for (const v of Object.values(value)) stringLeaves(v, out)
84
+ return out
85
+ }
86
+
87
+ // Guard candidates: every string leaf, plus every all-string array joined with spaces so
88
+ // an argv-style command (["rm", "-rf", "/"]) reads like the shell line it runs.
89
+ export function candidates(value) {
90
+ const out = stringLeaves(value)
91
+ const joinArrays = (v) => {
92
+ if (Array.isArray(v)) {
93
+ if (v.length > 1 && v.every((s) => typeof s === 'string')) out.push(v.join(' '))
94
+ v.forEach(joinArrays)
95
+ } else if (v !== null && typeof v === 'object') {
96
+ Object.values(v).forEach(joinArrays)
97
+ }
98
+ }
99
+ joinArrays(value)
100
+ return out
101
+ }
102
+
103
+ // The patterns tagged with `context` that match any text, in catalog order. Oversized input
104
+ // is not evaluated (regex cost is unbounded there) and reports TOO_LARGE_HIT instead.
105
+ export function matchPatterns(patterns, texts, context) {
106
+ let total = 0
107
+ for (const t of texts) {
108
+ total += t.length
109
+ if (t.length > LIMITS.maxCandidateChars || total > LIMITS.maxTotalChars) return [TOO_LARGE_HIT]
110
+ }
111
+ return patterns.filter((p) => p.context.includes(context) && texts.some((t) => p.rx.test(t)))
112
+ }
113
+
114
+ // One pattern pass after another until the text stops changing. Each pass can expose a new
115
+ // match (password-in-url can swallow an earlier marker), so a single pass is not enough.
116
+ function fixedPoint(text, patterns, hits) {
117
+ let x = text
118
+ for (let pass = 0; pass <= LIMITS.maxPasses; pass++) {
119
+ let y = x
120
+ for (const p of patterns) {
121
+ y = y.replace(p.rxg, (m) => {
122
+ if (m === '') return m
123
+ hits.add(p)
124
+ return p.marker
125
+ })
126
+ }
127
+ if (y === x) return x
128
+ x = y
129
+ }
130
+ hits.add(OVERFLOW_HIT)
131
+ return OVERFLOW_MARKER
132
+ }
133
+
134
+ // Redacts `s` with `ctx` in front so a secret split across a key and its value, or across
135
+ // argv elements ("Bearer", "<token>"), still matches; then strips the context back off.
136
+ function redactLeaf(s, ctx, patterns, hits) {
137
+ if (ctx === '') return fixedPoint(s, patterns, hits)
138
+ const pre = ctx.slice(-LIMITS.maxContextChars) + ' '
139
+ const y = fixedPoint(pre + s, patterns, hits)
140
+ if (y === OVERFLOW_MARKER) return y
141
+ const r = fixedPoint(pre, patterns, new Set())
142
+ if (y.startsWith(r)) return fixedPoint(y.slice(r.length), patterns, hits)
143
+ // A match spans the boundary: keep from the marker that covers it. Slicing changes
144
+ // anchors and boundaries, so also check the standalone leaf after contextual matching.
145
+ let d = 0
146
+ while (d < r.length && y[d] === r[d]) d++
147
+ const i = y.lastIndexOf(MARKER_PREFIX, d)
148
+ return fixedPoint(i >= 0 ? y.slice(i) : y, patterns, hits)
149
+ }
150
+
151
+ function walk(v, ctx, patterns, hits) {
152
+ if (typeof v === 'string') {
153
+ const local = new Set()
154
+ const out = redactLeaf(v, ctx, patterns, local)
155
+ if (out !== v) for (const h of local) hits.add(h)
156
+ return out
157
+ }
158
+ if (Array.isArray(v)) {
159
+ let prev = null
160
+ return v.map((el) => {
161
+ const out = walk(el, typeof prev === 'string' ? prev : ctx, patterns, hits)
162
+ prev = el
163
+ return out
164
+ })
165
+ }
166
+ if (v !== null && typeof v === 'object') {
167
+ const named = typeof v.name === 'string' ? v.name : typeof v.key === 'string' ? v.key : null
168
+ // fromEntries, not assignment: `out[k] = v` would drop an own "__proto__" key.
169
+ return Object.fromEntries(
170
+ Object.entries(v).map(([k, x]) => [
171
+ walk(k, '', patterns, hits),
172
+ walk(x, k === 'value' && named !== null ? named : k, patterns, hits),
173
+ ]),
174
+ )
175
+ }
176
+ return v
177
+ }
178
+
179
+ // Returns a copy of `value` with every match replaced by `[REDACTED:<id>]`, plus the
180
+ // patterns that fired (catalog order, unique). Keys are redacted too; two keys that redact
181
+ // to the same marker collapse into one, which loses data but never leaks it.
182
+ export function redactValue(value, patterns) {
183
+ const hits = new Set()
184
+ const out = walk(value, '', patterns, hits)
185
+ const order = [...patterns, OVERFLOW_HIT]
186
+ return { value: out, hits: order.filter((p) => hits.has(p)) }
187
+ }
188
+
189
+ // Same shape as `value`, every string replaced by `text`. The fallback when redaction
190
+ // itself failed: withhold everything rather than pass anything through.
191
+ export function maskValue(value, text) {
192
+ if (typeof value === 'string') return text
193
+ if (Array.isArray(value)) return value.map((v) => maskValue(v, text))
194
+ if (value !== null && typeof value === 'object') {
195
+ return Object.fromEntries(Object.entries(value).map(([k, v]) => [k, maskValue(v, text)]))
196
+ }
197
+ return value
198
+ }
199
+
200
+ // JSON with object keys sorted at every level, so equal values hash equally.
201
+ export function canonicalJson(value) {
202
+ if (Array.isArray(value)) return '[' + value.map((v) => (v === undefined ? 'null' : canonicalJson(v))).join(',') + ']'
203
+ if (value !== null && typeof value === 'object') {
204
+ const keys = Object.keys(value).filter((k) => value[k] !== undefined).sort()
205
+ return '{' + keys.map((k) => JSON.stringify(k) + ':' + canonicalJson(value[k])).join(',') + '}'
206
+ }
207
+ return JSON.stringify(value) ?? 'null'
208
+ }
209
+
210
+ export async function sha256Hex(text) {
211
+ const digest = await globalThis.crypto.subtle.digest('SHA-256', new TextEncoder().encode(text))
212
+ return Array.from(new Uint8Array(digest), (b) => b.toString(16).padStart(2, '0')).join('')
213
+ }