chainlesschain 0.166.15 → 0.166.17

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 (202) hide show
  1. package/README.md +3 -3
  2. package/completions/_cc +1 -1
  3. package/completions/cc.bash +1 -1
  4. package/completions/cc.fish +1 -1
  5. package/completions/cc.ps1 +1 -1
  6. package/package.json +2 -2
  7. package/src/assets/web-panel/assets/{AIOps-To-6rimH.js → AIOps-CVW2n2rh.js} +1 -1
  8. package/src/assets/web-panel/assets/{ActionButton-CpdCnTv3.js → ActionButton-DtuM163O.js} +1 -1
  9. package/src/assets/web-panel/assets/{Analytics-DK7VNES_.js → Analytics-BXvl_qHu.js} +3 -3
  10. package/src/assets/web-panel/assets/{AppLayout-NZ5FvhCx.js → AppLayout-VDBTFKOp.js} +5 -5
  11. package/src/assets/web-panel/assets/{Artifacts-NXceQPmN.js → Artifacts-BNz2xCiG.js} +1 -1
  12. package/src/assets/web-panel/assets/{Audit-B2d64DH2.js → Audit-DTu9Hn9P.js} +1 -1
  13. package/src/assets/web-panel/assets/{BackgroundAgents-ByYgvEVY.js → BackgroundAgents-D2OCPzLe.js} +1 -1
  14. package/src/assets/web-panel/assets/{Backup-BXvQLDvN.js → Backup-C9M-vG7G.js} +1 -1
  15. package/src/assets/web-panel/assets/{BaseInput-C4GZk-As.js → BaseInput-BUkCNiUQ.js} +1 -1
  16. package/src/assets/web-panel/assets/{Chat-CvPuohL8.js → Chat-OJ2ajF7l.js} +5 -5
  17. package/src/assets/web-panel/assets/{ChatBubbleRenderer-DTftcclX.js → ChatBubbleRenderer-BdZt76Ab.js} +1 -1
  18. package/src/assets/web-panel/assets/{Checkbox-C8dQGIqD.js → Checkbox-C1gXKDsc.js} +1 -1
  19. package/src/assets/web-panel/assets/{Codegen-DRM6S1W4.js → Codegen-BXbznLg7.js} +1 -1
  20. package/src/assets/web-panel/assets/{Col-BDM8l-r5.js → Col-CbCq5N0i.js} +1 -1
  21. package/src/assets/web-panel/assets/{Community-Bvmfi1fX.js → Community-V8Ks6Yun.js} +1 -1
  22. package/src/assets/web-panel/assets/{Compact-DGQTCima.js → Compact-DkeHQTpQ.js} +1 -1
  23. package/src/assets/web-panel/assets/{Compliance-D_0C-2P6.js → Compliance-JeLKwn4Q.js} +1 -1
  24. package/src/assets/web-panel/assets/{Cowork-3C_q99ok.js → Cowork-azfetcBz.js} +3 -3
  25. package/src/assets/web-panel/assets/{Cron-CIiMQ1Hl.js → Cron-Q691i_Gp.js} +2 -2
  26. package/src/assets/web-panel/assets/{Crosschain-peebGnES.js → Crosschain-BLjDRbr_.js} +1 -1
  27. package/src/assets/web-panel/assets/{DID-OkX4KB9T.js → DID-D8ptXplK.js} +2 -2
  28. package/src/assets/web-panel/assets/{Dashboard-D9lm0Wxa.js → Dashboard-Ctj2VVZL.js} +2 -2
  29. package/src/assets/web-panel/assets/{Dropdown-CitActXx.js → Dropdown-D8VgkMJ8.js} +1 -1
  30. package/src/assets/web-panel/assets/EmailListRenderer-CQPe5S6q.js +1 -0
  31. package/src/assets/web-panel/assets/{FamilyGuardDashboard-CBWAi8XL.js → FamilyGuardDashboard-ySA-7SiN.js} +1 -1
  32. package/src/assets/web-panel/assets/{Federation-afj8jDcp.js → Federation-1PMD-fBf.js} +1 -1
  33. package/src/assets/web-panel/assets/{FormItemContext-Dabt59KY.js → FormItemContext-C_jr6U4Q.js} +1 -1
  34. package/src/assets/web-panel/assets/{GenericCardRenderer-BQpXA9kj.js → GenericCardRenderer-Bu3bKf2B.js} +1 -1
  35. package/src/assets/web-panel/assets/{Git-D2p2Moo1.js → Git-Dv4Fa_T5.js} +2 -2
  36. package/src/assets/web-panel/assets/{Governance-DkFfPykv.js → Governance-DOEy1n1k.js} +1 -1
  37. package/src/assets/web-panel/assets/{Inference-C1DKpeN2.js → Inference-DkvqzWcJ.js} +1 -1
  38. package/src/assets/web-panel/assets/{KnowledgeGraph-CjfVg4oQ.js → KnowledgeGraph-DdUJK949.js} +1 -1
  39. package/src/assets/web-panel/assets/{Logs-D-zMvdSf.js → Logs-CRelcdUZ.js} +2 -2
  40. package/src/assets/web-panel/assets/{MarkdownRenderer-jzEpGkbc.js → MarkdownRenderer-0AHbbTA4.js} +1 -1
  41. package/src/assets/web-panel/assets/{Marketplace-DfAjJLyS.js → Marketplace-BuuBKsev.js} +1 -1
  42. package/src/assets/web-panel/assets/{McpTools-BGDHQES1.js → McpTools-DlgpEmA1.js} +3 -3
  43. package/src/assets/web-panel/assets/{Memory-BYqrGAPS.js → Memory-DTfdqUGy.js} +2 -2
  44. package/src/assets/web-panel/assets/{MobileBridge-CQ6FEtty.js → MobileBridge-CXLE5ZFO.js} +1 -1
  45. package/src/assets/web-panel/assets/{MobileProjects-BpMvh5JW.js → MobileProjects-Dz25OB0n.js} +1 -1
  46. package/src/assets/web-panel/assets/{Mtc-CWH25SrU.js → Mtc-kEL0fhgD.js} +2 -2
  47. package/src/assets/web-panel/assets/{MtcAudit-D6DiLmei.js → MtcAudit-BE4UvFJU.js} +2 -2
  48. package/src/assets/web-panel/assets/{Multisig-BSxRN4jD.js → Multisig-CSApSn8c.js} +3 -3
  49. package/src/assets/web-panel/assets/{NLProgramming-C0FDPKT4.js → NLProgramming-D11FpKWx.js} +1 -1
  50. package/src/assets/web-panel/assets/{Notes-VKR78bH-.js → Notes-CDL-D2CS.js} +3 -3
  51. package/src/assets/web-panel/assets/{NotificationSettings-DLSklSZB.js → NotificationSettings-C0Q6lO-c.js} +1 -1
  52. package/src/assets/web-panel/assets/OrderTableRenderer-ZRE0OKbj.js +1 -0
  53. package/src/assets/web-panel/assets/{Organization-BB3pB27e.js → Organization-Dc-maVAq.js} +4 -4
  54. package/src/assets/web-panel/assets/{Overflow-Cr7bzwcs.js → Overflow-B5mUpSvi.js} +1 -1
  55. package/src/assets/web-panel/assets/{P2P-D1fkMMzI.js → P2P-TjXV53wq.js} +2 -2
  56. package/src/assets/web-panel/assets/PdhVaultBrowser-DvV2btIr.js +7 -0
  57. package/src/assets/web-panel/assets/{Permissions-c3Ijv0v4.js → Permissions-DKRzQ2O6.js} +4 -4
  58. package/src/assets/web-panel/assets/{PersonalDataHub-BJwbh3P2.js → PersonalDataHub-Dx2eqft8.js} +4 -4
  59. package/src/assets/web-panel/assets/{Pipeline-C4fhXafB.js → Pipeline-CnYZMB5p.js} +1 -1
  60. package/src/assets/web-panel/assets/{Privacy-DzSx3CD-.js → Privacy-BonKpPnp.js} +1 -1
  61. package/src/assets/web-panel/assets/{ProjectInit-D9fL5fED.js → ProjectInit-DWO0Du3J.js} +2 -2
  62. package/src/assets/web-panel/assets/{ProjectSettings-CB5FVrOR.js → ProjectSettings-C9jqyYRv.js} +2 -2
  63. package/src/assets/web-panel/assets/{Projects-DWhwdgfY.js → Projects-CYpKwXbz.js} +1 -1
  64. package/src/assets/web-panel/assets/{Providers-B9ZH4faS.js → Providers-CW-fkwJq.js} +1 -1
  65. package/src/assets/web-panel/assets/{QrScannerModal-BLKDHT8_.js → QrScannerModal-Cw0J3Zud.js} +1 -1
  66. package/src/assets/web-panel/assets/{QuickAsk-D1oYUcSY.js → QuickAsk-BH4A4IpR.js} +1 -1
  67. package/src/assets/web-panel/assets/{Recommend-CC2nmrOD.js → Recommend-CDwMwJus.js} +1 -1
  68. package/src/assets/web-panel/assets/{RemoteSession-qW9QF4-r.js → RemoteSession-DKIPwzTf.js} +2 -2
  69. package/src/assets/web-panel/assets/{Reputation-BDWqfzcr.js → Reputation-CklDj7qS.js} +1 -1
  70. package/src/assets/web-panel/assets/{Row-BBV2iCjT.js → Row-DJWZKsH0.js} +1 -1
  71. package/src/assets/web-panel/assets/{RssFeed-CcwrNwZB.js → RssFeed-BfSLCewo.js} +3 -3
  72. package/src/assets/web-panel/assets/{Search-Dr6fZ0D_.js → Search-B0IST--x.js} +1 -1
  73. package/src/assets/web-panel/assets/{Security-BPzaoCU6.js → Security-h1sYNWtt.js} +4 -4
  74. package/src/assets/web-panel/assets/{Services-BoKUU1I_.js → Services-Gh0-oxI_.js} +2 -2
  75. package/src/assets/web-panel/assets/{Skeleton-C8lT8GUg.js → Skeleton-CNDV_jOs.js} +1 -1
  76. package/src/assets/web-panel/assets/{Skills-DPpWeicz.js → Skills-ByCLTh1n.js} +1 -1
  77. package/src/assets/web-panel/assets/{Sla-UiijzpMU.js → Sla-BfyrJTJz.js} +1 -1
  78. package/src/assets/web-panel/assets/{SpeechSettings-DoofrLwS.js → SpeechSettings-BQzo9kNX.js} +1 -1
  79. package/src/assets/web-panel/assets/{SyncSettings-DXnyfTDw.js → SyncSettings-B6fS6z_1.js} +2 -2
  80. package/src/assets/web-panel/assets/{Tasks-g_WHjKeg.js → Tasks-C-uKCP-l.js} +1 -1
  81. package/src/assets/web-panel/assets/{Templates-DRuCh9g2.js → Templates-D6oDy7wL.js} +1 -1
  82. package/src/assets/web-panel/assets/{Tenant-QHIq1pH1.js → Tenant-D3LT2wNQ.js} +1 -1
  83. package/src/assets/web-panel/assets/{Terminal-G0fuI216.js → Terminal-BteLcv_O.js} +2 -2
  84. package/src/assets/web-panel/assets/{TimelineRenderer-4MT7SpGk.js → TimelineRenderer-DHHuxfqH.js} +1 -1
  85. package/src/assets/web-panel/assets/{Tokens-B5xT5umA.js → Tokens-BRQeykXw.js} +1 -1
  86. package/src/assets/web-panel/assets/{Trigger-D7HPbNgc.js → Trigger-p2XaFWEu.js} +1 -1
  87. package/src/assets/web-panel/assets/{Trust-CxH69dU5.js → Trust-BxadBMft.js} +1 -1
  88. package/src/assets/web-panel/assets/{UkeySign-CPPosveH.js → UkeySign-DRBP7LDk.js} +1 -1
  89. package/src/assets/web-panel/assets/{VideoEditing-Du5lGwAt.js → VideoEditing-CZ3NZkh7.js} +1 -1
  90. package/src/assets/web-panel/assets/{Wallet-BBIGNESK.js → Wallet-C3NTBN7G.js} +4 -4
  91. package/src/assets/web-panel/assets/{WebAuthn-Cc3HSGmv.js → WebAuthn-C7LVgMlb.js} +5 -5
  92. package/src/assets/web-panel/assets/{WorkflowEditor-DBoVPNCh.js → WorkflowEditor-0_Iveog7.js} +1 -1
  93. package/src/assets/web-panel/assets/{chat-DHcjgiem.js → chat-DzzluYo3.js} +1 -1
  94. package/src/assets/web-panel/assets/{colors-DrRrvJIV.js → colors-DSMYXpaj.js} +1 -1
  95. package/src/assets/web-panel/assets/{compact-item-BuY_sAoN.js → compact-item-W76u1VSV.js} +1 -1
  96. package/src/assets/web-panel/assets/{createContext-CepH1Fha.js → createContext-otsESjb5.js} +1 -1
  97. package/src/assets/web-panel/assets/devWarning-BztaXF84.js +1 -0
  98. package/src/assets/web-panel/assets/{hasIn-Dh80Ot6o.js → hasIn-3sNLrvug.js} +1 -1
  99. package/src/assets/web-panel/assets/index-4jP_hG_4.js +1 -0
  100. package/src/assets/web-panel/assets/{index-LBkSOwky.js → index-B1I1AYJs.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-B0rvJjId.js → index-B5LOYdYm.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-D6tOVlq-.js → index-B9X_jqXe.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-D7lstbTh.js → index-BA8mZxd-.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-BPDEOOlA.js → index-BDlDO2px.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-DuSUCH7h.js → index-BJsQio5O.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-CEfVkKIY.js → index-BRsD1PT2.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-CYydOga6.js → index-BXLx9HfA.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-DkZP_QaB.js → index-BcGJtmHM.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-BnVIvc15.js → index-BdtmEKat.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-DuRYBWrj.js → index-BpcQYzP7.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-By0qqC3S.js → index-C2aht9-M.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-C10033eO.js → index-CE7tSow-.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-9crgJ3Id.js → index-CENWwjef.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-CNQK9DIr.js → index-CKoFyHYQ.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-DUNyTmkS.js → index-Cg86EzNp.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-CnRK8_Dd.js → index-ClOFeZ-t.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BW9Pycxh.js → index-CtFOt5x1.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-D5_K3GKu.js → index-D7A5U2IH.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-DRy3_beb.js → index-DH4cl52p.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-DWANFMlq.js → index-DLDh1OW3.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-C5vhNRqS.js → index-DMmdIpLA.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-Ca5d64IH.js → index-DS9WYDMf.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-zGH9zH3w.js → index-DTA8Iavv.js} +3 -3
  124. package/src/assets/web-panel/assets/{index-BoRy2yup.js → index-DZKAzEge.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-Cx21_p3m.js → index-DdU7IkKX.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-B9Gh-SOn.js → index-Ddd4mird.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-CiUx-tkV.js → index-DmpaC2yk.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-DYN49m7k.js → index-GlIOE4ja.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-CUnd5hyp.js → index-MhQQ3h8q.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CW_23-gU.js → index-NaO9AE-F.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-0TYLnd11.js → index-UUHCwfNq.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-NS9dW1F1.js → index-pVm_iUre.js} +1 -1
  133. package/src/assets/web-panel/assets/{index-hYaigdGm.js → index-q7ftLqNt.js} +1 -1
  134. package/src/assets/web-panel/assets/{index-A5lVwYVq.js → index-uSCkbpZH.js} +1 -1
  135. package/src/assets/web-panel/assets/{index-BtqigRzf.js → index-vBKPcmAP.js} +1 -1
  136. package/src/assets/web-panel/assets/{index-BiZQooWK.js → index-vUmYkqmu.js} +1 -1
  137. package/src/assets/web-panel/assets/index-zxwuAXL5.js +1 -0
  138. package/src/assets/web-panel/assets/{initDefaultProps-nI_X3wKl.js → initDefaultProps-DF6llUXV.js} +1 -1
  139. package/src/assets/web-panel/assets/{motion-LUHbQu_R.js → motion-vtDV8_5K.js} +1 -1
  140. package/src/assets/web-panel/assets/{move-DHI_KvJt.js → move-B6c0bdy2.js} +1 -1
  141. package/src/assets/web-panel/assets/{mtc-parser-AYdL_7Mo.js → mtc-parser-DlLYWuBK.js} +1 -1
  142. package/src/assets/web-panel/assets/{omit-CtV3HRKa.js → omit-BH0zOxLN.js} +1 -1
  143. package/src/assets/web-panel/assets/{pickAttrs-CcJLTOu4.js → pickAttrs-D9di8_In.js} +1 -1
  144. package/src/assets/web-panel/assets/{placementArrow-DHbiLUps.js → placementArrow-BDSngufM.js} +1 -1
  145. package/src/assets/web-panel/assets/{responsiveObserve-BC2DGkWP.js → responsiveObserve-C_EawMvP.js} +1 -1
  146. package/src/assets/web-panel/assets/{slide-B4ga4pPY.js → slide-B_4wShxA.js} +1 -1
  147. package/src/assets/web-panel/assets/{statusUtils-hzphgKfH.js → statusUtils-CbQu6qJ-.js} +1 -1
  148. package/src/assets/web-panel/assets/{styleChecker-IfN9Bxoi.js → styleChecker-CvXJWYXb.js} +1 -1
  149. package/src/assets/web-panel/assets/{useFlexGapSupport-pcGKJ4pT.js → useFlexGapSupport-jjY1XknO.js} +1 -1
  150. package/src/assets/web-panel/assets/{useFs-COXhQpTG.js → useFs-CMXBdsK_.js} +1 -1
  151. package/src/assets/web-panel/assets/{usePersonalDataHub-xp9F9szv.js → usePersonalDataHub-YbAhhPXx.js} +1 -1
  152. package/src/assets/web-panel/assets/{vnode-BdSQy1Ve.js → vnode-BFCTKc0H.js} +1 -1
  153. package/src/assets/web-panel/assets/{zoom-CHCt5P55.js → zoom-CY9aBl5O.js} +1 -1
  154. package/src/assets/web-panel/index.html +1 -1
  155. package/src/command-help-index.json +2 -2
  156. package/src/command-manifest.json +2 -2
  157. package/src/commands/evolution.js +30 -17
  158. package/src/commands/learning.js +95 -9
  159. package/src/commands/team.js +5 -0
  160. package/src/data/changelog.json +28 -2
  161. package/src/lib/agent-team/task-lease.js +12 -1
  162. package/src/lib/capability-manifest.js +212 -1
  163. package/src/lib/evolution/bounded-skill-improvement-pilot.js +498 -0
  164. package/src/lib/evolution/evolution-artifact-ports.js +3277 -0
  165. package/src/lib/evolution/evolution-eval-gate.js +6442 -0
  166. package/src/lib/evolution/evolution-evidence-projector.js +3786 -0
  167. package/src/lib/evolution/evolution-ledger-ports.js +2689 -0
  168. package/src/lib/evolution/evolution-ledger.js +4280 -0
  169. package/src/lib/evolution/evolution-run-runtime-adapter.js +5 -0
  170. package/src/lib/evolution/skill-candidate-registry.js +2258 -0
  171. package/src/lib/evolution/skill-evaluated-promotion-durability.js +899 -0
  172. package/src/lib/evolution/skill-evaluated-promotion.js +481 -0
  173. package/src/lib/evolution/skill-execution-manifest.js +942 -0
  174. package/src/lib/evolution/skill-mutation-authority.js +1677 -0
  175. package/src/lib/evolution/skill-promotion-controller.js +618 -0
  176. package/src/lib/evolution/skill-release-registry.js +3277 -0
  177. package/src/lib/evolution/skill-target-matrix-eval.js +3233 -0
  178. package/src/lib/evolution/skill-writer-inventory-manifest.js +998 -0
  179. package/src/lib/evolution/skill-writer-inventory-validator.js +816 -0
  180. package/src/lib/evolution/wiki-informed-skill-proposer.js +343 -0
  181. package/src/lib/formal-quality-eval-runtime.js +100 -0
  182. package/src/lib/graph-kernel/cutover-contract.js +7 -0
  183. package/src/lib/graph-kernel/cutover-ledger.js +8 -7
  184. package/src/lib/graph-kernel/evidence-digest.js +47 -0
  185. package/src/lib/graph-kernel/evolution-run-runtime-adapter.js +5 -0
  186. package/src/lib/graph-kernel/graph-runtime-surfaces.json +8 -1
  187. package/src/lib/graph-kernel/production-cutover-evidence.js +342 -15
  188. package/src/lib/graph-kernel/production-source-evidence.js +1599 -0
  189. package/src/lib/graph-kernel/retirement-evidence.js +15 -5
  190. package/src/lib/graph-kernel/runtime-surface-manifest.js +67 -2
  191. package/src/lib/headless-manifest.js +3 -1
  192. package/src/lib/hooks-v2-runtime.js +20 -0
  193. package/src/lib/learning/skill-improver.js +311 -43
  194. package/src/lib/learning/skill-synthesizer.js +236 -16
  195. package/src/lib/secure-fs.js +20 -0
  196. package/src/runtime/agent-core.js +68 -0
  197. package/src/assets/web-panel/assets/EmailListRenderer-DSmFkNKV.js +0 -1
  198. package/src/assets/web-panel/assets/OrderTableRenderer-Dpj7D6tC.js +0 -1
  199. package/src/assets/web-panel/assets/PdhVaultBrowser-BiDEbLNA.js +0 -7
  200. package/src/assets/web-panel/assets/devWarning-DIb8j04q.js +0 -1
  201. package/src/assets/web-panel/assets/index-BJc3t4PC.js +0 -1
  202. package/src/assets/web-panel/assets/index-CkksSUnI.js +0 -1
