@ai-sdlc/orchestrator 0.10.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 (156) hide show
  1. package/dist/adapters.d.ts +42 -3
  2. package/dist/adapters.js +133 -3
  3. package/dist/admission-composite.d.ts +112 -1
  4. package/dist/admission-composite.js +85 -4
  5. package/dist/admission-enrichment.d.ts +12 -3
  6. package/dist/admission-enrichment.js +64 -13
  7. package/dist/admission-score.d.ts +30 -0
  8. package/dist/admission-score.js +4 -1
  9. package/dist/backlog-adapter.d.ts +45 -0
  10. package/dist/backlog-adapter.js +145 -1
  11. package/dist/calibration.d.ts +81 -0
  12. package/dist/calibration.js +76 -0
  13. package/dist/cli/commands/init-features.d.ts +301 -2
  14. package/dist/cli/commands/init-features.js +634 -7
  15. package/dist/cli/commands/init-templates.d.ts +198 -1
  16. package/dist/cli/commands/init-templates.js +943 -1
  17. package/dist/cli/commands/init.d.ts +45 -0
  18. package/dist/cli/commands/init.js +147 -5
  19. package/dist/cli/commands/run.js +9 -1
  20. package/dist/cli/index.d.ts +11 -0
  21. package/dist/cli/index.js +42 -8
  22. package/dist/compliance/composer.d.ts +79 -0
  23. package/dist/compliance/composer.js +258 -0
  24. package/dist/compliance/errors.d.ts +64 -0
  25. package/dist/compliance/errors.js +85 -0
  26. package/dist/compliance/loader.d.ts +52 -0
  27. package/dist/compliance/loader.js +124 -0
  28. package/dist/compliance/types.d.ts +184 -0
  29. package/dist/compliance/types.js +41 -0
  30. package/dist/compliance-clearance.d.ts +269 -0
  31. package/dist/compliance-clearance.js +269 -0
  32. package/dist/config.js +17 -0
  33. package/dist/cost-tracker.d.ts +22 -0
  34. package/dist/cost-tracker.js +41 -0
  35. package/dist/database/adapters/external.js +5 -1
  36. package/dist/embedding/adapters/openai-text-embedding-3-small.d.ts +71 -0
  37. package/dist/embedding/adapters/openai-text-embedding-3-small.js +190 -0
  38. package/dist/embedding/consumers/tessellation-drift.d.ts +74 -0
  39. package/dist/embedding/consumers/tessellation-drift.js +76 -0
  40. package/dist/embedding/cross-provider.d.ts +78 -0
  41. package/dist/embedding/cross-provider.js +75 -0
  42. package/dist/embedding/deprecation.d.ts +151 -0
  43. package/dist/embedding/deprecation.js +229 -0
  44. package/dist/embedding/errors.d.ts +90 -0
  45. package/dist/embedding/errors.js +150 -0
  46. package/dist/embedding/index.d.ts +29 -0
  47. package/dist/embedding/index.js +24 -0
  48. package/dist/embedding/pipeline-load.d.ts +146 -0
  49. package/dist/embedding/pipeline-load.js +178 -0
  50. package/dist/embedding/registry.d.ts +45 -0
  51. package/dist/embedding/registry.js +61 -0
  52. package/dist/embedding/stale-vector.d.ts +110 -0
  53. package/dist/embedding/stale-vector.js +92 -0
  54. package/dist/embedding/storage/index.d.ts +51 -0
  55. package/dist/embedding/storage/index.js +43 -0
  56. package/dist/embedding/storage/jsonl-backend.d.ts +150 -0
  57. package/dist/embedding/storage/jsonl-backend.js +332 -0
  58. package/dist/embedding/storage/types.d.ts +135 -0
  59. package/dist/embedding/storage/types.js +13 -0
  60. package/dist/embedding/types.d.ts +180 -0
  61. package/dist/embedding/types.js +10 -0
  62. package/dist/execute.d.ts +29 -2
  63. package/dist/execute.js +171 -30
  64. package/dist/index.d.ts +14 -2
  65. package/dist/index.js +19 -2
  66. package/dist/journey/inheritance-validator.d.ts +396 -0
  67. package/dist/journey/inheritance-validator.js +370 -0
  68. package/dist/journey/state-id-drift-rule.d.ts +137 -0
  69. package/dist/journey/state-id-drift-rule.js +245 -0
  70. package/dist/journey-sa2-router.d.ts +395 -0
  71. package/dist/journey-sa2-router.js +308 -0
  72. package/dist/runners/review-agent.js +6 -2
  73. package/dist/runners/runner-registry.d.ts +36 -0
  74. package/dist/runners/runner-registry.js +90 -0
  75. package/dist/runtime/attestations.d.ts +173 -13
  76. package/dist/runtime/attestations.js +252 -40
  77. package/dist/runtime/index.d.ts +1 -1
  78. package/dist/runtime/index.js +1 -1
  79. package/dist/sa-scoring/layer3-llm.js +6 -1
  80. package/dist/sa-scoring/revision-proposal-config.d.ts +178 -0
  81. package/dist/sa-scoring/revision-proposal-config.js +198 -0
  82. package/dist/sa-scoring/revision-proposal.d.ts +285 -0
  83. package/dist/sa-scoring/revision-proposal.js +417 -0
  84. package/dist/signal-ingestion/adapters/community-thread.d.ts +43 -0
  85. package/dist/signal-ingestion/adapters/community-thread.js +55 -0
  86. package/dist/signal-ingestion/adapters/in-app-feedback.d.ts +67 -0
  87. package/dist/signal-ingestion/adapters/in-app-feedback.js +51 -0
  88. package/dist/signal-ingestion/adapters/manual.d.ts +78 -0
  89. package/dist/signal-ingestion/adapters/manual.js +112 -0
  90. package/dist/signal-ingestion/adapters/support-ticket.d.ts +47 -0
  91. package/dist/signal-ingestion/adapters/support-ticket.js +51 -0
  92. package/dist/signal-ingestion/classifier.d.ts +205 -0
  93. package/dist/signal-ingestion/classifier.js +494 -0
  94. package/dist/signal-ingestion/clustering-types.d.ts +36 -0
  95. package/dist/signal-ingestion/clustering-types.js +14 -0
  96. package/dist/signal-ingestion/clustering.d.ts +200 -0
  97. package/dist/signal-ingestion/clustering.js +413 -0
  98. package/dist/signal-ingestion/config.d.ts +351 -0
  99. package/dist/signal-ingestion/config.js +587 -0
  100. package/dist/signal-ingestion/d1.d.ts +252 -0
  101. package/dist/signal-ingestion/d1.js +235 -0
  102. package/dist/signal-ingestion/errors.d.ts +73 -0
  103. package/dist/signal-ingestion/errors.js +108 -0
  104. package/dist/signal-ingestion/governance-events.d.ts +181 -0
  105. package/dist/signal-ingestion/governance-events.js +189 -0
  106. package/dist/signal-ingestion/index.d.ts +35 -0
  107. package/dist/signal-ingestion/index.js +53 -0
  108. package/dist/signal-ingestion/manual-share-metric.d.ts +93 -0
  109. package/dist/signal-ingestion/manual-share-metric.js +106 -0
  110. package/dist/signal-ingestion/registry.d.ts +40 -0
  111. package/dist/signal-ingestion/registry.js +137 -0
  112. package/dist/signal-ingestion/residency.d.ts +227 -0
  113. package/dist/signal-ingestion/residency.js +238 -0
  114. package/dist/signal-ingestion/significance.d.ts +554 -0
  115. package/dist/signal-ingestion/significance.js +555 -0
  116. package/dist/signal-ingestion/types.d.ts +191 -0
  117. package/dist/signal-ingestion/types.js +8 -0
  118. package/dist/substrate/drift-composition.d.ts +270 -0
  119. package/dist/substrate/drift-composition.js +306 -0
  120. package/dist/substrate/drift-tui-surface.d.ts +61 -0
  121. package/dist/substrate/drift-tui-surface.js +102 -0
  122. package/dist/substrate/identity-class.d.ts +176 -0
  123. package/dist/substrate/identity-class.js +201 -0
  124. package/dist/tessellation/cross-soul-provenance-rule.d.ts +133 -0
  125. package/dist/tessellation/cross-soul-provenance-rule.js +171 -0
  126. package/dist/tessellation/inter-soul-embedding-distance-rule.d.ts +61 -0
  127. package/dist/tessellation/inter-soul-embedding-distance-rule.js +67 -0
  128. package/dist/tessellation/rule-registry.d.ts +269 -0
  129. package/dist/tessellation/rule-registry.js +92 -0
  130. package/dist/tessellation/soul-slug-ast-scan-rule.d.ts +90 -0
  131. package/dist/tessellation/soul-slug-ast-scan-rule.js +158 -0
  132. package/dist/tessellation-admission.d.ts +162 -0
  133. package/dist/tessellation-admission.js +146 -0
  134. package/dist/tessellation-drift.d.ts +246 -0
  135. package/dist/tessellation-drift.js +250 -0
  136. package/dist/validate-config.js +13 -0
  137. package/dist/validate-issue.js +2 -2
  138. package/dist/variant/cardinality-activation.d.ts +126 -0
  139. package/dist/variant/cardinality-activation.js +101 -0
  140. package/dist/variant/deprecation-lifecycle.d.ts +184 -0
  141. package/dist/variant/deprecation-lifecycle.js +208 -0
  142. package/dist/variant/drift-extension.d.ts +136 -0
  143. package/dist/variant/drift-extension.js +164 -0
  144. package/dist/variant/engineering-review.d.ts +185 -0
  145. package/dist/variant/engineering-review.js +142 -0
  146. package/dist/variant/index.d.ts +32 -0
  147. package/dist/variant/index.js +32 -0
  148. package/dist/variant/inheritance-validator.d.ts +165 -0
  149. package/dist/variant/inheritance-validator.js +139 -0
  150. package/dist/variant/internal-adopter/index.d.ts +11 -0
  151. package/dist/variant/internal-adopter/index.js +10 -0
  152. package/dist/variant/internal-adopter/products.d.ts +156 -0
  153. package/dist/variant/internal-adopter/products.js +366 -0
  154. package/dist/variant-admission.d.ts +316 -0
  155. package/dist/variant-admission.js +247 -0
  156. package/package.json +10 -8
