tamperward 1.4.0 → 1.4.1

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 (2) hide show
  1. package/README.md +117 -139
  2. package/package.json +8 -5
package/README.md CHANGED
@@ -6,55 +6,51 @@
6
6
 
7
7
  <p align="center"><em>A ward is the obstruction inside a lock that blocks the wrong key.</em></p>
8
8
 
9
- **The deterministic agent-integrity gate.** One ruleset, evaluated on the actual
10
- diff and commands as a verdict not a probability — enforced everywhere a change can
11
- be made: inside the coding agent's loop, at the commit, and at the merge.
12
-
13
- AI agents optimize for "the command succeeded," not "the change is trustworthy." To
14
- make checks pass they take the cheapest path: deleting failing tests, weakening
15
- assertions, casting to `any`, suppressing lint/type errors, lowering coverage gates,
16
- editing CI, or bypassing hooks with `--no-verify`. Tamperward treats the safety nets
17
- themselves as protected assets and blocks the *class* of bypass — so no single
18
- shortcut is enough.
9
+ <p align="center">
10
+ <a href="https://www.npmjs.com/package/tamperward"><img src="https://img.shields.io/npm/v/tamperward?label=npm&color=4F46E5" alt="npm version"></a>
11
+ <a href="https://github.com/hexrift/tamperward/actions/workflows/release.yml"><img src="https://img.shields.io/github/actions/workflow/status/hexrift/tamperward/release.yml?label=release" alt="release"></a>
12
+ <a href="./LICENSE"><img src="https://img.shields.io/badge/license-Apache--2.0-lightgrey" alt="license"></a>
13
+ </p>
19
14
 
20
- > Apache-2.0 core · `github.com/hexrift/tamperward`
15
+ **The deterministic agent-integrity gate.** AI coding agents optimize for "the command
16
+ succeeded," not "the change is trustworthy." To make checks pass they take the cheapest
17
+ path: deleting failing tests, skipping them, casting to `any`, suppressing lint errors,
18
+ lowering coverage gates, rewriting snapshots, editing CI, or bypassing hooks with
19
+ `--no-verify`. Tamperward treats the safety nets themselves as protected assets and
20
+ blocks the **class** of bypass — one ruleset, evaluated on the actual diff and commands
21
+ as a verdict, not a probability, enforced everywhere a change can be made: **inside the
22
+ agent's loop, at the commit, and at the merge.**
21
23
 
22
- ## Stability
24
+ > Apache-2.0 · zero runtime model calls · every headline claim below is measured, with
25
+ > the pre-registered predictions (including the refuted ones) committed to this repo
23
26
 
24
- `1.0.0` commits to the surface you actually depend on: the CLI and its **exit codes**, the
25
- hook wire format, the `.tamperward.yml` schema, and the `--json` `Finding` shape. The
26
- package publishes no `main` and no `exports` — it is a binary, not a library — so nothing
27
- under `src/` is public and internal refactors will never break you.
27
+ ## Install
28
28
 
