@ai-sdlc/orchestrator 0.9.0 → 0.13.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.
Files changed (172) hide show
  1. package/dist/__test-helpers/git-env.d.ts +40 -0
  2. package/dist/__test-helpers/git-env.js +73 -0
  3. package/dist/adapters.d.ts +42 -3
  4. package/dist/adapters.js +133 -3
  5. package/dist/admission-composite.d.ts +134 -1
  6. package/dist/admission-composite.js +170 -6
  7. package/dist/admission-enrichment.d.ts +12 -3
  8. package/dist/admission-enrichment.js +71 -10
  9. package/dist/admission-hc.d.ts +20 -1
  10. package/dist/admission-hc.js +12 -1
  11. package/dist/admission-score.d.ts +44 -0
  12. package/dist/admission-score.js +11 -2
  13. package/dist/backlog-adapter.d.ts +45 -0
  14. package/dist/backlog-adapter.js +145 -1
  15. package/dist/calibration.d.ts +81 -0
  16. package/dist/calibration.js +76 -0
  17. package/dist/cli/commands/git-remote.d.ts +25 -0
  18. package/dist/cli/commands/git-remote.js +80 -1
  19. package/dist/cli/commands/init-features.d.ts +507 -0
  20. package/dist/cli/commands/init-features.js +1100 -0
  21. package/dist/cli/commands/init-templates.d.ts +301 -0
  22. package/dist/cli/commands/init-templates.js +1341 -0
  23. package/dist/cli/commands/init.d.ts +67 -0
  24. package/dist/cli/commands/init.js +301 -2
  25. package/dist/cli/commands/run.js +9 -1
  26. package/dist/cli/index.d.ts +11 -0
  27. package/dist/cli/index.js +42 -8
  28. package/dist/compliance/composer.d.ts +79 -0
  29. package/dist/compliance/composer.js +258 -0
  30. package/dist/compliance/errors.d.ts +64 -0
  31. package/dist/compliance/errors.js +85 -0
  32. package/dist/compliance/loader.d.ts +52 -0
  33. package/dist/compliance/loader.js +124 -0
  34. package/dist/compliance/types.d.ts +184 -0
  35. package/dist/compliance/types.js +41 -0
  36. package/dist/compliance-clearance.d.ts +269 -0
  37. package/dist/compliance-clearance.js +269 -0
  38. package/dist/config.js +17 -0
  39. package/dist/cost-tracker.d.ts +22 -0
  40. package/dist/cost-tracker.js +41 -0
  41. package/dist/database/adapters/external.js +5 -1
  42. package/dist/design-authority.d.ts +13 -0
  43. package/dist/design-authority.js +13 -0
  44. package/dist/embedding/adapters/openai-text-embedding-3-small.d.ts +71 -0
  45. package/dist/embedding/adapters/openai-text-embedding-3-small.js +190 -0
  46. package/dist/embedding/consumers/tessellation-drift.d.ts +74 -0
  47. package/dist/embedding/consumers/tessellation-drift.js +76 -0
  48. package/dist/embedding/cross-provider.d.ts +78 -0
  49. package/dist/embedding/cross-provider.js +75 -0
  50. package/dist/embedding/deprecation.d.ts +151 -0
  51. package/dist/embedding/deprecation.js +229 -0
  52. package/dist/embedding/errors.d.ts +90 -0
  53. package/dist/embedding/errors.js +150 -0
  54. package/dist/embedding/index.d.ts +29 -0
  55. package/dist/embedding/index.js +24 -0
  56. package/dist/embedding/pipeline-load.d.ts +146 -0
  57. package/dist/embedding/pipeline-load.js +178 -0
  58. package/dist/embedding/registry.d.ts +45 -0
  59. package/dist/embedding/registry.js +61 -0
  60. package/dist/embedding/stale-vector.d.ts +110 -0
  61. package/dist/embedding/stale-vector.js +92 -0
  62. package/dist/embedding/storage/index.d.ts +51 -0
  63. package/dist/embedding/storage/index.js +43 -0
  64. package/dist/embedding/storage/jsonl-backend.d.ts +150 -0
  65. package/dist/embedding/storage/jsonl-backend.js +332 -0
  66. package/dist/embedding/storage/types.d.ts +135 -0
  67. package/dist/embedding/storage/types.js +13 -0
  68. package/dist/embedding/types.d.ts +180 -0
  69. package/dist/embedding/types.js +10 -0
  70. package/dist/execute.d.ts +29 -2
  71. package/dist/execute.js +179 -35
  72. package/dist/index.d.ts +14 -2
  73. package/dist/index.js +19 -2
  74. package/dist/journey/inheritance-validator.d.ts +396 -0
  75. package/dist/journey/inheritance-validator.js +370 -0
  76. package/dist/journey/state-id-drift-rule.d.ts +137 -0
  77. package/dist/journey/state-id-drift-rule.js +245 -0
  78. package/dist/journey-sa2-router.d.ts +395 -0
  79. package/dist/journey-sa2-router.js +308 -0
  80. package/dist/models/classifier.d.ts +3 -1
  81. package/dist/models/classifier.js +61 -5
  82. package/dist/pillar-breakdown.d.ts +23 -1
  83. package/dist/pillar-breakdown.js +9 -0
  84. package/dist/runners/review-agent.js +6 -2
  85. package/dist/runners/runner-registry.d.ts +36 -0
  86. package/dist/runners/runner-registry.js +90 -0
  87. package/dist/runtime/attestations.d.ts +663 -11
  88. package/dist/runtime/attestations.js +971 -24
  89. package/dist/runtime/index.d.ts +1 -1
  90. package/dist/runtime/index.js +1 -1
  91. package/dist/runtime/parallelism-flag.d.ts +21 -3
  92. package/dist/runtime/parallelism-flag.js +27 -6
  93. package/dist/runtime/port-allocator.d.ts +11 -0
  94. package/dist/runtime/port-allocator.js +30 -0
  95. package/dist/sa-scoring/layer3-llm.js +6 -1
  96. package/dist/sa-scoring/revision-proposal-config.d.ts +178 -0
  97. package/dist/sa-scoring/revision-proposal-config.js +198 -0
  98. package/dist/sa-scoring/revision-proposal.d.ts +285 -0
  99. package/dist/sa-scoring/revision-proposal.js +417 -0
  100. package/dist/signal-ingestion/adapters/community-thread.d.ts +43 -0
  101. package/dist/signal-ingestion/adapters/community-thread.js +55 -0
  102. package/dist/signal-ingestion/adapters/in-app-feedback.d.ts +67 -0
  103. package/dist/signal-ingestion/adapters/in-app-feedback.js +51 -0
  104. package/dist/signal-ingestion/adapters/manual.d.ts +78 -0
  105. package/dist/signal-ingestion/adapters/manual.js +112 -0
  106. package/dist/signal-ingestion/adapters/support-ticket.d.ts +47 -0
  107. package/dist/signal-ingestion/adapters/support-ticket.js +51 -0
  108. package/dist/signal-ingestion/classifier.d.ts +205 -0
  109. package/dist/signal-ingestion/classifier.js +494 -0
  110. package/dist/signal-ingestion/clustering-types.d.ts +36 -0
  111. package/dist/signal-ingestion/clustering-types.js +14 -0
  112. package/dist/signal-ingestion/clustering.d.ts +200 -0
  113. package/dist/signal-ingestion/clustering.js +413 -0
  114. package/dist/signal-ingestion/config.d.ts +351 -0
  115. package/dist/signal-ingestion/config.js +587 -0
  116. package/dist/signal-ingestion/d1.d.ts +252 -0
  117. package/dist/signal-ingestion/d1.js +235 -0
  118. package/dist/signal-ingestion/errors.d.ts +73 -0
  119. package/dist/signal-ingestion/errors.js +108 -0
  120. package/dist/signal-ingestion/governance-events.d.ts +181 -0
  121. package/dist/signal-ingestion/governance-events.js +189 -0
  122. package/dist/signal-ingestion/index.d.ts +35 -0
  123. package/dist/signal-ingestion/index.js +53 -0
  124. package/dist/signal-ingestion/manual-share-metric.d.ts +93 -0
  125. package/dist/signal-ingestion/manual-share-metric.js +106 -0
  126. package/dist/signal-ingestion/registry.d.ts +40 -0
  127. package/dist/signal-ingestion/registry.js +137 -0
  128. package/dist/signal-ingestion/residency.d.ts +227 -0
  129. package/dist/signal-ingestion/residency.js +238 -0
  130. package/dist/signal-ingestion/significance.d.ts +554 -0
  131. package/dist/signal-ingestion/significance.js +555 -0
  132. package/dist/signal-ingestion/types.d.ts +191 -0
  133. package/dist/signal-ingestion/types.js +8 -0
  134. package/dist/substrate/drift-composition.d.ts +270 -0
  135. package/dist/substrate/drift-composition.js +306 -0
  136. package/dist/substrate/drift-tui-surface.d.ts +61 -0
  137. package/dist/substrate/drift-tui-surface.js +102 -0
  138. package/dist/substrate/identity-class.d.ts +176 -0
  139. package/dist/substrate/identity-class.js +201 -0
  140. package/dist/tessellation/cross-soul-provenance-rule.d.ts +133 -0
  141. package/dist/tessellation/cross-soul-provenance-rule.js +171 -0
  142. package/dist/tessellation/inter-soul-embedding-distance-rule.d.ts +61 -0
  143. package/dist/tessellation/inter-soul-embedding-distance-rule.js +67 -0
  144. package/dist/tessellation/rule-registry.d.ts +269 -0
  145. package/dist/tessellation/rule-registry.js +92 -0
  146. package/dist/tessellation/soul-slug-ast-scan-rule.d.ts +90 -0
  147. package/dist/tessellation/soul-slug-ast-scan-rule.js +158 -0
  148. package/dist/tessellation-admission.d.ts +162 -0
  149. package/dist/tessellation-admission.js +146 -0
  150. package/dist/tessellation-drift.d.ts +246 -0
  151. package/dist/tessellation-drift.js +250 -0
  152. package/dist/validate-config.js +13 -0
  153. package/dist/validate-issue.js +2 -2
  154. package/dist/variant/cardinality-activation.d.ts +126 -0
  155. package/dist/variant/cardinality-activation.js +101 -0
  156. package/dist/variant/deprecation-lifecycle.d.ts +184 -0
  157. package/dist/variant/deprecation-lifecycle.js +208 -0
  158. package/dist/variant/drift-extension.d.ts +136 -0
  159. package/dist/variant/drift-extension.js +164 -0
  160. package/dist/variant/engineering-review.d.ts +185 -0
  161. package/dist/variant/engineering-review.js +142 -0
  162. package/dist/variant/index.d.ts +32 -0
  163. package/dist/variant/index.js +32 -0
  164. package/dist/variant/inheritance-validator.d.ts +165 -0
  165. package/dist/variant/inheritance-validator.js +139 -0
  166. package/dist/variant/internal-adopter/index.d.ts +11 -0
  167. package/dist/variant/internal-adopter/index.js +10 -0
  168. package/dist/variant/internal-adopter/products.d.ts +156 -0
  169. package/dist/variant/internal-adopter/products.js +366 -0
  170. package/dist/variant-admission.d.ts +316 -0
  171. package/dist/variant-admission.js +247 -0
  172. package/package.json +11 -8
