@codapult/guard 0.10.0 → 0.11.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 (40) hide show
  1. package/README.md +144 -4
  2. package/dist/adapters/agents/agent-integration.js +26 -3
  3. package/dist/cli/commands/guard.d.ts +2 -0
  4. package/dist/cli/commands/guard.js +77 -11
  5. package/dist/cli/index.js +2 -0
  6. package/dist/core/guard.d.ts +28 -1
  7. package/dist/core/guard.js +37 -0
  8. package/dist/core/history/runs.d.ts +64 -1
  9. package/dist/core/history/runs.js +109 -0
  10. package/dist/core/output/attestation.d.ts +17 -0
  11. package/dist/core/output/attestation.js +22 -0
  12. package/dist/core/policy/schemas.d.ts +47 -0
  13. package/dist/core/policy/schemas.js +67 -0
  14. package/dist/core/verification/github-actions.d.ts +25 -0
  15. package/dist/core/verification/github-actions.js +397 -0
  16. package/dist/core/verification/github-context.d.ts +8 -0
  17. package/dist/core/verification/github-context.js +41 -0
  18. package/dist/core/verification/verify.d.ts +23 -4
  19. package/dist/core/verification/verify.js +601 -47
  20. package/dist/index.d.ts +1 -0
  21. package/dist/index.js +1 -0
  22. package/dist/mcp/prompts.js +7 -4
  23. package/dist/mcp/tools/guard.js +9 -3
  24. package/docs/extensions.md +79 -0
  25. package/docs/guard-agent-integration.md +51 -0
  26. package/docs/guard-ci.yml +202 -0
  27. package/docs/guard-fixtures.md +127 -0
  28. package/docs/guard-trusted-pr-attestation.yml +213 -0
  29. package/docs/guard.md +1125 -0
  30. package/docs/integrations/README.md +38 -0
  31. package/docs/integrations/claude-code.md +92 -0
  32. package/docs/integrations/codex.md +58 -0
  33. package/docs/integrations/cursor.md +116 -0
  34. package/docs/integrations/gemini-cli.md +47 -0
  35. package/docs/integrations/generic.md +41 -0
  36. package/docs/integrations/github-copilot.md +72 -0
  37. package/docs/policy-mutations.md +75 -0
  38. package/docs/releasing.md +58 -0
  39. package/docs/verify-guard-attestation.mjs +166 -0
  40. package/package.json +4 -2
