@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 +162 -46
- package/dist/{chunk-V7W3CFNP.js → chunk-4PFD2DSY.js} +91 -6
- package/dist/chunk-4PFD2DSY.js.map +1 -0
- package/dist/hooks/pre-receive.cjs +920 -104
- package/dist/hooks/pre-receive.cjs.map +1 -1
- package/dist/index.js +6213 -1865
- package/dist/index.js.map +1 -1
- package/dist/server/bootstrap-review-key.cjs +196 -0
- package/dist/server/bootstrap-review-key.cjs.map +1 -0
- package/dist/server/stamp-review.cjs +8357 -0
- package/dist/server/stamp-review.cjs.map +1 -0
- package/dist/{ui-BC7UWSJW.js → ui-P5DRAT3P.js} +2 -2
- package/package.json +2 -1
- package/dist/chunk-V7W3CFNP.js.map +0 -1
- /package/dist/{ui-BC7UWSJW.js.map → ui-P5DRAT3P.js.map} +0 -0
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… |
|
|
51
|
+
| Shape | Origin is… | Trust source | Enforcement |
|
|
41
52
|
|---|---|---|---|
|
|
42
|
-
| **Server-gated** | A stamp server you deployed |
|
|
43
|
-
| **PR
|
|
44
|
-
| **Local-only** (
|
|
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
|
-
|
|
47
|
-
|
|
48
|
-
|
|
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
|
|
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)
|
|
70
|
-
`
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
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
|
|
83
|
-
|
|
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 #
|
|
95
|
-
|
|
96
|
-
|
|
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
|
|
329
|
-
> is the reviewer gate itself: `.stamp/config.yml` changes go
|
|
330
|
-
> same reviewers as any other code change, and your security
|
|
331
|
-
> should treat `required_checks` edits as high-scrutiny.
|
|
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
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
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,
|
|
257
|
-
|
|
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,
|
|
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,
|
|
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-
|
|
668
|
+
//# sourceMappingURL=chunk-4PFD2DSY.js.map
|