@@ -34,12 +34,27 @@
34
34
  /**
35
35
  * The currently-accepted predicate schema versions. CI rejects any envelope
36
36
  * whose `payload.schemaVersion` is not in this allowlist — this is the
37
- * forward-compatibility hatch (we add a new version here when we change the
38
- * predicate shape, and CI keeps accepting v1 until we explicitly remove it).
37
+ * forward-compatibility hatch.
38
+ *
39
+ * AISDLC-103 (Verifier Phase 3) narrowed this to `['v3']` only:
40
+ * - `v1` envelopes (pre-AISDLC-94, diffHash-only) are rejected.
41
+ * - `v2` was never landed as a distinct schemaVersion — the AISDLC-94
42
+ * `contentHash` and AISDLC-101 `contentHashV3` shipped under the v1
43
+ * schemaVersion as additive optional fields during the dual- and
44
+ * triple-hash soak windows.
45
+ * - `v3` envelopes carry `contentHashV3` as a required field and DO NOT
46
+ * carry `diffHash` or `contentHash` (the legacy hashes are forbidden;
47
+ * a v3 envelope smuggling either field is rejected by
48
+ * `validatePredicateShape`).
49
+ *
50
+ * AISDLC-362 (contentHashV5): adds `'v5'` to the allowlist. v5 envelopes
51
+ * carry `contentHashV5` AND `signedMergeBase` in addition to v3+v4 hashes
52
+ * (backward-compat dual-write). The schemaVersion field is bumped to `'v5'`
53
+ * on new envelopes so verifiers can detect the v5 fast-path immediately.
39
54
  *
40
55
  * Exported so the `verify-attestation` workflow can `import`/inline it.
41
56
  */
42
- export declare const ACCEPTED_SCHEMA_VERSIONS: readonly ["v1"];
57
+ export declare const ACCEPTED_SCHEMA_VERSIONS: readonly ["v3", "v5"];
43
58
  export type SchemaVersion = (typeof ACCEPTED_SCHEMA_VERSIONS)[number];
