@openthink/stamp 2.0.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 +4 -5
- package/dist/hooks/pre-receive.cjs.map +1 -1
- package/dist/index.js +295 -57
- package/dist/index.js.map +1 -1
- package/dist/server/stamp-review.cjs +4 -1
- package/dist/server/stamp-review.cjs.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -1567,11 +1567,31 @@ function parseResponseJson(raw) {
|
|
|
1567
1567
|
`review_server response: top-level verdict (${verdict}) and approval.verdict (${a.verdict}) disagree`
|
|
1568
1568
|
);
|
|
1569
1569
|
}
|
|
1570
|
+
let prAttestationV3 = null;
|
|
1571
|
+
const payloadB64 = obj["pr_attestation_v3_payload_b64"];
|
|
1572
|
+
const sigB64 = obj["pr_attestation_v3_signature_b64"];
|
|
1573
|
+
if (payloadB64 !== void 0 || sigB64 !== void 0) {
|
|
1574
|
+
if (typeof payloadB64 !== "string" || !payloadB64) {
|
|
1575
|
+
throw new Error(
|
|
1576
|
+
`review_server response.pr_attestation_v3_payload_b64 must be a non-empty string when present`
|
|
1577
|
+
);
|
|
1578
|
+
}
|
|
1579
|
+
if (typeof sigB64 !== "string" || !sigB64) {
|
|
1580
|
+
throw new Error(
|
|
1581
|
+
`review_server response.pr_attestation_v3_signature_b64 must be a non-empty string when present`
|
|
1582
|
+
);
|
|
1583
|
+
}
|
|
1584
|
+
prAttestationV3 = {
|
|
1585
|
+
payloadBytes: Buffer.from(payloadB64, "base64"),
|
|
1586
|
+
signatureB64: sigB64
|
|
1587
|
+
};
|
|
1588
|
+
}
|
|
1570
1589
|
return {
|
|
1571
1590
|
verdict,
|
|
1572
1591
|
prose: obj.prose,
|
|
1573
1592
|
approval,
|
|
1574
|
-
signature: obj.signature
|
|
1593
|
+
signature: obj.signature,
|
|
1594
|
+
prAttestationV3
|
|
1575
1595
|
};
|
|
1576
1596
|
}
|
|
1577
1597
|
function verifyServerSignature(opts) {
|
|
@@ -1709,12 +1729,26 @@ async function requestServerReview(input) {
|
|
|
1709
1729
|
`review_server returned a signed approval for diff_sha256 ${parsed.approval.diff_sha256} but we sent diff_sha256 ${diffSha256} \u2014 refusing.`
|
|
1710
1730
|
);
|
|
1711
1731
|
}
|
|
1732
|
+
if (parsed.prAttestationV3) {
|
|
1733
|
+
const localCanonical = canonicalSerializeApproval(parsed.approval);
|
|
1734
|
+
if (!parsed.prAttestationV3.payloadBytes.equals(localCanonical)) {
|
|
1735
|
+
throw new Error(
|
|
1736
|
+
`review_server returned pr_attestation_v3_payload_b64 bytes that do not match canonicalSerializeApproval(approval) recomputed locally \u2014 refusing. This indicates a server/client canonicalizer drift (server stamp version mismatch?). Server bytes (first 80): ${JSON.stringify(parsed.prAttestationV3.payloadBytes.toString("utf8").slice(0, 80))}; client bytes (first 80): ${JSON.stringify(localCanonical.toString("utf8").slice(0, 80))}.`
|
|
1737
|
+
);
|
|
1738
|
+
}
|
|
1739
|
+
if (parsed.prAttestationV3.signatureB64 !== parsed.signature) {
|
|
1740
|
+
throw new Error(
|
|
1741
|
+
`review_server returned pr_attestation_v3_signature_b64 that disagrees with the top-level signature \u2014 refusing.`
|
|
1742
|
+
);
|
|
1743
|
+
}
|
|
1744
|
+
}
|
|
1712
1745
|
return {
|
|
1713
1746
|
verdict: parsed.verdict,
|
|
1714
1747
|
prose: parsed.prose,
|
|
1715
1748
|
approval: parsed.approval,
|
|
1716
1749
|
signature: parsed.signature,
|
|
1717
|
-
approvalJson: JSON.stringify(parsed.approval)
|
|
1750
|
+
approvalJson: JSON.stringify(parsed.approval),
|
|
1751
|
+
prAttestationV3: parsed.prAttestationV3
|
|
1718
1752
|
};
|
|
1719
1753
|
}
|
|
1720
1754
|
function exitCodeHint(exitCode) {
|
|
@@ -10123,6 +10157,7 @@ function patchIdForSpan(base_sha, head_sha, repoRoot) {
|
|
|
10123
10157
|
|
|
10124
10158
|
// src/lib/prAttestation.ts
|
|
10125
10159
|
import { spawnSync as spawnSync13 } from "child_process";
|
|
10160
|
+
var PR_ATTESTATION_SCHEMA_VERSION = 3;
|
|
10126
10161
|
var LEGACY_CLIENT_PR_ATTESTATION_SCHEMA_VERSION = 2;
|
|
10127
10162
|
var MIN_ACCEPTED_PR_ATTESTATION_VERSION = 3;
|
|
10128
10163
|
var MAX_PR_ATTESTATION_BYTES = 64 * 1024;
|
|
@@ -10236,22 +10271,256 @@ function runAttest(opts) {
|
|
|
10236
10271
|
const branchRef = opts.branch ?? "HEAD";
|
|
10237
10272
|
const revspec = `${opts.into}..${branchRef}`;
|
|
10238
10273
|
const resolved = resolveDiff(revspec, repoRoot);
|
|
10239
|
-
const
|
|
10274
|
+
const patch_id = patchIdForSpan(resolved.base_sha, resolved.head_sha, repoRoot);
|
|
10275
|
+
const target_branch_tip_sha = runGit(
|
|
10276
|
+
["rev-parse", `${opts.into}^{commit}`],
|
|
10277
|
+
repoRoot
|
|
10278
|
+
).trim();
|
|
10279
|
+
const { keypair } = ensureUserKeypair();
|
|
10280
|
+
const result = rule.review_server ? buildV3Envelope({
|
|
10281
|
+
repoRoot,
|
|
10282
|
+
revspec,
|
|
10283
|
+
baseSha: resolved.base_sha,
|
|
10284
|
+
headSha: resolved.head_sha,
|
|
10285
|
+
diff: resolved.diff,
|
|
10286
|
+
targetBranch: opts.into,
|
|
10287
|
+
targetBranchTipSha: target_branch_tip_sha,
|
|
10288
|
+
patchId: patch_id,
|
|
10289
|
+
requiredReviewers: rule.required,
|
|
10290
|
+
operatorPrivateKeyPem: keypair.privateKeyPem,
|
|
10291
|
+
operatorFingerprint: keypair.fingerprint
|
|
10292
|
+
}) : buildV2Envelope({
|
|
10293
|
+
repoRoot,
|
|
10294
|
+
revspec,
|
|
10295
|
+
baseSha: resolved.base_sha,
|
|
10296
|
+
headSha: resolved.head_sha,
|
|
10297
|
+
targetBranch: opts.into,
|
|
10298
|
+
targetBranchTipSha: target_branch_tip_sha,
|
|
10299
|
+
patchId: patch_id,
|
|
10300
|
+
requiredReviewers: rule.required,
|
|
10301
|
+
operatorPrivateKeyPem: keypair.privateKeyPem,
|
|
10302
|
+
operatorFingerprint: keypair.fingerprint
|
|
10303
|
+
});
|
|
10304
|
+
const { ref, blob_sha } = writeAttestationRef(
|
|
10305
|
+
{ payload: result.payload, signature: result.signature },
|
|
10306
|
+
repoRoot
|
|
10307
|
+
);
|
|
10308
|
+
const bar = "\u2500".repeat(72);
|
|
10309
|
+
console.log(bar);
|
|
10310
|
+
console.log(`attested ${branchRef} for merge into '${opts.into}'`);
|
|
10311
|
+
console.log(bar);
|
|
10312
|
+
console.log(` patch-id: ${patch_id}`);
|
|
10313
|
+
console.log(
|
|
10314
|
+
` base\u2192head: ${resolved.base_sha.slice(0, 8)} \u2192 ${resolved.head_sha.slice(0, 8)}`
|
|
10315
|
+
);
|
|
10316
|
+
console.log(` signed by: ${keypair.fingerprint}`);
|
|
10317
|
+
console.log(` approvals: ${result.reviewerNames.join(", ")}`);
|
|
10318
|
+
console.log(
|
|
10319
|
+
` schema: v${result.payload.schema_version}${result.payload.schema_version === PR_ATTESTATION_SCHEMA_VERSION ? " (server-attested)" : " (legacy)"}`
|
|
10320
|
+
);
|
|
10321
|
+
console.log(` ref: ${ref}`);
|
|
10322
|
+
console.log(` blob: ${blob_sha.slice(0, 12)}`);
|
|
10323
|
+
console.log(bar);
|
|
10324
|
+
if (opts.pushTo) {
|
|
10325
|
+
pushBranchAndAttestation(opts.pushTo, ref, repoRoot);
|
|
10326
|
+
console.log(
|
|
10327
|
+
`
|
|
10328
|
+
\u2713 pushed branch + attestation ref to ${opts.pushTo}. Open the PR; stamp/verify-attestation@v1 will look up refs/stamp/attestations/<patch-id> from your head SHA's diff against the base.`
|
|
10329
|
+
);
|
|
10330
|
+
} else {
|
|
10331
|
+
console.log(
|
|
10332
|
+
`
|
|
10333
|
+
Next: push the branch + attestation ref to your remote, open a PR, and let stamp/verify-attestation@v1 (the GH Action) confirm it. To do both pushes in one shot:
|
|
10334
|
+
|
|
10335
|
+
git push <remote> HEAD ${ref}
|
|
10336
|
+
|
|
10337
|
+
Or re-run with --push <remote> next time.`
|
|
10338
|
+
);
|
|
10339
|
+
}
|
|
10340
|
+
}
|
|
10341
|
+
function buildV3Envelope(input) {
|
|
10342
|
+
const diffBytes = Buffer.from(input.diff, "utf8");
|
|
10343
|
+
const diffSha256 = createHash12("sha256").update(diffBytes).digest("hex");
|
|
10344
|
+
let manifestYaml;
|
|
10345
|
+
try {
|
|
10346
|
+
manifestYaml = showAtRef(
|
|
10347
|
+
input.baseSha,
|
|
10348
|
+
".stamp/trusted-keys/manifest.yml",
|
|
10349
|
+
input.repoRoot
|
|
10350
|
+
);
|
|
10351
|
+
} catch (err) {
|
|
10352
|
+
throw new Error(
|
|
10353
|
+
`review_server is configured but .stamp/trusted-keys/manifest.yml is missing at base ${input.baseSha.slice(0, 8)}: ${err instanceof Error ? err.message : String(err)}. Server-attested PR mode requires the manifest in the merge-base tree so each approval's server signature can be checked against the keys the repo trusted at attestation time. Commit a manifest with capabilities: [server] entries for the review server before attesting.`
|
|
10354
|
+
);
|
|
10355
|
+
}
|
|
10356
|
+
const manifest = parseManifest(manifestYaml);
|
|
10357
|
+
if (!manifest) {
|
|
10358
|
+
throw new Error(
|
|
10359
|
+
`.stamp/trusted-keys/manifest.yml at base ${input.baseSha.slice(0, 8)} failed to parse as a valid trusted-keys manifest. Fix the YAML (syntax error, duplicate fingerprint, unknown capability, etc.) and re-run \`stamp attest\`.`
|
|
10360
|
+
);
|
|
10361
|
+
}
|
|
10362
|
+
const pubFilenames = listFilesAtRef(
|
|
10363
|
+
input.baseSha,
|
|
10364
|
+
".stamp/trusted-keys",
|
|
10365
|
+
input.repoRoot
|
|
10366
|
+
);
|
|
10367
|
+
const pubkeyByFingerprint = buildPubkeyMap(
|
|
10368
|
+
pubFilenames,
|
|
10369
|
+
(relPath) => showAtRef(input.baseSha, relPath, input.repoRoot)
|
|
10370
|
+
);
|
|
10371
|
+
const db = openDb(stampStateDbPath(input.repoRoot));
|
|
10372
|
+
let entries;
|
|
10373
|
+
try {
|
|
10374
|
+
const rows = serverApprovalsFor(db, input.baseSha, input.headSha);
|
|
10375
|
+
const byReviewer = new Map(rows.map((r) => [r.reviewer, r]));
|
|
10376
|
+
entries = input.requiredReviewers.map((reviewerName) => {
|
|
10377
|
+
const row = byReviewer.get(reviewerName);
|
|
10378
|
+
if (!row) {
|
|
10379
|
+
throw new Error(
|
|
10380
|
+
`missing server signature for reviewer "${reviewerName}" at base\u2192head ${input.baseSha.slice(0, 8)}\u2192${input.headSha.slice(0, 8)}. Server-attested PR mode requires every required reviewer to have a stamp-server-signed approval in the local DB. Possible causes:
|
|
10381
|
+
\u2022 the stamp-server is older than 2.0.1 and doesn't produce PR-attestation v3 payloads (upgrade the server)
|
|
10382
|
+
\u2022 \`stamp review --diff ${input.revspec}\` hasn't been run against this exact (base, head) pair yet
|
|
10383
|
+
\u2022 the review was run in local-mode (no \`review_server\` configured at review time)
|
|
10384
|
+
Run \`stamp review --diff ${input.revspec}\` to populate the signed row, then re-run \`stamp attest\`.`
|
|
10385
|
+
);
|
|
10386
|
+
}
|
|
10387
|
+
let parsedJson;
|
|
10388
|
+
try {
|
|
10389
|
+
parsedJson = JSON.parse(row.approval_json);
|
|
10390
|
+
} catch (err) {
|
|
10391
|
+
throw new Error(
|
|
10392
|
+
`server approval row for reviewer "${reviewerName}" has malformed JSON in server_approval_json \u2014 DB corruption or a writer-side bug. Re-run \`stamp review --diff ${input.revspec}\` to write a fresh row. (parse error: ${err instanceof Error ? err.message : String(err)})`
|
|
10393
|
+
);
|
|
10394
|
+
}
|
|
10395
|
+
if (!parsedJson || typeof parsedJson !== "object" || Array.isArray(parsedJson)) {
|
|
10396
|
+
throw new Error(
|
|
10397
|
+
`server approval row for reviewer "${reviewerName}" parsed to a non-object value \u2014 DB corruption or a writer-side bug. Re-run \`stamp review --diff ${input.revspec}\` to write a fresh row.`
|
|
10398
|
+
);
|
|
10399
|
+
}
|
|
10400
|
+
const obj = parsedJson;
|
|
10401
|
+
for (const field of [
|
|
10402
|
+
"reviewer",
|
|
10403
|
+
"verdict",
|
|
10404
|
+
"prompt_sha256",
|
|
10405
|
+
"diff_sha256",
|
|
10406
|
+
"base_sha",
|
|
10407
|
+
"head_sha",
|
|
10408
|
+
"trusted_keys_snapshot_sha256",
|
|
10409
|
+
"issued_at",
|
|
10410
|
+
"server_key_id"
|
|
10411
|
+
]) {
|
|
10412
|
+
if (typeof obj[field] !== "string") {
|
|
10413
|
+
throw new Error(
|
|
10414
|
+
`server approval row for reviewer "${reviewerName}" is missing required field "${field}" (or it isn't a string) \u2014 DB corruption or a writer-side bug. Re-run \`stamp review --diff ${input.revspec}\` to write a fresh row.`
|
|
10415
|
+
);
|
|
10416
|
+
}
|
|
10417
|
+
}
|
|
10418
|
+
const approval = parsedJson;
|
|
10419
|
+
if (approval.reviewer !== reviewerName) {
|
|
10420
|
+
throw new Error(
|
|
10421
|
+
`server approval row for reviewer "${reviewerName}" carries approval.reviewer="${approval.reviewer}" \u2014 DB row drifted. Re-run \`stamp review --diff ${input.revspec}\`.`
|
|
10422
|
+
);
|
|
10423
|
+
}
|
|
10424
|
+
if (approval.base_sha !== input.baseSha) {
|
|
10425
|
+
throw new Error(
|
|
10426
|
+
`server approval for "${reviewerName}" was signed against base_sha ${approval.base_sha.slice(0, 8)} but we're attesting from ${input.baseSha.slice(0, 8)} \u2014 stale signature. Re-run \`stamp review --diff ${input.revspec}\` to refresh.`
|
|
10427
|
+
);
|
|
10428
|
+
}
|
|
10429
|
+
if (approval.head_sha !== input.headSha) {
|
|
10430
|
+
throw new Error(
|
|
10431
|
+
`server approval for "${reviewerName}" was signed against head_sha ${approval.head_sha.slice(0, 8)} but we're attesting head ${input.headSha.slice(0, 8)} \u2014 stale signature. Re-run \`stamp review --diff ${input.revspec}\` to refresh.`
|
|
10432
|
+
);
|
|
10433
|
+
}
|
|
10434
|
+
if (approval.diff_sha256 !== diffSha256) {
|
|
10435
|
+
throw new Error(
|
|
10436
|
+
`server approval for "${reviewerName}" was signed against diff_sha256 ${approval.diff_sha256.slice(0, 12)}\u2026 but the current diff hashes to ${diffSha256.slice(0, 12)}\u2026 \u2014 stale signature. The diff content drifted between review and attest (rebased base, modified head). Re-run \`stamp review --diff ${input.revspec}\`.`
|
|
10437
|
+
);
|
|
10438
|
+
}
|
|
10439
|
+
if (approval.verdict !== "approved") {
|
|
10440
|
+
throw new Error(
|
|
10441
|
+
`server approval for "${reviewerName}" carries verdict "${approval.verdict}", not "approved". Re-run \`stamp review --diff ${input.revspec}\` so the server signs the current approved verdict.`
|
|
10442
|
+
);
|
|
10443
|
+
}
|
|
10444
|
+
const caps = resolveCapability(manifest, approval.server_key_id);
|
|
10445
|
+
if (caps === null) {
|
|
10446
|
+
throw new Error(
|
|
10447
|
+
`server approval for "${reviewerName}" was signed by ${approval.server_key_id}, but that key isn't listed in .stamp/trusted-keys/manifest.yml at base ${input.baseSha.slice(0, 8)}. Either the server's signing key changed (commit the new fingerprint to the manifest with capabilities: [server]) or this row was written by a server the repo no longer trusts. Re-run \`stamp review --diff ${input.revspec}\` after fixing the manifest.`
|
|
10448
|
+
);
|
|
10449
|
+
}
|
|
10450
|
+
if (!caps.includes("server")) {
|
|
10451
|
+
throw new Error(
|
|
10452
|
+
`server approval for "${reviewerName}" was signed by ${approval.server_key_id}, but that key's capabilities in .stamp/trusted-keys/manifest.yml at base ${input.baseSha.slice(0, 8)} are [${caps.join(", ")}] \u2014 missing the required 'server' capability. Update the manifest entry and re-attest.`
|
|
10453
|
+
);
|
|
10454
|
+
}
|
|
10455
|
+
const serverPubPem = pubkeyByFingerprint.get(approval.server_key_id);
|
|
10456
|
+
if (!serverPubPem) {
|
|
10457
|
+
throw new Error(
|
|
10458
|
+
`server approval for "${reviewerName}" was signed by ${approval.server_key_id}, but no .pub file in .stamp/trusted-keys/ at base ${input.baseSha.slice(0, 8)} matches that fingerprint. Commit the server's public key alongside its manifest entry and re-attest.`
|
|
10459
|
+
);
|
|
10460
|
+
}
|
|
10461
|
+
const sigOk = verifyBytes(
|
|
10462
|
+
serverPubPem,
|
|
10463
|
+
canonicalSerializeApproval(approval),
|
|
10464
|
+
row.signature_b64
|
|
10465
|
+
);
|
|
10466
|
+
if (!sigOk) {
|
|
10467
|
+
throw new Error(
|
|
10468
|
+
`server signature for "${reviewerName}" failed Ed25519 verification against key ${approval.server_key_id}. The DB row's signature does not match the canonical bytes of its approval body \u2014 either the row was tampered with or the writer was buggy. Re-run \`stamp review --diff ${input.revspec}\` to refresh the signed row.`
|
|
10469
|
+
);
|
|
10470
|
+
}
|
|
10471
|
+
return {
|
|
10472
|
+
approval,
|
|
10473
|
+
server_attestation: {
|
|
10474
|
+
server_key_id: approval.server_key_id,
|
|
10475
|
+
signature: row.signature_b64
|
|
10476
|
+
}
|
|
10477
|
+
};
|
|
10478
|
+
});
|
|
10479
|
+
} finally {
|
|
10480
|
+
db.close();
|
|
10481
|
+
}
|
|
10482
|
+
const trustAnchorSignatures = [];
|
|
10483
|
+
const payload = {
|
|
10484
|
+
schema_version: PR_ATTESTATION_SCHEMA_VERSION,
|
|
10485
|
+
patch_id: input.patchId,
|
|
10486
|
+
base_sha: input.baseSha,
|
|
10487
|
+
head_sha: input.headSha,
|
|
10488
|
+
target_branch: input.targetBranch,
|
|
10489
|
+
target_branch_tip_sha: input.targetBranchTipSha,
|
|
10490
|
+
diff_sha256: diffSha256,
|
|
10491
|
+
approvals: entries,
|
|
10492
|
+
checks: [],
|
|
10493
|
+
// Phase-1 deliberate omission — see file-level comment.
|
|
10494
|
+
trust_anchor_signatures: trustAnchorSignatures,
|
|
10495
|
+
signer_key_id: input.operatorFingerprint
|
|
10496
|
+
};
|
|
10497
|
+
const signature = signBytes(
|
|
10498
|
+
input.operatorPrivateKeyPem,
|
|
10499
|
+
serializePayload2(payload)
|
|
10500
|
+
);
|
|
10501
|
+
return {
|
|
10502
|
+
payload,
|
|
10503
|
+
signature,
|
|
10504
|
+
reviewerNames: input.requiredReviewers
|
|
10505
|
+
};
|
|
10506
|
+
}
|
|
10507
|
+
function buildV2Envelope(input) {
|
|
10508
|
+
const db = openDb(stampStateDbPath(input.repoRoot));
|
|
10240
10509
|
let approvals;
|
|
10241
10510
|
try {
|
|
10242
|
-
const reviews = latestReviews(db,
|
|
10511
|
+
const reviews = latestReviews(db, input.baseSha, input.headSha);
|
|
10243
10512
|
const byReviewer = new Map(reviews.map((r) => [r.reviewer, r]));
|
|
10244
10513
|
const missing = [];
|
|
10245
|
-
for (const r of
|
|
10514
|
+
for (const r of input.requiredReviewers) {
|
|
10246
10515
|
const rev = byReviewer.get(r);
|
|
10247
10516
|
if (!rev || rev.verdict !== "approved") missing.push(r);
|
|
10248
10517
|
}
|
|
10249
10518
|
if (missing.length > 0) {
|
|
10250
10519
|
throw new Error(
|
|
10251
|
-
`gate CLOSED: missing approved verdicts for: ${missing.join(", ")}. Run \`stamp status --diff ${revspec}\` to inspect, then \`stamp review --diff ${revspec}\` to review.`
|
|
10520
|
+
`gate CLOSED: missing approved verdicts for: ${missing.join(", ")}. Run \`stamp status --diff ${input.revspec}\` to inspect, then \`stamp review --diff ${input.revspec}\` to review.`
|
|
10252
10521
|
);
|
|
10253
10522
|
}
|
|
10254
|
-
approvals =
|
|
10523
|
+
approvals = input.requiredReviewers.map((name) => {
|
|
10255
10524
|
const rev = byReviewer.get(name);
|
|
10256
10525
|
const toolCalls = redactToolCallsForAttestation(
|
|
10257
10526
|
parseToolCalls(rev.tool_calls)
|
|
@@ -10267,20 +10536,20 @@ function runAttest(opts) {
|
|
|
10267
10536
|
db.close();
|
|
10268
10537
|
}
|
|
10269
10538
|
const baseConfigYaml = showAtRef(
|
|
10270
|
-
|
|
10539
|
+
input.baseSha,
|
|
10271
10540
|
".stamp/config.yml",
|
|
10272
|
-
repoRoot
|
|
10541
|
+
input.repoRoot
|
|
10273
10542
|
);
|
|
10274
10543
|
const baseReviewers = readReviewersFromYaml(baseConfigYaml);
|
|
10275
10544
|
approvals = approvals.map((a) => {
|
|
10276
10545
|
const def = baseReviewers[a.reviewer];
|
|
10277
10546
|
if (!def) {
|
|
10278
10547
|
throw new Error(
|
|
10279
|
-
`reviewer "${a.reviewer}" approved the diff but is not defined in .stamp/config.yml at base ${
|
|
10548
|
+
`reviewer "${a.reviewer}" approved the diff but is not defined in .stamp/config.yml at base ${input.baseSha.slice(0, 8)}. This shouldn't happen \u2014 runReview reads from the same base. File a bug at https://github.com/OpenThinkAi/stamp-cli/issues.`
|
|
10280
10549
|
);
|
|
10281
10550
|
}
|
|
10282
|
-
const promptText = showAtRef(
|
|
10283
|
-
const source = readReviewerSource2(a.reviewer, repoRoot);
|
|
10551
|
+
const promptText = showAtRef(input.baseSha, def.prompt, input.repoRoot);
|
|
10552
|
+
const source = readReviewerSource2(a.reviewer, input.repoRoot);
|
|
10284
10553
|
return {
|
|
10285
10554
|
...a,
|
|
10286
10555
|
prompt_sha256: hashPromptBytes(Buffer.from(promptText, "utf8")),
|
|
@@ -10289,58 +10558,27 @@ function runAttest(opts) {
|
|
|
10289
10558
|
...source ? { reviewer_source: source } : {}
|
|
10290
10559
|
};
|
|
10291
10560
|
});
|
|
10292
|
-
const patch_id = patchIdForSpan(resolved.base_sha, resolved.head_sha, repoRoot);
|
|
10293
|
-
const target_branch_tip_sha = runGit(
|
|
10294
|
-
["rev-parse", `${opts.into}^{commit}`],
|
|
10295
|
-
repoRoot
|
|
10296
|
-
).trim();
|
|
10297
|
-
const { keypair } = ensureUserKeypair();
|
|
10298
10561
|
const payload = {
|
|
10299
10562
|
schema_version: LEGACY_CLIENT_PR_ATTESTATION_SCHEMA_VERSION,
|
|
10300
|
-
patch_id,
|
|
10301
|
-
base_sha:
|
|
10302
|
-
head_sha:
|
|
10303
|
-
target_branch:
|
|
10304
|
-
target_branch_tip_sha,
|
|
10563
|
+
patch_id: input.patchId,
|
|
10564
|
+
base_sha: input.baseSha,
|
|
10565
|
+
head_sha: input.headSha,
|
|
10566
|
+
target_branch: input.targetBranch,
|
|
10567
|
+
target_branch_tip_sha: input.targetBranchTipSha,
|
|
10305
10568
|
approvals,
|
|
10306
10569
|
checks: [],
|
|
10307
10570
|
// Phase-1 deliberate omission — see file-level comment.
|
|
10308
|
-
signer_key_id:
|
|
10571
|
+
signer_key_id: input.operatorFingerprint
|
|
10309
10572
|
};
|
|
10310
|
-
const signature = signBytes(
|
|
10311
|
-
|
|
10312
|
-
|
|
10313
|
-
repoRoot
|
|
10573
|
+
const signature = signBytes(
|
|
10574
|
+
input.operatorPrivateKeyPem,
|
|
10575
|
+
serializePayload2(payload)
|
|
10314
10576
|
);
|
|
10315
|
-
|
|
10316
|
-
|
|
10317
|
-
|
|
10318
|
-
|
|
10319
|
-
|
|
10320
|
-
console.log(
|
|
10321
|
-
` base\u2192head: ${resolved.base_sha.slice(0, 8)} \u2192 ${resolved.head_sha.slice(0, 8)}`
|
|
10322
|
-
);
|
|
10323
|
-
console.log(` signed by: ${keypair.fingerprint}`);
|
|
10324
|
-
console.log(` approvals: ${approvals.map((a) => a.reviewer).join(", ")}`);
|
|
10325
|
-
console.log(` ref: ${ref}`);
|
|
10326
|
-
console.log(` blob: ${blob_sha.slice(0, 12)}`);
|
|
10327
|
-
console.log(bar);
|
|
10328
|
-
if (opts.pushTo) {
|
|
10329
|
-
pushBranchAndAttestation(opts.pushTo, ref, repoRoot);
|
|
10330
|
-
console.log(
|
|
10331
|
-
`
|
|
10332
|
-
\u2713 pushed branch + attestation ref to ${opts.pushTo}. Open the PR; stamp/verify-attestation@v1 will look up refs/stamp/attestations/<patch-id> from your head SHA's diff against the base.`
|
|
10333
|
-
);
|
|
10334
|
-
} else {
|
|
10335
|
-
console.log(
|
|
10336
|
-
`
|
|
10337
|
-
Next: push the branch + attestation ref to your remote, open a PR, and let stamp/verify-attestation@v1 (the GH Action) confirm it. To do both pushes in one shot:
|
|
10338
|
-
|
|
10339
|
-
git push <remote> HEAD ${ref}
|
|
10340
|
-
|
|
10341
|
-
Or re-run with --push <remote> next time.`
|
|
10342
|
-
);
|
|
10343
|
-
}
|
|
10577
|
+
return {
|
|
10578
|
+
payload,
|
|
10579
|
+
signature,
|
|
10580
|
+
reviewerNames: approvals.map((a) => a.reviewer)
|
|
10581
|
+
};
|
|
10344
10582
|
}
|
|
10345
10583
|
function pushBranchAndAttestation(remote, attestationRef, repoRoot) {
|
|
10346
10584
|
const result = spawnSync14(
|