@@ -47,9 +47,14 @@
47
47
  * a v3 envelope smuggling either field is rejected by
48
48
  * `validatePredicateShape`).
49
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.
54
+ *
50
55
  * Exported so the `verify-attestation` workflow can `import`/inline it.
51
56
  */
52
- export declare const ACCEPTED_SCHEMA_VERSIONS: readonly ["v3"];
57
+ export declare const ACCEPTED_SCHEMA_VERSIONS: readonly ["v3", "v5"];
53
58
  export type SchemaVersion = (typeof ACCEPTED_SCHEMA_VERSIONS)[number];
54
59
  /**
55
60
  * The DSSE PAE payload type for our predicate. DSSE spec mandates a payload
@@ -152,6 +157,45 @@ export interface AttestationPredicate {
152
157
  * ancestor walk in that case.
153
158
  */
154
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;
155
199
  /** sha256 of `.ai-sdlc/review-policy.md` at attestation time. */
156
200
  policyHash: string;
157
201
  /** Reviewer entries — typically 3 (code/test/security). */
@@ -218,7 +262,7 @@ export interface DsseSignature {
218
262
  /**
219
263
  * Identifier of the public key that produced this signature. Used to
220
264
  * look up the trusted-reviewer entry. Free-form — typically `<identity>:
221
- * <machine>` (e.g. `dominique@reliablegenius.io:laptop-2025`).
265
+ * <machine>` (e.g. `contributor@example.com:laptop-2025`).
222
266
  */
223
267
  keyid: string;
224
268
  /** base64-encoded raw ed25519 signature (64 bytes → 88 chars b64). */
@@ -369,15 +413,15 @@ export declare const ATTESTATION_ENVELOPE_PATH_PATTERN: RegExp;
369
413
  */
370
414
  export declare function isAttestationEnvelopePath(path: string): boolean;
371
415
  /**
372
- * The "shared churn" exclude list for `contentHashV4` (AISDLC-258).
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.
373
419
  *
374
- * Files in this list are EXCLUDED from the v4 file collector in BOTH the
375
- * signer (`collectChangedFileDeltaEntries`) and the verifier
376
- * (`computeHeadContentHashV4` in `scripts/verify-attestation.mjs`). When
377
- * a file appears in this list, changes to it after signing (e.g. from a
378
- * merge-queue rebase that regenerated `pnpm-lock.yaml`) do NOT cause
379
- * `contentHashV4` to mismatch, so the operator is never asked to re-sign
380
- * just because a shared tooling file was regenerated automatically.
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.
381
425
  *
382
426
  * **Security trade-off (operator-approved, 2026-05-10):** An attacker
383
427
  * COULD slip malicious changes through these files undetected (the
@@ -401,11 +445,18 @@ export declare function isAttestationEnvelopePath(path: string): boolean;
401
445
  * Exported so `scripts/verify-attestation.mjs` can import it from the
402
446
  * orchestrator barrel and apply the same exclusions on the verifier side.
403
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
+ */
404
454
  export declare const CONTENTHASHV4_IGNORE_FILES: readonly string[];
405
455
  /**
406
- * Predicate to determine whether a file path should be excluded from the
407
- * `contentHashV4` computation because it is a "shared churn" file (see
408
- * `CONTENTHASHV4_IGNORE_FILES`). Defensive about backslash normalization.
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.
409
460
  *
410
461
  * Note: this predicate is intentionally separate from
411
462
  * `isAttestationEnvelopePath` because the two exclusions serve different
@@ -441,6 +492,88 @@ export interface ChangedFileHeadEntry {
441
492
  path: string;
442
493
  headBlobSha: string;
443
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;
444
577
  /** Inputs for building an attestation predicate. */
445
578
  export interface BuildPredicateInputs {
446
579
  commitSha: string;
@@ -487,6 +620,19 @@ export interface BuildPredicateInputs {
487
620
  * still verifiable).
488
621
  */
489
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;
490
636
  }
491
637
  /**
492
638
  * Compute a sha256 hex digest. Single source of truth for the hashing
@@ -669,6 +815,10 @@ export declare function projectDeltaEntriesToHeadEntries(deltas: ChangedFileDelt
669
815
  * MUST provide `changedFileDeltas` (use `[]` for no-op PRs); the legacy
670
816
  * `diff` + `changedFiles` inputs were dropped along with the legacy
671
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.
672
822
  */
673
823
  export declare function buildPredicate(inputs: BuildPredicateInputs): AttestationPredicate;
674
824
  /**
@@ -738,6 +888,16 @@ export interface VerifyOptions {
738
888
  * of whether `expected.contentHashV4` is supplied.
739
889
  */
740
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;
741
901
  policyHash: string;
742
902
  expectedAgentFileHashes: Record<string, string>;
743
903
  };
