@openthink/stamp 1.10.0 → 2.0.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.
package/README.md CHANGED
@@ -1,11 +1,22 @@
1
1
  # stamp-cli
2
2
 
3
+ > **stamp 2.0 — server-attested reviews.** The LLM call moves into stamp-server,
4
+ > which holds its own signing key and signs every verdict. A forged review now
5
+ > requires forging a server signature — the operator no longer constructs both
6
+ > sides of "what the LLM saw" and "what the LLM said." On the 1.x line and
7
+ > upgrading? See the [**1.x → 2.x migration guide**](./docs/migration-1.x-to-2.x.md).
8
+ > 1.x receives security patches only; pin to `@openthink/stamp@legacy-1` if you
9
+ > need to stay on it.
10
+
3
11
  Local, headless pull-request system for agent-to-agent code review workflows.
4
12
 
5
13
  An author-agent opens a diff, reviewer-agents consume it and return structured
6
14
  feedback, the author iterates until merge rules are satisfied, and the merge
7
15
  is pushed to a remote that cryptographically rejects any push that wasn't
8
- properly reviewed and signed.
16
+ properly reviewed and signed. In 2.x, the reviewer LLM lives on stamp-server,
17
+ the verdict carries a server signature, and trust-anchor changes (the
18
+ reviewer prompts themselves, the trusted-key manifest, `path_rules`) require
19
+ admin counter-signatures.
9
20
 
10
21
  **Not a GitHub replacement.** GitHub is for humans collaborating. stamp-cli
11
22
  is for agent fleets cycling fast while keeping `main` clean. No web UI, no
@@ -13,7 +24,7 @@ PR dashboard, no human comment threads in core. Just a CLI + a git hook.
13
24
 