44
59
  /**
45
60
  * The DSSE PAE payload type for our predicate. DSSE spec mandates a payload
@@ -83,14 +98,142 @@ export interface AttestationPredicate {
83
98
  subject: {
84
99
  digest: SubjectDigest;
85
100
  };
86
- /** sha256 of `git diff origin/main...HEAD` at attestation time. */
87
- diffHash: string;
101
+ /**
102
+ * Per-file-delta content binding (AISDLC-101 — required as of AISDLC-103
103
+ * Phase 3). sha256 over a canonical line-per-file string of the form
104
+ * `<path>\t<fileDeltaHash>\n` (sorted ascending by path), where
105
+ * `fileDeltaHash[path] = sha256(<base_blob_sha> + ' -> ' +
106
+ * <head_blob_sha>)`. The base blob SHA comes from the merge-base of the
107
+ * PR's `<baseRef>` and `<headRef>`; the head blob SHA from the PR's
108
+ * `<headRef>`.
109
+ *
110
+ * Why this is the only content binding in v3:
111
+ * - `diffHash` (legacy v1, sha256 of literal `git diff` text) broke on
112
+ * every rebase because `@@` hunk headers shift even when the
113
+ * post-apply file content doesn't change.
114
+ * - `contentHash` (AISDLC-94, sha256 of `(path, head_blob_sha)` per
115
+ * file) was rebase-tolerant for the no-overlap case but broke in the
116
+ * AISDLC-93 / PR #102 sibling-overlap case (the rebased file's HEAD
117
+ * blob contained the sibling's contributions, so the head blob SHA
118
+ * changed even though OUR contribution was unchanged).
119
+ * - `contentHashV3` commits to the (base, head) blob-pair TRANSITION
120
+ * per file ("we moved file F from blob A to blob B"). Stable when
121
+ * paired with the producer-side pre-sign rebase from AISDLC-102 even
122
+ * in the sibling-overlap case, and a genuine content tampering still
123
+ * flips the head blob SHA → fileDeltaHash flips → reject (threat
124
+ * model preserved).
125
+ *
126
+ * Required for v3 envelopes. The dual-hash (v1 → AISDLC-94) and
127
+ * triple-hash (AISDLC-94 → AISDLC-101) windows kept this optional under
128
+ * schemaVersion `v1`; AISDLC-103 narrows the accepted-schema-versions
129
+ * allowlist to `['v3']` and makes `contentHashV3` mandatory in
130
+ * `validatePredicateShape`. Legacy envelopes carrying only `diffHash`
131
+ * and/or `contentHash` are rejected with a schemaVersion-allowlist reason.
132
+ */
133
+ contentHashV3: string;
134
+ /**
135
+ * Base-independent per-file head-blob binding (AISDLC-193.1). sha256
136
+ * over `JSON.stringify(sorted([{path, headBlobSha}]))` for every
137
+ * changed file in `<base>...<head>`, EXCLUDING the envelope file
138
+ * itself (`.ai-sdlc/attestations/<sha>.dsse.json` — see
139
+ * `isAttestationEnvelopePath` for the rationale).
140
+ *
141
+ * Why both v3 AND v4 ship side by side during the transition:
142
+ * - In-flight envelopes signed before AISDLC-193.1 carry only `v3`.
143
+ * Verifier accepts those via the existing v3 ancestor walk so the
144
+ * queue doesn't reject envelopes that were valid yesterday.
145
+ * - New envelopes carry BOTH so the verifier prefers v4 (skip the
146
+ * walk, base-independent) but can fall back to v3 if v4 doesn't
147
+ * match (= the rare case where head blobs DID change between
148
+ * signing and verification — e.g. amend-after-sign).
149
+ *
150
+ * After the transition window, `contentHashV3` will be deprecated
151
+ * and the verifier will require `contentHashV4`. For now both are
152
+ * populated by `buildPredicate` so any envelope this code emits is
153
+ * verifiable on both legs. The TypeScript type marks v4 as OPTIONAL
154
+ * because envelopes parsed from disk that pre-date AISDLC-193.1 will
155
+ * not carry the field; `validatePredicateShape` accepts the absence
156
+ * (= legacy v3 envelope) and the verifier falls back to the v3
157
+ * ancestor walk in that case.
158
+ */
159
+ contentHashV4?: string;
160
+ /**
161
+ * Delta-hash with embedded frozen merge-base (AISDLC-362 — contentHashV5).
162
+ *
163
+ * SHA-256 of canonical JSON: `JSON.stringify({schemaVersion:'v5',
164
+ * signedMergeBase:'<40-char-sha>',files:[{path,blobSha}...]})` where:
165
+ * - `signedMergeBase` is `git merge-base origin/main HEAD` captured at
166
+ * sign time and embedded in BOTH the envelope predicate AND the v5 hash
167
+ * input so verifier can REPRODUCE the EXACT diff the signer used.
168
+ * - `files` is `git diff <signedMergeBase>..HEAD --name-only` with blob
169
+ * SHAs from HEAD — same approach as v4 `{path, headBlobSha}` but
170
+ * diffed against the FROZEN merge-base, not the moving `origin/main`.
171
+ *
172
+ * Why this is the ultimate fix:
173
+ * - v4 (`{path, headBlobSha}` from `origin/main..HEAD`) still invalidates
174
+ * when a sibling PR merges and its files overlap with our PR — the
175
+ * diff base moves from `merge-base-A` to `merge-base-B` and new files
176
+ * appear in the diff enumeration even though OUR blobs didn't change.
177
+ * - v5 FREEZES the diff base at `signedMergeBase`. On queue rebase:
178
+ * verifier uses `git diff <signedMergeBase>..<probe-HEAD>` which
179
+ * enumerates the SAME file set as the signer. Non-overlapping sibling
180
+ * merges don't add new files to the set → v5 matches.
181
+ * - Overlapping sibling merges (a sibling changed the SAME file our PR
182
+ * touches) → the head blob SHA of that file differs → v5 hash flips →
183
+ * verifier correctly rejects (operator must re-review).
184
+ *
185
+ * Optional (populated for v5 envelopes, absent for v3-only legacy envelopes).
186
+ * When present, the verifier prefers v5 over v4 over v3 (priority order).
187
+ */
188
+ contentHashV5?: string;
189
+ /**
190
+ * The `git merge-base origin/main HEAD` SHA captured at sign time.
191
+ * Embedded in the envelope predicate so the verifier can reproduce the
192
+ * EXACT diff the signer used (`git diff <signedMergeBase>..<probe-HEAD>`).
193
+ *
194
+ * Only present when `contentHashV5` is populated (= v5 envelopes). Absent
195
+ * on legacy v3/v4 envelopes; the verifier falls back to v4 then v3 in
196
+ * those cases.
197
+ */
198
+ signedMergeBase?: string;
88
199
  /** sha256 of `.ai-sdlc/review-policy.md` at attestation time. */
89
200
  policyHash: string;
90
201
  /** Reviewer entries — typically 3 (code/test/security). */
91
202
  reviewers: ReviewerEntry[];
92
203
  /** Plugin version from `ai-sdlc-plugin/plugin.json`. */
93
204
  pluginVersion: string;