@@ -50,9 +50,14 @@ import { cleanGitEnv } from './git-env.js';
50
50
  * a v3 envelope smuggling either field is rejected by
51
51
  * `validatePredicateShape`).
52
52
  *
53
+ * AISDLC-362 (contentHashV5): adds `'v5'` to the allowlist. v5 envelopes
54
+ * carry `contentHashV5` AND `signedMergeBase` in addition to v3+v4 hashes
55
+ * (backward-compat dual-write). The schemaVersion field is bumped to `'v5'`
56
+ * on new envelopes so verifiers can detect the v5 fast-path immediately.
57
+ *
53
58
  * Exported so the `verify-attestation` workflow can `import`/inline it.
54
59
  */
55
- export const ACCEPTED_SCHEMA_VERSIONS = ['v3'];
60
+ export const ACCEPTED_SCHEMA_VERSIONS = ['v3', 'v5'];
56
61
  /**
57
62
  * The DSSE PAE payload type for our predicate. DSSE spec mandates a payload
58
63
  * type URI — we use a project-controlled vendor URI rather than the
@@ -175,6 +180,23 @@ export function validatePredicateShape(parsed) {
175
180
  return 'schema validation failed: contentHashV4 does not match pattern';
176
181
  }
177
182
  }
183
+ // contentHashV5 (AISDLC-362) — OPTIONAL. When present, MUST be a 64-char
184
+ // hex sha256. v5 envelopes also carry `signedMergeBase` (40-char SHA-1).
185
+ if (p['contentHashV5'] !== undefined) {
186
+ const ch5 = p['contentHashV5'];
187
+ if (typeof ch5 !== 'string' || !SHA256_HEX.test(ch5)) {
188
+ return 'schema validation failed: contentHashV5 does not match pattern';
189
+ }
190
+ }
191
+ // signedMergeBase (AISDLC-362) — OPTIONAL, accompanies contentHashV5.
192
+ // When present, MUST be a 40-char SHA-1 hex string (the frozen merge-base
193
+ // that the v5 diff was computed against at sign time).
194
+ if (p['signedMergeBase'] !== undefined) {
195
+ const smb = p['signedMergeBase'];
196
+ if (typeof smb !== 'string' || !SHA1_HEX.test(smb)) {
197
+ return 'schema validation failed: signedMergeBase does not match SHA-1 pattern';
198
+ }
199
+ }
178
200
  // pluginVersion — short ID (no CR/LF, no `=`).
179
201
  const pluginVersion = p['pluginVersion'];
180
202
  if (typeof pluginVersion !== 'string' ||
@@ -361,15 +383,15 @@ export function isAttestationEnvelopePath(path) {
361
383
  return ATTESTATION_ENVELOPE_PATH_PATTERN.test(normalized);
362
384
  }
363
385
  /**
364
- * The "shared churn" exclude list for `contentHashV4` (AISDLC-258).
386
+ * The "shared churn" exclude list for content-hash computations (AISDLC-258,
387
+ * AISDLC-362). Applied to v3, v4, and v5 file collectors on BOTH the signer
388
+ * and verifier sides.
365
389
  *
366
- * Files in this list are EXCLUDED from the v4 file collector in BOTH the
367
- * signer (`collectChangedFileDeltaEntries`) and the verifier
368
- * (`computeHeadContentHashV4` in `scripts/verify-attestation.mjs`). When
369
- * a file appears in this list, changes to it after signing (e.g. from a
370
- * merge-queue rebase that regenerated `pnpm-lock.yaml`) do NOT cause
371
- * `contentHashV4` to mismatch, so the operator is never asked to re-sign
372
- * just because a shared tooling file was regenerated automatically.
390
+ * Files in this list are EXCLUDED from all file collectors. When a file
391
+ * appears in this list, changes to it after signing (e.g. from a merge-queue
392
+ * rebase that regenerated `pnpm-lock.yaml`) do NOT cause content-hash
393
+ * mismatches, so the operator is never asked to re-sign just because a shared
394
+ * tooling file was regenerated automatically.
373
395
  *
374
396
  * **Security trade-off (operator-approved, 2026-05-10):** An attacker
375
397
  * COULD slip malicious changes through these files undetected (the
@@ -393,22 +415,33 @@ export function isAttestationEnvelopePath(path) {
393
415
  * Exported so `scripts/verify-attestation.mjs` can import it from the
394
416
  * orchestrator barrel and apply the same exclusions on the verifier side.
395
417
  */