package/docs/guard.md ADDED
@@ -0,0 +1,1125 @@
1
+ # Codapult Guard: Architecture Guardrails for AI Coding Agents
2
+
3
+ Codapult Guard is local-first architecture guardrail software for AI coding agents and JavaScript
4
+ or TypeScript repositories. It detects architectural drift by observing the existing software
5
+ architecture, recording project memory, and checking code changes against team-approved rules and
6
+ contracts. It also provides bounded project context for AI code review and deterministic checks for
7
+ local development and CI.
8
+
9
+ Guard is not a replacement for ESLint, TypeScript, a test runner, SAST, or a PR review service.
10
+ It is universal at the core and especially useful for Next.js SaaS projects, where server/client
11
+ boundaries, routes, persistence, authentication, billing, jobs, and AI integrations commonly need
12
+ project-specific architectural protection.
13
+ It can run a project's existing commands as an optional completion gate, but its own responsibility
14
+ is architectural memory, project-specific contracts, regression detection, impact context, and
15
+ deterministic evidence.
16
+
17
+ ## Contents
18
+
19
+ - [The operating model](#the-operating-model)
20
+ - [Install and initialize](#install-and-initialize)
21
+ - [The generated state](#the-generated-state)
22
+ - [The daily workflow](#the-daily-workflow)
23
+ - [Policy scope and state changes](#policy-scope-and-state-changes)
24
+ - [Command reference](#command-reference)
25
+ - [Rules, contracts, and proposals](#rules-contracts-and-proposals)
26
+ - [What Guard discovers](#what-guard-discovers)
27
+ - [AI-agent and MCP workflow](#ai-agent-and-mcp-workflow)
28
+ - [CI and machine-readable output](#ci-and-machine-readable-output)
29
+ - [Security and data handling](#security-and-data-handling)
30
+ - [Troubleshooting](#troubleshooting)
31
+ - [Implementation boundaries](#implementation-boundaries)
32
+
33
+ ## The operating model
34
+
35
+ Guard separates four concerns:
36
+
37
+ ```text
38
+ Facts → Policy → Verification → Decision
39
+ AST contracts changed diff pass / fail
40
+ files rules impact paths warning
41
+ Git baseline project tools needs-review
42
+ graph conventions adapters not-configured
43
+ ```
44
+
45
+ ### Facts
46
+
47
+ Facts are discovered from the working tree and are not architectural guesses. Depending on the
48
+ project, the model includes:
49
+
50
+ - files, source files, tests, configs, schemas, dependencies, workspaces, and scripts;
51
+ - TypeScript/JavaScript AST modules, imports, resolved imports, calls, and dependency graph edges;
52
+ - Next.js routes and route methods, React client components, server actions, and API boundaries;
53
+ - detected capabilities such as persistence, identity, payments, email, AI, jobs, storage,
54
+ observability, GraphQL/RPC, i18n, deployment, webhooks, cache, analytics, search, and content;
55
+ - environment references, Git state, changed files, history snapshots, cycles, layer edges,
56
+ hotspots, boundaries, and impact paths;
57
+ - evidence-based patterns and signals. A capability or pattern is reported with evidence and is
58
+ not automatically treated as a mandatory architecture.
59
+
60
+ ### Policy
61
+
62
+ Policy is the part the project explicitly accepts. It is stored in Guard state and may contain:
63
+
64
+ - active or proposed rules;
65
+ - contracts describing project-specific boundaries and required calls;
66
+ - conventions and architecture memory generated from observed evidence;
67
+ - a baseline of findings that existed before Guard was enabled.
68
+ - optional scoped budgets for explicitly selected risk boundaries;
69
+
70
+ Guard does not assume that every project must have `UI → actions → services → repositories → DB`.
71
+ That shape may be discovered as evidence, proposed for review, and accepted only by a developer.
72
+
73
+ ### Verification and decision
74
+
75
+ Verification evaluates the current working tree or a diff. It can check Guard policy, configured
76
+ project scripts, detected external-tool adapters, runtime compatibility, and contract validity.
77
+ The normalized result is one of:
78
+
79
+ | Outcome | Meaning |
80
+ | ---------------- | ---------------------------------------------------------------------------------------------------------- |
81
+ | `pass` | No blocking Guard or configured verification failure. |
82
+ | `fail` | A blocking finding, invalid contract, failed command, incompatible runtime, or strict missing tool exists. |
83
+ | `warning` | No blocking failure, but warnings were found. |
84
+ | `needs-review` | Deterministic context is ready for semantic AI review. |
85
+ | `not-configured` | The project has not been initialized or the requested state is unavailable. |
86
+
87
+ Command-specific `status` fields may still exist for compatibility with that command. Consumers
88
+ that need one common decision should use `outcome`.
89
+
90
+ ## Install and initialize
91
+
92
+ Install Guard as a development dependency so local and CI versions are reproducible:
93
+
94
+ ```bash
95
+ pnpm add -D @codapult/guard
96
+ ```
97
+
98
+ Run commands from the project root. Guard finds the nearest directory containing `.git`,
99
+ `package.json`, `tsconfig.json`, or `jsconfig.json`.
100
+
101
+ Initialize once:
102
+
103
+ ```bash
104
+ pnpm exec codapult-guard init
105
+ ```
106
+
107
+ Initialization:
108
+
109
+ 1. discovers the current project model;
110
+ 2. writes project, architecture, convention, proposal, contract, and agent artifacts;
111
+ 3. generates evidence-based proposed rules and contracts;
112
+ 4. records current findings in the baseline;
113
+ 5. adds generated Guard state to the existing formatter ignore file when supported.
114
+
115
+ Initialization is protected. If a baseline already exists, the command stops instead of silently
116
+ changing project memory. Use `--force` only when intentionally replacing the Guard state:
117
+
118
+ ```bash
119
+ pnpm exec codapult-guard init --force
120
+ ```
121
+
122
+ `--force` is a new baseline decision, not a routine refresh operation. Review and commit the
123
+ result deliberately.
124
+
125
+ ## The generated state
126
+
127
+ Guard stores its project memory under `.codapult/guard/`:
128
+
129
+ ```text
130
+ .codapult/
131
+ └── guard/
132
+ ├── agent.json
133
+ ├── architecture.json
134
+ ├── baseline.json
135
+ ├── baseline-meta.json
136
+ ├── cache.json
137
+ ├── conventions.json
138
+ ├── contracts.json
139
+ ├── history/
140
+ ├── project.json
141
+ ├── proposals.json
142
+ └── rules.json
143
+ ```
144
+
145
+ Guard refuses to create or update state when `.codapult` or `.codapult/guard` is a symbolic link.
146
+ This keeps generated state inside the selected project root.
147
+
148
+ | File | Purpose |
149
+ | -------------------- | ------------------------------------------------------------------------------------------------------------------------- |
150
+ | `project.json` | Persisted discovered project model: files, modules, dependencies, routes, capabilities, patterns, Git data, and insights. |
151
+ | `architecture.json` | Human/agent-readable architecture memory derived from observed project facts. |
152
+ | `conventions.json` | Observed conventions and recurring project patterns. |
153
+ | `rules.json` | Guard rules, optional scoped budgets, and approval policy. Active items are enforced; proposed items are not. |
154
+ | `contracts.json` | Project-specific guidance, import boundaries, and required-call contracts. |
155
+ | `proposals.json` | Evidence, confidence, questions, and approval/rejection history for proposed policy. |
156
+ | `baseline.json` | Fingerprints of accepted pre-existing findings. Baseline suppression is fingerprint-based. |
157
+ | `baseline-meta.json` | Metadata describing the baseline and its project snapshot. |
158
+ | `waivers.json` | Time-bounded, owner-attributed exceptions for active findings. |
159
+ | `agent.json` | Host-facing completion-gate and external-tool policy. It does not execute an LLM. |
160
+ | `cache.json` | Optional discovery cache. Unchanged AST modules can be reused by content hash. |
161
+ | `history/` | Project model snapshots, history diffs, and local verification run manifests. |
162
+
163
+ Discovery is bounded by default to 100,000 files and 25 MiB per file. These limits prevent an
164
+ accidental scan of build artifacts or unusually large inputs from exhausting local resources.
165
+ Use the public discovery API options `maxFiles` and `maxFileBytes` to raise them deliberately after
166
+ configuring the project's ignore boundaries.
167
+
168
+ Keep the policy and memory files under version control when the team wants shared architecture
169
+ guardrails. Treat `cache.json` as disposable implementation cache if the team does not want it
170
+ committed. Never commit secrets; Guard excludes common secret files from review input.
171
+
172
+ ## The daily workflow
173
+
174
+ The normal loop is:
175
+
176
+ ```text
177
+ init once → edit → check --changed → review → verify → commit / merge
178
+ ↘ analyze after structural changes
179
+ ```
180
+
181
+ ### During a task
182
+
183
+ Run the fast architecture gate on changed and untracked source files:
184
+
185
+ ```bash
186
+ pnpm exec codapult-guard check --changed
187
+ ```
188
+
189
+ This compares new findings with the baseline. It does not require the team to repair every old
190
+ finding immediately.
191
+
192
+ ### Before completion or merge
193
+
194
+ Run the unified gate:
195
+
196
+ ```bash
197
+ pnpm exec codapult-guard verify
198
+ ```
199
+
200
+ By default, `verify` combines Guard checks with the configured completion-gate checks (`lint`,
201
+ `typecheck`, `test`, and `build`) and detected external adapters. To run only Guard's own checks:
202
+
203
+ ```bash
204
+ pnpm exec codapult-guard verify --no-project-checks
205
+ ```
206
+
207
+ Use `--checks lint,typecheck,test` to select project commands for one invocation. Use
208
+ `--tools auto|on|off` to control external adapters. `auto` is the default and only runs adapters
209
+ whose matching project scripts exist; `on` also reports missing adapters; `off` skips them.
210
+
211
+ ### When the project structure changes
212
+
213
+ Refresh persisted project memory without changing the baseline:
214
+
215
+ ```bash
216
+ pnpm exec codapult-guard analyze
217
+ ```
218
+
219
+ Use this after adding a package, moving modules, changing routes/configuration, changing a
220
+ workspace, or introducing a new capability. Do not run `init` after every edit. `check`, `review`,
221
+ `verify`, and refreshed MCP context discover the current working tree themselves.
222
+
223
+ ### Periodic maintenance
224
+
225
+ See all current findings, including baseline-suppressed findings:
226
+
227
+ ```bash
228
+ pnpm exec codapult-guard audit
229
+ ```
230
+
231
+ Inspect Guard state and missing artifacts:
232
+
233
+ ```bash
234
+ pnpm exec codapult-guard doctor
235
+ ```
236
+
237
+ ## Policy scope and state changes
238
+
239
+ Guard keeps one project policy, and each policy item can be scoped to the boundary it protects:
240
+
241
+ | Policy item | Scope controls |
242
+ | ----------- | ---------------------------------------------------------------------- |
243
+ | Rule | `files`, import patterns, and deterministic rule kind |
244
+ | Contract | `scope`, `entrypoints`, import/package constraints, and required calls |
245
+ | Budget | `scope`, metric, limit, severity, and written reason |
246
+ | Baseline | Exact finding fingerprints accepted as legacy debt |
247
+ | Waiver | One active finding fingerprint, owner, reason, and expiry |
248
+
249
+ The normal verification path does not change source code or approved policy. The operations below
250
+ have explicit state-changing behavior:
251
+
252
+ | Operation | Reads | Writes | Policy effect |
253
+ | --------------------------------------------- | --------------------------------- | -------------------------------------------- | ----------------------------------------------- |
254
+ | `check`, `audit`, `review`, `impact` | Project and Guard state | Output only | None |
255
+ | `verify` | Project and Guard state | Local run manifest; runs configured commands | None |
256
+ | `analyze` | Project source and Git state | Derived facts and snapshot | Does not change baseline or policy |
257
+ | `init` | Project source and Git state | Initial Guard state | Creates the initial baseline |
258
+ | `propose` | Project source and policy | Optional proposal file | Proposals are not active policy |
259
+ | `rules approve` / `contracts approve\|reject` | Policy and proposal state | Policy and decision history | Activates rules; activates or rejects contracts |
260
+ | `baseline accept\|remove` | Current findings and baseline | Baseline and decision history | Changes legacy-debt suppression |
261
+ | `waiver add\|renew\|remove` | Current findings and waiver state | Waivers and decision history | Changes a temporary exception |
262
+
263
+ `check` and MCP `codapult_guard_check` scan the full current project unless changed-only mode is
264
+ requested. `check --changed`, `verify --changed`, and MCP `changed_only: true` scope deterministic
265
+ findings to changed files; `review` is changed-only by default and also includes impact evidence.
266
+ `audit` intentionally ignores baseline suppression so maintenance can see the complete current debt.
267
+
268
+ Guard fails closed when `rules.json` or `contracts.json` is malformed: `verify` returns a structured
269
+ failure and CLI checks do not treat corrupted policy as an uninitialized project. Repair the file
270
+ or review the diagnosis before using `init --force`.
271
+
272
+ CLI and MCP expose the same structured error payload for `GUARD_NOT_CONFIGURED`,
273
+ `GUARD_CONFIG_INVALID`, and retryable `GUARD_STATE_BUSY` concurrent-write conflicts.
274
+ Malformed `agent.json`, `proposals.json`, and `baseline.json` are also rejected instead of being
275
+ silently replaced with defaults or an empty state.
276
+
277
+ Guard state writes use a root-level transaction lock with atomic rename. The lock records PID,
278
+ hostname, command, token, and lease metadata. Writers wait with bounded backoff by default; use
279
+ `init --no-wait` or `analyze --no-wait` for an immediate retryable `GUARD_STATE_BUSY` result.
280
+ If the recorded PID is no longer alive, the orphaned lock is recovered. Age alone is used only for
281
+ malformed legacy locks, so a long-running live process is not interrupted.
282
+
283
+ Derived facts are committed as one generation bundle under `state/generations/` and selected by an
284
+ atomic `state/current.json` pointer. The root-level JSON files remain compatibility mirrors. Policy
285
+ writes carry a revision and reject stale writers with `GUARD_STATE_STALE` instead of silently
286
+ overwriting a newer policy.
287
+
288
+ Policy activation also uses a small local transaction journal. If the process stops between writing
289
+ the policy and its proposal decision history, the next Guard read repairs both artifacts from the
290
+ journal before continuing. The journal is local recovery metadata, not a distributed transaction
291
+ system; Git/CI remains the source of truth for collaboration and protected review.
292
+
293
+ Compare persisted project states:
294
+
295
+ ```bash
296
+ pnpm exec codapult-guard history
297
+ pnpm exec codapult-guard history-diff <from> <to>
298
+ ```
299
+
300
+ ## Command reference
301
+
302
+ All commands return a non-zero exit code when their decision is blocking. Add `--json` where the
303
+ command supports it for automation.
304
+
305
+ | Command | Use |
306
+ | ------------------------------------------------ | ------------------------------------------------------------------------------------------------- |
307
+ | `codapult-guard init [--no-wait]` | Create Guard state and establish the initial baseline. Refuses an existing baseline. |
308
+ | `codapult-guard init --force [--no-wait]` | Replace existing Guard state intentionally. |
309
+ | `codapult-guard analyze [--refresh] [--no-wait]` | Refresh persisted discovery, architecture, conventions, and snapshot. Does not alter baseline. |
310
+ | `codapult-guard propose` | Generate evidence-based rules/contracts for review. Does not activate them. |
311
+ | `codapult-guard doctor [--fix-cache]` | Diagnose state; optionally remove the disposable discovery cache. |
312
+ | `codapult-guard history` | List persisted project snapshots. |
313
+ | `codapult-guard runs` | List local verification runs and outcome/latency summary. |
314
+ | `codapult-guard history-diff <from> <to>` | Compare files, modules, dependencies, capabilities, graph edges, and cycles. |
315
+ | `codapult-guard impact <files...>` | Explain direct/transitive dependencies, dependents, capabilities, and relevant contracts. |
316
+ | `codapult-guard policy explain <id>` | Explain an active or proposed rule/contract, its evidence, and approval history. |
317
+ | `codapult-guard check [--changed]` | Enforce active Guard rules/contracts; `--changed` limits findings to changed and untracked files. |
318
+ | `codapult-guard audit` | Run a full current Guard scan without baseline suppression and validate policy definitions. |
319
+ | `codapult-guard review` | Produce a bounded diff + project-context packet for semantic AI review. |
320
+ | `codapult-guard review --base origin/main` | Build the review packet from a PR base ref. |
321
+ | `codapult-guard verify` | Run Guard, project checks, adapters, runtime, and contract verification as configured. |
322
+ | `codapult-guard governance [--json] [--strict]` | Audit declared proposal/approval provenance and links from decisions to verification runs. |
323
+ | `codapult-guard rules approve <ids>` | Activate selected proposed rules. |
324
+ | `codapult-guard rules approve --all` | Activate every proposed rule deliberately. |
325
+ | `codapult-guard contracts approve <ids>` | Activate selected proposed contracts. |
326
+ | `codapult-guard contracts reject <ids>` | Record rejection for selected proposed contracts. |
327
+ | `codapult-guard baseline accept\|remove <ids>` | Add or remove baseline fingerprints; a written reason is required. |
328
+ | `codapult-guard waiver add\|renew\|remove` | Manage owner-attributed, time-bounded exceptions to exact finding fingerprints. |
329
+ | `codapult-guard install-agent <target>` | Add or update a managed Guard instruction block for an AI host. |
330
+
331
+ `codapult-guard review` does not call an LLM. It creates input for one. A review packet includes changed
332
+ files, typed file changes, a bounded/redacted diff, project model, contracts, deterministic
333
+ findings, and review instructions. It also includes directional impact: the changed modules'
334
+ dependencies, transitive dependents, affected capabilities, impact paths, relevant contracts,
335
+ and graph edges. If the Git base is invalid, the packet contains `diffError` and has a failing
336
+ outcome.
337
+
338
+ `changes` distinguishes `added`, `modified`, `deleted`, and `renamed` paths. Deleted and renamed
339
+ paths are still checked against the repository root safely; they are not silently discarded just
340
+ because the old file no longer exists.
341
+
342
+ Manage baseline entries without rebuilding the entire project state:
343
+
344
+ ```bash
345
+ pnpm exec codapult-guard baseline list
346
+ pnpm exec codapult-guard baseline accept <fingerprint> --reason "Accepted legacy boundary"
347
+ pnpm exec codapult-guard baseline remove <fingerprint> --reason "Fixed in the current architecture"
348
+ ```
349
+
350
+ Use `--all` only as an explicit decision. `baseline accept --all` accepts all findings from the
351
+ current full scan; `baseline remove --all` removes fingerprints represented by the current scan.
352
+ Each update preserves a decision record in `baseline-meta.json`. A written `--reason` is required
353
+ for every manual baseline add or remove; initialization is the only operation that may create the
354
+ initial baseline without a manual reason.
355
+
356
+ ### Fingerprint stability
357
+
358
+ Finding fingerprints intentionally omit line numbers. Formatting, whitespace changes, and moving a
359
+ violation to another line therefore do not recreate the same finding. A fingerprint includes the
360
+ rule or contract identity and the affected file/import identity. Renaming or moving a module can
361
+ therefore produce a new fingerprint; inspect that result and accept it explicitly when the move is
362
+ intentional. Guard does not silently transfer a baseline entry between files.
363
+
364
+ ### Temporary waivers
365
+
366
+ Use a waiver for a known, temporary exception to an active rule or contract. Do not use it to
367
+ replace baseline management or to create a broad path-level exemption. A waiver is tied to one
368
+ finding fingerprint and requires an owner, reason, creation time, and expiry time:
369
+
370
+ ```bash
371
+ pnpm exec codapult-guard waiver add <fingerprint> \
372
+ --owner platform-team \
373
+ --reason "Migration in progress" \
374
+ --expires 2026-12-28 \
375
+ --issue https://github.com/example/project/issues/123
376
+ ```
377
+
378
+ Use `waiver list`, `waiver renew`, and `waiver remove` to manage the lifecycle. The default warning
379
+ window is 14 days before expiry. Active waivers suppress only their exact fingerprint and remain
380
+ visible in reports. Expired waivers no longer suppress findings. `check`, `audit`, and `verify`
381
+ reports, review packets, the `codapult_guard_waivers` MCP tool, and the `codapult://guard/waivers` resource
382
+ expose waiver state. Protected policy can require approval outside MCP.
383
+
384
+ Projects may cap the duration of each newly added or renewed waiver without imposing a global
385
+ expiry policy:
386
+
387
+ ```json
388
+ {
389
+ "waiverPolicy": {
390
+ "warningDays": 14,
391
+ "maxDays": 90
392
+ }
393
+ }
394
+ ```
395
+
396
+ `maxDays` limits the requested lifetime from the time of the add or renew operation. It is an
397
+ optional governance control; omit it when the project wants explicit, owner-reviewed expiry dates
398
+ without a fixed maximum. Boundary violations reported by Guard remain blocking when their finding
399
+ severity is `error`; warnings and expiry reminders are advisory.
400
+
401
+ `codapult-guard doctor` also reports explicitly downgraded client, import, or package boundaries as
402
+ warnings. It does not change their configured severity; the project owner decides whether the
403
+ boundary should become blocking.
404
+
405
+ ### Scoped budgets
406
+
407
+ Budgets are optional policy for a specific risk boundary, not a global file-size rule. They support
408
+ `lines`, `bytes`, and `imports` and require a non-empty scope, limit, severity, and reason:
409
+
410
+ ```json
411
+ {
412
+ "budgets": [
413
+ {
414
+ "id": "service-lines",
415
+ "description": "Services must remain reviewable.",
416
+ "metric": "lines",
417
+ "scope": ["src/services"],
418
+ "limit": 300,
419
+ "severity": "warning",
420
+ "reason": "Keep service changes reviewable by one owner.",
421
+ "status": "active"
422
+ }
423
+ ]
424
+ }
425
+ ```
426
+
427
+ Use budgets only where size or dependency count is evidence of a concrete project risk. Do not
428
+ apply them indiscriminately to generated code, schemas, migrations, localization files, or UI
429
+ composition. Explicitly scoped generated files and schemas are supported, but invalid, absolute,
430
+ parent-directory, or nonexistent scopes fail the Guard gate. Guard does not automatically invent
431
+ budgets during `init`.
432
+
433
+ ## Rules, contracts, and proposals
434
+
435
+ ### Rules
436
+
437
+ Rules are deterministic checks. The current rule kinds are:
438
+
439
+ ```json
440
+ {
441
+ "id": "no-client-db",
442
+ "description": "Client modules must not import the database adapter.",
443
+ "severity": "error",
444
+ "kind": "client-forbidden-import",
445
+ "patterns": ["@/lib/db", "drizzle-orm"],
446
+ "files": ["src/components"],
447
+ "status": "active",
448
+ "confidence": "high",
449
+ "evidence": ["src/components/ExistingClient.tsx"]
450
+ }
451
+ ```
452
+
453
+ Use an existing specialist tool for generic syntax/style/type rules. Guard rules should express
454
+ project-specific boundaries and architectural invariants, not duplicate ESLint or TypeScript.
455
+
456
+ ### Contracts
457
+
458
+ Contracts express intent and boundaries that are meaningful in this project. Supported contract
459
+ kinds are:
460
+
461
+ - `guidance`: a statement, optional guidance, and references for an AI reviewer;
462
+ - `import-boundary`: modules must or must not import specified patterns;
463
+ - `required-call`: an entrypoint must call one of the specified functions or patterns.
464
+
465
+ Example:
466
+
467
+ ```json
468
+ {
469
+ "id": "server-actions-use-auth",
470
+ "statement": "Every billing server action authenticates the active organization.",
471
+ "kind": "required-call",
472
+ "severity": "error",
473
+ "scope": ["src/lib/actions/billing"],
474
+ "mustCall": ["requireOrganizationMember"],
475
+ "references": ["src/lib/auth/require-organization-member.ts"],
476
+ "status": "active",
477
+ "confidence": "high"
478
+ }
479
+ ```
480
+
481
+ Package boundaries use workspace package names discovered from manifests. They are useful in
482
+ monorepos where a relative path rule would be too fragile:
483
+
484
+ ```json
485
+ {
486
+ "id": "apps-cannot-import-db-package",
487
+ "statement": "Application packages must not import the database package directly.",
488
+ "kind": "package-boundary",
489
+ "severity": "error",
490
+ "fromPackages": ["@acme/web", "@acme/admin"],
491
+ "mustNotImportPackages": ["@acme/db"],
492
+ "status": "active",
493
+ "confidence": "high"
494
+ }
495
+ ```
496
+
497
+ `verify` also returns an impact analysis: changed modules, direct dependencies, transitive
498
+ dependents, affected capabilities, and relevant contracts. This is evidence for review, not an
499
+ automatic claim that every dependent needs modification. `check --changed` includes changed files
500
+ and their transitive dependents so a changed implementation cannot bypass an unchanged entrypoint
501
+ contract.
502
+
503
+ Rule scopes, contract paths, and budget scopes are repository-relative paths. Guard rejects
504
+ absolute paths, `..` traversal, and symlinks that resolve outside the project root. Rule scopes may
505
+ target future files, while contract and budget scopes must still exist. `verify` and `audit` validate
506
+ these paths and policy definitions before scanning. A stale or unsafe policy is a policy problem,
507
+ not a source-code finding, and causes verification to fail.
508
+
509
+ Import-boundary contracts cover static imports, re-exports, and literal dynamic imports. Repeated
510
+ references to the same module are deduplicated into one finding. When TypeScript resolves an alias
511
+ or re-export, findings retain the original import specifier and expose the resolved project file as
512
+ `resolvedPath`, so an agent can repair the actual boundary without losing source context.
513
+
514
+ ### Proposals and approval
515
+
516
+ Generate proposals after initialization or a structural change:
517
+
518
+ ```bash
519
+ pnpm exec codapult-guard propose --json
520
+ ```
521
+
522
+ The proposal includes evidence, confidence, questions, and a freshness marker. Review every
523
+ proposal against the actual code. Activate only the decisions the project wants to preserve:
524
+
525
+ ```bash
526
+ pnpm exec codapult-guard rules approve no-client-db
527
+ pnpm exec codapult-guard contracts approve server-actions-use-auth
528
+ ```
529
+
530
+ Proposals become stale when the project changes. Regenerate them rather than approving a proposal
531
+ based on old evidence. The CLI and MCP proposal paths protect activation and preserve decision
532
+ history; the AI must not activate policy silently.
533
+
534
+ ### Approval trust modes
535
+
536
+ The default policy is local mode. `confirm: true` or a CLI approval means that the caller
537
+ explicitly requested the state change; it does not prove that a human made the decision.
538
+
539
+ For agent-driven or protected workflows, configure the policy in `rules.json`:
540
+
541
+ ```json
542
+ {
543
+ "approval": {
544
+ "mode": "protected",
545
+ "allowMcpApproval": false,
546
+ "requireDistinctActor": true
547
+ }
548
+ }
549
+ ```
550
+
551
+ In protected mode, MCP can inspect and propose policy but cannot approve it. Approval must go
552
+ through the CLI or an external protected review process such as branch protection. Decision
553
+ records include the proposal fingerprint, current commit when available, source (`cli`, `mcp`, or
554
+ `external`), and optional `GUARD_APPROVER` metadata. When `requireDistinctActor` is enabled, CLI
555
+ and permitted MCP approvals require `GUARD_APPROVER`; if the proposal declares `generatedBy` via
556
+ `GUARD_PROPOSER`, Guard rejects the same declared actor approving it. These environment variables
557
+ are declared provenance, not cryptographic identity verification; reviewer permissions belong to
558
+ the host CI/review system.
559
+
560
+ This separation is intentional: Guard checks that an approval matches the current evidence, while
561
+ GitHub or another protected system determines who is authorized to approve it.
562
+
563
+ See [Policy mutation control](policy-mutations.md) for the machine-readable mutation contract and
564
+ the host/CI command boundary that prevents an agent from granting itself a waiver or replacing a
565
+ baseline.
566
+
567
+ ### Policy provenance in CI
568
+
569
+ `verify` normally reads policy from the working tree. For pull requests, pass the protected base
570
+ commit:
571
+
572
+ ```bash
573
+ codapult-guard verify --policy-base "$BASE_SHA" --json
574
+ ```
575
+
576
+ The base snapshot includes `rules.json`, `contracts.json`, `baseline.json`, `waivers.json`, and
577
+ `agent.json` when present. Guard uses that snapshot for the verdict and records its revision and
578
+ effective fingerprint in the run result and local run manifest. The result also reports whether
579
+ the branch changed the policy relative to the base.
580
+
581
+ Use `--fail-on-policy-change` for a dedicated policy gate. Repository hosting controls such as
582
+ CODEOWNERS and protected branches remain responsible for requiring and authorizing the separate
583
+ review; Guard does not impersonate a reviewer or verify provider credentials.
584
+
585
+ Guard also compares verification-sensitive paths against the base. Built-in paths include CI
586
+ workflows, CODEOWNERS, package manifests and lockfiles, and common formatter, linter, typecheck,
587
+ and test configuration. Add project-specific globs to `protectedPaths` in the base revision's
588
+ `.codapult/guard/rules.json`. The `--fail-on-protected-changes` option requires `--policy-base`
589
+ and fails if a listed non-workflow path changes, or if a changed contracted workflow fails its
590
+ CI integrity checks. JSON output lists the matched paths and reports whether a workflow changed.
591
+
592
+ For an explicit and classified policy surface, configure `policySurface.paths` in that same base
593
+ policy. Each entry has a repository-relative `pattern`, a `kind` (`ci-workflow`, `guard-policy`, `ownership`,
594
+ `dependency-input`, `tool-config`, `ignore-config`, `suppression-source`, or `custom`), and a
595
+ `severity` (`error` or `warning`). Error entries block when `--fail-on-protected-changes` is set;
596
+ warning entries are reported without blocking. The legacy `protectedPaths` list remains supported
597
+ and maps to blocking custom entries. Built-in sensitive paths cannot be downgraded by project
598
+ configuration. Entries and match details are included in the JSON result and receipt.
599
+
600
+ The same option reports added `eslint-disable`, `@ts-ignore`, `@ts-nocheck`, `@ts-expect-error`,
601
+ `biome-ignore`, and `prettier-ignore` markers in the PR diff. Guard identifies markers but leaves
602
+ their semantics to the relevant tool. Changes to or removal of the workflow itself still require
603
+ host-enforced required checks and protected review; a workflow cannot guarantee its own integrity.
604
+ The supplied workflow template also rejects PR changes to package manifests, common npm/pnpm/Yarn/Bun
605
+ lockfiles, package-manager configuration, and workspace install inputs before setting up pnpm or installing
606
+ dependencies. Handle those changes in a separate reviewed PR. This does not replace protecting the
607
+ workflow in repository settings.
608
+
609
+ ### Required stages and CI integrity
610
+
611
+ `agent.json` can list required stages in `completionGate.requiredStages`. Supported values are
612
+ `runtime`, `project-checks`, `adapters`, `architecture-policy`, `policy-surface`, `ci-integrity`, and
613
+ `verification-scope`. The compatibility default requires only `architecture-policy`; skipped required
614
+ stages produce `incomplete`, a nonzero CLI exit, and a list in the receipt. Using a base policy
615
+ automatically requires the policy-surface comparison. Declaring a GitHub Actions integrity
616
+ contract automatically requires the CI audit. Verification scope is always required: Guard hashes
617
+ discovered project files and changed paths before running checks, then verifies that the scope
618
+ remained stable through the run.
619
+
620
+ For GitHub Actions, `rules.json` may declare exact workflow paths, a required job, required command
621
+ fragments, expected dependencies, permission ceilings, and whether external actions must use full
622
+ commit SHA pins. Guard reads this contract and the base workflow from the base policy revision for
623
+ PR verification. It compares action refs with that base and classifies additions, removals, or SHA
624
+ changes as `policy-weakened` for separate review, even if the new ref is pinned. It classifies
625
+ missing protections as `policy-weakened` and unreadable or invalid workflow data as `incomplete`.
626
+ It does not evaluate arbitrary GitHub expressions or verify repository settings, so branch
627
+ protection or rulesets must still require the status check from the expected source.
628
+ The audit rejects `pull_request_target` on a contracted verification workflow because Guard cannot
629
+ prove that it never executes untrusted PR content with that event's elevated context.
630
+ Local actions cannot use immutable commit SHA references. If the workflow contract disables
631
+ `requireFullShaActions` to permit them, add each local action's source directory to `protectedPaths`
632
+ in the base policy so a PR cannot change the code being invoked.
633
+ The receipt records the effective Guard invocation and project check commands/arguments, with
634
+ credential-like values redacted.
635
+
636
+ The receipt's `verificationScope` contains SHA-256 hashes and paths for files discovered by Guard,
637
+ plus changed and deleted paths. Its fingerprint binds the inventory to the effective policy and
638
+ Guard version. If a path cannot be hashed or the inventory changes during verification, the
639
+ `verification-scope` stage is incomplete and the overall result cannot pass. The receipt includes
640
+ hashes and paths but no source contents; it is not signed and does not prove who ran Guard.
641
+
642
+ ### Exporting and signing a verification attestation
643
+
644
+ `verify --attestation-out <file>` writes a versioned in-toto Statement containing the verification
645
+ receipt. It is emitted only when the result is `pass` and the scope is complete and stable. The
646
+ Statement subject digest is the SHA-256 fingerprint of the verified content and policy scope; its
647
+ predicate embeds the receipt, including per-file digests, policy fingerprint, base commit, Guard
648
+ version, and result. The in-toto `predicateType` URI versions the predicate schema independently
649
+ from the receipt's numeric `version` field. Consumers should reject unsupported versions rather than
650
+ guessing how to interpret them.
651
+
652
+ On GitHub pull request events, the receipt also records `headSha` and `baseSha` from the event
653
+ payload. `mergeSha` is recorded only when `GITHUB_SHA` identifies the checked-out merge result rather
654
+ than the PR head. These commit identifiers describe the hosting event context; the content
655
+ fingerprint remains authoritative for the exact files and policy Guard hashed. The output contains
656
+ paths and hashes, not file contents. The supplied [CI template](guard-ci.yml) exports and uploads
657
+ this unsigned statement alongside the JSON receipt when verification passes.
658
+
659
+ Guard does not sign the Statement. Signing stays in CI so Guard needs no signing key or Sigstore
660
+ dependency. The in-toto Statement format does not require GitHub or Sigstore: another CI system can
661
+ sign or attest the same exported file with its own trusted mechanism. The workflow below is a
662
+ GitHub-specific integration example; its signature verification commands and trust checks apply to
663
+ GitHub artifact attestations. For another CI provider, configure its signer and verifier to bind the
664
+ statement digest to the expected repository, workflow identity, source revision, and Guard receipt.
665
+
666
+ #### Install the trusted GitHub Actions workflow
667
+
668
+ The [trusted PR workflow template](guard-trusted-pr-attestation.yml) independently runs Guard in a
669
+ workflow triggered by completion of the ordinary `Guard` workflow. It does not consume artifacts
670
+ from that run. It first validates that the event identifies exactly one PR and compares the head and
671
+ base repository names. For same-repository PRs, it checks out the PR head, fetches the exact base
672
+ commit, installs a fixed Guard version with lifecycle scripts disabled, runs Guard without project
673
+ checks, checks that the receipt matches the PR head and base SHAs, and signs the statement in a
674
+ separate job.
675
+
676
+ This attestation covers **Guard's architecture and policy verification only**. The template skips
677
+ project checks and does not attest the result of tests, builds, deployments, or other jobs in the
678
+ ordinary workflow. It also does not inspect or require that workflow's conclusion. Keep the normal
679
+ Guard workflow as a required PR check; configure other required checks separately.
680
+
681
+ Requirements:
682
+
683
+ - GitHub Actions is enabled, and the repository or organization policy permits artifact attestations
684
+ and the permissions declared in the template (`id-token: write`, `attestations: write`, and
685
+ `artifact-metadata: write` in the signer job).
686
+ - GitHub artifact attestations are available for public repositories on current GitHub plans. Private
687
+ and internal repositories require GitHub Enterprise Cloud; GitHub Enterprise Server is not
688
+ supported by `actions/attest`. Check the current
689
+ [actions/attest support requirements](https://github.com/actions/attest#readme) for plan changes.
690
+ - The repository has Guard initialized and its protected base revision contains a valid Guard policy.
691
+ - The ordinary pull request workflow is named exactly `Guard` and runs for `pull_request` events. If
692
+ you use a different workflow name, update `workflow_run.workflows` in the template to match it.
693
+ - A Guard release tag is available that records PR context for `workflow_run` events. Configure
694
+ both its exact version and the full commit SHA of that tag; the workflow checks that they match.
695
+ - The trusted workflow builds Guard from that immutable source commit using its committed pnpm
696
+ lockfile with `--frozen-lockfile`. Lifecycle scripts are disabled during dependency installation.
697
+ The lockfile's package versions and integrity values pin the complete dependency graph.
698
+
699
+ Copy the template into the project and commit it on the repository's default branch:
700
+
701
+ ```bash
702
+ mkdir -p .github/workflows
703
+ cp node_modules/@codapult/guard/docs/guard-trusted-pr-attestation.yml \
704
+ .github/workflows/guard-trusted-pr-attestation.yml
705
+ ```
706
+
707
+ In **Settings → Secrets and variables → Actions → Variables**, create these repository variables:
708
+
709
+ - `CODAPULT_GUARD_VERSION`: the exact release version that includes `workflow_run` receipt support,
710
+ without a `v` prefix or range (for example, `0.10.0`).
711
+ - `CODAPULT_GUARD_COMMIT`: the full 40-character Git commit SHA that the matching `v<version>` tag
712
+ resolves to.
713
+
714
+ Both are non-secret values. The workflow fetches the tag from the public Guard repository, confirms
715
+ that it resolves to the configured SHA and that the package version matches, then installs from the
716
+ Guard repository's frozen pnpm lockfile. If the release tag moves or either value is wrong, the
717
+ workflow stops before installing or running Guard. To obtain the commit SHA for a release, fetch the
718
+ version tag and resolve its commit, for example:
719
+ `git fetch https://github.com/codapult/codapult-guard.git refs/tags/v0.10.0` followed by
720
+ `git rev-parse 'FETCH_HEAD^{commit}'`.
721
+
722
+ Checkout v7 blocks fork PR checkouts in `workflow_run` by default, and this template keeps that
723
+ protection enabled. The context job detects a fork by comparing the PR head and base repository names.
724
+ If the event has no associated PR, it compares the workflow run's head repository with the event's
725
+ base repository; only a clear repository mismatch is treated as an unattached fork. For a detected
726
+ fork, the `verify` and `attest` jobs are skipped before checkout, and a separate read-only job adds a
727
+ summary explaining that no signed attestation was created. This is an expected skip, not a Guard pass:
728
+ the ordinary Guard pull request check remains responsible for its own result. Fork PRs receive no
729
+ trusted attestation unless a future version adds a separately reviewed safe lookup and verification
730
+ path. Missing or ambiguous PR context, missing repository identity, or invalid SHAs fail closed. The
731
+ `verify` job has only `contents: read`, does not run project checks or package lifecycle scripts, and
732
+ does not receive signing permissions or secrets. The separate `attest` job signs only after the
733
+ verifier has passed and the receipt has been matched to the PR. Do not add commands that execute
734
+ files from the PR to the verifier job.
735
+
736
+ The `workflow_run` event only runs a workflow file present on the default branch. It has a different
737
+ trust context from the original PR workflow and can receive more privileges, so keep its permissions
738
+ minimal and protect this workflow file with CODEOWNERS/repository rules. GitHub may omit the PR
739
+ association in some events; an empty association is treated as a fork only when the trusted event
740
+ metadata identifies a different head repository. Otherwise, the context job fails closed and no
741
+ attestation is produced. The ordinary PR workflow can still run, but fork support for signed receipts
742
+ requires a separately reviewed PR lookup design. Never solve this by running PR-controlled scripts
743
+ in the trusted workflow.
744
+ Although this template does not execute project files, Guard still parses attacker-controlled source
745
+ and configuration. Use GitHub-hosted ephemeral runners and keep verifier and signer permissions at
746
+ the template's minimum; parser vulnerabilities remain a residual risk.
747
+
748
+ The template uses `--fail-on-policy-change` and `--fail-on-protected-changes`. A PR that changes
749
+ Guard policy, the trusted workflow, or another protected verification input will therefore receive
750
+ no attestation from this workflow. Review such changes through the repository's protected owner
751
+ process, merge the approved policy, then verify subsequent source PRs against that base.
752
+
753
+ The workflow does not create a required check or status on the PR. GitHub branch protection or a
754
+ ruleset must require the ordinary Guard check and protect both workflow definitions. The attestation
755
+ is additional evidence for consumers that need to verify who ran Guard and which PR content was
756
+ checked; it does not replace host-side merge policy.
757
+
758
+ #### Verify a signed receipt
759
+
760
+ Find the trusted workflow run for the PR in the Actions tab. Download its
761
+ `guard-trusted-pr-attestation` artifact, which contains `guard-verification.intoto.json`. You can
762
+ also download it with GitHub CLI. Install `gh`, authenticate it to GitHub, and use an account that
763
+ can read the repository's workflow runs and pull requests:
764
+
765
+ ```bash
766
+ gh run download RUN_ID --repo ORG/REPO \
767
+ --name guard-trusted-pr-attestation \
768
+ --dir .guard-attestation
769
+ ```
770
+
771
+ Verify the signature, repository, signer workflow, and protected source ref:
772
+
773
+ ```bash
774
+ gh attestation verify .guard-attestation/guard-verification.intoto.json \
775
+ --repo ORG/REPO \
776
+ --signer-workflow ORG/REPO/.github/workflows/guard-trusted-pr-attestation.yml \
777
+ --source-ref refs/heads/main
778
+ ```
779
+
780
+ The command above verifies the signed artifact file and its GitHub workflow identity. To also check
781
+ the embedded Guard receipt's schema version, passing outcome, complete and stable scope, scope digest,
782
+ and the exact PR head and base SHAs, copy the bundled consumer script and run it after downloading
783
+ the artifact:
784
+
785
+ ```bash
786
+ cp node_modules/@codapult/guard/docs/verify-guard-attestation.mjs .guard-attestation/
787
+ node .guard-attestation/verify-guard-attestation.mjs \
788
+ .guard-attestation/guard-verification.intoto.json \
789
+ --repo ORG/REPO \
790
+ --signer-workflow ORG/REPO/.github/workflows/guard-trusted-pr-attestation.yml \
791
+ --source-ref refs/heads/main \
792
+ --expected-head HEAD_SHA \
793
+ --expected-base BASE_SHA
794
+ ```
795
+
796
+ The script calls `gh attestation verify` with the repository, signer workflow, and source-ref
797
+ constraints before inspecting the statement. It exits nonzero if signature verification fails or if
798
+ the embedded statement is unsupported, unsuccessful, incomplete, unstable, or bound to different
799
+ PR SHAs. Obtain the expected head and base values from the current PR; do not copy them from the
800
+ receipt being checked.
801
+
802
+ Replace `ORG/REPO` and `main` with the repository and protected default branch. This command checks
803
+ that the attestation is for the exact downloaded statement file and that the signer identity matches
804
+ the expected workflow and ref. Then compare the embedded receipt with the PR revision you intend to
805
+ accept. Set `EXPECTED_HEAD_SHA` and `EXPECTED_BASE_SHA` to the PR's current head and base commit SHAs
806
+ using the GitHub API:
807
+
808
+ ```bash
809
+ EXPECTED_HEAD_SHA=$(gh api repos/ORG/REPO/pulls/PR_NUMBER --jq .head.sha)
810
+ EXPECTED_BASE_SHA=$(gh api repos/ORG/REPO/pulls/PR_NUMBER --jq .base.sha)
811
+ ```
812
+
813
+ Replace `ORG/REPO` and `PR_NUMBER`, then run:
814
+
815
+ ```bash
816
+ jq -e \
817
+ --arg head "$EXPECTED_HEAD_SHA" \
818
+ --arg base "$EXPECTED_BASE_SHA" \
819
+ '.predicateType == "https://codapult.dev/attestations/guard-verification/v1" and
820
+ .predicate.receipt.outcome == "pass" and
821
+ .predicate.receipt.subject.headSha == $head and
822
+ .predicate.receipt.subject.baseSha == $base and
823
+ .predicate.receipt.verificationScope.complete == true and
824
+ .predicate.receipt.verificationScope.stable == true and
825
+ .subject[0].digest.sha256 == .predicate.receipt.verificationScope.fingerprint' \
826
+ .guard-attestation/guard-verification.intoto.json >/dev/null
827
+ ```
828
+
829
+ The comparison must use the exact PR revisions being reviewed. If the PR head or target branch has
830
+ advanced since the workflow ran, require a new trusted workflow run and verify its receipt instead.
831
+ The statement's `mergeSha`, when present, identifies the merge commit checked by Guard; the head and
832
+ base SHA checks above bind the attestation to the PR revisions.
833
+
834
+ For a first setup check, open a test PR from a branch in the same repository, wait for both workflow
835
+ runs to finish, confirm that `guard-trusted-pr-attestation` appears as an artifact on the trusted
836
+ workflow run, download it, and run both verification commands above. The template intentionally
837
+ fails closed when PR context is missing or ambiguous and cannot be identified as an unattached fork,
838
+ when the Guard receipt is not a pass, when its scope is incomplete or unstable, or when the PR SHAs
839
+ do not match the event. A fork that can be identified safely is an expected skip and receives no
840
+ attestation.
841
+
842
+ Signature verification proves which workflow signed the statement and that the statement file was
843
+ not changed after signing. It does not, by itself, prove that arbitrary claims inside the statement
844
+ are true. This template acts as the trusted verifier by re-running Guard before signing. Do not give
845
+ `id-token: write` to a job that executes PR-controlled workflow code and treat its signature as
846
+ trusted verification: a `pull_request` workflow file comes from the PR merge commit. GitHub also
847
+ warns that predicates may be controlled by the workflow that produced them unless signing is done
848
+ by a trusted builder. See GitHub's [guidance on untrusted code checkout](https://docs.github.com/en/actions/reference/security/secure-use#mitigating-the-risks-of-untrusted-code-checkout),
849
+ [workflow_run event details](https://docs.github.com/en/actions/reference/workflows-and-actions/events-that-trigger-workflows#workflow_run),
850
+ the [in-toto Statement spec](https://github.com/in-toto/attestation/blob/main/spec/README.md),
851
+ [GitHub artifact attestations](https://docs.github.com/en/actions/how-tos/secure-your-work/use-artifact-attestations/use-artifact-attestations),
852
+ [actions/attest](https://github.com/actions/attest), and
853
+ [gh attestation verify](https://cli.github.com/manual/gh_attestation_verify).
854
+
855
+ ### Governance audit
856
+
857
+ Use the read-only governance audit to inspect whether approval records retain enough declared
858
+ provenance for later review:
859
+
860
+ ```bash
861
+ codapult-guard governance --json
862
+ codapult-guard governance --strict
863
+ ```
864
+
865
+ The report counts declared proposal authors and approval actors, identifies overlaps, missing
866
+ actors/authors/commits, checks policy fingerprints, and follows retained `decision ID → verification
867
+ run` links. `--strict` exits non-zero when the report contains completeness warnings. The audit
868
+ reports metadata recorded by Guard; it does not prove personal identity, cryptographic signatures,
869
+ a trusted clock, immutable storage, or authorization. Those guarantees belong to Git, CI, branch
870
+ protection, and the review platform.
871
+
872
+ ## What Guard discovers
873
+
874
+ Guard is intentionally evidence-driven. It can model a small JavaScript utility, a React app, a
875
+ Next.js App Router SaaS, a Node service, or a workspace without requiring one fixed architecture.
876
+
877
+ The TypeScript AST path uses `ts-morph` where AST semantics are useful. Lightweight file/config/Git
878
+ inspection remains direct code because introducing a large parser for a small fact would add cost
879
+ without improving accuracy. This hybrid boundary is deliberate.
880
+
881
+ The model is useful for questions such as:
882
+
883
+ - Which changed routes, actions, services, repositories, or tests are affected?
884
+ - Which client modules cross a server-only boundary?
885
+ - Which dependencies, environment variables, schemas, or configs are connected to a change?
886
+ - Which capabilities are actually present, and what files provide the evidence?
887
+ - Are there cycles, layer violations, high-fan-out modules, or changed impact paths?
888
+ - Which modules depend on a changed module, and which callers may be affected transitively?
889
+ - How did the project model, module graph, and layer edges change between two saved snapshots?
890
+
891
+ Domain signals for payments, auth, jobs, email, AI, storage, database, deployment, security,
892
+ observability, and other areas are review context by default. They do not become blocking rules
893
+ merely because a package name or filename resembles a domain.
894
+
895
+ ## AI-agent and MCP workflow
896
+
897
+ MCP exposes the same Guard model without requiring the host to parse CLI output. The Guard tools
898
+ are:
899
+
900
+ | MCP tool | Purpose | Writes by default? |
901
+ | -------------------------------- | -------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------- |
902
+ | `codapult_guard_context` | Read explicitly separated observations, proposals, active policy, and verification state. | No |
903
+ | `codapult_guard_analyze` | Refresh persisted facts and snapshots without changing policy or baseline. | Requires `confirm: true`. |
904
+ | `codapult_guard_propose` | Generate evidence-based proposals. | No; persistence requires `persist: true` and `confirm: true`. |
905
+ | `codapult_guard_init` | Initialize Guard. | Requires `confirm: true`; force also requires confirmation. |
906
+ | `codapult_guard_proposal_decide` | Approve/reject current proposals. | Requires `confirm: true`; protected mode can forbid MCP approval. |
907
+ | `codapult_guard_check` | Check active Guard policy and changed files. | No |
908
+ | `codapult_guard_review` | Prepare a bounded/redacted semantic review packet. | No |
909
+ | `codapult_guard_verify` | Run the completion gate and return structured results. | Runs configured project commands; does not edit source. |
910
+ | `codapult_guard_governance` | Audit declared approval provenance and decision-to-verification reachability. | No |
911
+ | `codapult_guard_audit` | Full current scan and contract validation. | No |
912
+ | `codapult_guard_waivers` | List or manage owner-attributed, time-bounded exceptions to exact finding fingerprints. | Writes require confirmation and policy approval. |
913
+ | `codapult_guard_impact` | Explain dependencies, transitive dependents, impact paths, capabilities, contracts, and graph edges for files. | No |
914
+ | `codapult_guard_explain` | Explain one rule/contract, its evidence, and suggested next steps. | No |
915
+ | `codapult_guard_next_action` | Return the next bounded Guard action for an agent without changing project state. | No |
916
+
917
+ Every tool accepts an optional `root`. If omitted, the MCP process working directory is used. The
918
+ host should pass a project root when its MCP process is not started there.
919
+
920
+ `codapult_guard_context` keeps four states separate:
921
+
922
+ - `observations`: facts discovered from the current project. They have `status: "observed-only"`
923
+ and are not enforced merely because a pattern is common.
924
+ - `proposals`: rules and contracts suggested from evidence. Only pending items appear in
925
+ `pendingRules` and `pendingContracts`; they have `status: "pending-approval"` and
926
+ `requiresApproval: true`. Decided proposal history is not presented as pending policy.
927
+ - `policy`: active rules and contracts, plus baseline and waiver state. Only this policy is used
928
+ for deterministic enforcement.
929
+ - `verification`: the status of an actual check. Context reads state and returns
930
+ `status: "not-run"` with `findings: null`; call `codapult_guard_check` or
931
+ `codapult_guard_verify` for findings.
932
+
933
+ This distinction prevents an observed convention from being mistaken for an approved rule or a
934
+ context response from being mistaken for a passing verification.
935
+
936
+ ### Registering the MCP server
937
+
938
+ Install Guard in the project that the agent will inspect:
939
+
940
+ ```bash
941
+ pnpm add -D @codapult/guard
942
+ ```
943
+
944
+ Guard exposes a local stdio MCP server through the `codapult-guard` executable:
945
+
946
+ ```bash
947
+ pnpm exec codapult-guard mcp-server
948
+ ```
949
+
950
+ Register that command in the selected AI host and use the project root as its working directory.
951
+ For example, Cursor can use `.cursor/mcp.json`:
952
+
953
+ ```json
954
+ {
955
+ "mcpServers": {
956
+ "codapult-guard": {
957
+ "type": "stdio",
958
+ "command": "pnpm",
959
+ "args": ["exec", "codapult-guard", "mcp-server"],
960
+ "cwd": "${workspaceFolder}"
961
+ }
962
+ }
963
+ }
964
+ ```
965
+
966
+ For a client with a generic stdio configuration, use the same command and set `cwd` to the
967
+ repository root. Do not use `@codapult/cli` for standalone Guard projects: that package is the
968
+ Codapult SaaS CLI and is a separate integration. Host-specific setup and instruction files are
969
+ available in [`docs/integrations/`](integrations/README.md).
970
+
971
+ Guard is also discoverable in the official [MCP Registry](https://registry.modelcontextprotocol.io/?q=io.github.codapult%2Fguard)
972
+ as `io.github.codapult/guard`. Registry discovery identifies the published npm-backed server; the
973
+ selected host still needs a local installation and stdio configuration with the project root.
974
+
975
+ After connecting, verify the server by calling `codapult_guard_context` or
976
+ `codapult_guard_audit`. If the MCP process starts outside the repository, pass the absolute
977
+ project path as the tool's `root` argument.
978
+
979
+ Recommended agent sequence:
980
+
981
+ ```text
982
+ task complete
983
+ → codapult_guard_next_action
984
+ → codapult_guard_context
985
+ → codapult_guard_review(requirement, diff)
986
+ → codapult_guard_verify(iteration: 1)
987
+ → if fail and canRetry: repair, then repeat with iteration + 1
988
+ → report warnings and unresolved requirements
989
+ ```
990
+
991
+ The host agent owns the loop, model call, permissions, and source edits. Guard does not invoke an
992
+ LLM, silently loop, or repair files. `agent.json` supplies policy such as `enabled`,
993
+ `maxIterations`, `projectChecks`, selected checks, and external-tool mode. A host may use the
994
+ managed instruction block:
995
+
996
+ ```bash
997
+ pnpm exec codapult-guard install-agent codex
998
+ pnpm exec codapult-guard install-agent cursor
999
+ pnpm exec codapult-guard install-agent all
1000
+ ```
1001
+
1002
+ Supported targets are `generic`, `codex`, `cursor`, `claude`, `copilot`, and `gemini`. The managed
1003
+ block is marker-based and updates only its own section. Host-specific MCP registration and
1004
+ completion-hook behavior are documented in the [integration kits](integrations/README.md).
1005
+
1006
+ An agent instruction can be as short as:
1007
+
1008
+ ```text
1009
+ Before declaring a task complete, call codapult_guard_next_action, then
1010
+ follow its bounded next step. Normally call codapult_guard_context,
1011
+ codapult_guard_review with the requirement and diff, then codapult_guard_verify.
1012
+ If verification fails and canRetry is true, repair the code and repeat up to
1013
+ completionGate.maxIterations. Report warnings and unresolved requirements.
1014
+ ```
1015
+
1016
+ ## CI and machine-readable output
1017
+
1018
+ CI should independently run Guard after an AI agent finishes. Do not accept an agent's statement
1019
+ that a finding was fixed; accept the next deterministic result.
1020
+
1021
+ JSON gate output:
1022
+
1023
+ ```bash
1024
+ pnpm exec codapult-guard verify --json
1025
+ pnpm exec codapult-guard check --changed --json
1026
+ ```
1027
+
1028
+ `verify --json` includes a `run` object with a unique run ID, stage durations, outcome, and the
1029
+ policy gate that determined the result. The same manifest is saved under
1030
+ `.codapult/guard/history/runs/`. Guard keeps this diagnostic record local and does not send traces
1031
+ or source code to a remote service. The manifest includes a compact `verification-receipt` with the
1032
+ checked commit, policy fingerprint/base, decision IDs, check statuses, finding and waiver counts,
1033
+ contract issue IDs, Guard version, and a SHA-256 inventory of verification inputs. It is an
1034
+ explainability artifact, not a signed proof of CI identity. With `--policy-base`, the receipt also distinguishes `baseChangedFiles` from
1035
+ `workingTreeFiles`; this makes a dirty local checkout visible in the machine-readable result.
1036
+
1037
+ SARIF output for GitHub Code Scanning or another SARIF consumer:
1038
+
1039
+ ```bash
1040
+ pnpm exec codapult-guard check --changed --sarif > guard-results.sarif
1041
+ ```
1042
+
1043
+ The repository includes a copyable consumer workflow at [`docs/guard-ci.yml`](guard-ci.yml). On pull
1044
+ requests it installs dependencies without lifecycle scripts and runs the deterministic Guard gate
1045
+ without executing PR-owned project scripts; trusted pushes can run configured project checks. It uploads SARIF
1046
+ with `github/codeql-action/upload-sarif` and also runs the unified verification gate. Projects
1047
+ using protected or forked pull requests should review their `security-events: write` permissions
1048
+ and artifact policy.
1049
+
1050
+ For maintainers, the fixture and real-PR harnesses are documented in
1051
+ [`guard-fixtures.md`](guard-fixtures.md). The isolated golden flow is run with:
1052
+
1053
+ ```bash
1054
+ pnpm test:guard:golden
1055
+ ```
1056
+
1057
+ ## Security and data handling
1058
+
1059
+ - Guard excludes `.env`, credential/secret-named files, and common private-key extensions from
1060
+ review diffs.
1061
+ - Common credential-shaped values are redacted before a review packet or structured output is
1062
+ returned. Redaction is a defense-in-depth measure, not a guarantee that arbitrary secrets are
1063
+ recognized.
1064
+ - Requirement files must remain inside the detected project root and are size-limited.
1065
+ - Git base refs are validated before being used by review commands.
1066
+ - Project checks and external adapters execute the project's own scripts. Run Guard only in a
1067
+ trusted workspace and review scripts before enabling `tools: auto` or `tools: on`.
1068
+ - MCP roots are local filesystem paths supplied by the host. Run the MCP server with the least
1069
+ filesystem access appropriate for the projects it serves.
1070
+ - Guard does not send source code to a remote service by itself. An AI host may send the review
1071
+ packet to its configured model; apply that host's data-retention and provider policy.
1072
+
1073
+ ## Troubleshooting
1074
+
1075
+ ### `Guard is not initialized`
1076
+
1077
+ Run `codapult-guard init` from the project root. For MCP, pass the correct `root` or start the server in
1078
+ the project directory.
1079
+
1080
+ ### `Guard is already initialized`
1081
+
1082
+ This is intentional protection. Use `codapult-guard analyze` to refresh facts, or use `codapult-guard init --force`
1083
+ only when replacing the baseline is a deliberate decision.
1084
+
1085
+ ### Too many findings appear after initialization
1086
+
1087
+ That is the expected baseline boundary: old findings are recorded and suppressed by `check`, while
1088
+ new regressions remain visible. Use `codapult-guard audit` to inspect the complete current state.
1089
+
1090
+ ### A proposal is stale
1091
+
1092
+ The code changed after the proposal was generated. Run `codapult-guard propose` again and review the new
1093
+ evidence.
1094
+
1095
+ ### Verification says a tool is not configured
1096
+
1097
+ Use `--tools auto` for normal operation, `--tools off` when external adapters are not relevant,
1098
+ or `--strict` when the project requires every selected check/adapter to exist.
1099
+
1100
+ ### Review says the diff is truncated or redacted
1101
+
1102
+ This is intentional. Review packets are bounded, and sensitive-looking content is removed before
1103
+ it reaches an AI reviewer. Narrow the change or inspect the source locally if more context is
1104
+ needed.
1105
+
1106
+ ### Native fixture checks fail because of Node
1107
+
1108
+ Fixture projects retain their own engine requirements. Use a compatible Node version for the
1109
+ fixture rather than weakening Guard or changing the fixture's production configuration. Guard's
1110
+ runtime diagnostic reports the active and declared versions.
1111
+
1112
+ ## Implementation boundaries
1113
+
1114
+ Guard currently provides deterministic discovery, policy checks, project-context packets, contract
1115
+ validation, completion-gate orchestration, MCP exposure, JSON/SARIF output, and host instruction
1116
+ templates. It intentionally does not:
1117
+
1118
+ - call an LLM or choose architectural policy without approval;
1119
+ - replace specialist linters, type checkers, test runners, SAST, dependency scanners, or builds;
1120
+ - silently rewrite source code or automatically reset a baseline;
1121
+ - guarantee semantic correctness from filename/package-name heuristics alone;
1122
+ - implement host-specific agent hooks as runtime dependencies.
1123
+
1124
+ That boundary keeps Guard local-first, model-agnostic, and project-specific while allowing existing
1125
+ tools and AI platforms to remain useful adapters around one shared project model.