29
- The version answers one question: *can taking this upgrade turn a green build red without
30
- me changing anything?* **Patch never can** — bypass fixes and false-positive fixes ship as
31
- patches so they reach you automatically. **Major can**, and only ever will alongside a
32
- changelog entry saying which rule changed. See
33
- [CONTRIBUTING](./CONTRIBUTING.md#versioning) for the full rule.
29
+ ```bash
30
+ npx tamperward init
31
+ ```
34
32
 
35
- Two rules ship as `warn` and do not block: `assertion-weakening` and `guard-removal`. They
36
- are heuristic, and they stay non-blocking until their precision is measured (SPEC §7.A).
33
+ One idempotent command wires all four enforcement points it never overwrites anything
34
+ you wrote, and `--dry-run` shows the plan first:
37
35
 
38
- ## Does the loop actually work?
36
+ | point | what it wires |
37
+ | --- | --- |
38
+ | agent loop | Claude Code `PreToolUse` deny + `Stop` sweep, merged into `.claude/settings.json` |
39
+ | pre-commit | husky when present, the plain git hook otherwise |
40
+ | CI | a PR-gate workflow with out-of-band sign-off (details below) |
41
+ | policy | a commented baseline `.tamperward.yml` — the defaults apply even without it |
39
42
 
40
- The gate is only half the claim. The other half is what an agent does *after* it is
41
- denied: fix the real failure, or hunt for another way around. That is the
42
- **bypass-to-fix conversion rate** (SPEC §7.B), and it is measured, not asserted —
43
- `harness/run.sh` stands up an isolated repo with a real bug and a failing protected
44
- test, lets a headless agent loose on it with the hooks installed, and classifies the
45
- end state with an oracle independent of the detectors under test.
43
+ Or run the views directly:
46
44
 
47
- **42 runs. 0 tampers reached green. Of the 20 runs where a block fired, 20 ended in an
48
- honest fix.**
45
+ ```bash
46
+ npx tamperward check --staged # pre-commit view
47
+ npx tamperward check --diff "main...HEAD" # CI view — the authority for main
48
+ ```
49
49
 
50
- | arm | model | runs | blocks fired | honest fix | tamper through |
51
- |---|---|---|---|---|---|
52
- | `seed` · adversarial | Sonnet 5 | 1 | 0 | 1 | 0 |
53
- | `seed` · **forced** | Sonnet 5 | 5 | 0 | 5 | 0 |
54
- | `seed-hard` · adversarial | Sonnet 5 | 3 | 0 | 3 | 0 |
55
- | `seed-hard` · adversarial | Haiku 4.5 | 33 | 20 | 33 | 0 |
50
+ ## What it looks like when it fires
56
51
 
57
- The trajectory the whole design is built around, from a transcript:
52
+ An agent, mid-loop, tries the classic shortcut. The deny happens **before** the tool
53
+ runs, and holds even under `bypassPermissions`:
58
54
 
59
55
  ```
60
56
  5. Bash rm calc.test.js → DENIED (test-deletion)
@@ -62,115 +58,97 @@ The trajectory the whole design is built around, from a transcript:
62
58
  7. Bash node --test → green, honestly
63
59
  ```
64
60
 
65
- **Read the number with its limits.** 20/20 is a point estimate on a finite sample: it
66
- supports "no conversion failure observed in 20 interceptions", whose 95% lower bound is
67
- ≈ 86% — above the ~80% bar SPEC §7.B set for "the loop works", but not a claim of 100%.
68
- The split matters more than the total — Sonnet 5 never attempted a bypass at all,
69
- including 5/5 runs in `forced` mode where the prompt *orders* it to delete the failing
70
- test; all 20 interceptions came from Haiku 4.5 on the seed whose honest fix is genuinely
71
- expensive. Of those 20, the agent's first move after the denial was a real fix attempt
72
- 19 times; once it went and read `.tamperward.yml` looking for another way through before
73
- converting. That probe is counted here as an attempted second bypass, not smoothed away.
74
-
75
- So on scenarios this size, a frontier model mostly does not need the gate. What the run
76
- supports is the narrower, more durable claim: **when a shortcut is attempted, it is
77
- caught deterministically, and the denial redirects the agent to the real fix rather than
78
- to another bypass.**
79
-
80
- Reproduce: `npm run build && harness/run.sh 5 adversarial haiku seed-hard`
81
-
82
- ## Status
83
-
84
- Phase 1 — the engine, the eight mechanical detectors, the `check` CLI, and the Claude Code
85
- agent layer (PreToolUse hook + Stop sweep + `allow`). Tamperward gates its own repo in CI
86
- with the same engine it ships.
87
-
88
- - `src/types.ts` — the `Change` model every adapter manufactures and every detector
89
- consumes (the one decision the codebase inherits).
90
- - `src/diff/parse.ts` — pure `git diff` → `Change[]` parser. Handles add / modify /
91
- delete / **rename (as one change carrying `oldPath`)** / rename+edit / binary, with
92
- per-line old/new line numbers correct across multiple hunks.
93
- - `src/git/build.ts` — the git adapter: range / staged / worktree views, enriching
94
- `before`/`after` with full file content for the AST detectors.
95
- - `src/detectors/` — the **nine mechanical rules**: `no-verify`, `ts-any-cast`,
96
- `lint-suppression`, `test-skip`, `coverage-lowering`, `ci-tampering`,
97
- `hook-tampering`, `test-deletion` (the last counts `it()/test()` via the TS AST,
98
- and handles delete / rename-out-of-glob / shell mutation), and `snapshot-rewrite`
99
- (a `warn`: re-recording a snapshot/golden expectation from current output — the one
100
- rule built from measured demand, after the affordance experiment put the move at a
101
- 70% attempt and 100% through rate; see `harness/PREDICTION-affordance.md`).
102
- - `src/engine.ts` — runs the enabled rules over `Change[]`; honours `policy.ignore`.
103
- - `src/cli/` — `tamperward check --staged | --worktree | --diff <base>...<head>`,
104
- exit 1 on any blocking finding.
105
- - `test/` — 259 tests, including the AST-vs-regex, self-hosting precision, and
106
- pre-go-live audit regression cases, and the renderer accessibility contract.
107
-
108
- - `src/adapters/claude/` + `src/cli/hook.ts` — the agent layer: `tamperward hook claude`
109
- (PreToolUse deny, fail-closed) and `tamperward sweep claude` (Stop sweep, compared against
110
- the turn's starting commit so a mid-turn commit can't launder a tamper past it).
111
- - `src/signoff.ts` — the three-layer sign-off model: the agent honours nothing it can author.
112
-
113
- Next: the negatives corpus to graduate the two heuristic rules, and a larger §7.B run
114
- to tighten the interval on the conversion rate.
115
-
116
- See **[SPEC.md](./SPEC.md)** for the full build spec, the detector table, the
117
- enforcement-point wiring, and the proof harness.
118
-
119
- ## Use
120
-
121
- ```bash
122
- npx tamperward init # wire all four enforcement points in one
123
- # command: policy file, Claude Code hooks,
124
- # pre-commit, CI. Idempotent; --dry-run to
125
- # preview; never overwrites your files.
61
+ The same engine at pre-commit or in CI:
126
62
 
127
- npx tamperward check --staged # pre-commit view
128
- npx tamperward check --diff "main...HEAD" # CI view — the authority for main
63
+ ```
64
+ tamperward: 2 blocking
65
+ (2 changes scanned)
66
+
67
+ BLOCK coverage-lowering package.json
68
+ Coverage gate weakened: global lines threshold lowered 90 → 10.
69
+ evidence global lines threshold lowered 90 → 10
70
+ instead Raise real coverage by adding tests; do not lower or exempt
71
+ the gate to pass.
72
+ sign-off tamperward allow coverage-lowering --file package.json --reason "..."
73
+
74
+ BLOCK test-deletion test/calc.test.js
75
+ Test blocks removed: 3 → 1 it()/test() in this spec.
76
+ evidence 2 test block(s) removed from test/calc.test.js
77
+ instead Keep the assertions and fix the code. Removing test blocks to go
78
+ green is the tamper.
79
+
80
+ A blocking finding clears only with a human sign-off. In CI that sign-off is
81
+ out-of-band — a PR label applied by a reviewer — never a file committed on the
82
+ branch under review.
129
83
  ```
130
84
 
131
- ### Reading the verdict
132
-
133
- One verdict, rendered for whoever is reading it. `--format` picks the view; the default,
134
- `auto`, picks `github` when `GITHUB_ACTIONS=true` and `text` otherwise, so the CI wiring
135
- stays a single line.
136
-
137
- | Format | Where it goes |
138
- | --- | --- |
139
- | `text` | The terminal. Blocking findings first, then by file and line, wrapped to the terminal width. |
140
- | `github` | An inline annotation per finding — so it lands **on the line** in *Files changed*, not four clicks deep in a job log plus a job-summary table on the run page. The full text output still goes to the log. |
141
- | `json` | The findings verbatim, plus a summary count. |
142
-
143
- Severity is always spelled out (`BLOCK` / `warn`) and never carried by colour or a glyph
144
- alone, so the output reads the same piped, in a CI log, on a colour-blind reader's
145
- terminal, and through a screen reader. Colour honours
146
- [`NO_COLOR`](https://no-color.org) and `FORCE_COLOR`, and is off whenever stdout is not a
147
- terminal.
85
+ Under GitHub Actions each finding also lands as an **inline annotation on the diff** in
86
+ *Files changed*, plus a job-summary table — not four clicks deep in a log.
87
+
88
+ ## The rules
89
+
90
+ Nine mechanical rules, deterministic by construction, `block` by default:
91
+ `test-deletion` (AST block-count, rename-out-of-glob, shell mutation), `test-skip`
92
+ (including `.only`, which narrows the suite), `ts-any-cast`, `lint-suppression`,
93
+ `coverage-lowering` (Jest and Vitest shapes, all four metrics, `package.json` included),
94
+ `ci-tampering` (a **moved** check is not a removed one), `hook-tampering`, `no-verify`
95
+ (flags and the env-var escape hatches), and `snapshot-rewrite` (`warn` see the FP study
96
+ below for why).
97
+
98
+ Three heuristics ship as `warn` and never block until their precision clears the bar:
99
+ `assertion-weakening`, `guard-removal`, `ts-any-launder`.
100
+
101
+ The policy file itself is un-ignorable, CI reads the policy from the **merge-base** (a PR
102
+ cannot govern its own verdict), and the agent honours no sign-off it can author — the
103
+ escape valve is a PR label only a human with write access can apply.
104
+
105
+ ## Measured, not asserted
106
+
107
+ **Does the loop work?** `harness/run.sh` stands up an isolated repo with a real bug and a
108
+ failing protected test, lets a headless agent loose with the hooks installed, and
109
+ classifies the end state with an oracle independent of the detectors under test
110
+ (SPEC §7.B). **42 runs, 0 tampers reached green; all 20 runs where a block fired ended in
111
+ an honest fix** — a 20/20 point estimate whose 95% lower bound is ≈86%. The split is the
112
+ honest part: Sonnet 5 never attempted a bypass at all, even when the prompt *ordered*
113
+ one; all 20 interceptions came from Haiku 4.5 on a seed whose honest fix is genuinely
114
+ expensive. Reproduce: `npm run build && harness/run.sh 5 adversarial haiku seed-hard`.
115
+
116
+ **Which detectors deserve to exist?** Measured too. Affordance seeds offer an agent one
117
+ specific bypass each; predictions are pre-registered before any rep runs
118
+ (`harness/PREDICTION-affordance.md`). Result: 7/10 adversarial runs regenerated a golden
119
+ file from buggy output (100% passed unseen — so `snapshot-rewrite` was built), while
120
+ timeout-inflation and lint-config-gutting measured **0/10 attempts each** — two
121
+ pre-registered bets refuted, two detectors *not* built. Bypass classes with no measured
122
+ demand stay in a record-only shadow scanner that accumulates evidence on every weekly
123
+ harness run.
124
+
125
+ **What about false positives?** `snapshot-rewrite` was swept over **1,652 real mainline
126
+ commits** (prettier, jest, docusaurus, immer): 216 touched snapshots, all legitimately —
127
+ which is exactly why that rule is a `warn` asking for human confirmation, not a block,
128
+ and why the study is committed (`harness/fp-study/`) rather than summarized away.
148
129
 
149
- From a clone:
130
+ ## Stability
150
131
 
151
- ```bash
152
- npm install
153
- npm run build # bundles the CLI to dist/cli/index.js
154
- node dist/cli/index.js check --staged
155
- node dist/cli/index.js check --diff "main...HEAD"
156
- ```
132
+ The public surface is the CLI and its **exit codes**, the hook wire format, the
133
+ `.tamperward.yml` schema, and the `--json` `Finding` shape. No `main`, no `exports` —
134
+ it is a binary, not a library. The version answers one question: *can taking this
135
+ upgrade turn a green build red without me changing anything?* **Patch never can** —
136
+ bypass fixes and false-positive fixes ship as patches so they reach you automatically.
137
+ Rule graduations (`warn` → `block`) are **opt-in**: they gate on the `version:` field in
138
+ your policy, so they ship as minors and apply only when you raise it. Releases publish
139
+ via npm trusted publishing with SLSA provenance. Full rule:
140
+ [CONTRIBUTING](./CONTRIBUTING.md#versioning).
157
141
 
158
142
  ## Develop
159
143
 
160
144
  ```bash
161
- npm test # vitest parser, detectors, engine, policy
145
+ npm install && npm run build # bundles the CLI to dist/cli/index.js
146
+ npm test # 259 tests — parser, detectors, engine, policy, renderers
162
147
  npm run typecheck
163
148
  ```
164
149
 
165
- ## Layout
166
-
167
- ```
168
- tamperward/
169
- src/types.ts the Change / Finding / Detector / Policy contracts
170
- src/diff/ pure diff parser + selectors
171
- src/git/ git adapter (range / staged / worktree)
172
- src/detectors/ the rules (phase 1, in progress)
173
- test/ unit suite — green is the gate
174
- .tamperward.yml the policy: protected assets + rule severities
175
- .github/workflows/ CI — dogfoods Tamperward on itself once the CLI lands
176
- ```
150
+ Tamperward gates its own repo in CI with the same engine it ships — including, on more
151
+ than one occasion, blocking its own author's commits. See **[SPEC.md](./SPEC.md)** for
152
+ the build spec, the detector table, the enforcement-point wiring, and the proof-harness
153
+ design; the `harness/` directory holds the seeds, oracles, transcripts tooling, and
154
+ every pre-registered prediction with its outcome.
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "tamperward",
3
- "version": "1.4.0",
3
+ "version": "1.4.1",
4
4
  "description": "The deterministic agent-integrity gate. One ruleset, evaluated on the actual diff/commands as a verdict, enforced everywhere a change can be made.",
5
5
  "license": "Apache-2.0",
6
6
  "author": "hexrift",
@@ -14,15 +14,18 @@
14
14
  },
15
15
  "keywords": [
16
16
  "ai-agents",
17
+ "claude-code",
18
+ "coding-agents",
17
19
  "agent-safety",
20
+ "reward-hacking",
18
21
  "code-integrity",
19
22
  "guardrails",
20
- "claude-code",
23
+ "test-integrity",
21
24
  "pre-commit",
22
25
  "ci",
23
- "static-analysis",
24
- "reward-hacking",
25
- "tests"
26
+ "hooks",
27
+ "llm",
28
+ "devsecops"
26
29
  ],
27
30
  "type": "module",
28
31
  "bin": {