205
+ /**
206
+ * Pipeline-cli version from `pipeline-cli/package.json` (RFC-0012 Phase 6 /
207
+ * AISDLC-100.6). Forensic / audit purpose only — the verifier logs this
208
+ * but does NOT enforce a specific version. Equivalent to AISDLC-87/AISDLC-94's
209
+ * `pluginVersion` field but for the `@ai-sdlc/pipeline-cli` workspace package.
210
+ *
211
+ * Optional in v1 for backward compatibility — envelopes signed BEFORE
212
+ * pipeline-cli existed (and BEFORE this field landed) carry no
213
+ * `pipelineVersion` and the verifier still accepts them, logging
214
+ * `<missing> (legacy envelope)` instead.
215
+ */
216
+ pipelineVersion?: string;
217
+ /**
218
+ * Harness that produced the developer + reviewer verdicts (AISDLC-202.3).
219
+ * Populated by the calling adapter (e.g. `CodexHarnessAdapter` sets
220
+ * `{ name: 'codex', version: '0.128.0' }` when signing a Codex-run task).
221
+ * Claude Code paths omit this field or set `{ name: 'claude-code' }`.
222
+ *
223
+ * Optional for backward compatibility: envelopes produced before
224
+ * AISDLC-202.3 carry no `harness` field; the verifier accepts them and
225
+ * logs `<unknown>` when the field is absent. Downstream trust decisions
226
+ * (e.g. "require Codex review for Claude-developed PRs") can filter on
227
+ * `harness.name` without failing envelopes that predate this field.
228
+ *
229
+ * `name` is constrained to `SHORT_ID` (letters, digits, dot, dash,
230
+ * underscore) to prevent CR/LF injection into GITHUB_OUTPUT. `version`
231
+ * is constrained to `SEMVER` when present.
232
+ */
233
+ harness?: {
234
+ name: string;
235
+ version?: string;
236
+ };
94
237
  /** Iteration count — how many dev rounds the work went through. */
95
238
  iterationCount: number;
96
239
  /**
@@ -119,7 +262,7 @@ export interface DsseSignature {
119
262
  /**
120
263
  * Identifier of the public key that produced this signature. Used to
121
264
  * look up the trusted-reviewer entry. Free-form — typically `<identity>:
122
- * <machine>` (e.g. `dominique@reliablegenius.io:laptop-2025`).
265
+ * <machine>` (e.g. `contributor@example.com:laptop-2025`).
123
266
  */
124
267
  keyid: string;
125
268
  /** base64-encoded raw ed25519 signature (64 bytes → 88 chars b64). */
@@ -148,13 +291,18 @@ export type VerifyResult = {
148
291
  reason: string;
149
292
  };
150
293
  /**
151
- * Validate a parsed predicate against the v1 schema regex patterns.
294
+ * Validate a parsed predicate against the v3 schema regex patterns.
152
295
  *
153
296
  * Returns `null` when the predicate is shape-valid; otherwise returns
154
297
  * a static failure reason that does NOT embed any user-controlled
155
298
  * value (just the field path). This is the load-bearing property:
156
299
  * the malicious value never reaches the `reason` string, so it can't
157
300
  * propagate to GITHUB_OUTPUT or commit-status descriptions.
301
+ *
302
+ * AISDLC-103 (Verifier Phase 3): `contentHashV3` is now required, and the
303
+ * legacy `diffHash` / `contentHash` fields are FORBIDDEN — a predicate
304
+ * carrying either is treated as a v1/v2 envelope smuggling itself into the
305
+ * v3 window and rejected with a static reason.
158
306
  */
159
307
  export declare function validatePredicateShape(parsed: unknown): string | null;
160
308
  /**
@@ -165,10 +313,270 @@ export declare function validatePredicateShape(parsed: unknown): string | null;
165
313
  * Frozen to discourage callers from mutating it.
166
314
  */
167
315
  export declare const REQUIRED_REVIEWER_AGENT_IDS: readonly string[];