14
25
  Part of the [OpenThink](https://openthink.dev) suite.
15
26
 
16
- **Docs:** [server quickstart](./docs/quickstart-server.md) (from-zero project on a stamp server) · [DESIGN](./DESIGN.md) (spec) · [threat model](./docs/threat-model.md) (who attacks, how, what defends) · [ROADMAP](./docs/ROADMAP.md) (what's shipped + what's next) · [personas](./docs/personas.md) (writing reviewer prompts) · [troubleshooting](./docs/troubleshooting.md) · [server](./server/README.md) (Railway deploy)
27
+ **Docs:** [server quickstart](./docs/quickstart-server.md) (from-zero project on a stamp server) · [DESIGN](./DESIGN.md) (spec) · [server-attested reviews](./docs/plans/server-attested-reviews.md) (2.x design + threat model) · [threat model](./docs/threat-model.md) (who attacks, how, what defends) · [ROADMAP](./docs/ROADMAP.md) (what's shipped + what's next) · [personas](./docs/personas.md) (writing reviewer prompts) · [local-only mode](./docs/local-only-mode.md) (no-trust iteration via `stamp review --plan` + Claude Code skill) · [1.x → 2.x migration](./docs/migration-1.x-to-2.x.md) (upgrade guide) · [troubleshooting](./docs/troubleshooting.md) · [server](./server/README.md) (Railway deploy) · [CHANGELOG](./CHANGELOG.md)
17
28
 
18
29
  ## Install
19
30
 
@@ -35,17 +46,18 @@ npm audit signatures
35
46
  ## Quick start
36
47
 
37
48
  **Pick your deployment shape first** — it determines which command you run and
38
- what guarantees you actually get:
49
+ what trust guarantees you actually get. In 2.x, the trust ladder is explicit:
39
50
 
40
- | Shape | Origin is… | Enforcement | Command to run |
51
+ | Shape | Origin is… | Trust source | Enforcement |
41
52
  |---|---|---|---|
42
- | **Server-gated** | A stamp server you deployed | The server's pre-receive hook rejects any push without a valid stamped merge | `stamp bootstrap` on a clone of a server-provisioned repo |
43
- | **PR-check** (recommended for GitHub teams) | GitHub directly | A GitHub Action (`stamp/verify-attestation@v1.6.0`) runs on every PR; branch protection requires the green check before the GitHub merge button works | `stamp init` (defaults to PR-check on a github.com origin) |
44
- | **Local-only** (advisory) | GitHub / GitLab / etc. directly | None — direct `git push origin main` succeeds; the stamp config is documentation + a discipline aid | `stamp init --mode local-only --no-pr-check` |
53
+ | **Server-gated** (Shape 1, recommended) | A stamp server you deployed | Server signs every verdict; admin-cap keys gate `.stamp/**` changes | Pre-receive hook rejects unstamped pushes; v4 envelope verified end-to-end |
54
+ | **PR mode** (Shape 2) | GitHub directly | Mirror Action pushes to stamp-server for review; v3 PR-attestation signed by server | GitHub Action `stamp/verify-attestation@v1` runs on every PR as a required check |
55
+ | **Local-only** (Shape 3, no trust) | Anywhere | None — `--plan` / `--headless` produce no attestation | Discipline-only; signs merges but the remote doesn't enforce anything |
45
56
 
46
- These are not interchangeable. Server-gated and PR-check both enforce the
47
- gate; local-only signs your merges with a verifiable attestation but the
48
- remote does not reject anything. Pick deliberately.
57
+ Server-gated and PR mode both ride on server-attested verdicts and produce
58
+ verifying v4 attestations. Local-only is for fast reviewer iteration when you
59
+ haven't deployed a server — it makes no trust claim. **Pick deliberately**;
60
+ they're not interchangeable.
49
61
 
50
62
  PR-check mode is the natural fit for teams that already merge through GitHub
51
63
  PRs. The reviewer flow stays local (`stamp review` runs your AI personas on
@@ -54,50 +66,72 @@ content-addressed (survives squash + rebase + merge-commit), and the PR's
54
66
  green-check requirement keeps the human in the merge loop. No server to
55
67
  host, no on-call, no separate trust root.
56
68
 
57
- ### Server-gated path
69
+ ### Server-gated path (Shape 1)
58
70
 
59
- If you've deployed the stamp server (see [`docs/quickstart-server.md`](./docs/quickstart-server.md)
71
+ If you've deployed a stamp server (see [`docs/quickstart-server.md`](./docs/quickstart-server.md)
60
72
  for the full Railway walkthrough), the from-zero flow is three commands:
61
73
 
62
74
  ```sh
63
75
  ssh stamp new-stamp-repo myproject # provision bare repo + hook
64
76
  git clone ssh://stamp/srv/git/myproject.git
65
- cd myproject && stamp bootstrap # install real reviewers + push
77
+ cd myproject && stamp bootstrap # install real reviewers + trusted-keys manifest, push
66
78
  ```
67
79
 
68
80
  `stamp bootstrap` detects the freshly-provisioned placeholder state, scaffolds
69
- the three starter reviewers (security, standards, product), and lands them on
70
- `main` via a single signed merge that the server hook accepts. From there it's
71
- the normal review/merge cycle.
72
-
73
- ### PR-check path
74
-
75
- For teams that merge through GitHub PRs, `stamp init` (with no `--mode`
76
- flag against a github.com origin) drops a `.github/workflows/stamp-verify.yml`
77
- that runs the verifier on every PR. Wire it into branch protection and the
78
- gate is real:
81
+ the three starter reviewers (security, standards, product) plus the
82
+ `.stamp/trusted-keys/manifest.yml` declaring trust capabilities, and lands
83
+ them on `main` via a single signed merge that the server hook accepts. From
84
+ there `stamp review` calls the server's `stamp-review` SSH verb to fetch each
85
+ verdict; verdicts come back signed and persist in the v4 attestation envelope
86
+ the merge commit carries.
87
+
88
+ Migrating an existing 1.x repo? Use `stamp init --migrate-to-server-attested`
89
+ it scaffolds the trusted-keys manifest, `path_rules`, and `review_server`
90
+ config without touching your reviewer prompts. See the
91
+ [migration guide](./docs/migration-1.x-to-2.x.md) for the full walkthrough.
92
+
93
+ ### PR mode (Shape 2)
94
+
95
+ For teams whose source of truth is GitHub, `stamp init` against a github.com
96
+ origin auto-scaffolds `.github/workflows/stamp-verify.yml` (the verifier that
97
+ runs `stamp/verify-attestation@v1` on every PR). To get **server-attested**
98
+ PR mode — verdicts signed by stamp-server, not just locally — also pass
99
+ `--pr-mode` to install the mirror workflow that pushes every PR head to
100
+ stamp-server for review:
79
101
 
80
102
  ```sh
81
103
  cd myproject
82
- stamp init # scaffolds .stamp/ + workflow file
83
- git add .stamp .github && git commit -m "stamp: scaffold + PR-check workflow"
104
+ stamp init --pr-mode # scaffolds .stamp/ + stamp-verify.yml (auto)
105
+ # + stamp-mirror.yml (--pr-mode opt-in)
106
+ git add .stamp .github && git commit -m "stamp: scaffold PR mode"
84
107
  git push origin main
85
108
  # In GitHub: Settings → Branches → main → Require status checks →
86
109
  # add `stamp verify` (the workflow's job name) as required
87
110
  ```
88
111
 
112
+ Without `--pr-mode`, `stamp init` still wires the advisory PR-check from 1.x
113
+ — the verifier runs but verdicts are produced locally rather than by
114
+ stamp-server. The advisory path remains supported for teams that don't run a
115
+ stamp-server but want the attestation audit trail in PR checks.
116
+
89
117
  Per-PR developer flow:
90
118
 
91
119
  ```sh
92
120
  git checkout -b feature
93
121
  # ...make changes, commit...
94
- stamp review --diff main..HEAD # local AI reviewers run + verdicts land in DB
95
- stamp attest --into main --push origin # signs the attestation + atomically pushes
96
- # branch + refs/stamp/attestations/<patch-id>
122
+ stamp review --diff main..HEAD # calls stamp-server's stamp-review verb;
123
+ # verdicts come back signed by the server key
124
+ stamp attest --into main --push origin # signs the v4 attestation envelope + atomically
125
+ # pushes branch + refs/stamp/attestations/<patch-id>
97
126
  # Open the PR; the workflow runs stamp/verify-attestation against your branch
98
127
  # Reviewer's check goes green → human clicks merge in the GitHub UI
99
128
  ```
100
129
 
130
+ > **2.0.1:** server-side v3 PR-attestation production (AGT-355) ships in
131
+ > this release. `stamp attest` now folds server-signed approvals into a
132
+ > v3 envelope when the branch rule declares `review_server`; the GH
133
+ > Action accepts the envelope directly with no 1.x-action pin needed.
134
+
101
135
  The attestation is keyed on the **content** of the diff (`git patch-id`), so
102
136
  it survives every GitHub merge strategy: squash, rebase, and merge-commit
103
137
  all preserve the same patch-id and the same attestation.
@@ -133,6 +167,10 @@ validates them on any clone. What you don't get: server-side rejection of
133
167
  unstamped pushes. Anyone with repo write access can `git push origin main`
134
168
  of any commit, stamped or not.
135
169
 
170
+ ### Local-only iteration mode (no server, no attestation)
171
+
172
+ Local-only mode is a sibling pathway focused on fast reviewer feedback during iteration when you have not deployed a stamp server. `stamp review --plan --diff <revspec>` emits a structured JSON plan (diff + reviewers + per-reviewer prompts + fence hex) on stdout and a `note:`-prefixed no-trust advisory on stderr. The plan is consumed by a parent agent — the [`stamp-review` Claude Code skill](./skills/stamp-review.md) ships in this repo — which fans out one subagent per reviewer in parallel, surfaces their verdicts, and reprints the no-attestation banner. **No verdict signed, no server round-trip, no `stamp merge` gate change.** See [`docs/local-only-mode.md`](./docs/local-only-mode.md) for the consumer contract, the schema-versioning rules, the security boundary, and the headless fallback (AGT-341, in flight).
173
+
136
174
  ### Local-test (no server, on-disk bare repo)
137
175
 
138
176
  Run everything on one machine using a bare git repo on disk as the "remote".
@@ -284,6 +322,28 @@ stamp keys export # print your public key
284
322
  stamp keys trust <pub-file> # deposit a key into .stamp/trusted-keys/
285
323
  ```
286
324
 
325
+ **Trust-anchor administration (2.x, server-gated mode):**
326
+
327
+ ```
328
+ stamp admin list-keys # show manifest entries (name, fingerprint, capabilities)
329
+ stamp admin add-key <pubkey.pub> --name <n> --capabilities admin,operator
330
+ # add a key to .stamp/trusted-keys/manifest.yml +
331
+ # copy the pubkey into .stamp/trusted-keys/.
332
+ # refuses non-public-key PEMs.
333
+ stamp admin revoke <sha256:fingerprint> # remove entry from manifest.yml
334
+ stamp admin sign --pending [<sha>] # list/collect admin counter-sigs for
335
+ # in-flight .stamp/** commits. Sigs land in
336
+ # refs/notes/stamp-trust-anchor-sigs, folded
337
+ # into the v4 envelope at merge time
338
+ ```
339
+
340
+ `revoke` and `add-key` mutate `.stamp/trusted-keys/manifest.yml`, so the
341
+ resulting commits trip the `path_rules` gate and need admin counter-sigs
342
+ collected via `stamp admin sign --pending <sha>` before they can land.
343
+ Revocation is **lenient**: past attestations remain valid (they reference
344
+ the manifest snapshot as it was at attestation time); only future merges
345
+ are blocked.
346
+
287
347
  **Maintenance:**
288
348
 
289
349
  ```
@@ -325,16 +385,57 @@ matches the committed config.
325
385
  > **Security note.** `required_checks[].run` values execute as shell commands
326
386
  > on the merger's machine via `spawnSync(cmd, { shell: true })`. Anyone who
327
387
  > can land a PR that touches `.stamp/config.yml` can introduce arbitrary
328
- > code that will run on the next person to call `stamp merge`. The mitigation
329
- > is the reviewer gate itself: `.stamp/config.yml` changes go through the
330
- > same reviewers as any other code change, and your security reviewer prompt
331
- > should treat `required_checks` edits as high-scrutiny. Unlike GitHub
332
- > Actions, these commands are **not** sandboxed. See
333
- > [`DESIGN.md`](./DESIGN.md#security-model) for the full threat model.
388
+ > code that will run on the next person to call `stamp merge`. The 1.x
389
+ > mitigation is the reviewer gate itself: `.stamp/config.yml` changes go
390
+ > through the same reviewers as any other code change, and your security
391
+ > reviewer prompt should treat `required_checks` edits as high-scrutiny.
392
+ > Unlike GitHub Actions, these commands are **not** sandboxed. See
393
+ > [`DESIGN.md`](./DESIGN.md#security-model) for the full threat model and
394
+ > the 2.x server-attested resolution, which moves `.stamp/**` (including
395
+ > `required_checks`) under admin-only signing via `path_rules`.
334
396
 
335
397
  Optional: `.stamp/mirror.yml` enables GitHub mirroring via the post-receive
336
398
  hook. See [`server/README.md`](./server/README.md).
337
399
 
400
+ ### 2.x: server-attested config
401
+
402
+ In 2.x server-gated mode, three additional config surfaces apply:
403
+
404
+ ```yaml
405
+ # .stamp/config.yml — review_server tells stamp where to fetch signed verdicts
406
+ review_server:
407
+ host: stamp # ssh alias for your stamp-server
408
+ pubkey_fingerprint: sha256:abc... # the server's review-signing key fingerprint
409
+ trusted_keys_snapshot_sha256: sha256:def... # optional pin; verifier recomputes if absent
410
+
411
+ branches:
412
+ main:
413
+ required: [security, standards, product]
414
+ path_rules:
415
+ - pattern: ".stamp/**"
416
+ require_capability: admin
417
+ minimum_signatures: 2
418
+ bypass_review_cycle: true # reviewers can't approve their own prompt changes
419
+ ```
420
+
421
+ ```yaml
422
+ # .stamp/trusted-keys/manifest.yml — declares each key's capabilities
423
+ keys:
424
+ alice:
425
+ fingerprint: sha256:aaa...
426
+ capabilities: [admin, operator]
427
+ review-server-prod:
428
+ fingerprint: sha256:ddd...
429
+ capabilities: [server]
430
+ role_source: server # auto-published by stamp-server; don't hand-edit
431
+ ```
432
+
433
+ The manifest's canonical-JSON snapshot hash is bound into every attestation
434
+ as `trusted_keys_snapshot_sha256` — that's the load-bearing primitive behind
435
+ lenient revocation. Hand-edit by running `stamp admin add-key` /
436
+ `stamp admin revoke` (see the [Trust-anchor administration](#trust-anchor-administration-2x-server-gated-mode)
437
+ commands above), not by editing the YAML directly.
438
+
338
439
  ### Per-user reviewer-model selection
339
440
 
340
441
  `~/.stamp/config.yml` lets each operator pick which Anthropic model each
@@ -595,18 +696,33 @@ content you're not free to share.
595
696
  **What this protects against.** Author-agents cannot merge unreviewed code,
596
697
  cannot forge merges (the signing key isn't on disk anywhere they can
597
698
  exfiltrate without the operator's explicit consent), and cannot bypass the
598
- remote's verification.
599
-
600
- **What this doesn't protect against.** You, the human holding the signing
601
- key, can still produce a valid signed merge for arbitrary content. That's
602
- inherent to any local-first system. What signing gives you is
603
- **non-repudiation** — every merge on `main` is permanently attributed to a
604
- specific key's owner, provable from git history alone. For the
605
- agent-can't-bypass threat model this is exactly right.
606
-
607
- See [`DESIGN.md`](./DESIGN.md) for the full bootstrap, key-management, and
608
- verification-rule details, including the security model around user-configured
609
- pre-merge checks.
699
+ remote's verification. In 2.x server-gated mode, the operator also cannot
700
+ forge a reviewer verdict without compromising stamp-server's signing key
701
+ or stealing it from the server the operator's machine never sees the
702
+ prompt the server used, and the server signs the verdict, prompt hash, and
703
+ diff hash together.
704
+
705
+ **What this doesn't protect against:**
706
+
707
+ - **Server compromise.** If stamp-server's review-signing key is stolen,
708
+ forged verdicts verify cleanly until rotation. Mitigated by standard
709
+ infra hygiene plus lenient revocation: revoking the compromised key
710
+ via `stamp admin revoke` blocks future merges without invalidating past
711
+ ones. Rotate by adding the new key first, collecting admin sigs on the
712
+ manifest change, then revoking the old key in a follow-up commit.
713
+ - **Local-only mode (Shape 3).** Produces no attestation by design — `--plan`
714
+ and `--headless` are iteration aids, not trust claims. Anything producible
715
+ without a server can be forged by the operator. See
716
+ [`docs/local-only-mode.md`](./docs/local-only-mode.md).
717
+ - **The human holding the operator key in 1.x.** 1.x operator-trust mode
718
+ still relies on convention to bind verdict to model invocation. The 2.x
719
+ upgrade closes this gap structurally; the
720
+ [migration guide](./docs/migration-1.x-to-2.x.md) walks the upgrade per-repo.
721
+
722
+ For the full threat model, deferred-to-Phase-2 list, and the cryptographic
723
+ guarantees behind the v4 envelope, see
724
+ [`docs/plans/server-attested-reviews.md`](./docs/plans/server-attested-reviews.md)
725
+ and [`DESIGN.md#security-model`](./DESIGN.md#security-model).
610
726
 
611
727
  ## License
612
728
 
@@ -1,4 +1,10 @@
1
1
  #!/usr/bin/env node
2
+ var __require = /* @__PURE__ */ ((x) => typeof require !== "undefined" ? require : typeof Proxy !== "undefined" ? new Proxy(x, {
3
+ get: (a, b) => (typeof require !== "undefined" ? require : a)[b]
4
+ }) : x)(function(x) {
5
+ if (typeof require !== "undefined") return require.apply(this, arguments);
6
+ throw Error('Dynamic require of "' + x + '" is not supported');
7
+ });
2
8
 
3
9
  // src/lib/git.ts
4
10
  import { execFileSync, spawnSync } from "child_process";
@@ -36,6 +42,25 @@ function commitMessage(sha, cwd) {
36
42
  function showAtRef(ref, path, cwd) {
37
43
  return runGit(["show", `${ref}:${path}`], cwd);
38
44
  }
45
+ function listFilesAtRef(ref, dirPath, cwd) {
46
+ const result = spawnSync(
47
+ "git",
48
+ ["ls-tree", "--name-only", `${ref}`, `${dirPath}/`],
49
+ { cwd, stdio: ["ignore", "pipe", "pipe"] }
50
+ );
51
+ if (result.status === 128) return [];
52
+ if (result.status !== 0) {
53
+ const stderr = result.stderr?.toString("utf8").trim() ?? "";
54
+ throw new Error(
55
+ `git ls-tree ${ref}:${dirPath} failed (status ${result.status}): ${stderr || "(no stderr)"}`
56
+ );
57
+ }
58
+ const text = result.stdout?.toString("utf8") ?? "";
59
+ return text.split("\n").filter((line) => line.length > 0).map((line) => {
60
+ const prefix = `${dirPath}/`;
61
+ return line.startsWith(prefix) ? line.slice(prefix.length) : line;
62
+ });
63
+ }
39
64
  function pathExistsAtRef(ref, path, cwd) {
40
65
  const result = spawnSync("git", ["cat-file", "-e", `${ref}:${path}`], {
41
66
  cwd,
@@ -202,6 +227,7 @@ function isFile(path) {
202
227
  import { chmodSync, existsSync as existsSync2 } from "fs";
203
228
  import { DatabaseSync } from "node:sqlite";
204
229
  import { dirname as dirname2 } from "path";
230
+ var REVIEW_ROW_SCHEMA_V4 = 4;
205
231
  function openDb(path) {
206
232
  const dir = dirname2(path);
207
233
  ensureDir(dir, 448);
@@ -245,16 +271,33 @@ function initSchema(db) {
245
271
  if (!have.has("prompt_hash")) {
246
272
  db.exec("ALTER TABLE reviews ADD COLUMN prompt_hash TEXT");
247
273
  }
274
+ if (!have.has("server_approval_json")) {
275
+ db.exec("ALTER TABLE reviews ADD COLUMN server_approval_json TEXT");
276
+ }
277
+ if (!have.has("server_signature_b64")) {
278
+ db.exec("ALTER TABLE reviews ADD COLUMN server_signature_b64 TEXT");
279
+ }
280
+ if (!have.has("server_key_id")) {
281
+ db.exec("ALTER TABLE reviews ADD COLUMN server_key_id TEXT");
282
+ }
283
+ if (!have.has("schema_version")) {
284
+ db.exec("ALTER TABLE reviews ADD COLUMN schema_version INTEGER");
285
+ }
248
286
  db.exec(`
249
287
  CREATE INDEX IF NOT EXISTS idx_reviews_cache
250
288
  ON reviews(reviewer, diff_hash, prompt_hash, created_at)
251
289
  `);
252
290
  }
253
291
  function recordReview(db, input) {
292
+ const sa = input.serverAttestation ?? null;
293
+ const schemaVersion = sa === null ? null : REVIEW_ROW_SCHEMA_V4;
254
294
  const stmt = db.prepare(
255
295
  `INSERT INTO reviews
256
- (reviewer, base_sha, head_sha, verdict, issues, tool_calls, diff_hash, prompt_hash)
257
- VALUES (?, ?, ?, ?, ?, ?, ?, ?)`
296
+ (reviewer, base_sha, head_sha, verdict, issues, tool_calls,
297
+ diff_hash, prompt_hash,
298
+ server_approval_json, server_signature_b64, server_key_id,
299
+ schema_version)
300
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`
258
301
  );
259
302
  const result = stmt.run(
260
303
  input.reviewer,
@@ -264,10 +307,41 @@ function recordReview(db, input) {
264
307
  input.issues ?? null,
265
308
  input.tool_calls ?? null,
266
309
  input.diff_hash ?? null,
267
- input.prompt_hash ?? null
310
+ input.prompt_hash ?? null,
311
+ sa?.approval_json ?? null,
312
+ sa?.signature_b64 ?? null,
313
+ sa?.server_key_id ?? null,
314
+ schemaVersion
268
315
  );
269
316
  return Number(result.lastInsertRowid);
270
317
  }
318
+ function serverApprovalsFor(db, base_sha, head_sha) {
319
+ const stmt = db.prepare(`
320
+ SELECT id, reviewer, base_sha, head_sha, verdict,
321
+ server_approval_json AS approval_json,
322
+ server_signature_b64 AS signature_b64,
323
+ server_key_id,
324
+ created_at
325
+ FROM (
326
+ SELECT
327
+ id, reviewer, base_sha, head_sha, verdict,
328
+ server_approval_json,
329
+ server_signature_b64,
330
+ server_key_id,
331
+ created_at,
332
+ ROW_NUMBER() OVER (
333
+ PARTITION BY reviewer
334
+ ORDER BY created_at DESC, id DESC
335
+ ) AS rn
336
+ FROM reviews
337
+ WHERE base_sha = ? AND head_sha = ?
338
+ AND server_approval_json IS NOT NULL
339
+ )
340
+ WHERE rn = 1
341
+ ORDER BY reviewer ASC
342
+ `);
343
+ return stmt.all(base_sha, head_sha);
344
+ }
271
345
  function findCachedVerdict(db, reviewer, diff_hash, prompt_hash) {
272
346
  const stmt = db.prepare(`
273
347
  SELECT verdict, issues, base_sha, head_sha, created_at
@@ -326,7 +400,10 @@ function priorReviewByReviewer(db, reviewer, base_sha, excludeHeadSha) {
326
400
  function reviewHistory(db, opts = {}) {
327
401
  const limit = opts.limit ?? 50;
328
402
  const stmt = db.prepare(`
329
- SELECT id, reviewer, base_sha, head_sha, verdict, issues, created_at
403
+ SELECT id, reviewer, base_sha, head_sha, verdict, issues,
404
+ tool_calls, diff_hash, prompt_hash,
405
+ server_approval_json, server_signature_b64, server_key_id,
406
+ schema_version, created_at
330
407
  FROM reviews
331
408
  ORDER BY created_at DESC, id DESC
332
409
  LIMIT ?
@@ -358,7 +435,10 @@ function reviewerStats(db, reviewer) {
358
435
  }
359
436
  function recentReviewsByReviewer(db, reviewer, limit) {
360
437
  const stmt = db.prepare(`
361
- SELECT id, reviewer, base_sha, head_sha, verdict, issues, created_at
438
+ SELECT id, reviewer, base_sha, head_sha, verdict, issues,
439
+ tool_calls, diff_hash, prompt_hash,
440
+ server_approval_json, server_signature_b64, server_key_id,
441
+ schema_version, created_at
362
442
  FROM reviews
363
443
  WHERE reviewer = ?
364
444
  ORDER BY created_at DESC, id DESC
@@ -483,6 +563,7 @@ function findTrustedKey(repoRoot, fingerprint) {
483
563
 
484
564
  // src/lib/attestation.ts
485
565
  var CURRENT_PAYLOAD_VERSION = 3;
566
+ var MIN_ACCEPTED_PAYLOAD_VERSION = 3;
486
567
  var STAMP_PAYLOAD_TRAILER = "Stamp-Payload";
487
568
  var STAMP_VERIFIED_TRAILER = "Stamp-Verified";
488
569
  var MAX_TRAILER_BYTES = 64 * 1024;
@@ -531,10 +612,12 @@ function verifyBytes(publicKeyPem, data, signatureBase64) {
531
612
  }
532
613
 
533
614
  export {
615
+ __require,
534
616
  currentBranch,
535
617
  firstParentCommits,
536
618
  commitMessage,
537
619
  showAtRef,
620
+ listFilesAtRef,
538
621
  pathExistsAtRef,
539
622
  isPathTracked,
540
623
  repoHasAnyCommit,
@@ -557,6 +640,7 @@ export {
557
640
  ensureDir,
558
641
  openDb,
559
642
  recordReview,
643
+ serverApprovalsFor,
560
644
  findCachedVerdict,
561
645
  latestVerdicts,
562
646
  latestReviews,
@@ -574,10 +658,11 @@ export {
574
658
  publicKeyFingerprintFilename,
575
659
  findTrustedKey,
576
660
  CURRENT_PAYLOAD_VERSION,
661
+ MIN_ACCEPTED_PAYLOAD_VERSION,
577
662
  serializePayload,
578
663
  parseCommitAttestation,
579
664
  formatTrailers,
580
665
  signBytes,
581
666
  verifyBytes
582
667
  };
583
- //# sourceMappingURL=chunk-V7W3CFNP.js.map
668
+ //# sourceMappingURL=chunk-4PFD2DSY.js.map