396
- export const CONTENTHASHV4_IGNORE_FILES = Object.freeze([
418
+ export const CONTENTHASH_SHARED_CHURN_FILES = Object.freeze([
397
419
  'pnpm-lock.yaml',
398
420
  'CHANGELOG.md',
399
421
  'pipeline-cli/CHANGELOG.md',
400
422
  'orchestrator/CHANGELOG.md',
401
- // NOTE: `reference/src/core/generated-schemas.ts` was REMOVED from this
402
- // list per AISDLC-258 code-review CRITICAL finding. Even though it's
403
- // auto-generated from spec/schemas/, it remains a `.ts` source file
404
- // shipped in reference/dist. An attacker who hand-edited it post-signing
405
- // would bypass attestation. Keep it in the hash; PRs that change schemas
406
- // pay the re-sign cost (which is correct — the schema change IS reviewable).
423
+ // AISDLC-342 — re-added after PR #498 was kicked from the merge queue 3+
424
+ // times in 2 hours with `contentHashV4 mismatch` triggered by sibling PRs
425
+ // touching this file. The earlier AISDLC-258 concern (attacker hand-edits
426
+ // the generated file to bypass attestation) is mitigated by keeping the
427
+ // SOURCE-of-truth in the hash: every byte in this file is derived from
428
+ // `spec/schemas/*.schema.json` via `pnpm build`, and those schema JSONs
429
+ // remain in v4/v5. An attacker who hand-edits generated-schemas.ts without
430
+ // also editing a source schema produces output that the next `pnpm build`
431
+ // regenerates away — the change is non-load-bearing.
432
+ 'reference/src/core/generated-schemas.ts',
407
433
  ]);
408
434
  /**
409
- * Predicate to determine whether a file path should be excluded from the
410
- * `contentHashV4` computation because it is a "shared churn" file (see
411
- * `CONTENTHASHV4_IGNORE_FILES`). Defensive about backslash normalization.
435
+ * Backward-compatible alias for `CONTENTHASH_SHARED_CHURN_FILES` (renamed in
436
+ * AISDLC-362). Callers that imported the v4-specific name continue to work.
437
+ * @deprecated Use `CONTENTHASH_SHARED_CHURN_FILES` instead.
438
+ */
439
+ export const CONTENTHASHV4_IGNORE_FILES = CONTENTHASH_SHARED_CHURN_FILES;
440
+ /**
441
+ * Predicate to determine whether a file path should be excluded from
442
+ * content-hash computations (v3/v4/v5) because it is a "shared churn" file
443
+ * (see `CONTENTHASH_SHARED_CHURN_FILES`). Defensive about backslash
444
+ * normalization.
412
445
  *
413
446
  * Note: this predicate is intentionally separate from
414
447
  * `isAttestationEnvelopePath` because the two exclusions serve different
@@ -419,7 +452,167 @@ export function isIgnoredForContentHash(path) {
419
452
  if (typeof path !== 'string')
420
453
  return false;
421
454
  const normalized = path.replace(/\\/g, '/');
422
- return CONTENTHASHV4_IGNORE_FILES.includes(normalized);
455
+ return CONTENTHASH_SHARED_CHURN_FILES.includes(normalized);
456
+ }
457
+ /**
458
+ * Compute the rebase-stable `contentHashV5` (AISDLC-362) over a file set
459
+ * and a frozen merge-base SHA.
460
+ *
461
+ * Canonical form: `SHA-256(JSON.stringify({schemaVersion:'v5',
462
+ * signedMergeBase:'<40-char>', files:[{path,blobSha}...]}))` where `files`
463
+ * is sorted ascending by path.
464
+ *
465
+ * Why v5 beats v4 for rebase-stability:
466
+ * - v4 enumerates files via `git diff origin/main..HEAD`. When a sibling
467
+ * PR merges between sign-time and verify-time, `origin/main` moves
468
+ * forward. Files the sibling touched now appear in the v4 diff that
469
+ * weren't there at sign-time → v4 hash diverges even though OUR blobs
470
+ * are unchanged.
471
+ * - v5 enumerates files via `git diff <signedMergeBase>..HEAD`. The
472
+ * `signedMergeBase` is computed ONCE at sign time and frozen in the
473
+ * envelope. At verify time, the verifier recomputes
474
+ * `git diff <signedMergeBase>..<probe-HEAD>` using the SAME frozen
475
+ * base → same file set → same hash, regardless of how many sibling
476
+ * PRs merged on `main` in the interim.
477
+ * - Overlapping sibling merges (sibling changed a file OUR PR also touches)
478
+ * → the head blob SHA of that file differs from what we signed → v5
479
+ * hash flips → verifier correctly rejects (operator must re-review).
480
+ *
481
+ * Threat model preserved: any genuine post-sign content tampering flips the
482
+ * head blob SHA → v5 hash flips → verifier rejects.
483
+ *
484
+ * Pure function. Idempotent against double-enumeration via dedup-by-path.
485
+ */
486
+ export function computeContentHashV5(entries, signedMergeBase) {
487
+ if (typeof signedMergeBase !== 'string' || !/^[0-9a-f]{40}$/i.test(signedMergeBase)) {
488
+ throw new Error(`computeContentHashV5: signedMergeBase must be a 40-char hex SHA-1, got ${JSON.stringify(signedMergeBase)}`);
489
+ }
490
+ // Dedup by path (last entry wins) — mirrors computeContentHashV4 and
491
+ // computeContentHashV3 for idempotency.
492
+ const byPath = new Map();
493
+ for (const e of entries) {
494
+ if (typeof e?.path !== 'string' || e.path.length === 0) {
495
+ throw new Error(`computeContentHashV5: entry path must be a non-empty string`);
496
+ }
497
+ if (typeof e.blobSha !== 'string') {
498
+ throw new Error(`computeContentHashV5: entry blobSha must be a string for path ${e.path}`);
499
+ }
500
+ // Reject path entries containing JSON-control characters.
501
+ if (e.path.includes('\t') || e.path.includes('\n')) {
502
+ throw new Error(`computeContentHashV5: entry path must not contain tab or newline characters (got ${JSON.stringify(e.path)})`);
503
+ }
504
+ const normalizedPath = e.path.replace(/\\/g, '/');
505
+ byPath.set(normalizedPath, e.blobSha.toLowerCase());
506
+ }
507
+ const sortedFiles = [...byPath.entries()]
508
+ .sort(([a], [b]) => (a < b ? -1 : a > b ? 1 : 0))
509
+ .map(([path, blobSha]) => ({ path, blobSha }));
510
+ const canonical = JSON.stringify({
511
+ schemaVersion: 'v5',
512
+ signedMergeBase: signedMergeBase.toLowerCase(),
513
+ files: sortedFiles,
514
+ });
515
+ return sha256Hex(canonical);
516
+ }
517
+ /**
518
+ * Collect the file set for `computeContentHashV5` (AISDLC-362).
519
+ *
520
+ * This is the LOAD-BEARING CHANGE vs v4:
521
+ * - v4: `git diff origin/main..HEAD` — the base moves as siblings merge.
522
+ * - v5: `git merge-base <baseRef> <headRef>` → frozen SHA, then
523
+ * `git diff <signedMergeBase>..HEAD` — the base is FROZEN.
524
+ *
525
+ * The frozen merge-base is returned alongside the entries so the signer can
526
+ * embed it in both the v5 hash and the predicate's `signedMergeBase` field.
527
+ *
528
+ * File enumeration applies the same exclusions as v3/v4:
529
+ * - Attestation envelope files (`ATTESTATION_ENVELOPE_PATH_PATTERN`)
530
+ * - Shared churn files (`CONTENTHASH_SHARED_CHURN_FILES`)
531
+ *
532
+ * Blob SHAs are resolved from `headRef` (= HEAD at sign time).
533
+ *
534
+ * @param repoRoot Absolute path to the git worktree root.
535
+ * @param baseRef Typically `'origin/main'`. Used ONLY for computing the
536
+ * merge-base — the diff itself is against the frozen SHA.
537
+ * @param headRef Typically `'HEAD'`.
538
+ * @param options Optional injection points for tests (stub `runGit`).
539
+ */
540
+ export function collectChangedFileEntriesForV5(repoRoot, baseRef = 'origin/main', headRef = 'HEAD', options = {}) {
541
+ const runGit = options.runGit ??
542
+ ((args, cwd) => execFileSync('git', args, {
543
+ cwd,
544
+ env: cleanGitEnv(),
545
+ encoding: 'utf-8',
546
+ maxBuffer: 64 * 1024 * 1024,
547
+ }));
548
+ // Step 1: compute and FREEZE the merge-base. This is the key invariant:
549
+ // sign once, freeze the base, verify against the frozen base — NOT against
550
+ // the moving `origin/main`.
551
+ let signedMergeBase;
552
+ try {
553
+ signedMergeBase = runGit(['merge-base', baseRef, headRef], repoRoot).trim();
554
+ }
555
+ catch (err) {
556
+ const msg = err instanceof Error ? err.message : String(err);
557
+ throw new Error(`collectChangedFileEntriesForV5: git merge-base failed: ${msg}`);
558
+ }
559
+ if (!/^[0-9a-f]{40}$/i.test(signedMergeBase)) {
560
+ throw new Error(`collectChangedFileEntriesForV5: git merge-base returned non-SHA output: ${JSON.stringify(signedMergeBase)}`);
561
+ }
562
+ // Step 2: enumerate files changed between the FROZEN merge-base and HEAD.
563
+ // Using two-dot range (`<signedMergeBase>..<headRef>`) — NOT three-dot —
564
+ // because the merge-base is already resolved. Three-dot would re-compute
565
+ // merge-base(mergeBase, headRef) = mergeBase itself, which is fine, but
566
+ // two-dot is more explicit and avoids any ambiguity.
567
+ let nameOnly;
568
+ try {
569
+ nameOnly = runGit([
570
+ '-c',
571
+ 'core.quotepath=false',
572
+ 'diff',
573
+ '--name-only',
574
+ '--no-renames',
575
+ `${signedMergeBase}..${headRef}`,
576
+ ], repoRoot);
577
+ }
578
+ catch (err) {
579
+ const msg = err instanceof Error ? err.message : String(err);
580
+ throw new Error(`collectChangedFileEntriesForV5: git diff --name-only failed: ${msg}`);
581
+ }
582
+ const paths = nameOnly.split('\n').filter((p) => p.length > 0);
583
+ const entries = [];
584
+ /**
585
+ * Resolve a file's blob SHA at `headRef` via `git ls-tree -r`. Returns the
586
+ * empty string when the path doesn't exist at the ref (= deleted file).
587
+ */
588
+ const resolveBlobSha = (path) => {
589
+ try {
590
+ const lsOut = runGit(['-c', 'core.quotepath=false', 'ls-tree', '-r', headRef, '--', path], repoRoot);
591
+ const line = lsOut.split('\n').find((l) => l.length > 0);
592
+ if (line) {
593
+ const m = line.match(/^[0-9]+\s+blob\s+([0-9a-f]{40})\t/);
594
+ if (m)
595
+ return m[1];
596
+ }
597
+ }
598
+ catch {
599
+ // ls-tree failed → treat as deleted.
600
+ }
601
+ return '';
602
+ };
603
+ for (const path of paths) {
604
+ if (path.includes('\t') || path.includes('\n')) {
605
+ throw new Error(`collectChangedFileEntriesForV5: path must not contain tab or newline characters (got ${JSON.stringify(path)})`);
606
+ }
607
+ // Exclude the attestation envelope itself (chore-commit pattern).
608
+ if (isAttestationEnvelopePath(path))
609
+ continue;
610
+ // Exclude shared-churn files (same set as v3/v4).
611
+ if (isIgnoredForContentHash(path))
612
+ continue;
613
+ entries.push({ path, blobSha: resolveBlobSha(path) });
614
+ }
615
+ return { entries, signedMergeBase: signedMergeBase.toLowerCase() };
423
616
  }
424
617
  /**
425
618
  * Compute a sha256 hex digest. Single source of truth for the hashing
@@ -837,6 +1030,10 @@ export function projectDeltaEntriesToHeadEntries(deltas) {
837
1030
  * MUST provide `changedFileDeltas` (use `[]` for no-op PRs); the legacy
838
1031
  * `diff` + `changedFiles` inputs were dropped along with the legacy
839
1032
  * `diffHash` + `contentHash` fields.
1033
+ *
1034
+ * AISDLC-362 (contentHashV5): when `v5Entries` + `v5MergeBase` are provided,
1035
+ * also emits `contentHashV5` and `signedMergeBase` in the predicate and bumps
1036
+ * `schemaVersion` to `'v5'`. The verifier prefers v5 when present.
840
1037
  */
841
1038
  export function buildPredicate(inputs) {
842
1039
  if (!/^[0-9a-f]{40}$/i.test(inputs.commitSha)) {
@@ -869,8 +1066,14 @@ export function buildPredicate(inputs) {
869
1066
  // Producers therefore can't accidentally compute v3 over one file
870
1067
  // set and v4 over another.
871
1068
  const headEntries = projectDeltaEntriesToHeadEntries(inputs.changedFileDeltas);
1069
+ // AISDLC-362: emit v5 when the caller provided v5 collection results.
1070
+ const hasV5 = Array.isArray(inputs.v5Entries) &&
1071
+ typeof inputs.v5MergeBase === 'string' &&
1072
+ /^[0-9a-f]{40}$/i.test(inputs.v5MergeBase);
872
1073
  const predicate = {
873
- schemaVersion: 'v3',
1074
+ // Bump schemaVersion to 'v5' when v5 data is present. Backward-compat:
1075
+ // the verifier's ACCEPTED_SCHEMA_VERSIONS now includes both 'v3' and 'v5'.
1076
+ schemaVersion: hasV5 ? 'v5' : 'v3',
874
1077
  subject: { digest: { sha1: inputs.commitSha.toLowerCase() } },
875
1078
  contentHashV3: computeContentHashV3(inputs.changedFileDeltas),
876
1079
  contentHashV4: computeContentHashV4(headEntries),
@@ -887,6 +1090,11 @@ export function buildPredicate(inputs) {
887
1090
  harnessNote: inputs.harnessNote,
888
1091
  signedAt: inputs.signedAt ?? new Date().toISOString(),
889
1092
  };
1093
+ // AISDLC-362: embed the frozen merge-base and v5 hash when available.
1094
+ if (hasV5) {
1095
+ predicate.signedMergeBase = inputs.v5MergeBase.toLowerCase();
1096
+ predicate.contentHashV5 = computeContentHashV5(inputs.v5Entries, inputs.v5MergeBase);
1097
+ }
890
1098
  // AISDLC-100.6: include `pipelineVersion` only when the caller provided
891
1099
  // it. Omitted otherwise so envelopes signed in environments without
892
1100
  // pipeline-cli installed still round-trip identically through
@@ -1051,28 +1259,32 @@ export function verifyAttestation(opts) {
1051
1259
  reason: `subject digest mismatch (envelope was signed for a different commit)`,
1052
1260
  };
1053
1261
  }
1054
- // AISDLC-193.1: v4-prefer, v3-fallback. The verifier prefers
1055
- // `contentHashV4` (base-independent — survives queue rebases) when
1056
- // BOTH the envelope and the expected state carry it. v3 is only
1057
- // consulted when the envelope is legacy v3-only OR when v4 is
1058
- // present but doesn't match (unusual — implies the head blobs
1059
- // genuinely changed, which we still reject below).
1262
+ // AISDLC-362: v5-prefer, v4-fallback, v3-last-resort.
1060
1263
  //
1061
- // Why prefer v4 absolute when both are present:
1062
- // - v3 binds to (base_blob, head_blob) per file. The merge queue's
1063
- // rebase moves the merge-base forward → base_blob changes for
1064
- // shared files v3 mismatches even though reviewed content is
1065
- // unchanged. Net: required check fails on every queued PR.
1066
- // - v4 binds only to head_blob per file. Reviewers approved the
1067
- // content at head_blob; whatever the rebase did to base, v4
1068
- // stays valid.
1264
+ // Priority order (highest rebase-stability first):
1265
+ // 1. v5 (frozen merge-base delta hash) prefers when BOTH envelope AND
1266
+ // expected carry contentHashV5. Survives non-overlapping sibling merges.
1267
+ // 2. v4 (base-independent head-blob hash) prefers when BOTH carry v4
1268
+ // but not v5. Survives queue rebases when files don't overlap.
1269
+ // 3. v3 (base+head blob-pair delta hash) legacy fallback only.
1069
1270
  //
1070
- // Threat model preserved: a genuine post-sign content tampering
1071
- // (someone amends the PR to add unreviewed code) flips head_blob
1072
- // v4 flips verifier rejects.
1271
+ // When a higher-priority hash matches, lower-priority hashes are NOT
1272
+ // consulted (the merge-base shift that would invalidate them is exactly
1273
+ // what the higher-priority hash was designed to survive).
1274
+ const envelopeHasV5 = typeof predicate.contentHashV5 === 'string';
1275
+ const expectedHasV5 = typeof opts.expected.contentHashV5 === 'string';
1073
1276
  const envelopeHasV4 = typeof predicate.contentHashV4 === 'string';
1074
1277
  const expectedHasV4 = typeof opts.expected.contentHashV4 === 'string';
1075
- if (envelopeHasV4 && expectedHasV4) {
1278
+ if (envelopeHasV5 && expectedHasV5) {
1279
+ if (predicate.contentHashV5 !== opts.expected.contentHashV5) {
1280
+ return {
1281
+ valid: false,
1282
+ reason: 'contentHashV5 mismatch (PR content changed since attestation)',
1283
+ };
1284
+ }
1285
+ // v5 matched → skip v4 and v3 entirely.
1286
+ }
1287
+ else if (envelopeHasV4 && expectedHasV4) {
1076
1288
  if (predicate.contentHashV4 !== opts.expected.contentHashV4) {
1077
1289
  return {
1078
1290
  valid: false,
@@ -1084,7 +1296,7 @@ export function verifyAttestation(opts) {
1084
1296
  // rebase, sibling overlap); the v4 match is the source of truth.
1085
1297
  }
1086
1298
  else {
1087
- // Legacy v3-only envelope OR caller did not supply expected.contentHashV4
1299
+ // Legacy v3-only envelope OR caller did not supply expected.contentHashV4/V5
1088
1300
  // → fall back to v3. Same as pre-AISDLC-193.1 behavior.
1089
1301
  if (predicate.contentHashV3 !== opts.expected.contentHashV3) {
1090
1302
  return {
@@ -3,5 +3,5 @@ export { slugifyBranch, worktreePath, verifyOwnership, assertOwnership, isExisti
3
3
  export { WorktreePoolManager, WorktreePoolError, DEFAULT_POOL_ROOT, DEFAULT_STALE_THRESHOLD_DAYS, type WorktreePoolSpec, type WorktreePoolManagerDeps, type AllocateOptions, type WorktreeHandle, } from './worktree-pool.js';
4
4
  export { readParallelismMode, isParallelismEnabled, FLAG_NAME as PARALLELISM_FLAG, type ParallelismMode, } from './parallelism-flag.js';
5
5
  export { cleanGitEnv, gitExecFile } from './git-env.js';
6
- export { ACCEPTED_SCHEMA_VERSIONS, DSSE_PAYLOAD_TYPE, buildPredicate, generateSigningKeyPair, paeEncode, sha1Hex, sha256Hex, signAttestation, validateTrustedReviewers, verifyAttestation, type AttestationPredicate, type DsseEnvelope, type DsseSignature, type ReviewerEntry, type SchemaVersion, type SubjectDigest, type TrustedReviewer, type VerifyResult, } from './attestations.js';
6
+ export { ACCEPTED_SCHEMA_VERSIONS, ATTESTATION_ENVELOPE_PATH_PATTERN, CONTENTHASH_SHARED_CHURN_FILES, CONTENTHASHV4_IGNORE_FILES, DSSE_PAYLOAD_TYPE, INDEPENDENCE_REQUIRED_ROLES, REQUIRED_REVIEWER_AGENT_IDS, REVIEWER_ROLE_EQUIVALENCES, buildPredicate, collectChangedFileDeltaEntries, collectChangedFileEntries, collectChangedFileEntriesForV5, computeContentHash, computeContentHashV3, computeContentHashV4, computeContentHashV5, generateSigningKeyPair, isAttestationEnvelopePath, isIgnoredForContentHash, paeEncode, projectDeltaEntriesToHeadEntries, sha1Hex, sha256Hex, signAttestation, validatePredicateShape, validateTrustedReviewers, verifyAttestation, type AttestationPredicate, type BuildPredicateInputs, type ChangedFileDeltaEntry, type ChangedFileEntry, type ChangedFileHeadEntry, type ChangedFileV5Entry, type CollectChangedFileEntriesOptions, type DsseEnvelope, type DsseSignature, type ReviewerEntry, type SchemaVersion, type SubjectDigest, type TrustedReviewer, type V5CollectResult, type VerifyOptions, type VerifyResult, } from './attestations.js';
7
7
  //# sourceMappingURL=index.d.ts.map
@@ -3,5 +3,5 @@ export { slugifyBranch, worktreePath, verifyOwnership, assertOwnership, isExisti
3
3
  export { WorktreePoolManager, WorktreePoolError, DEFAULT_POOL_ROOT, DEFAULT_STALE_THRESHOLD_DAYS, } from './worktree-pool.js';
4
4
  export { readParallelismMode, isParallelismEnabled, FLAG_NAME as PARALLELISM_FLAG, } from './parallelism-flag.js';
5
5
  export { cleanGitEnv, gitExecFile } from './git-env.js';
6
- export { ACCEPTED_SCHEMA_VERSIONS, DSSE_PAYLOAD_TYPE, buildPredicate, generateSigningKeyPair, paeEncode, sha1Hex, sha256Hex, signAttestation, validateTrustedReviewers, verifyAttestation, } from './attestations.js';
6
+ export { ACCEPTED_SCHEMA_VERSIONS, ATTESTATION_ENVELOPE_PATH_PATTERN, CONTENTHASH_SHARED_CHURN_FILES, CONTENTHASHV4_IGNORE_FILES, DSSE_PAYLOAD_TYPE, INDEPENDENCE_REQUIRED_ROLES, REQUIRED_REVIEWER_AGENT_IDS, REVIEWER_ROLE_EQUIVALENCES, buildPredicate, collectChangedFileDeltaEntries, collectChangedFileEntries, collectChangedFileEntriesForV5, computeContentHash, computeContentHashV3, computeContentHashV4, computeContentHashV5, generateSigningKeyPair, isAttestationEnvelopePath, isIgnoredForContentHash, paeEncode, projectDeltaEntriesToHeadEntries, sha1Hex, sha256Hex, signAttestation, validatePredicateShape, validateTrustedReviewers, verifyAttestation, } from './attestations.js';
7
7
  //# sourceMappingURL=index.js.map