316
+ /**
317
+ * Name-equivalence map for the reviewer-set completeness check (AISDLC-252).
318
+ *
319
+ * A "role" is satisfied when any of the listed agentIds is present in the
320
+ * envelope's reviewer set. This lets codex-harness variants (`code-reviewer-codex`,
321
+ * `test-reviewer-codex`) satisfy the same role as their Claude counterparts,
322
+ * enabling the bidirectional cross-harness review goal without requiring a
323
+ * redundant Claude review on Codex-reviewed PRs.
324
+ *
325
+ * Security stays Claude-only: `security-reviewer` has no codex variant per
326
+ * `feedback_subagent_model_selection.md` (Claude Opus for security reasoning
327
+ * depth is not yet validated for Codex o4-mini).
328
+ *
329
+ * The map is keyed by role name (= the canonical agentId), each value is the
330
+ * set of ALL agentIds that satisfy the role (including the canonical one).
331
+ *
332
+ * Frozen to discourage callers from mutating it.
333
+ */
334
+ export declare const REVIEWER_ROLE_EQUIVALENCES: Readonly<Record<string, readonly string[]>>;
335
+ /**
336
+ * When the implementer ran in Codex (`predicate.harness.name === 'codex'`),
337
+ * these reviewer roles MUST be satisfied by a reviewer whose `harness` field
338
+ * differs from `codex`. Per RFC-0010 §13.10 `requiresIndependentHarnessFrom`:
339
+ * code and test reviewers must come from a different harness than the
340
+ * implementer to preserve cross-harness independence.
341
+ *
342
+ * Security is excluded — it is always Claude-only regardless.
343
+ *
344
+ * Frozen to discourage callers from mutating it.
345
+ */
346
+ export declare const INDEPENDENCE_REQUIRED_ROLES: readonly string[];
347
+ /**
348
+ * One entry in the changed-file set used to compute `contentHash`
349
+ * (AISDLC-94). `path` is the repo-relative forward-slash path; `blobSha`
350
+ * is the git blob SHA-1 (40 lowercase hex chars) of the file's CURRENT
351
+ * post-apply content at the attested commit.
352
+ *
353
+ * For deleted files, set `blobSha` to the empty string — the canonical
354
+ * line still includes the path so a delete-vs-keep difference between
355
+ * two PRs produces different hashes.
356
+ */
357
+ export interface ChangedFileEntry {
358
+ path: string;
359
+ blobSha: string;
360
+ }
361
+ /**
362
+ * One entry in the per-file-delta set used to compute `contentHashV3`
363
+ * (AISDLC-101). `path` is the repo-relative forward-slash path;
364
+ * `baseBlobSha` is the git blob SHA-1 of the file at the merge-base of
365
+ * `<baseRef>` and `<headRef>` (= the file's content BEFORE the PR's
366
+ * commits replayed); `headBlobSha` is the git blob SHA-1 of the file at
367
+ * `<headRef>` (= AFTER the PR's commits).
368
+ *
369
+ * For files that don't exist at one of the endpoints (newly added or
370
+ * deleted), the corresponding `*BlobSha` is the empty string. The
371
+ * canonical line still includes the path so:
372
+ * - "added file" (`base=''`, `head=<sha>`) → distinct from "kept file"
373
+ * (`base=<old>`, `head=<new>`)
374
+ * - "deleted file" (`base=<old>`, `head=''`) → distinct from "added file"
375
+ */
376
+ export interface ChangedFileDeltaEntry {
377
+ path: string;
378
+ baseBlobSha: string;
379
+ headBlobSha: string;
380
+ }
381
+ /**
382
+ * Regex matching the envelope self-exclusion path pattern
383
+ * `.ai-sdlc/attestations/<sha>.dsse.json`. Used to filter out the
384
+ * envelope file itself from the file collector for AISDLC-193.1
385
+ * `contentHashV4` and AISDLC-101 `contentHashV3` purposes.
386
+ *
387
+ * The chore-commit pattern signs the predicate at the dev-commit (HEAD
388
+ * BEFORE the envelope file exists), then the chore commit on top adds
389
+ * the envelope file at `.ai-sdlc/attestations/<sha>.dsse.json`. If the
390
+ * collector includes the envelope file in the hashed file set, the
391
+ * verifier (which runs against PR HEAD = dev-commit + chore commit)
392
+ * will see an EXTRA entry for the envelope that the signer never saw
393
+ * → mismatch even on direct PR HEAD without any rebase.
394
+ *
395
+ * The exclusion applies to the file COLLECTOR for HASHING purposes
396
+ * only. The verifier's chore-commit allowlist (`scripts/verify-attestation.mjs`
397
+ * `CHORE_COMMIT_PATH_ALLOWLIST`) STILL allows the envelope file in the
398
+ * chore commit's diff — that's a separate concern from "what is in the
399
+ * file set we hash."
400
+ *
401
+ * Anchored with `^...$` against the forward-slash-normalized path so
402
+ * an attacker cannot bypass with `./.ai-sdlc/attestations/x.dsse.json`
403
+ * or `foo/.ai-sdlc/attestations/x.dsse.json`. Note that git's
404
+ * `--name-only` always emits paths relative to the repo root with
405
+ * forward slashes, so the match is straightforward in practice.
406
+ */
407
+ export declare const ATTESTATION_ENVELOPE_PATH_PATTERN: RegExp;
408
+ /**
409
+ * Predicate to determine whether a file path identifies an attestation
410
+ * envelope and should therefore be excluded from `contentHashV3` /
411
+ * `contentHashV4` file enumeration. Defensive about backslash
412
+ * normalization (Windows callers).
413
+ */
414
+ export declare function isAttestationEnvelopePath(path: string): boolean;
415
+ /**
416
+ * The "shared churn" exclude list for content-hash computations (AISDLC-258,
417
+ * AISDLC-362). Applied to v3, v4, and v5 file collectors on BOTH the signer
418
+ * and verifier sides.
419
+ *
420
+ * Files in this list are EXCLUDED from all file collectors. When a file
421
+ * appears in this list, changes to it after signing (e.g. from a merge-queue
422
+ * rebase that regenerated `pnpm-lock.yaml`) do NOT cause content-hash
423
+ * mismatches, so the operator is never asked to re-sign just because a shared
424
+ * tooling file was regenerated automatically.
425
+ *
426
+ * **Security trade-off (operator-approved, 2026-05-10):** An attacker
427
+ * COULD slip malicious changes through these files undetected (the
428
+ * attestation would still pass even if the ignore-listed file was
429
+ * tampered). The operator accepted this risk because:
430
+ * - None of these files contain reviewable hand-written code.
431
+ * - `pnpm-lock.yaml` is generated from `package.json` (which IS hashed).
432
+ * - `CHANGELOG.md` variants are auto-generated by release-please from
433
+ * commit history (which IS hashed via the commit-level binding).
434
+ * - `generated-schemas.ts` is generated from spec schemas (reviewed
435
+ * separately in the spec/ PR that changed them).
436
+ *
437
+ * **DO NOT add to this list:** `package.json` (real dep changes are
438
+ * reviewable), source files, test files, configs, RFCs, or anything a
439
+ * human writes by hand. The list is intentionally narrow.
440
+ *
441
+ * Paths are exact matches against the forward-slash-normalized repo-relative
442
+ * path emitted by `git diff --name-only`. Patterns (globs/regex) are NOT
443
+ * supported to keep the list auditable — every entry must be exact.
444
+ *
445
+ * Exported so `scripts/verify-attestation.mjs` can import it from the
446
+ * orchestrator barrel and apply the same exclusions on the verifier side.
447
+ */
448
+ export declare const CONTENTHASH_SHARED_CHURN_FILES: readonly string[];
449
+ /**
450
+ * Backward-compatible alias for `CONTENTHASH_SHARED_CHURN_FILES` (renamed in
451
+ * AISDLC-362). Callers that imported the v4-specific name continue to work.
452
+ * @deprecated Use `CONTENTHASH_SHARED_CHURN_FILES` instead.
453
+ */
454
+ export declare const CONTENTHASHV4_IGNORE_FILES: readonly string[];
455
+ /**
456
+ * Predicate to determine whether a file path should be excluded from
457
+ * content-hash computations (v3/v4/v5) because it is a "shared churn" file
458
+ * (see `CONTENTHASH_SHARED_CHURN_FILES`). Defensive about backslash
459
+ * normalization.
460
+ *
461
+ * Note: this predicate is intentionally separate from
462
+ * `isAttestationEnvelopePath` because the two exclusions serve different
463
+ * purposes and may diverge independently. Merge them only if the list
464
+ * becomes large enough to warrant a single unified predicate.
465
+ */
466
+ export declare function isIgnoredForContentHash(path: string): boolean;
467
+ /**
468
+ * One entry in the base-independent per-file head-blob set used to
469
+ * compute `contentHashV4` (AISDLC-193.1). Identical in shape to
470
+ * `ChangedFileEntry` (AISDLC-94's `contentHash`) but with `headBlobSha`
471
+ * naming for clarity — v4 binds reviewers' approval to "I approved
472
+ * THESE files at THESE specific head blobs," nothing about the base.
473
+ *
474
+ * Why v4 was added on top of v3:
475
+ * - v3 binds the (base_blob, head_blob) PAIR per file. When the
476
+ * merge queue rebases the PR onto a sibling-merged main, the base
477
+ * blob SHA for shared files changes (the merge-base shifts forward
478
+ * to include the sibling's contributions). The fileDeltaHash flips
479
+ * even though the post-apply file content the reviewers approved
480
+ * hasn't moved → contentHashV3 invalidates → required check fails
481
+ * → queue rejects the PR. Net: every queued code-touching PR
482
+ * deadlocks at the gate.
483
+ * - v4 binds only `{path, headBlobSha}`. The head blob SHA captures
484
+ * "this is the EXACT file content the reviewers signed off on."
485
+ * Whatever the rebase does to the base ref, as long as the head
486
+ * blob SHAs are unchanged, the v4 hash matches.
487
+ * - Threat model preserved: any genuine post-sign content tampering
488
+ * (someone amends the PR to add unreviewed code) flips the head
489
+ * blob SHA → v4 hash flips → verifier rejects.
490
+ */
491
+ export interface ChangedFileHeadEntry {
492
+ path: string;
493
+ headBlobSha: string;
494
+ }
495
+ /**
496
+ * One entry in the v5 file set used by `computeContentHashV5` (AISDLC-362).
497
+ * Structurally identical to `ChangedFileHeadEntry` — `path` plus the HEAD
498
+ * blob SHA. The difference from v4 is that the FILE SET is enumerated via
499
+ * `git diff <signedMergeBase>..HEAD` (frozen diff base) instead of
500
+ * `git diff origin/main..HEAD` (moving diff base). Same fields, different
501
+ * enumeration semantics — separate type for clarity.
502
+ */
503
+ export interface ChangedFileV5Entry {
504
+ path: string;
505
+ blobSha: string;
506
+ }
507
+ /**
508
+ * Return shape of `collectChangedFileEntriesForV5`. Bundles the frozen
509
+ * `signedMergeBase` SHA with the file entries so the signer can embed it in
510
+ * the predicate and the hash without a separate git call.
511
+ */
512
+ export interface V5CollectResult {
513
+ /** Files changed between `signedMergeBase` and `headRef`. */
514
+ entries: ChangedFileV5Entry[];
515
+ /**
516
+ * The `git merge-base <baseRef> <headRef>` SHA captured at collection time.
517
+ * This is the FROZEN diff base embedded in the envelope predicate and the
518
+ * v5 hash. The verifier uses it to reproduce the exact file set the signer
519
+ * used: `git diff <signedMergeBase>..<probe-HEAD> --name-only`.
520
+ */
521
+ signedMergeBase: string;
522
+ }
523
+ /**
524
+ * Compute the rebase-stable `contentHashV5` (AISDLC-362) over a file set
525
+ * and a frozen merge-base SHA.
526
+ *
527
+ * Canonical form: `SHA-256(JSON.stringify({schemaVersion:'v5',
528
+ * signedMergeBase:'<40-char>', files:[{path,blobSha}...]}))` where `files`
529
+ * is sorted ascending by path.
530
+ *
531
+ * Why v5 beats v4 for rebase-stability:
532
+ * - v4 enumerates files via `git diff origin/main..HEAD`. When a sibling
533
+ * PR merges between sign-time and verify-time, `origin/main` moves
534
+ * forward. Files the sibling touched now appear in the v4 diff that
535
+ * weren't there at sign-time → v4 hash diverges even though OUR blobs
536
+ * are unchanged.
537
+ * - v5 enumerates files via `git diff <signedMergeBase>..HEAD`. The
538
+ * `signedMergeBase` is computed ONCE at sign time and frozen in the
539
+ * envelope. At verify time, the verifier recomputes
540
+ * `git diff <signedMergeBase>..<probe-HEAD>` using the SAME frozen
541
+ * base → same file set → same hash, regardless of how many sibling
542
+ * PRs merged on `main` in the interim.
543
+ * - Overlapping sibling merges (sibling changed a file OUR PR also touches)
544
+ * → the head blob SHA of that file differs from what we signed → v5
545
+ * hash flips → verifier correctly rejects (operator must re-review).
546
+ *
547
+ * Threat model preserved: any genuine post-sign content tampering flips the
548
+ * head blob SHA → v5 hash flips → verifier rejects.
549
+ *
550
+ * Pure function. Idempotent against double-enumeration via dedup-by-path.
551
+ */
552
+ export declare function computeContentHashV5(entries: ChangedFileV5Entry[], signedMergeBase: string): string;
553
+ /**
554
+ * Collect the file set for `computeContentHashV5` (AISDLC-362).
555
+ *
556
+ * This is the LOAD-BEARING CHANGE vs v4:
557
+ * - v4: `git diff origin/main..HEAD` — the base moves as siblings merge.
558
+ * - v5: `git merge-base <baseRef> <headRef>` → frozen SHA, then
559
+ * `git diff <signedMergeBase>..HEAD` — the base is FROZEN.
560
+ *
561
+ * The frozen merge-base is returned alongside the entries so the signer can
562
+ * embed it in both the v5 hash and the predicate's `signedMergeBase` field.
563
+ *
564
+ * File enumeration applies the same exclusions as v3/v4:
565
+ * - Attestation envelope files (`ATTESTATION_ENVELOPE_PATH_PATTERN`)
566
+ * - Shared churn files (`CONTENTHASH_SHARED_CHURN_FILES`)
567
+ *
568
+ * Blob SHAs are resolved from `headRef` (= HEAD at sign time).
569
+ *
570
+ * @param repoRoot Absolute path to the git worktree root.
571
+ * @param baseRef Typically `'origin/main'`. Used ONLY for computing the
572
+ * merge-base — the diff itself is against the frozen SHA.
573
+ * @param headRef Typically `'HEAD'`.
574
+ * @param options Optional injection points for tests (stub `runGit`).
575
+ */
576
+ export declare function collectChangedFileEntriesForV5(repoRoot: string, baseRef?: string, headRef?: string, options?: CollectChangedFileEntriesOptions): V5CollectResult;
168
577
  /** Inputs for building an attestation predicate. */
