@openthink/stamp 2.0.2 → 2.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -7341,6 +7341,7 @@ __export(pre_receive_exports, {
7341
7341
  verifyV4Approvals: () => verifyV4Approvals,
7342
7342
  verifyV4Checks: () => verifyV4Checks,
7343
7343
  verifyV4DiffHash: () => verifyV4DiffHash,
7344
+ verifyV4ManifestSnapshot: () => verifyV4ManifestSnapshot,
7344
7345
  verifyV4MergeStructure: () => verifyV4MergeStructure,
7345
7346
  verifyV4OuterSignature: () => verifyV4OuterSignature,
7346
7347
  verifyV4SignerTrust: () => verifyV4SignerTrust,
@@ -7380,7 +7381,7 @@ function parseCommitAttestation(commitMessage) {
7380
7381
  }
7381
7382
 
7382
7383
  // src/lib/attestationV4.ts
7383
- var MIN_ACCEPTED_V4_SCHEMA_VERSION = 4;
7384
+ var MIN_ACCEPTED_V4_SCHEMA_VERSION = 5;
7384
7385
  var MAX_V4_ENVELOPE_BYTES = 64 * 1024;
7385
7386
  function sortKeysDeep(value) {
7386
7387
  if (value === null || typeof value !== "object") return value;
@@ -7663,6 +7664,11 @@ var COMMIT_PHASES_V4 = [
7663
7664
  { name: "verifyV4TargetBranch", fn: verifyV4TargetBranch },
7664
7665
  { name: "verifyV4SignerTrust", fn: verifyV4SignerTrust },
7665
7666
  { name: "verifyV4OuterSignature", fn: verifyV4OuterSignature },
7667
+ // AGT-370: envelope-level manifest snapshot binding (lifted from
7668
+ // the per-approval slot in v4). Runs once before the per-approval
7669
+ // loop in verifyV4ApprovalSignatures — a single check replaces the
7670
+ // N-checks per envelope the v4 verifier did.
7671
+ { name: "verifyV4ManifestSnapshot", fn: verifyV4ManifestSnapshot },
7666
7672
  { name: "verifyV4Approvals", fn: verifyV4Approvals },
7667
7673
  { name: "verifyV4DiffHash", fn: verifyV4DiffHash },
7668
7674
  { name: "verifyV4ApprovalSignatures", fn: verifyV4ApprovalSignatures },
@@ -7795,10 +7801,19 @@ function verifyV4DiffHash(input) {
7795
7801
  }
7796
7802
  return { ok: true };
7797
7803
  }
7804
+ function verifyV4ManifestSnapshot(input) {
7805
+ const { sha, payload, manifest } = input;
7806
+ const computed = snapshotSha256(manifest);
7807
+ if (payload.manifest_snapshot_sha256 !== computed) {
7808
+ return {
7809
+ ok: false,
7810
+ reason: `commit ${sha.slice(0, 8)}: v4 manifest_snapshot_sha256 (${payload.manifest_snapshot_sha256.slice(0, 16)}\u2026) does not match the manifest at base ${payload.base_sha.slice(0, 8)} (${computed.slice(0, 16)}\u2026). The envelope was signed against a different snapshot of the trust set than the one committed at the merge base. Re-run \`stamp merge\` (or \`stamp attest\`) so the outer signature binds to the current manifest.`
7811
+ };
7812
+ }
7813
+ return { ok: true };
7814
+ }
7798
7815
  function verifyV4ApprovalSignatures(input) {
7799
7816
  const { sha, payload, manifest, pubkeyByFingerprint } = input;
7800
- const manifestSnapshot = snapshotSha256(manifest);
7801
- const reviewerDefs = readReviewerDefsAtRef(payload.base_sha);
7802
7817
  for (const entry of payload.approvals) {
7803
7818
  const a = entry.approval;
7804
7819
  const reviewerLabel = `"${a.reviewer}"`;
@@ -7820,12 +7835,6 @@ function verifyV4ApprovalSignatures(input) {
7820
7835
  reason: `commit ${sha.slice(0, 8)}: v4 approval ${reviewerLabel}: server_attestation.server_key_id (${entry.server_attestation.server_key_id}) does not match inner approval.server_key_id (${a.server_key_id}). The inner signed payload is authoritative; one of the two was tampered with after signing.`
7821
7836
  };
7822
7837
  }
7823
- if (a.trusted_keys_snapshot_sha256 !== manifestSnapshot) {
7824
- return {
7825
- ok: false,
7826
- reason: `commit ${sha.slice(0, 8)}: v4 approval ${reviewerLabel}: trusted_keys_snapshot_sha256 (${a.trusted_keys_snapshot_sha256.slice(0, 16)}\u2026) does not match the manifest at base ${payload.base_sha.slice(0, 8)} (${manifestSnapshot.slice(0, 16)}\u2026). The server signed against a different snapshot of the trust set than the one committed at the merge base.`
7827
- };
7828
- }
7829
7838
  const caps = resolveCapability(manifest, a.server_key_id);
7830
7839
  if (caps === null) {
7831
7840
  return {
@@ -7865,29 +7874,6 @@ function verifyV4ApprovalSignatures(input) {
7865
7874
  reason: `commit ${sha.slice(0, 8)}: v4 approval ${reviewerLabel}: server signature does not verify against ${a.server_key_id} over canonical approval bytes`
7866
7875
  };
7867
7876
  }
7868
- const def = reviewerDefs[a.reviewer];
7869
- if (!def) {
7870
- return {
7871
- ok: false,
7872
- reason: `commit ${sha.slice(0, 8)}: v4 approval ${reviewerLabel}: reviewer is not defined in .stamp/config.yml at base ${payload.base_sha.slice(0, 8)}`
7873
- };
7874
- }
7875
- let promptText;
7876
- try {
7877
- promptText = run(["show", `${payload.base_sha}:${def.prompt}`]);
7878
- } catch {
7879
- return {
7880
- ok: false,
7881
- reason: `commit ${sha.slice(0, 8)}: v4 approval ${reviewerLabel}: prompt "${def.prompt}" is unreadable at base ${payload.base_sha.slice(0, 8)}`
7882
- };
7883
- }
7884
- const recomputedPromptSha = hashPromptBytes(Buffer.from(promptText, "utf8"));
7885
- if (recomputedPromptSha !== a.prompt_sha256) {
7886
- return {
7887
- ok: false,
7888
- reason: `commit ${sha.slice(0, 8)}: v4 approval ${reviewerLabel}: prompt_sha256 mismatch \u2014 server signed ${a.prompt_sha256.slice(0, 12)}\u2026 but prompt file at base hashes to ${recomputedPromptSha.slice(0, 12)}\u2026. The reviewer prompt the server reviewed differs from the one in the merge-base tree.`
7889
- };
7890
- }
7891
7877
  }
7892
7878
  return { ok: true };
7893
7879
  }
@@ -8053,22 +8039,6 @@ function readPubkeyMapAt(ref) {
8053
8039
  }
8054
8040
  return buildPubkeyMap(names, (relPath) => run(["show", `${ref}:${relPath}`]));
8055
8041
  }
8056
- function readReviewerDefsAtRef(ref) {
8057
- let yaml;
8058
- try {
8059
- yaml = run(["show", `${ref}:.stamp/config.yml`]);
8060
- } catch {
8061
- return {};
8062
- }
8063
- const defs = readReviewersFromYaml(yaml);
8064
- const out = {};
8065
- for (const [name, def] of Object.entries(defs)) {
8066
- if (def && typeof def.prompt === "string") {
8067
- out[name] = { prompt: def.prompt };
8068
- }
8069
- }
8070
- return out;
8071
- }
8072
8042
  function readChangedFilesAtRef(baseSha, headSha) {
8073
8043
  let out;
8074
8044
  try {
@@ -8457,6 +8427,12 @@ function verifyReviewerHashesAtMergeBase(input) {
8457
8427
  reason: `${prefix} reviewer "${approval.reviewer}" not defined in .stamp/config.yml at merge-base`
8458
8428
  };
8459
8429
  }
8430
+ if (def.prompt === void 0) {
8431
+ return {
8432
+ ok: false,
8433
+ reason: `${prefix} reviewer "${approval.reviewer}" has no \`prompt:\` in .stamp/config.yml at merge-base; v3 attestation references prompt_sha256 but the producer flow for server-bundled prompts is v4 (server-attested). The attestation envelope and the config shape are inconsistent.`
8434
+ };
8435
+ }
8460
8436
  let promptBytes;
8461
8437
  try {
8462
8438
  promptBytes = run2(["show", `${baseSha}:${def.prompt}`]);
@@ -8715,6 +8691,7 @@ if (isMainModule()) {
8715
8691
  verifyV4Approvals,
8716
8692
  verifyV4Checks,
8717
8693
  verifyV4DiffHash,
8694
+ verifyV4ManifestSnapshot,
8718
8695
  verifyV4MergeStructure,
8719
8696
  verifyV4OuterSignature,
8720
8697
  verifyV4SignerTrust,