@@ -1,7 +1,18 @@
1
- import { GRAPH_CUTOVER_REQUIRED_PLATFORMS } from "./cutover-ledger.js";
2
- import { graphDigest } from "./compiler.js";
1
+ import { GRAPH_CUTOVER_REQUIRED_PLATFORMS } from "./cutover-contract.js";
3
2
  import {
3
+ canonicalGraphEvidenceJson as canonicalGraphJson,
4
+ graphEvidenceDigest as graphDigest,
5
+ } from "./evidence-digest.js";
6
+ import {
7
+ GRAPH_PRODUCTION_SOURCE_ENVIRONMENT,
8
+ GRAPH_PRODUCTION_SOURCE_REF,
9
+ GRAPH_PRODUCTION_SOURCE_WORKFLOW,
10
+ normalizeGraphProductionSourceBundle,
11
+ } from "./production-source-evidence.js";
12
+ import {
13
+ assertGraphProductionRuntimeSurfaceManifest,
4
14
  graphRuntimeEntryManifestDigest,
15
+ graphRuntimeSurfaceManifestDigest,
5
16
  loadGraphRuntimeSurfaceManifest,
6
17
  } from "./runtime-surface-manifest.js";
7
18
  import {
@@ -16,6 +27,19 @@ export const GRAPH_ENTRY_WRITER_OBSERVATION_SCHEMA =
16
27
  "chainlesschain.graph-entry-writer-observation/v1";
17
28
  export const GRAPH_PRODUCTION_CUTOVER_RECEIPT_SCHEMA =
18
29
  "chainlesschain.graph-production-cutover-receipt/v1";
30
+ export const GRAPH_PRODUCTION_EVIDENCE_WORKFLOW =
31
+ GRAPH_PRODUCTION_SOURCE_WORKFLOW;
32
+ export const GRAPH_PRODUCTION_EVIDENCE_ENVIRONMENT =
33
+ GRAPH_PRODUCTION_SOURCE_ENVIRONMENT;
34
+ export const GRAPH_PRODUCTION_EVIDENCE_REF = GRAPH_PRODUCTION_SOURCE_REF;
35
+ export const GRAPH_MINIMUM_SHADOW_OBSERVATION_MS = 30 * 60 * 1_000;
36
+ export const GRAPH_MINIMUM_CANARY_OBSERVATION_MS = 60 * 60 * 1_000;
37
+ export const GRAPH_MINIMUM_RETIREMENT_OBSERVATION_MS = 60 * 60 * 1_000;
38
+ export const GRAPH_MINIMUM_LEGACY_WRITER_OBSERVATION_MS = 60 * 60 * 1_000;
39
+ export const GRAPH_PRODUCTION_SOURCE_KINDS = Object.freeze([
40
+ "physical_self_hosted_runner",
41
+ "authenticated_production_endpoint",
42
+ ]);
19
43
 
20
44
  export const GRAPH_REQUIRED_CUTOVER_STAGES = Object.freeze([
21
45
  "shadow",
@@ -153,7 +177,11 @@ function timestamp(value, field) {
153
177
  return { value: normalized, milliseconds };
154
178
  }
155
179
 
156
- function windowEvidence(input, field, { notBefore } = {}) {
180
+ function windowEvidence(
181
+ input,
182
+ field,
183
+ { notBefore, minimumDurationMs = 1 } = {},
184
+ ) {
157
185
  const started = timestamp(input?.startedAt, `${field}.startedAt`);
158
186
  const ended = timestamp(input?.endedAt, `${field}.endedAt`);
159
187
  if (ended.milliseconds <= started.milliseconds) {
@@ -169,10 +197,18 @@ function windowEvidence(input, field, { notBefore } = {}) {
169
197
  { notBefore, startedAt: started.value },
170
198
  );
171
199
  }
200
+ const durationMs = ended.milliseconds - started.milliseconds;
201
+ if (durationMs < minimumDurationMs || input?.durationMs !== durationMs) {
202
+ throw evidenceError(
203
+ "CC_GRAPH_PRODUCTION_OBSERVATION_TOO_SHORT",
204
+ `${field}.durationMs must equal its timestamp window and be at least ${minimumDurationMs}`,
205
+ { field, durationMs, minimumDurationMs },
206
+ );
207
+ }
172
208
  return {
173
209
  startedAt: started.value,
174
210
  endedAt: ended.value,
175
- durationMs: ended.milliseconds - started.milliseconds,
211
+ durationMs,
176
212
  };
177
213
  }
178
214
 
@@ -206,6 +242,109 @@ function exactMembers(actual, expected, code, message, details = {}) {
206
242
  }
207
243
  }
208
244
 
245
+ function normalizeObservationSources(input, expectedDigestsByPlatform, field) {
246
+ const sources = Array.isArray(input)
247
+ ? input.map((entry, index) => {
248
+ const sourceKind = text(
249
+ entry?.sourceKind,
250
+ `${field}[${index}].sourceKind`,
251
+ );
252
+ const platform = String(entry?.platform || "").toLowerCase();
253
+ if (
254
+ !GRAPH_PRODUCTION_SOURCE_KINDS.includes(sourceKind) ||
255
+ !GRAPH_CUTOVER_REQUIRED_PLATFORMS.includes(platform)
256
+ ) {
257
+ throw evidenceError(
258
+ "CC_GRAPH_PRODUCTION_SOURCE_INVALID",
259
+ `${field}[${index}] must identify a physical self-hosted runner or authenticated production endpoint on a required platform`,
260
+ { field, sourceKind, platform },
261
+ );
262
+ }
263
+ const observationDigests = Array.isArray(entry?.observationDigests)
264
+ ? entry.observationDigests.map((value, digestIndex) =>
265
+ digest(
266
+ value,
267
+ `${field}[${index}].observationDigests[${digestIndex}]`,
268
+ ),
269
+ )
270
+ : [];
271
+ exactMembers(
272
+ observationDigests,
273
+ expectedDigestsByPlatform.get(platform) || [],
274
+ "CC_GRAPH_PRODUCTION_SOURCE_BINDING_INCOMPLETE",
275
+ "each production source must bind every required observation digest for its platform",
276
+ { field, platform },
277
+ );
278
+ return {
279
+ platform,
280
+ sourceKind,
281
+ sourceId: text(entry?.sourceId, `${field}[${index}].sourceId`),
282
+ identityDigest: digest(
283
+ entry?.identityDigest,
284
+ `${field}[${index}].identityDigest`,
285
+ ),
286
+ keyId: digest(entry?.keyId, `${field}[${index}].keyId`),
287
+ registryDigest: digest(
288
+ entry?.registryDigest,
289
+ `${field}[${index}].registryDigest`,
290
+ ),
291
+ runnerRegistrationId: positive(
292
+ entry?.runnerRegistrationId,
293
+ `${field}[${index}].runnerRegistrationId`,
294
+ ),
295
+ runnerName: text(entry?.runnerName, `${field}[${index}].runnerName`),
296
+ runnerLabels: Array.isArray(entry?.runnerLabels)
297
+ ? [...entry.runnerLabels]
298
+ .map((label, labelIndex) =>
299
+ text(label, `${field}[${index}].runnerLabels[${labelIndex}]`),
300
+ )
301
+ .sort()
302
+ : [],
303
+ challenge: text(entry?.challenge, `${field}[${index}].challenge`),
304
+ nonce: text(entry?.nonce, `${field}[${index}].nonce`),
305
+ rawLogRoot: digest(
306
+ entry?.rawLogRoot,
307
+ `${field}[${index}].rawLogRoot`,
308
+ ),
309
+ receiptDigest: digest(
310
+ entry?.receiptDigest,
311
+ `${field}[${index}].receiptDigest`,
312
+ ),
313
+ authenticationDigest: digest(
314
+ entry?.authenticationDigest,
315
+ `${field}[${index}].authenticationDigest`,
316
+ ),
317
+ observationDigests: [...observationDigests].sort(),
318
+ };
319
+ })
320
+ : [];
321
+ sources.sort((left, right) =>
322
+ compareCanonicalText(left.platform, right.platform),
323
+ );
324
+ exactMembers(
325
+ sources.map((entry) => entry.platform),
326
+ GRAPH_CUTOVER_REQUIRED_PLATFORMS,
327
+ "CC_GRAPH_PRODUCTION_SOURCE_COVERAGE_INCOMPLETE",
328
+ "production observations must cover Linux, macOS, and Windows sources",
329
+ { field },
330
+ );
331
+ if (
332
+ new Set(sources.map((entry) => entry.identityDigest)).size !==
333
+ sources.length ||
334
+ new Set(sources.map((entry) => entry.sourceId)).size !== sources.length ||
335
+ new Set(sources.map((entry) => entry.runnerRegistrationId)).size !==
336
+ sources.length ||
337
+ new Set(sources.map((entry) => entry.keyId)).size !== sources.length
338
+ ) {
339
+ throw evidenceError(
340
+ "CC_GRAPH_PRODUCTION_HOST_IDENTITY_REUSED",
341
+ "Linux, macOS, and Windows production observations must come from distinct source identities",
342
+ { field },
343
+ );
344
+ }
345
+ return sources;
346
+ }
347
+
209
348
  function verifyDigest(input, normalized, domain, field = "evidenceDigest") {
210
349
  const supplied = digest(input?.[field], field);
211
350
  const unsigned = clone(normalized);
@@ -292,7 +431,9 @@ function normalizeShadow(input) {
292
431
  "shadow evidence must cover every semantic projection dimension",
293
432
  );
294
433
  return {
295
- ...windowEvidence(input, "shadow"),
434
+ ...windowEvidence(input, "shadow", {
435
+ minimumDurationMs: GRAPH_MINIMUM_SHADOW_OBSERVATION_MS,
436
+ }),
296
437
  runCount: positive(input?.runCount, "shadow.runCount"),
297
438
  divergenceCount: zero(input?.divergenceCount, "shadow.divergenceCount"),
298
439
  unknownEffectCount: zero(
@@ -347,7 +488,9 @@ function normalizeCanary(input, commitSha) {
347
488
  "canary evidence must cover every required platform",
348
489
  );
349
490
  return {
350
- ...windowEvidence(input, "canary"),
491
+ ...windowEvidence(input, "canary", {
492
+ minimumDurationMs: GRAPH_MINIMUM_CANARY_OBSERVATION_MS,
493
+ }),
351
494
  internalRunCount: positive(
352
495
  input?.internalRunCount,
353
496
  "canary.internalRunCount",
@@ -542,7 +685,10 @@ function normalizeMigratedWriterObservation(input, identity, entry, notBefore) {
542
685
  );
543
686
  const normalized = {
544
687
  ...observedIdentity,
545
- ...windowEvidence(input, "legacyReadOnly", { notBefore }),
688
+ ...windowEvidence(input, "legacyReadOnly", {
689
+ notBefore,
690
+ minimumDurationMs: GRAPH_MINIMUM_LEGACY_WRITER_OBSERVATION_MS,
691
+ }),
546
692
  observationSampleCount: positive(
547
693
  input?.observationSampleCount,
548
694
  "legacyReadOnly.observationSampleCount",
@@ -666,6 +812,7 @@ function normalizeEntry(input, expected, commitSha) {
666
812
  commitSha,
667
813
  contract,
668
814
  requiredPlatforms: GRAPH_CUTOVER_REQUIRED_PLATFORMS,
815
+ minimumDurationMs: GRAPH_MINIMUM_RETIREMENT_OBSERVATION_MS,
669
816
  },
670
817
  );
671
818
  normalized.legacyReadOnly = normalizeGraphLegacyWriterObservation(
@@ -677,6 +824,7 @@ function normalizeEntry(input, expected, commitSha) {
677
824
  commitSha,
678
825
  contract,
679
826
  notBefore: finalLedger.canonicalActivatedAt,
827
+ minimumDurationMs: GRAPH_MINIMUM_LEGACY_WRITER_OBSERVATION_MS,
680
828
  },
681
829
  );
682
830
  } else {
@@ -687,6 +835,42 @@ function normalizeEntry(input, expected, commitSha) {
687
835
  finalLedger.canonicalActivatedAt,
688
836
  );
689
837
  }
838
+ const expectedLegacyErrorCode =
839
+ expected.manifest?.cutoverPolicy?.legacyMutationErrorCodes?.[
840
+ identity.surface
841
+ ];
842
+ for (const probe of [
843
+ ...normalized.legacyReadOnly.mutationProbes,
844
+ ...(normalized.retirementEvidence?.mutationProbes || []),
845
+ ]) {
846
+ if (probe.errorCode !== expectedLegacyErrorCode) {
847
+ throw evidenceError(
848
+ "CC_GRAPH_PRODUCTION_LEGACY_ERROR_CODE_MISMATCH",
849
+ `${probe.mutationFunction} did not return the manifest-pinned legacy writer error code`,
850
+ );
851
+ }
852
+ }
853
+ const commonObservationDigests = [
854
+ normalized.shadow.evidenceDigest,
855
+ normalized.canary.evidenceDigest,
856
+ normalized.rollback.evidenceDigest,
857
+ normalized.finalLedger.evidenceDigest,
858
+ normalized.legacyReadOnly.evidenceDigest,
859
+ ...(normalized.retirementEvidence
860
+ ? [normalized.retirementEvidence.evidenceDigest]
861
+ : []),
862
+ ];
863
+ const expectedDigestsByPlatform = new Map(
864
+ normalized.canary.platformJourneys.map(({ platform }) => [
865
+ platform,
866
+ [...commonObservationDigests],
867
+ ]),
868
+ );
869
+ normalized.observationSources = normalizeObservationSources(
870
+ input?.observationSources,
871
+ expectedDigestsByPlatform,
872
+ "observationSources",
873
+ );
690
874
  normalized.evidenceDigest = verifyDigest(
691
875
  input,
692
876
  normalized,
@@ -730,6 +914,10 @@ function normalizeDisabledEntry(input, expected, commitSha) {
730
914
  input?.durableAuthorityClaimCount,
731
915
  "disabledEntry.durableAuthorityClaimCount",
732
916
  ),
917
+ disablementProbeDigest: digest(
918
+ input?.disablementProbeDigest,
919
+ "disabledEntry.disablementProbeDigest",
920
+ ),
733
921
  };
734
922
  if (
735
923
  normalized.surface !== expected.surface.originSurface ||
@@ -748,6 +936,16 @@ function normalizeDisabledEntry(input, expected, commitSha) {
748
936
  { surface: expected.surface.originSurface, entryId: expected.entry.id },
749
937
  );
750
938
  }
939
+ normalized.observationSources = normalizeObservationSources(
940
+ input?.observationSources,
941
+ new Map(
942
+ GRAPH_CUTOVER_REQUIRED_PLATFORMS.map((platform) => [
943
+ platform,
944
+ [normalized.disablementProbeDigest],
945
+ ]),
946
+ ),
947
+ "disabledEntry.observationSources",
948
+ );
751
949
  normalized.evidenceDigest = verifyDigest(
752
950
  input,
753
951
  normalized,
@@ -789,6 +987,41 @@ export function graphProductionCutoverEvidenceDigest(value) {
789
987
  return graphDigest(unsigned, "cc.graph.production-cutover-evidence/v1");
790
988
  }
791
989
 
990
+ export function graphProductionExpectedIdentityBinding({
991
+ commitSha,
992
+ repository,
993
+ workflow = GRAPH_PRODUCTION_EVIDENCE_WORKFLOW,
994
+ ref = GRAPH_PRODUCTION_EVIDENCE_REF,
995
+ environment = GRAPH_PRODUCTION_EVIDENCE_ENVIRONMENT,
996
+ workflowRunId,
997
+ workflowRunAttempt,
998
+ manifestDigest,
999
+ sourceRegistryDigest,
1000
+ challenge,
1001
+ }) {
1002
+ return graphDigest(
1003
+ {
1004
+ commitSha: commit(commitSha),
1005
+ repository: text(repository, "provenance.repository"),
1006
+ workflow: text(workflow, "provenance.workflow"),
1007
+ ref: text(ref, "provenance.ref"),
1008
+ environment: text(environment, "provenance.environment"),
1009
+ workflowRunId: positive(workflowRunId, "provenance.workflowRunId"),
1010
+ workflowRunAttempt: positive(
1011
+ workflowRunAttempt,
1012
+ "provenance.workflowRunAttempt",
1013
+ ),
1014
+ manifestDigest: digest(manifestDigest, "provenance.manifestDigest"),
1015
+ sourceRegistryDigest: digest(
1016
+ sourceRegistryDigest,
1017
+ "provenance.sourceRegistryDigest",
1018
+ ),
1019
+ challenge: text(challenge, "provenance.challenge"),
1020
+ },
1021
+ "cc.graph.production-expected-identity/v1",
1022
+ );
1023
+ }
1024
+
792
1025
  export function normalizeGraphProductionCutoverEvidence(
793
1026
  input,
794
1027
  {
@@ -796,8 +1029,13 @@ export function normalizeGraphProductionCutoverEvidence(
796
1029
  expectedCommitSha = undefined,
797
1030
  expectedRepository = undefined,
798
1031
  expectedEnvironment = undefined,
1032
+ expectedWorkflow = GRAPH_PRODUCTION_EVIDENCE_WORKFLOW,
799
1033
  expectedWorkflowRunId = undefined,
800
1034
  expectedWorkflowRunAttempt = undefined,
1035
+ expectedRegistryDigest = undefined,
1036
+ expectedChallenge = undefined,
1037
+ jobsInventory = undefined,
1038
+ clock = Date.now,
801
1039
  } = {},
802
1040
  ) {
803
1041
  if (input?.schema !== GRAPH_PRODUCTION_CUTOVER_EVIDENCE_SCHEMA) {
@@ -813,8 +1051,19 @@ export function normalizeGraphProductionCutoverEvidence(
813
1051
  "production cutover evidence does not match the expected commit",
814
1052
  );
815
1053
  }
1054
+ const manifestContract =
1055
+ assertGraphProductionRuntimeSurfaceManifest(manifest);
1056
+ const manifestDigest = digest(input?.manifestDigest, "manifestDigest");
1057
+ if (manifestDigest !== manifestContract.manifestDigest) {
1058
+ throw evidenceError(
1059
+ "CC_GRAPH_PRODUCTION_MANIFEST_CONTRACT_MISMATCH",
1060
+ "production evidence does not bind the frozen five-surface manifest",
1061
+ );
1062
+ }
816
1063
  const provenance = {
817
1064
  repository: text(input?.provenance?.repository, "provenance.repository"),
1065
+ workflow: text(input?.provenance?.workflow, "provenance.workflow"),
1066
+ ref: text(input?.provenance?.ref, "provenance.ref"),
818
1067
  environment: text(input?.provenance?.environment, "provenance.environment"),
819
1068
  workflowRunId: positive(
820
1069
  input?.provenance?.workflowRunId,
@@ -824,24 +1073,47 @@ export function normalizeGraphProductionCutoverEvidence(
824
1073
  input?.provenance?.workflowRunAttempt,
825
1074
  "provenance.workflowRunAttempt",
826
1075
  ),
827
- oidcAttestationDigest: digest(
828
- input?.provenance?.oidcAttestationDigest,
829
- "provenance.oidcAttestationDigest",
1076
+ manifestDigest: digest(
1077
+ input?.provenance?.manifestDigest,
1078
+ "provenance.manifestDigest",
1079
+ ),
1080
+ sourceRegistryDigest: digest(
1081
+ input?.provenance?.sourceRegistryDigest,
1082
+ "provenance.sourceRegistryDigest",
1083
+ ),
1084
+ challenge: text(input?.provenance?.challenge, "provenance.challenge"),
1085
+ expectedIdentityBinding: digest(
1086
+ input?.provenance?.expectedIdentityBinding,
1087
+ "provenance.expectedIdentityBinding",
830
1088
  ),
831
1089
  };
1090
+ const expectedIdentityBinding = graphProductionExpectedIdentityBinding({
1091
+ commitSha,
1092
+ ...provenance,
1093
+ });
832
1094
  if (
833
1095
  (expectedRepository && provenance.repository !== expectedRepository) ||
1096
+ provenance.workflow !== GRAPH_PRODUCTION_EVIDENCE_WORKFLOW ||
1097
+ provenance.ref !== GRAPH_PRODUCTION_EVIDENCE_REF ||
1098
+ provenance.environment !== GRAPH_PRODUCTION_EVIDENCE_ENVIRONMENT ||
1099
+ (expectedWorkflow && provenance.workflow !== expectedWorkflow) ||
834
1100
  (expectedEnvironment && provenance.environment !== expectedEnvironment) ||
835
1101
  (expectedWorkflowRunId !== undefined &&
836
1102
  provenance.workflowRunId !== Number(expectedWorkflowRunId)) ||
837
1103
  (expectedWorkflowRunAttempt !== undefined &&
838
- provenance.workflowRunAttempt !== Number(expectedWorkflowRunAttempt))
1104
+ provenance.workflowRunAttempt !== Number(expectedWorkflowRunAttempt)) ||
1105
+ provenance.manifestDigest !== manifestDigest ||
1106
+ (expectedRegistryDigest &&
1107
+ provenance.sourceRegistryDigest !== expectedRegistryDigest) ||
1108
+ (expectedChallenge && provenance.challenge !== expectedChallenge) ||
1109
+ provenance.expectedIdentityBinding !== expectedIdentityBinding
839
1110
  ) {
840
1111
  throw evidenceError(
841
1112
  "CC_GRAPH_PRODUCTION_PROVENANCE_MISMATCH",
842
1113
  "production evidence provenance does not match the protected producer",
843
1114
  {
844
1115
  expectedRepository,
1116
+ expectedWorkflow,
845
1117
  expectedEnvironment,
846
1118
  expectedWorkflowRunId:
847
1119
  expectedWorkflowRunId === undefined
@@ -855,6 +1127,30 @@ export function normalizeGraphProductionCutoverEvidence(
855
1127
  },
856
1128
  );
857
1129
  }
1130
+ if (!jobsInventory) {
1131
+ throw evidenceError(
1132
+ "CC_GRAPH_PRODUCTION_SOURCE_INVENTORY_REQUIRED",
1133
+ "production evidence verification requires the complete current-attempt Actions job inventory",
1134
+ );
1135
+ }
1136
+ const sourceBundle = normalizeGraphProductionSourceBundle(
1137
+ {
1138
+ registry: input?.sourceRegistry,
1139
+ fragments: input?.sourceFragments,
1140
+ },
1141
+ {
1142
+ manifest,
1143
+ expectedCommitSha: commitSha,
1144
+ expectedRepository: provenance.repository,
1145
+ expectedRegistryDigest:
1146
+ expectedRegistryDigest || provenance.sourceRegistryDigest,
1147
+ expectedWorkflowRunId: provenance.workflowRunId,
1148
+ expectedWorkflowRunAttempt: provenance.workflowRunAttempt,
1149
+ expectedChallenge: provenance.challenge,
1150
+ jobsInventory,
1151
+ clock,
1152
+ },
1153
+ );
858
1154
  const allEntries = manifestEntries(manifest);
859
1155
  const durable = allEntries.filter(
860
1156
  ({ entry }) => entry.cutoverStrategy !== "disabled",
@@ -913,21 +1209,38 @@ export function normalizeGraphProductionCutoverEvidence(
913
1209
  `${right.surface}/${right.entryId}`,
914
1210
  ),
915
1211
  );
1212
+ if (
1213
+ canonicalGraphJson(entries) !== canonicalGraphJson(sourceBundle.entries) ||
1214
+ canonicalGraphJson(disabledEntries) !==
1215
+ canonicalGraphJson(sourceBundle.disabledEntries)
1216
+ ) {
1217
+ throw evidenceError(
1218
+ "CC_GRAPH_PRODUCTION_SOURCE_DERIVATION_MISMATCH",
1219
+ "aggregate entries do not exactly match values derived from signed raw source receipts",
1220
+ );
1221
+ }
916
1222
  const observedAt = timestamp(input?.observedAt, "observedAt");
917
- const latestEntryObservation = Math.max(
918
- ...entries.map((entry) => Date.parse(entry.legacyReadOnly.endedAt)),
1223
+ const latestSignedCollection = Math.max(
1224
+ ...sourceBundle.fragments.flatMap((fragment) =>
1225
+ fragment.receipts.map((receipt) =>
1226
+ Date.parse(receipt.payload.collectorEndedAt),
1227
+ ),
1228
+ ),
919
1229
  );
920
- if (observedAt.milliseconds < latestEntryObservation) {
1230
+ if (observedAt.milliseconds !== latestSignedCollection) {
921
1231
  throw evidenceError(
922
1232
  "CC_GRAPH_PRODUCTION_OBSERVATION_INVALID",
923
- "aggregate observedAt cannot predate an entry observation window",
1233
+ "aggregate observedAt must equal the latest signed collector completion",
924
1234
  );
925
1235
  }
926
1236
  const normalized = {
927
1237
  schema: GRAPH_PRODUCTION_CUTOVER_EVIDENCE_SCHEMA,
928
1238
  commitSha,
1239
+ manifestDigest,
929
1240
  observedAt: observedAt.value,
930
1241
  provenance,
1242
+ sourceRegistry: sourceBundle.registry,
1243
+ sourceFragments: sourceBundle.fragments,
931
1244
  entries,
932
1245
  disabledEntries,
933
1246
  };
@@ -941,6 +1254,10 @@ export function normalizeGraphProductionCutoverEvidence(
941
1254
 
942
1255
  export function createGraphProductionCutoverReceipt(input, options = {}) {
943
1256
  const evidence = normalizeGraphProductionCutoverEvidence(input, options);
1257
+ const observationSources = [
1258
+ ...evidence.entries.flatMap((entry) => entry.observationSources),
1259
+ ...evidence.disabledEntries.flatMap((entry) => entry.observationSources),
1260
+ ];
944
1261
  const surfaceNames = new Set([
945
1262
  ...evidence.entries.map((entry) => entry.surface),
946
1263
  ...evidence.disabledEntries.map((entry) => entry.surface),
@@ -966,6 +1283,16 @@ export function createGraphProductionCutoverReceipt(input, options = {}) {
966
1283
  (total, entry) => total + entry.legacyReadOnly.mutationProbes.length,
967
1284
  0,
968
1285
  ),
1286
+ observationSourceCount: observationSources.length,
1287
+ distinctObservationSourceCount: new Set(
1288
+ observationSources.map((source) => source.identityDigest),
1289
+ ).size,
1290
+ minimumObservationWindowMs: {
1291
+ shadow: GRAPH_MINIMUM_SHADOW_OBSERVATION_MS,
1292
+ canary: GRAPH_MINIMUM_CANARY_OBSERVATION_MS,
1293
+ retirement: GRAPH_MINIMUM_RETIREMENT_OBSERVATION_MS,
1294
+ legacyWriter: GRAPH_MINIMUM_LEGACY_WRITER_OBSERVATION_MS,
1295
+ },
969
1296
  provenance: clone(evidence.provenance),
970
1297
  };
971
1298
  receipt.receiptDigest = graphDigest(