169
578
  export interface BuildPredicateInputs {
170
579
  commitSha: string;
171
- diff: string | Buffer;
172
580
  policy: string | Buffer;
173
581
  reviewers: Array<{
174
582
  agentId: string;
@@ -178,10 +586,53 @@ export interface BuildPredicateInputs {
178
586
  findings: ReviewerEntry['findings'];
179
587
  }>;
180
588
  pluginVersion: string;
589
+ /**
590
+ * Pipeline-cli version from `pipeline-cli/package.json` (AISDLC-100.6).
591
+ * Optional — when omitted (e.g. legacy callers, environments where
592
+ * pipeline-cli isn't installed), the predicate's `pipelineVersion` field
593
+ * is also omitted. Forensic / audit purpose only — the verifier logs
594
+ * this but does not enforce.
595
+ */
596
+ pipelineVersion?: string;
597
+ /**
598
+ * Harness that produced the developer + reviewer verdicts (AISDLC-202.3).
599
+ * Optional — when omitted, the predicate carries no `harness` field
600
+ * (back-compat with pre-202.3 envelopes). When provided, the adapter
601
+ * populates both `name` (required, SHORT_ID) and optionally `version`
602
+ * (SEMVER). Example: `{ name: 'codex', version: '0.128.0' }`.
603
+ *
604
+ * The signing script (`sign-attestation.mjs`) passes this via
605
+ * `--harness-name` + `--harness-version` CLI flags.
606
+ */
607
+ harness?: {
608
+ name: string;
609
+ version?: string;
610
+ };
181
611
  iterationCount: number;
182
612
  harnessNote: string;
183
613
  /** Override `signedAt` for deterministic tests. */
184
614
  signedAt?: string;
615
+ /**
616
+ * Per-file-delta set for `contentHashV3` (AISDLC-101 / AISDLC-103).
617
+ * REQUIRED for v3 envelopes — captures the (base_blob_sha →
618
+ * head_blob_sha) transition per file. Pass `[]` for no-op PRs (the
619
+ * resulting `contentHashV3` is `sha256('')`, which is well-defined and
620
+ * still verifiable).
621
+ */
622
+ changedFileDeltas: ChangedFileDeltaEntry[];
623
+ /**
624
+ * v5 file entries from `collectChangedFileEntriesForV5` (AISDLC-362).
625
+ * Optional — when omitted, no `contentHashV5` or `signedMergeBase` fields
626
+ * are emitted in the predicate (= a legacy v3+v4 envelope). When provided
627
+ * alongside `v5MergeBase`, the predicate carries all three hashes (v3, v4,
628
+ * v5) for maximum backward + forward compatibility.
629
+ */
630
+ v5Entries?: ChangedFileV5Entry[];
631
+ /**
632
+ * The frozen merge-base SHA from `collectChangedFileEntriesForV5`.
633
+ * Required when `v5Entries` is provided. Must be a 40-char hex SHA-1.
634
+ */
635
+ v5MergeBase?: string;
185
636
  }
186
637
  /**
187
638
  * Compute a sha256 hex digest. Single source of truth for the hashing
@@ -190,10 +641,184 @@ export interface BuildPredicateInputs {
190
641
  export declare function sha256Hex(input: string | Buffer): string;
191
642
  /** Compute a sha1 hex digest (used for git commit SHAs in the subject). */
192
643
  export declare function sha1Hex(input: string | Buffer): string;
644
+ /**
645
+ * Compute the rebase-tolerant `contentHash` (AISDLC-94) over a changed-file
646
+ * set. The canonical encoding is one line per entry, sorted ascending by
647
+ * path, with `<path>\t<blobSha>\n` per line. The whole string is sha256-ed.
648
+ *
649
+ * Why this beats `diffHash`:
650
+ * - Rebasing PR-X onto a new `main` that already touched the same files
651
+ * does NOT change the post-apply blob SHAs (assuming no conflict),
652
+ * so `contentHash` stays stable across the rebase.
653
+ * - A conflict resolution that picks different content WILL change the
654
+ * blob SHA → `contentHash` changes → attestation correctly invalidated.
655
+ * - Force-pushing a no-op edit (e.g. `git commit --amend --no-edit`) keeps
656
+ * blob SHAs identical → `contentHash` stays stable.
657
+ *
658
+ * The deduplication step makes the function idempotent if a caller
659
+ * accidentally passes the same path twice (last-write-wins per path).
660
+ *
661
+ * Pure function. The caller (sign-attestation script) is responsible for
662
+ * gathering the file set (via `git diff --name-only` + `git ls-tree`).
663
+ */
664
+ export declare function computeContentHash(entries: ChangedFileEntry[]): string;
665
+ /**
666
+ * Optional injection points for `collectChangedFileEntries`. Defaults are
667
+ * production behaviour; tests pass synthetic `runGit` to avoid spawning git.
668
+ */
669
+ export interface CollectChangedFileEntriesOptions {
670
+ /**
671
+ * Run `git <args>` in `cwd` and return stdout (utf-8). Defaults to
672
+ * `execFileSync` with the git-context env scrubbed (see `cleanGitEnv`).
673
+ * Tests pass a stub so they don't depend on a real worktree.
674
+ */
675
+ runGit?: (args: string[], cwd: string) => string;
676
+ }
677
+ /**
678
+ * Collect the changed-file set used to compute `contentHash` (AISDLC-94).
679
+ *
680
+ * Returns one `{ path, blobSha }` entry per file in
681
+ * `git diff --name-only <baseRef>...<headRef>` with the blob SHA from
682
+ * `git ls-tree -r <headRef> -- <path>`. Deleted files get an empty
683
+ * `blobSha` (the path still appears so the canonical encoding distinguishes
684
+ * "deleted" from "kept").
685
+ *
686
+ * `--no-renames` so a rename shows up as add+delete (= two entries) — that
687
+ * way a rebase that resolved a conflict by renaming differently produces a
688
+ * different hash. `-c core.quotepath=false` mirrors the verifier's git
689
+ * helper so unicode paths come back as raw UTF-8.
690
+ *
691
+ * Path entries containing `\t` or `\n` are rejected to keep the canonical
692
+ * encoding injective (mirrors the rejection in `computeContentHash`). Such
693
+ * paths are exceedingly rare in practice — git's default config disallows
694
+ * `\n` in tracked filenames on most platforms — but we defend in depth so
695
+ * malicious or pathological inputs can't smuggle entries past the binding.
696
+ *
697
+ * Extracted from the previously-duplicated helpers in
698
+ * `ai-sdlc-plugin/scripts/sign-attestation.mjs` so a single source of truth
699
+ * applies the same parsing + validation at every signing site.
700
+ */
701
+ export declare function collectChangedFileEntries(baseRef: string, headRef: string, repoRoot: string, options?: CollectChangedFileEntriesOptions): ChangedFileEntry[];
702
+ /**
703
+ * Compute the per-file-delta `contentHashV3` (AISDLC-101) over a set of
704
+ * `{path, baseBlobSha, headBlobSha}` triples. The canonical encoding is
705
+ * one line per entry, sorted ascending by path, with
706
+ * `<path>\t<fileDeltaHash>\n` per line, where
707
+ * `fileDeltaHash = sha256(baseBlobSha + ' -> ' + headBlobSha)`. The
708
+ * outer `contentHashV3` is the sha256 of the concatenated lines.
709
+ *
710
+ * Why per-file delta hashing — and what it adds vs. AISDLC-94's `contentHash`:
711
+ * - `contentHash` (AISDLC-94) hashes the post-apply blob SHA per file.
712
+ * If a sibling PR landed between OUR sign + OUR merge AND modified
713
+ * the SAME file, the rebased file's HEAD blob SHA contains both the
714
+ * sibling contribution AND ours → contentHash diverges (false reject).
715
+ * - `contentHashV3` (AISDLC-101) hashes the (base, head) blob-pair
716
+ * transition per file. Provides a stricter "we moved file F from blob
717
+ * A to blob B" binding than just "we ended up at blob B". Any genuine
718
+ * content change still flips the head blob SHA → fileDeltaHash flips
719
+ * → contentHashV3 flips → reject (threat model preserved).
720
+ *
721
+ * This is the SECOND line of defense in the 3-layer rebase-tolerance
722
+ * plan (AISDLC-94 = Phase 1 verifier-side dual-hash, AISDLC-102 = Phase 1.5
723
+ * producer-side pre-sign rebase, AISDLC-101 = Phase 2 per-file delta).
724
+ * The verifier OR's all three legs during the triple-hash window.
725
+ *
726
+ * Path-delimiter rejection (\t / \n) mirrors `computeContentHash` so the
727
+ * canonical encoding stays injective regardless of caller input.
728
+ *
729
+ * Pure function. Idempotent against double-enumeration via dedup-by-path
730
+ * (last-write-wins per path), same as `computeContentHash`.
731
+ */
732
+ export declare function computeContentHashV3(entries: ChangedFileDeltaEntry[]): string;
733
+ /**
734
+ * Compute the BASE-INDEPENDENT per-file head-blob `contentHashV4`
735
+ * (AISDLC-193.1) over a set of `{path, headBlobSha}` pairs. The
736
+ * canonical encoding is `JSON.stringify(sorted-by-path-array-of-{path,
737
+ * headBlobSha}-objects)`, hashed with sha256.
738
+ *
739
+ * Why JSON-of-sorted-array (and not the v3 `<path>\t<fileDeltaHash>\n`
740
+ * canonical) for v4:
741
+ * - JSON's quoting rules already cover delimiter injection
742
+ * (a malicious path containing tab/newline can't smuggle through
743
+ * because they round-trip as escape sequences). We still reject
744
+ * such paths defensively so the canonical stays injective and
745
+ * the on-the-wire representation is what readers expect.
746
+ * - JSON is unambiguous about field ordering (stringify of a
747
+ * `{path, headBlobSha}` literal always emits `path` first,
748
+ * `headBlobSha` second — V8's object-key ordering is insertion
749
+ * order, and we insert in this order in the .map() below).
750
+ * - Easier to extend: future hash versions can add fields
751
+ * (`mode`, `executable bit`, etc) to the entry objects without
752
+ * breaking the canonical encoding scheme.
753
+ *
754
+ * Why this is BASE-INDEPENDENT (= the whole point):
755
+ * - v3's per-file delta hashes the (base_blob, head_blob) pair.
756
+ * When the merge queue rebases the PR onto current main (which
757
+ * advanced past the merge-base the producer signed against), the
758
+ * base blob SHA for any shared file changes → v3 invalidates.
759
+ * - v4 hashes only `{path, headBlobSha}`. Whatever the rebase does
760
+ * to the base ref or the merge-base, as long as the head blob SHA
761
+ * (= the actual reviewed file content) is unchanged, v4 matches.
762
+ * - The reviewer never approved "base_blob X → head_blob Y"; they
763
+ * approved "the file contents at head_blob Y." v4 binds to that
764
+ * directly.
765
+ *
766
+ * Threat model preserved:
767
+ * - Genuine post-sign content tampering (someone amends the PR to
768
+ * add unreviewed code) flips the head blob SHA → v4 hash flips →
769
+ * verifier rejects. Same threat-model surface as v3.
770
+ * - The signing key still has to be a trusted reviewer's; v4
771
+ * doesn't change the signature/key flow, just what the predicate
772
+ * binds to.
773
+ *
774
+ * Pure function. Idempotent against double-enumeration via dedup-by-path
775
+ * (last-write-wins per path), same as `computeContentHash` and
776
+ * `computeContentHashV3`.
777
+ */
778
+ export declare function computeContentHashV4(entries: ChangedFileHeadEntry[]): string;
779
+ /**
780
+ * Collect the per-file-delta set used to compute `contentHashV3` (AISDLC-101).
781
+ *
782
+ * Returns one `{ path, baseBlobSha, headBlobSha }` entry per file in
783
+ * `git diff --name-only <baseRef>...<headRef>`. The base blob SHA is read
784
+ * from the *merge-base* of `<baseRef>` and `<headRef>` (which the `...`
785
+ * 3-dot diff range already targets — `A...B` diffs against
786
+ * `merge-base(A,B)`); the head blob SHA from `<headRef>`. Files newly
787
+ * added in the PR have empty `baseBlobSha`; deleted files have empty
788
+ * `headBlobSha`.
789
+ *
790
+ * Mirrors `collectChangedFileEntries`'s flag set (`--no-renames`,
791
+ * `core.quotepath=false`) for consistency with the other binding's file
792
+ * enumeration.
793
+ *
794
+ * Extracted so a single source of truth handles the two ls-tree lookups
795
+ * (one per endpoint) at every signing site (`sign-attestation.mjs`).
796
+ */
797
+ export declare function collectChangedFileDeltaEntries(baseRef: string, headRef: string, repoRoot: string, options?: CollectChangedFileEntriesOptions): ChangedFileDeltaEntry[];
798
+ /**
799
+ * Project a v3 `ChangedFileDeltaEntry` set down to the v4
800
+ * `ChangedFileHeadEntry` shape (`{path, headBlobSha}`). Convenience
801
+ * for callers that already collected v3 deltas and want to dual-emit
802
+ * both hashes from the same file enumeration. Pure function.
803
+ *
804
+ * The envelope self-exclusion is enforced upstream by
805
+ * `collectChangedFileDeltaEntries`, so this projection is a simple
806
+ * field-pick — no path filtering needed here.
807
+ */
808
+ export declare function projectDeltaEntriesToHeadEntries(deltas: ChangedFileDeltaEntry[]): ChangedFileHeadEntry[];
193
809
  /**
194
810
  * Build the predicate payload from raw inputs. Pure function — no I/O,
195
811
  * no signing. The caller (`/ai-sdlc execute` Step 10) reads files and git
196
812
  * output, then hands them here.
813
+ *
814
+ * AISDLC-103 (Verifier Phase 3): always emits a v3 envelope. The caller
815
+ * MUST provide `changedFileDeltas` (use `[]` for no-op PRs); the legacy
816
+ * `diff` + `changedFiles` inputs were dropped along with the legacy
817
+ * `diffHash` + `contentHash` fields.
818
+ *
819
+ * AISDLC-362 (contentHashV5): when `v5Entries` + `v5MergeBase` are provided,
820
+ * also emits `contentHashV5` and `signedMergeBase` in the predicate and bumps
821
+ * `schemaVersion` to `'v5'`. The verifier prefers v5 when present.
197
822
  */
198
823
  export declare function buildPredicate(inputs: BuildPredicateInputs): AttestationPredicate;
199
824
  /**
@@ -236,8 +861,9 @@ export interface VerifyOptions {
236
861
  */
237
862
  trustedReviewers: TrustedReviewer[];
238
863
  /**
239
- * What the predicate's `subject.digest.sha1`, `diffHash`, `policyHash`,
240
- * and `reviewers[].agentFileHash` MUST equal. Mismatch = invalid.
864
+ * What the predicate's `subject.digest.sha1`, `contentHashV3`,
865
+ * `policyHash`, and `reviewers[].agentFileHash` MUST equal. Mismatch =
866
+ * invalid.
241
867
  *
242
868
  * `expectedAgentFileHashes` is a map from agentId to its sha256 — we
243
869
  * tolerate the predicate listing fewer or more reviewers than the map,
@@ -245,7 +871,33 @@ export interface VerifyOptions {
245
871
  */
246
872
  expected: {
247
873
  commitSha: string;
248
- diffHash: string;
874
+ contentHashV3: string;
875
+ /**
876
+ * AISDLC-193.1 base-independent per-file head-blob binding.
877
+ * Optional — callers that want v4-prefer behavior pass it; callers
878
+ * that only have v3 (legacy) leave it undefined.
879
+ *
880
+ * When BOTH this AND the envelope's `contentHashV4` are present,
881
+ * the verifier prefers v4 (base-independent → survives queue
882
+ * rebases). When v4 matches, v3 is NOT consulted (this is the
883
+ * whole point — v3 will mismatch on a queue rebase even though
884
+ * the reviewed content is unchanged).
885
+ *
886
+ * When the envelope is legacy v3-only (no `contentHashV4`), the
887
+ * verifier falls back to the v3 check unconditionally regardless
888
+ * of whether `expected.contentHashV4` is supplied.
889
+ */
890
+ contentHashV4?: string;
891
+ /**
892
+ * AISDLC-362 frozen-merge-base delta hash. Optional — callers that
893
+ * want v5-prefer behavior pass it; callers without v5 leave it
894
+ * undefined (fallback to v4 then v3).
895
+ *
896
+ * When BOTH this AND the envelope's `contentHashV5` are present,
897
+ * the verifier prefers v5 (highest rebase-stability). When v5
898
+ * matches, v4 and v3 are NOT consulted.
899
+ */
900
+ contentHashV5?: string;
249
901
  policyHash: string;
250
902
  expectedAgentFileHashes: Record<string, string>;
251
903
  };