chainlesschain 0.166.14 → 0.166.16

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 (195) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-C8SyFhOo.js → AIOps-CTxa2Wbq.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-Dup5gFK0.js → ActionButton-DHrQyYhy.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-8FyOWA7V.js → Analytics-DQiUqcNl.js} +3 -3
  5. package/src/assets/web-panel/assets/{AppLayout-DBzdU9dg.js → AppLayout-_Iz4r6wt.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-C_0Z8gNy.js → Artifacts-BNs63YHo.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-Cf2a-58m.js → Audit-BgHIqRLn.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-M7w-2zgB.js → BackgroundAgents-DUQ5axBB.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-CCTHQXoL.js → Backup-CJ51E9Vw.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-DjXpeFwJ.js → BaseInput-o-5toA_R.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-cAvchsJU.js → Chat-Cu7a-x1L.js} +6 -6
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-DvEvxlZv.js → ChatBubbleRenderer-BUyYkAXr.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-DKs6qvOn.js → Checkbox-DwdmZ71L.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-DlkA7b9X.js → Codegen-BrH2eYiP.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-CkfdKhyJ.js → Col-BVJQEP2j.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-iNXMHzj6.js → Community-FGO3vZPq.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-DQxPEd09.js → Compact-DYGAFVnT.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-CSKvMCjs.js → Compliance-CRvc8Bdt.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-CkWqazpD.js → Cowork-D6Ah2m7A.js} +3 -3
  20. package/src/assets/web-panel/assets/{Cron-k6W8IzhB.js → Cron-AXbU2ifR.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-C8MS1OuY.js → Crosschain-C5DXgEFd.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-DJuCcgiQ.js → DID-Bxh9QV9v.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-LeThKzp3.js → Dashboard-B-ZHEta-.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-BvxwgpE4.js → Dropdown-66Ik1jqa.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-n3e_a1M3.js → EmailListRenderer-CCWk1BIM.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-509W6W6v.js → FamilyGuardDashboard--9faIULK.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-xgZReQm0.js → Federation-CejW9CgX.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-jZOhKcQx.js → FormItemContext-acHgcXki.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-Jo0ULzHv.js → GenericCardRenderer-D2ghd_K4.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-BsRcyWQP.js → Git-BOe1YJrV.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-JlpMYTAk.js → Governance-BjgzJsof.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-CUEz872-.js → Inference-CXCD5rnP.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-DgrvvEDU.js → KnowledgeGraph-CHA8QgJT.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-Df-KY4U3.js → Logs-DORPMcL6.js} +2 -2
  35. package/src/assets/web-panel/assets/MarkdownRenderer-CPPHz01z.js +1 -0
  36. package/src/assets/web-panel/assets/{Marketplace-kPZDTOeI.js → Marketplace-BCFlo1Yx.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-CRRzPqNX.js → McpTools-Dl_uZAKh.js} +2 -2
  38. package/src/assets/web-panel/assets/{Memory-D6pNSkg9.js → Memory-DupHamwz.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-g-pvtwRC.js → MobileBridge-BRSMPJxW.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-Bzayfrfe.js → MobileProjects-BHpuzsDl.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-B7S3W-XL.js → Mtc-Ch0jOQfZ.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-B8OMEWVb.js → MtcAudit-qzwQrDi7.js} +2 -2
  43. package/src/assets/web-panel/assets/{Multisig-CowRp7GH.js → Multisig-B5wHzUcm.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-BkQNyi4c.js → NLProgramming-Hy9uAiA0.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-Cm7gtkKz.js → Notes-DkuchMC7.js} +4 -4
  46. package/src/assets/web-panel/assets/{NotificationSettings-CbPTVh9m.js → NotificationSettings-CYZhD7Pi.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-Dswi26mJ.js → OrderTableRenderer-DKOoqajt.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-BkjQ9yy7.js → Organization-D9AXn6j8.js} +4 -4
  49. package/src/assets/web-panel/assets/{Overflow-DmRFXP7Y.js → Overflow-Bg15J6hX.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-DQE1toAu.js → P2P--B54HoQn.js} +2 -2
  51. package/src/assets/web-panel/assets/PdhVaultBrowser-Cz9Kk2Nb.js +7 -0
  52. package/src/assets/web-panel/assets/{Permissions-xSjfOaiU.js → Permissions-DHx38eS1.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-B9ICLqVP.js → PersonalDataHub-Dw8i2hAs.js} +4 -4
  54. package/src/assets/web-panel/assets/{Pipeline-CUNwyN9E.js → Pipeline-B2o6Ur2G.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-SOoCricX.js → Privacy-8g-h2fnu.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-CSaFul0F.js → ProjectInit-DuVTcINx.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-CueiB_oe.js → ProjectSettings-C8njvdZX.js} +2 -2
  58. package/src/assets/web-panel/assets/Projects-Ca9BAfu7.js +1 -0
  59. package/src/assets/web-panel/assets/{Providers-CTqf_31h.js → Providers-wFqgod_W.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-C8ZP31E9.js → QrScannerModal-9NCXrpmA.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-CPgn4713.js → QuickAsk-WUTNvFkY.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-8_e0zw0i.js → Recommend-xT4nYmju.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-iT7lbGqw.js → RemoteSession-fak1PBGC.js} +3 -3
  64. package/src/assets/web-panel/assets/{Reputation-B4q9SUOz.js → Reputation-D1AkQvD-.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-CA_nASyL.js → Row-DVfm6xGt.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-Cwb0E3yv.js → RssFeed-BDunoLSE.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-D9iVsOxJ.js → Search-OPycRoU3.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-DPLbYchG.js → Security-C8-Fm3Dt.js} +3 -3
  69. package/src/assets/web-panel/assets/{Services-DGMVL_LF.js → Services-qM_sMpMl.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-B_6kym0D.js → Skeleton-D3HBJjVF.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-Ds6r0dXB.js → Skills-jr67X-tP.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-BLHAbBhb.js → Sla-CS3ggyCg.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-CZuddd14.js → SpeechSettings-CTOCYxOi.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-CdBq7oSl.js → SyncSettings-D3s3cLT5.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-DfT60Nyl.js → Tasks-CdfoJ25m.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-RLfsKy2V.js → Templates-Drb4AI8X.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-Cw8xCNLE.js → Tenant-DzwR3Qkz.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-BOUkNu4f.js → Terminal-D-ESq7xp.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-CgGZWaRm.js → TimelineRenderer-BK-AUuws.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-CpwjVyir.js → Tokens-xGTouuNp.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-DL21H-eb.js → Trigger-BvZj-v2H.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-B3ffFclW.js → Trust-BLC9CGmc.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-CkBeb5aI.js → UkeySign-BSuCXpmE.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-U2iVaE3r.js → VideoEditing-DT3A-_Ql.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-CQk2wzYd.js → Wallet-c1OXao85.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-DYIuIYzT.js → WebAuthn-CuA-F7KJ.js} +5 -5
  87. package/src/assets/web-panel/assets/{WorkflowEditor-tg1c2mZQ.js → WorkflowEditor-MANZrf82.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-CtL61spq.js → chat-D4Y4Ey0O.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-DKpbeo8s.js → colors-DDH06hKO.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-gPZsrswt.js → compact-item-Cym756Xj.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-CfHlwzVr.js → createContext-By4Nb1k-.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-KO3pAGeO.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-MV47g_6X.js → hasIn-Ch7OY258.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-DMMvlLp9.js → index-6-2eYBXr.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-DsH-i_3d.js → index-6CpOV3Pn.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-CB1HnbNK.js → index-ArMrnx0v.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DU4rZpiK.js → index-B51otVoZ.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-CRv_uMLV.js → index-BBBUn5v7.js} +1 -1
  99. package/src/assets/web-panel/assets/{index-srBc9U_s.js → index-BFydNU6a.js} +1 -1
  100. package/src/assets/web-panel/assets/{index-DnZ_zS1M.js → index-BIrRX-ra.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-DGtEBodr.js → index-BZB9q-Lp.js} +1 -1
  102. package/src/assets/web-panel/assets/index-Bh4Rgkg2.js +1 -0
  103. package/src/assets/web-panel/assets/{index-BjlPD7GO.js → index-Bo20BfdS.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-C_B9LiKs.js → index-CF3Lhyns.js} +1 -1
  105. package/src/assets/web-panel/assets/{index-Rb7CRMw9.js → index-CGsvEbbq.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-DIeJmlAA.js → index-CJPks33Y.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-xUlgG0Xe.js → index-CT-3FYwd.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-6G0ETFTW.js → index-CUSUzaB8.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-CzfxTSPW.js → index-CV1UC4zo.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-CmPJpLoD.js → index-CV8qXTvb.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-fYcS7NS0.js → index-CZn6dmZe.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-Cjv0kKCU.js → index-CjNUTBwe.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-Df9sWKpl.js → index-CpudmDhf.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-C9RF4ieu.js → index-CykROEJA.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-Zvkt9xqi.js → index-D5sADPGY.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-yxxXlqBA.js → index-D9le0-dc.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-BBw52sEl.js → index-DAkk1M7x.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-ExAGPAnX.js → index-DNEJzc0r.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-BQp5M0s1.js → index-DNlxihs8.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-Ddcpj1El.js → index-DiewptTL.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-BOyGUuBE.js → index-DjwgJ5Jd.js} +1 -1
  122. package/src/assets/web-panel/assets/index-Dox_coTY.js +1 -0
  123. package/src/assets/web-panel/assets/{index-CmM9jqwe.js → index-DtGValNi.js} +1 -1
  124. package/src/assets/web-panel/assets/{index-DfLyHCWi.js → index-DvnywwUj.js} +1 -1
  125. package/src/assets/web-panel/assets/{index-BN-wxSQ8.js → index-OU8DZ9BL.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-D1XB5-t5.js → index-XGeQMvWK.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-C4lF-f_z.js → index-ZY2Qs_Sn.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-C9gtY4nG.js → index-jx7kr6mB.js} +3 -3
  129. package/src/assets/web-panel/assets/{index-C6nC_DyY.js → index-mL3nOcH6.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-Dd19vRwy.js → index-oBsfUbyH.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-bSFrAszw.js → index-q-s04OKn.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-D_Pl1GnW.js → index-tZ5BUdad.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-BeOZoSZ4.js → initDefaultProps-DGcX9iPP.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-BXE8VMKx.js → motion-2SeL1D8A.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-I9sdvPGX.js → move-3cYbQCpS.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-d25zrJd0.js → mtc-parser-IDdxfw0z.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-D71V_T4e.js → omit-BNswdKO6.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-BNZKpIAR.js → pickAttrs-BwCnem2I.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-jOcgxTTz.js → placementArrow-B9PtZ5rq.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-pta_O8AU.js → responsiveObserve-CPB6iSzV.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-Dlq6om7z.js → slide-DZgyblZa.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-BnkO_yRv.js → statusUtils-D82IBdsx.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-p8NfWmcG.js → styleChecker-qSHtosM-.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-KcjZZ5U7.js → useFlexGapSupport-CmWO7h0I.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-gqonDMWB.js → useFs-yLrs_ub4.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-jIIfE4go.js → usePersonalDataHub-CJ1VW6rh.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-Dyo7fBrM.js → vnode-CzY_moQ4.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-6l8BMg9W.js → zoom-DAa7rEY5.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/commands/agent.js +62 -11
  151. package/src/commands/learning.js +71 -1
  152. package/src/commands/team.js +100 -3
  153. package/src/data/changelog.json +29 -3
  154. package/src/harness/worktree-isolator.js +74 -9
  155. package/src/lib/agent-team/task-lease.js +12 -1
  156. package/src/lib/agent-team/team-graph-runtime-adapter.js +6 -0
  157. package/src/lib/agent-team/team-runner.js +4 -0
  158. package/src/lib/agent-team/team-worktree.js +4 -0
  159. package/src/lib/capability-manifest.js +212 -1
  160. package/src/lib/evolution/evolution-artifact-ports.js +3277 -0
  161. package/src/lib/evolution/evolution-eval-gate.js +6442 -0
  162. package/src/lib/evolution/evolution-evidence-projector.js +3786 -0
  163. package/src/lib/evolution/evolution-ledger-ports.js +2689 -0
  164. package/src/lib/evolution/evolution-ledger.js +4280 -0
  165. package/src/lib/evolution/skill-candidate-registry.js +2258 -0
  166. package/src/lib/evolution/skill-execution-manifest.js +942 -0
  167. package/src/lib/evolution/skill-mutation-authority.js +1677 -0
  168. package/src/lib/evolution/skill-promotion-controller.js +506 -0
  169. package/src/lib/evolution/skill-release-registry.js +3277 -0
  170. package/src/lib/evolution/skill-target-matrix-eval.js +3233 -0
  171. package/src/lib/evolution/skill-writer-inventory-manifest.js +998 -0
  172. package/src/lib/evolution/skill-writer-inventory-validator.js +816 -0
  173. package/src/lib/formal-quality-eval-runtime.js +192 -0
  174. package/src/lib/graph-kernel/cutover-contract.js +7 -0
  175. package/src/lib/graph-kernel/cutover-ledger.js +8 -7
  176. package/src/lib/graph-kernel/evidence-digest.js +47 -0
  177. package/src/lib/graph-kernel/graph-runtime-surfaces.json +8 -1
  178. package/src/lib/graph-kernel/production-cutover-evidence.js +342 -15
  179. package/src/lib/graph-kernel/production-source-evidence.js +1599 -0
  180. package/src/lib/graph-kernel/retirement-evidence.js +15 -5
  181. package/src/lib/graph-kernel/runtime-surface-manifest.js +67 -2
  182. package/src/lib/headless-manifest.js +3 -1
  183. package/src/lib/learning/skill-improver.js +311 -43
  184. package/src/lib/learning/skill-synthesizer.js +236 -16
  185. package/src/lib/process-execution-broker/index.js +32 -0
  186. package/src/lib/process-execution-broker/platform-sandbox.js +5 -4
  187. package/src/lib/secure-fs.js +45 -2
  188. package/src/runtime/agent-core.js +17 -5
  189. package/src/runtime/headless-runner.js +16 -9
  190. package/src/assets/web-panel/assets/MarkdownRenderer-BEPDcSwz.js +0 -1
  191. package/src/assets/web-panel/assets/PdhVaultBrowser-BujyoDRG.js +0 -7
  192. package/src/assets/web-panel/assets/Projects-BOeXvU7y.js +0 -1
  193. package/src/assets/web-panel/assets/devWarning-DdqnMhzi.js +0 -1
  194. package/src/assets/web-panel/assets/index-BcK8o4ke.js +0 -1
  195. package/src/assets/web-panel/assets/index-DvbAjyYI.js +0 -1
@@ -0,0 +1,1599 @@
1
+ import {
2
+ createHash,
3
+ createPrivateKey,
4
+ createPublicKey,
5
+ sign,
6
+ verify,
7
+ } from "node:crypto";
8
+ import {
9
+ canonicalGraphEvidenceJson as canonicalGraphJson,
10
+ graphEvidenceDigest as graphDigest,
11
+ } from "./evidence-digest.js";
12
+ import { GRAPH_CUTOVER_REQUIRED_PLATFORMS } from "./cutover-contract.js";
13
+ import {
14
+ GRAPH_LEGACY_WRITER_OBSERVATION_SCHEMA,
15
+ GRAPH_RETIREMENT_EVIDENCE_SCHEMA,
16
+ graphLegacyWriterObservationDigest,
17
+ graphRetirementEvidenceDigest,
18
+ } from "./retirement-evidence.js";
19
+ import {
20
+ assertGraphProductionRuntimeSurfaceManifest,
21
+ graphRuntimeEntryManifestDigest,
22
+ graphRuntimeSurfaceEntry,
23
+ graphRuntimeSurfaceManifestDigest,
24
+ } from "./runtime-surface-manifest.js";
25
+
26
+ export const GRAPH_PRODUCTION_SOURCE_REGISTRY_SCHEMA =
27
+ "chainlesschain.graph-production-source-registry/v1";
28
+ export const GRAPH_PRODUCTION_SOURCE_FRAGMENT_SCHEMA =
29
+ "chainlesschain.graph-production-source-fragment/v1";
30
+ export const GRAPH_PRODUCTION_SOURCE_RECEIPT_SCHEMA =
31
+ "chainlesschain.graph-production-source-receipt/v1";
32
+ export const GRAPH_PRODUCTION_RAW_LOG_SCHEMA =
33
+ "chainlesschain.graph-production-raw-log/v1";
34
+ export const GRAPH_PRODUCTION_SOURCE_WORKFLOW =
35
+ ".github/workflows/graph-kernel-production-evidence.yml";
36
+ export const GRAPH_PRODUCTION_SOURCE_REF = "refs/heads/main";
37
+ export const GRAPH_PRODUCTION_SOURCE_ENVIRONMENT = "graph-kernel-production";
38
+ export const GRAPH_PRODUCTION_SOURCE_JOB = "source";
39
+ export const GRAPH_PRODUCTION_REQUIRED_RUNNER_LABELS = Object.freeze([
40
+ "graph-kernel-production",
41
+ "physical",
42
+ "self-hosted",
43
+ ]);
44
+ // Covers the 120-minute physical source matrix plus hosted aggregation while
45
+ // still rejecting observations from another dispatch/challenge.
46
+ export const GRAPH_PRODUCTION_RECEIPT_MAX_AGE_MS = 3 * 60 * 60 * 1_000;
47
+ export const GRAPH_PRODUCTION_OBSERVATION_MAX_AGE_MS = 7 * 24 * 60 * 60 * 1_000;
48
+ export const GRAPH_PRODUCTION_CLOCK_SKEW_MS = 30 * 1_000;
49
+
50
+ const COMMIT = /^[a-f0-9]{40}$/u;
51
+ const DIGEST = /^sha256:[a-f0-9]{64}$/u;
52
+ const CHALLENGE = /^[A-Za-z0-9_-]{32,128}$/u;
53
+ const BASE64URL = /^[A-Za-z0-9_-]+$/u;
54
+ const ZERO_DIGEST = `sha256:${"0".repeat(64)}`;
55
+ const WINDOW_EVENT_TYPES = new Set([
56
+ "shadow_observation",
57
+ "canary_observation",
58
+ "retirement_observation",
59
+ "legacy_writer_observation",
60
+ ]);
61
+ const BASE_EVENT_TYPES = Object.freeze([
62
+ "shadow_observation",
63
+ "canary_observation",
64
+ "rollback_observation",
65
+ "final_ledger_observation",
66
+ "legacy_writer_observation",
67
+ ]);
68
+
69
+ function sourceError(code, message, details = {}) {
70
+ const error = new Error(message);
71
+ error.name = "GraphProductionSourceEvidenceError";
72
+ error.code = code;
73
+ Object.assign(error, details);
74
+ return error;
75
+ }
76
+
77
+ function clone(value) {
78
+ return value == null ? value : JSON.parse(JSON.stringify(value));
79
+ }
80
+
81
+ function text(value, field, maximumBytes = 1_024) {
82
+ const normalized = String(value || "").trim();
83
+ if (!normalized || Buffer.byteLength(normalized, "utf8") > maximumBytes) {
84
+ throw sourceError(
85
+ "CC_GRAPH_PRODUCTION_SOURCE_INVALID",
86
+ `${field} must be a non-empty bounded string`,
87
+ { field },
88
+ );
89
+ }
90
+ return normalized;
91
+ }
92
+
93
+ function exactCommit(value, field = "commitSha") {
94
+ const normalized = String(value || "").toLowerCase();
95
+ if (!COMMIT.test(normalized)) {
96
+ throw sourceError(
97
+ "CC_GRAPH_PRODUCTION_SOURCE_INVALID",
98
+ `${field} must be an exact 40-character commit`,
99
+ { field },
100
+ );
101
+ }
102
+ return normalized;
103
+ }
104
+
105
+ function digest(value, field) {
106
+ const normalized = String(value || "").toLowerCase();
107
+ if (!DIGEST.test(normalized)) {
108
+ throw sourceError(
109
+ "CC_GRAPH_PRODUCTION_SOURCE_INVALID",
110
+ `${field} must be a sha256 digest`,
111
+ { field },
112
+ );
113
+ }
114
+ return normalized;
115
+ }
116
+
117
+ function positive(value, field) {
118
+ const normalized = Number(value);
119
+ if (!Number.isSafeInteger(normalized) || normalized < 1) {
120
+ throw sourceError(
121
+ "CC_GRAPH_PRODUCTION_SOURCE_INVALID",
122
+ `${field} must be a positive safe integer`,
123
+ { field, value },
124
+ );
125
+ }
126
+ return normalized;
127
+ }
128
+
129
+ function nonNegative(value, field) {
130
+ const normalized = Number(value);
131
+ if (!Number.isSafeInteger(normalized) || normalized < 0) {
132
+ throw sourceError(
133
+ "CC_GRAPH_PRODUCTION_SOURCE_INVALID",
134
+ `${field} must be a non-negative safe integer`,
135
+ { field, value },
136
+ );
137
+ }
138
+ return normalized;
139
+ }
140
+
141
+ function timestamp(value, field) {
142
+ const normalized = String(value || "");
143
+ const milliseconds = Date.parse(normalized);
144
+ const canonical = Number.isFinite(milliseconds)
145
+ ? new Date(milliseconds).toISOString()
146
+ : "";
147
+ if (
148
+ !Number.isFinite(milliseconds) ||
149
+ (canonical !== normalized &&
150
+ canonical.replace(/\.000Z$/u, "Z") !== normalized)
151
+ ) {
152
+ throw sourceError(
153
+ "CC_GRAPH_PRODUCTION_SOURCE_INVALID",
154
+ `${field} must be a canonical ISO timestamp`,
155
+ { field },
156
+ );
157
+ }
158
+ return { value: canonical, milliseconds };
159
+ }
160
+
161
+ function exactMembers(actual, expected, field) {
162
+ const normalizedActual = [...actual].sort();
163
+ const normalizedExpected = [...expected].sort();
164
+ if (
165
+ new Set(normalizedActual).size !== normalizedActual.length ||
166
+ normalizedActual.length !== normalizedExpected.length ||
167
+ normalizedActual.some((value, index) => value !== normalizedExpected[index])
168
+ ) {
169
+ throw sourceError(
170
+ "CC_GRAPH_PRODUCTION_SOURCE_COVERAGE_INCOMPLETE",
171
+ `${field} must contain exactly ${normalizedExpected.join(", ")}`,
172
+ { field, expected: normalizedExpected, actual: normalizedActual },
173
+ );
174
+ }
175
+ }
176
+
177
+ function uniqueStrings(value, field) {
178
+ if (!Array.isArray(value) || value.length === 0) {
179
+ throw sourceError(
180
+ "CC_GRAPH_PRODUCTION_SOURCE_INVALID",
181
+ `${field} must be a non-empty array`,
182
+ { field },
183
+ );
184
+ }
185
+ const normalized = value.map((entry, index) =>
186
+ text(entry, `${field}[${index}]`, 128),
187
+ );
188
+ if (new Set(normalized).size !== normalized.length) {
189
+ throw sourceError(
190
+ "CC_GRAPH_PRODUCTION_SOURCE_INVALID",
191
+ `${field} must not contain duplicates`,
192
+ { field },
193
+ );
194
+ }
195
+ return normalized.sort();
196
+ }
197
+
198
+ function uniqueLabels(value, field) {
199
+ if (!Array.isArray(value) || value.length === 0) {
200
+ throw sourceError(
201
+ "CC_GRAPH_PRODUCTION_SOURCE_INVALID",
202
+ `${field} must be a non-empty array`,
203
+ { field },
204
+ );
205
+ }
206
+ const normalized = value.map((entry, index) =>
207
+ text(entry, `${field}[${index}]`, 128).toLowerCase(),
208
+ );
209
+ if (new Set(normalized).size !== normalized.length) {
210
+ throw sourceError(
211
+ "CC_GRAPH_PRODUCTION_SOURCE_INVALID",
212
+ `${field} must not contain case-insensitive duplicates`,
213
+ { field },
214
+ );
215
+ }
216
+ return normalized.sort();
217
+ }
218
+
219
+ function includesEveryLabel(actual, required) {
220
+ const labels = new Set(actual);
221
+ return required.every((label) => labels.has(label));
222
+ }
223
+
224
+ function publicKeyDigest(publicKeySpki) {
225
+ const bytes = Buffer.from(publicKeySpki, "base64url");
226
+ return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
227
+ }
228
+
229
+ function ed25519PublicKey(publicKeySpki, field) {
230
+ if (!BASE64URL.test(String(publicKeySpki || ""))) {
231
+ throw sourceError(
232
+ "CC_GRAPH_PRODUCTION_SOURCE_REGISTRY_INVALID",
233
+ `${field} must be base64url-encoded SPKI`,
234
+ { field },
235
+ );
236
+ }
237
+ let key;
238
+ try {
239
+ key = createPublicKey({
240
+ key: Buffer.from(publicKeySpki, "base64url"),
241
+ format: "der",
242
+ type: "spki",
243
+ });
244
+ } catch (cause) {
245
+ throw sourceError(
246
+ "CC_GRAPH_PRODUCTION_SOURCE_REGISTRY_INVALID",
247
+ `${field} is not a valid public key`,
248
+ { field, cause },
249
+ );
250
+ }
251
+ if (key.asymmetricKeyType !== "ed25519") {
252
+ throw sourceError(
253
+ "CC_GRAPH_PRODUCTION_SOURCE_REGISTRY_INVALID",
254
+ `${field} must contain an Ed25519 public key`,
255
+ { field },
256
+ );
257
+ }
258
+ return key;
259
+ }
260
+
261
+ export function graphProductionSourceRegistryDigest(value) {
262
+ const unsigned = clone(value);
263
+ delete unsigned.registryDigest;
264
+ return graphDigest(unsigned, "cc.graph.production-source-registry/v1");
265
+ }
266
+
267
+ function normalizeJobsInventory(value, expected) {
268
+ if (value === undefined) return null;
269
+ const jobs = Array.isArray(value) ? value : value?.jobs;
270
+ if (!Array.isArray(jobs)) {
271
+ throw sourceError(
272
+ "CC_GRAPH_PRODUCTION_JOB_INVENTORY_INVALID",
273
+ "Actions job inventory must contain a jobs array",
274
+ );
275
+ }
276
+ if (
277
+ !Number.isSafeInteger(Number(value?.apiTotalCount)) ||
278
+ Number(value.apiTotalCount) !== jobs.length
279
+ ) {
280
+ throw sourceError(
281
+ "CC_GRAPH_PRODUCTION_JOB_INVENTORY_INVALID",
282
+ "paginated Actions job inventory must match the API total_count",
283
+ );
284
+ }
285
+ if (
286
+ Number(value?.workflowRunId) !== expected.workflowRunId ||
287
+ Number(value?.workflowRunAttempt) !== expected.workflowRunAttempt ||
288
+ exactCommit(value?.headSha, "jobsInventory.headSha") !== expected.commitSha
289
+ ) {
290
+ throw sourceError(
291
+ "CC_GRAPH_PRODUCTION_JOB_BINDING_MISMATCH",
292
+ "Actions job inventory envelope does not bind its exact queried run, attempt, and commit",
293
+ );
294
+ }
295
+ const sourceJobs = jobs.filter((job) =>
296
+ /^Collect signed (?:linux|windows|macos) source receipts$/u.test(
297
+ String(job?.name || ""),
298
+ ),
299
+ );
300
+ if (sourceJobs.length !== GRAPH_CUTOVER_REQUIRED_PLATFORMS.length) {
301
+ throw sourceError(
302
+ "CC_GRAPH_PRODUCTION_JOB_INVENTORY_INVALID",
303
+ "Actions job inventory must contain exactly the three fixed platform source jobs",
304
+ );
305
+ }
306
+ const byId = new Map();
307
+ for (const job of sourceJobs) {
308
+ const id = positive(job?.id, "jobsInventory.id");
309
+ const normalized = {
310
+ id,
311
+ runId: positive(job?.run_id, `jobsInventory.${id}.run_id`),
312
+ headSha: exactCommit(job?.head_sha, `jobsInventory.${id}.head_sha`),
313
+ name: text(job?.name, `jobsInventory.${id}.name`, 256),
314
+ status: text(job?.status, `jobsInventory.${id}.status`, 32),
315
+ conclusion: text(job?.conclusion, `jobsInventory.${id}.conclusion`, 32),
316
+ runnerId: positive(job?.runner_id, `jobsInventory.${id}.runner_id`),
317
+ runnerName: text(
318
+ job?.runner_name,
319
+ `jobsInventory.${id}.runner_name`,
320
+ 256,
321
+ ),
322
+ labels: uniqueLabels(job?.labels, `jobsInventory.${id}.labels`),
323
+ startedAt: timestamp(job?.started_at, `jobsInventory.${id}.started_at`),
324
+ completedAt: timestamp(
325
+ job?.completed_at,
326
+ `jobsInventory.${id}.completed_at`,
327
+ ),
328
+ };
329
+ if (
330
+ normalized.runId !== expected.workflowRunId ||
331
+ (job?.run_attempt != null &&
332
+ positive(job.run_attempt, `jobsInventory.${id}.run_attempt`) !==
333
+ expected.workflowRunAttempt) ||
334
+ normalized.headSha !== expected.commitSha
335
+ ) {
336
+ throw sourceError(
337
+ "CC_GRAPH_PRODUCTION_JOB_BINDING_MISMATCH",
338
+ `Actions job ${id} does not belong to the exact producer run, attempt, and commit`,
339
+ );
340
+ }
341
+ if (
342
+ normalized.status !== "completed" ||
343
+ normalized.conclusion !== "success" ||
344
+ normalized.completedAt.milliseconds < normalized.startedAt.milliseconds
345
+ ) {
346
+ throw sourceError(
347
+ "CC_GRAPH_PRODUCTION_JOB_BINDING_MISMATCH",
348
+ `Actions source job ${id} must be completed successfully with a canonical execution window`,
349
+ );
350
+ }
351
+ if (byId.has(id)) {
352
+ throw sourceError(
353
+ "CC_GRAPH_PRODUCTION_JOB_INVENTORY_INVALID",
354
+ `Actions job inventory duplicates id ${id}`,
355
+ );
356
+ }
357
+ byId.set(id, normalized);
358
+ }
359
+ return byId;
360
+ }
361
+
362
+ export function normalizeGraphProductionSourceRegistry(
363
+ input,
364
+ { expectedRepository, expectedManifestDigest, expectedRegistryDigest } = {},
365
+ ) {
366
+ if (input?.schema !== GRAPH_PRODUCTION_SOURCE_REGISTRY_SCHEMA) {
367
+ throw sourceError(
368
+ "CC_GRAPH_PRODUCTION_SOURCE_REGISTRY_INVALID",
369
+ `source registry schema must be ${GRAPH_PRODUCTION_SOURCE_REGISTRY_SCHEMA}`,
370
+ );
371
+ }
372
+ const sources = Array.isArray(input?.sources)
373
+ ? input.sources.map((entry, index) => {
374
+ const platform = String(entry?.platform || "").toLowerCase();
375
+ if (!GRAPH_CUTOVER_REQUIRED_PLATFORMS.includes(platform)) {
376
+ throw sourceError(
377
+ "CC_GRAPH_PRODUCTION_SOURCE_REGISTRY_INVALID",
378
+ `sources[${index}].platform is not supported`,
379
+ );
380
+ }
381
+ if (entry?.sourceKind !== "physical_self_hosted_runner") {
382
+ throw sourceError(
383
+ "CC_GRAPH_PRODUCTION_SOURCE_REGISTRY_INVALID",
384
+ "the required platform mapping must use physical self-hosted runners",
385
+ );
386
+ }
387
+ const runnerLabels = uniqueLabels(
388
+ entry?.runner?.labels,
389
+ `sources[${index}].runner.labels`,
390
+ );
391
+ for (const requiredLabel of [
392
+ ...GRAPH_PRODUCTION_REQUIRED_RUNNER_LABELS,
393
+ platform,
394
+ ]) {
395
+ if (!runnerLabels.includes(requiredLabel)) {
396
+ throw sourceError(
397
+ "CC_GRAPH_PRODUCTION_SOURCE_REGISTRY_INVALID",
398
+ `sources[${index}] is missing runner label ${requiredLabel}`,
399
+ );
400
+ }
401
+ }
402
+ const publicKeySpki = text(
403
+ entry?.publicKeySpki,
404
+ `sources[${index}].publicKeySpki`,
405
+ 512,
406
+ );
407
+ ed25519PublicKey(publicKeySpki, `sources[${index}].publicKeySpki`);
408
+ const normalized = {
409
+ sourceId: text(entry?.sourceId, `sources[${index}].sourceId`, 128),
410
+ platform,
411
+ sourceKind: "physical_self_hosted_runner",
412
+ enabled: entry?.enabled === true,
413
+ validFrom: timestamp(entry?.validFrom, `sources[${index}].validFrom`)
414
+ .value,
415
+ validUntil: timestamp(
416
+ entry?.validUntil,
417
+ `sources[${index}].validUntil`,
418
+ ).value,
419
+ keyId: digest(entry?.keyId, `sources[${index}].keyId`),
420
+ publicKeySpki,
421
+ hardwareIdentityDigest: digest(
422
+ entry?.hardwareIdentityDigest,
423
+ `sources[${index}].hardwareIdentityDigest`,
424
+ ),
425
+ operatorIdentityDigest: digest(
426
+ entry?.operatorIdentityDigest,
427
+ `sources[${index}].operatorIdentityDigest`,
428
+ ),
429
+ attester: {
430
+ identityDigest: digest(
431
+ entry?.attester?.identityDigest,
432
+ `sources[${index}].attester.identityDigest`,
433
+ ),
434
+ measurementDigest: digest(
435
+ entry?.attester?.measurementDigest,
436
+ `sources[${index}].attester.measurementDigest`,
437
+ ),
438
+ logAuthorityDigest: digest(
439
+ entry?.attester?.logAuthorityDigest,
440
+ `sources[${index}].attester.logAuthorityDigest`,
441
+ ),
442
+ },
443
+ runner: {
444
+ registrationId: positive(
445
+ entry?.runner?.registrationId,
446
+ `sources[${index}].runner.registrationId`,
447
+ ),
448
+ name: text(
449
+ entry?.runner?.name,
450
+ `sources[${index}].runner.name`,
451
+ 256,
452
+ ),
453
+ labels: runnerLabels,
454
+ },
455
+ collector: {
456
+ endpoint: text(
457
+ entry?.collector?.endpoint,
458
+ `sources[${index}].collector.endpoint`,
459
+ 2_048,
460
+ ),
461
+ credentialDigest: digest(
462
+ entry?.collector?.credentialDigest,
463
+ `sources[${index}].collector.credentialDigest`,
464
+ ),
465
+ },
466
+ };
467
+ if (
468
+ !normalized.enabled ||
469
+ Date.parse(normalized.validFrom) >= Date.parse(normalized.validUntil)
470
+ ) {
471
+ throw sourceError(
472
+ "CC_GRAPH_PRODUCTION_SOURCE_KEY_INACTIVE",
473
+ `${normalized.sourceId} signing key is revoked or has an invalid validity window; historical validity is checked against the signed receipt and Actions job`,
474
+ );
475
+ }
476
+ let collectorUrl;
477
+ try {
478
+ collectorUrl = new URL(normalized.collector.endpoint);
479
+ } catch {
480
+ collectorUrl = null;
481
+ }
482
+ if (
483
+ !collectorUrl ||
484
+ collectorUrl.protocol !== "https:" ||
485
+ collectorUrl.username ||
486
+ collectorUrl.password ||
487
+ collectorUrl.search ||
488
+ collectorUrl.hash
489
+ ) {
490
+ throw sourceError(
491
+ "CC_GRAPH_PRODUCTION_SOURCE_REGISTRY_INVALID",
492
+ `sources[${index}].collector.endpoint must be a credential-free HTTPS URL`,
493
+ );
494
+ }
495
+ if (normalized.keyId !== publicKeyDigest(publicKeySpki)) {
496
+ throw sourceError(
497
+ "CC_GRAPH_PRODUCTION_SOURCE_REGISTRY_INVALID",
498
+ `sources[${index}].keyId does not bind the Ed25519 public key`,
499
+ );
500
+ }
501
+ return normalized;
502
+ })
503
+ : [];
504
+ sources.sort((left, right) => left.platform.localeCompare(right.platform));
505
+ exactMembers(
506
+ sources.map((source) => source.platform),
507
+ GRAPH_CUTOVER_REQUIRED_PLATFORMS,
508
+ "source registry platforms",
509
+ );
510
+ exactMembers(
511
+ sources.map((source) => source.sourceId),
512
+ [...new Set(sources.map((source) => source.sourceId))],
513
+ "source registry ids",
514
+ );
515
+ if (
516
+ new Set(sources.map((source) => source.runner.registrationId)).size !==
517
+ sources.length ||
518
+ new Set(sources.map((source) => source.runner.name)).size !==
519
+ sources.length ||
520
+ new Set(sources.map((source) => source.keyId)).size !== sources.length ||
521
+ new Set(sources.map((source) => source.hardwareIdentityDigest)).size !==
522
+ sources.length ||
523
+ new Set(sources.map((source) => source.operatorIdentityDigest)).size !==
524
+ sources.length ||
525
+ new Set(sources.map((source) => source.attester.identityDigest)).size !==
526
+ sources.length ||
527
+ new Set(sources.map((source) => source.attester.logAuthorityDigest))
528
+ .size !== sources.length ||
529
+ new Set(sources.map((source) => source.collector.credentialDigest)).size !==
530
+ sources.length ||
531
+ new Set(
532
+ sources.map((source) => {
533
+ const endpoint = new URL(source.collector.endpoint);
534
+ return `${endpoint.origin}${endpoint.pathname}`;
535
+ }),
536
+ ).size !== sources.length
537
+ ) {
538
+ throw sourceError(
539
+ "CC_GRAPH_PRODUCTION_SOURCE_REGISTRY_INVALID",
540
+ "platform sources must use independent runners, hardware, operators, attesters, append-only log authorities, endpoints, credentials, and Ed25519 keys",
541
+ );
542
+ }
543
+ const normalized = {
544
+ schema: GRAPH_PRODUCTION_SOURCE_REGISTRY_SCHEMA,
545
+ repository: text(input?.repository, "registry.repository"),
546
+ ref: text(input?.ref, "registry.ref"),
547
+ workflow: text(input?.workflow, "registry.workflow"),
548
+ environment: text(input?.environment, "registry.environment"),
549
+ manifestDigest: digest(input?.manifestDigest, "registry.manifestDigest"),
550
+ sources,
551
+ };
552
+ if (
553
+ (expectedRepository && normalized.repository !== expectedRepository) ||
554
+ normalized.ref !== GRAPH_PRODUCTION_SOURCE_REF ||
555
+ normalized.workflow !== GRAPH_PRODUCTION_SOURCE_WORKFLOW ||
556
+ normalized.environment !== GRAPH_PRODUCTION_SOURCE_ENVIRONMENT ||
557
+ (expectedManifestDigest &&
558
+ normalized.manifestDigest !== expectedManifestDigest)
559
+ ) {
560
+ throw sourceError(
561
+ "CC_GRAPH_PRODUCTION_SOURCE_REGISTRY_INVALID",
562
+ "source registry does not match the protected producer identity or manifest",
563
+ );
564
+ }
565
+ normalized.registryDigest = digest(
566
+ input?.registryDigest,
567
+ "registry.registryDigest",
568
+ );
569
+ const calculated = graphProductionSourceRegistryDigest(normalized);
570
+ if (
571
+ normalized.registryDigest !== calculated ||
572
+ (expectedRegistryDigest &&
573
+ normalized.registryDigest !== expectedRegistryDigest)
574
+ ) {
575
+ throw sourceError(
576
+ "CC_GRAPH_PRODUCTION_SOURCE_REGISTRY_DIGEST_MISMATCH",
577
+ "source registry digest does not match its canonical contents or protected pin",
578
+ { calculated, expectedRegistryDigest },
579
+ );
580
+ }
581
+ return Object.freeze(normalized);
582
+ }
583
+
584
+ export function graphProductionRawEventDigest(event, sourceId) {
585
+ const unsigned = clone(event);
586
+ delete unsigned.eventDigest;
587
+ return graphDigest(
588
+ { sourceId, event: unsigned },
589
+ "cc.graph.production-raw-event/v1",
590
+ );
591
+ }
592
+
593
+ export function graphProductionRawLogMerkleRoot(digests) {
594
+ if (!Array.isArray(digests) || digests.length === 0) {
595
+ throw sourceError(
596
+ "CC_GRAPH_PRODUCTION_RAW_LOG_INVALID",
597
+ "raw log cannot be empty",
598
+ );
599
+ }
600
+ const leaves = digests.map((value, index) => {
601
+ const eventDigest = digest(value, `rawLog.eventDigests[${index}]`);
602
+ return createHash("sha256")
603
+ .update(Buffer.concat([Buffer.from([0]), Buffer.from(eventDigest)]))
604
+ .digest();
605
+ });
606
+ const treeHash = (nodes) => {
607
+ if (nodes.length === 1) return nodes[0];
608
+ let split = 1;
609
+ while (split * 2 < nodes.length) split *= 2;
610
+ return createHash("sha256")
611
+ .update(
612
+ Buffer.concat([
613
+ Buffer.from([1]),
614
+ treeHash(nodes.slice(0, split)),
615
+ treeHash(nodes.slice(split)),
616
+ ]),
617
+ )
618
+ .digest();
619
+ };
620
+ return `sha256:${treeHash(leaves).toString("hex")}`;
621
+ }
622
+
623
+ function expectedEventTypes(strategy) {
624
+ if (strategy === "disabled") return ["disabled_probe"];
625
+ return strategy === "retire"
626
+ ? [...BASE_EVENT_TYPES, "retirement_observation"]
627
+ : [...BASE_EVENT_TYPES];
628
+ }
629
+
630
+ function payloadObservationTime(type, payload, field) {
631
+ if (WINDOW_EVENT_TYPES.has(type)) {
632
+ return timestamp(payload?.endedAt, `${field}.payload.endedAt`);
633
+ }
634
+ if (type === "final_ledger_observation") {
635
+ return timestamp(
636
+ payload?.canonicalActivatedAt,
637
+ `${field}.payload.canonicalActivatedAt`,
638
+ );
639
+ }
640
+ return timestamp(payload?.observedAt, `${field}.payload.observedAt`);
641
+ }
642
+
643
+ function validateWindowPayload(payload, field, collectorEnded, nowMs) {
644
+ const started = timestamp(payload?.startedAt, `${field}.startedAt`);
645
+ const ended = timestamp(payload?.endedAt, `${field}.endedAt`);
646
+ const durationMs = ended.milliseconds - started.milliseconds;
647
+ const monotonicDurationMs = positive(
648
+ payload?.monotonicDurationMs,
649
+ `${field}.monotonicDurationMs`,
650
+ );
651
+ const driftTolerance = Math.max(5_000, Math.round(durationMs * 0.01));
652
+ if (
653
+ durationMs < 1 ||
654
+ payload?.durationMs !== durationMs ||
655
+ Math.abs(monotonicDurationMs - durationMs) > driftTolerance ||
656
+ ended.milliseconds > collectorEnded.milliseconds ||
657
+ started.milliseconds < nowMs - GRAPH_PRODUCTION_OBSERVATION_MAX_AGE_MS ||
658
+ ended.milliseconds > nowMs + GRAPH_PRODUCTION_CLOCK_SKEW_MS
659
+ ) {
660
+ throw sourceError(
661
+ "CC_GRAPH_PRODUCTION_SOURCE_CLOCK_INVALID",
662
+ `${field} does not bind a current wall-clock and monotonic observation window`,
663
+ { durationMs, monotonicDurationMs },
664
+ );
665
+ }
666
+ }
667
+
668
+ function normalizeRawLog(
669
+ input,
670
+ { source, strategy, collectorStarted, collectorEnded, nowMs, field },
671
+ ) {
672
+ if (input?.schema !== GRAPH_PRODUCTION_RAW_LOG_SCHEMA) {
673
+ throw sourceError(
674
+ "CC_GRAPH_PRODUCTION_RAW_LOG_INVALID",
675
+ `${field}.schema must be ${GRAPH_PRODUCTION_RAW_LOG_SCHEMA}`,
676
+ );
677
+ }
678
+ const events = Array.isArray(input?.events)
679
+ ? input.events.map((entry, index) => {
680
+ const eventField = `${field}.events[${index}]`;
681
+ const type = text(entry?.type, `${eventField}.type`, 64);
682
+ if (!expectedEventTypes(strategy).includes(type)) {
683
+ throw sourceError(
684
+ "CC_GRAPH_PRODUCTION_RAW_LOG_INVALID",
685
+ `${eventField}.type is not valid for ${strategy}`,
686
+ );
687
+ }
688
+ if (
689
+ !entry?.payload ||
690
+ typeof entry.payload !== "object" ||
691
+ Array.isArray(entry.payload)
692
+ ) {
693
+ throw sourceError(
694
+ "CC_GRAPH_PRODUCTION_RAW_LOG_INVALID",
695
+ `${eventField}.payload must be an object`,
696
+ );
697
+ }
698
+ const normalized = {
699
+ sequence: positive(entry?.sequence, `${eventField}.sequence`),
700
+ wallTime: timestamp(entry?.wallTime, `${eventField}.wallTime`).value,
701
+ monotonicMs: nonNegative(
702
+ entry?.monotonicMs,
703
+ `${eventField}.monotonicMs`,
704
+ ),
705
+ type,
706
+ payload: clone(entry.payload),
707
+ previousEventDigest: digest(
708
+ entry?.previousEventDigest,
709
+ `${eventField}.previousEventDigest`,
710
+ ),
711
+ };
712
+ normalized.eventDigest = digest(
713
+ entry?.eventDigest,
714
+ `${eventField}.eventDigest`,
715
+ );
716
+ const calculated = graphProductionRawEventDigest(
717
+ normalized,
718
+ source.sourceId,
719
+ );
720
+ if (normalized.eventDigest !== calculated) {
721
+ throw sourceError(
722
+ "CC_GRAPH_PRODUCTION_RAW_EVENT_DIGEST_MISMATCH",
723
+ `${eventField}.eventDigest does not match its canonical event`,
724
+ );
725
+ }
726
+ return normalized;
727
+ })
728
+ : [];
729
+ exactMembers(
730
+ events.map((event) => event.type),
731
+ expectedEventTypes(strategy),
732
+ `${field}.event types`,
733
+ );
734
+ let previousDigest = ZERO_DIGEST;
735
+ let previousWall = -Infinity;
736
+ let previousMonotonic = -1;
737
+ for (const [index, event] of events.entries()) {
738
+ const eventWall = Date.parse(event.wallTime);
739
+ const wallDelta = eventWall - previousWall;
740
+ const monotonicDelta = event.monotonicMs - previousMonotonic;
741
+ const eventDriftTolerance = Number.isFinite(wallDelta)
742
+ ? Math.max(5_000, Math.round(wallDelta * 0.01))
743
+ : 0;
744
+ if (
745
+ event.sequence !== index + 1 ||
746
+ event.previousEventDigest !== previousDigest ||
747
+ eventWall < previousWall ||
748
+ event.monotonicMs < previousMonotonic ||
749
+ (Number.isFinite(wallDelta) &&
750
+ Math.abs(monotonicDelta - wallDelta) > eventDriftTolerance) ||
751
+ eventWall > collectorStarted.milliseconds ||
752
+ eventWall > nowMs + GRAPH_PRODUCTION_CLOCK_SKEW_MS ||
753
+ eventWall < nowMs - GRAPH_PRODUCTION_OBSERVATION_MAX_AGE_MS
754
+ ) {
755
+ throw sourceError(
756
+ "CC_GRAPH_PRODUCTION_RAW_LOG_NOT_APPEND_ONLY",
757
+ `${field} is not a monotonic append-only event chain`,
758
+ { index },
759
+ );
760
+ }
761
+ const observationTime = payloadObservationTime(
762
+ event.type,
763
+ event.payload,
764
+ `${field}.events[${index}]`,
765
+ );
766
+ if (observationTime.value !== event.wallTime) {
767
+ throw sourceError(
768
+ "CC_GRAPH_PRODUCTION_SOURCE_CLOCK_INVALID",
769
+ `${field}.events[${index}] wall time does not match its observation payload`,
770
+ );
771
+ }
772
+ if (WINDOW_EVENT_TYPES.has(event.type)) {
773
+ validateWindowPayload(
774
+ event.payload,
775
+ `${field}.events[${index}].payload`,
776
+ collectorEnded,
777
+ nowMs,
778
+ );
779
+ }
780
+ previousDigest = event.eventDigest;
781
+ previousWall = eventWall;
782
+ previousMonotonic = event.monotonicMs;
783
+ }
784
+ const eventDigests = events.map((event) => event.eventDigest);
785
+ const chainHead = digest(input?.chainHead, `${field}.chainHead`);
786
+ const merkleRoot = digest(input?.merkleRoot, `${field}.merkleRoot`);
787
+ if (
788
+ chainHead !== eventDigests.at(-1) ||
789
+ merkleRoot !== graphProductionRawLogMerkleRoot(eventDigests)
790
+ ) {
791
+ throw sourceError(
792
+ "CC_GRAPH_PRODUCTION_RAW_LOG_ROOT_MISMATCH",
793
+ `${field} chain head or Merkle root does not match the signed raw events`,
794
+ );
795
+ }
796
+ return {
797
+ schema: GRAPH_PRODUCTION_RAW_LOG_SCHEMA,
798
+ bootIdDigest: digest(input?.bootIdDigest, `${field}.bootIdDigest`),
799
+ events,
800
+ chainHead,
801
+ merkleRoot,
802
+ };
803
+ }
804
+
805
+ export function graphProductionSourceReceiptSigningBytes(payload) {
806
+ return Buffer.from(
807
+ `cc.graph.production-source-receipt-signature/v1\0${canonicalGraphJson(payload)}`,
808
+ "utf8",
809
+ );
810
+ }
811
+
812
+ export function graphProductionSourceReceiptDigest(value) {
813
+ const unsigned = clone(value);
814
+ delete unsigned.receiptDigest;
815
+ return graphDigest(unsigned, "cc.graph.production-source-receipt/v1");
816
+ }
817
+
818
+ export function signGraphProductionSourceReceipt(payload, privateKeyInput) {
819
+ const privateKey =
820
+ privateKeyInput?.type === "private"
821
+ ? privateKeyInput
822
+ : createPrivateKey(privateKeyInput);
823
+ if (privateKey.asymmetricKeyType !== "ed25519") {
824
+ throw sourceError(
825
+ "CC_GRAPH_PRODUCTION_SOURCE_SIGNATURE_INVALID",
826
+ "source receipt signing key must be Ed25519",
827
+ );
828
+ }
829
+ const signature = sign(
830
+ null,
831
+ graphProductionSourceReceiptSigningBytes(payload),
832
+ privateKey,
833
+ ).toString("base64url");
834
+ const receipt = { payload: clone(payload), signature };
835
+ receipt.receiptDigest = graphProductionSourceReceiptDigest(receipt);
836
+ return receipt;
837
+ }
838
+
839
+ function normalizeSourceReceipt(
840
+ input,
841
+ {
842
+ source,
843
+ registry,
844
+ expected,
845
+ strategy,
846
+ surface,
847
+ entryId,
848
+ entryManifestDigest,
849
+ nowMs,
850
+ jobsInventory,
851
+ field,
852
+ },
853
+ ) {
854
+ const payloadInput = input?.payload;
855
+ if (payloadInput?.schema !== GRAPH_PRODUCTION_SOURCE_RECEIPT_SCHEMA) {
856
+ throw sourceError(
857
+ "CC_GRAPH_PRODUCTION_SOURCE_RECEIPT_INVALID",
858
+ `${field}.payload schema is invalid`,
859
+ );
860
+ }
861
+ const collectorStarted = timestamp(
862
+ payloadInput?.collectorStartedAt,
863
+ `${field}.payload.collectorStartedAt`,
864
+ );
865
+ const collectorEnded = timestamp(
866
+ payloadInput?.collectorEndedAt,
867
+ `${field}.payload.collectorEndedAt`,
868
+ );
869
+ const wallElapsed =
870
+ collectorEnded.milliseconds - collectorStarted.milliseconds;
871
+ const monotonicElapsedMs = nonNegative(
872
+ payloadInput?.collectorMonotonicElapsedMs,
873
+ `${field}.payload.collectorMonotonicElapsedMs`,
874
+ );
875
+ const collectorTolerance = Math.max(2_000, Math.round(wallElapsed * 0.2));
876
+ if (
877
+ wallElapsed < 0 ||
878
+ Math.abs(monotonicElapsedMs - wallElapsed) > collectorTolerance ||
879
+ collectorEnded.milliseconds > nowMs + GRAPH_PRODUCTION_CLOCK_SKEW_MS ||
880
+ nowMs - collectorEnded.milliseconds > GRAPH_PRODUCTION_RECEIPT_MAX_AGE_MS
881
+ ) {
882
+ throw sourceError(
883
+ "CC_GRAPH_PRODUCTION_SOURCE_CLOCK_INVALID",
884
+ `${field} collector wall-clock/monotonic binding is stale or invalid`,
885
+ );
886
+ }
887
+ const payload = {
888
+ schema: GRAPH_PRODUCTION_SOURCE_RECEIPT_SCHEMA,
889
+ sourceId: text(payloadInput?.sourceId, `${field}.payload.sourceId`, 128),
890
+ platform: String(payloadInput?.platform || "").toLowerCase(),
891
+ keyId: digest(payloadInput?.keyId, `${field}.payload.keyId`),
892
+ registryDigest: digest(
893
+ payloadInput?.registryDigest,
894
+ `${field}.payload.registryDigest`,
895
+ ),
896
+ manifestDigest: digest(
897
+ payloadInput?.manifestDigest,
898
+ `${field}.payload.manifestDigest`,
899
+ ),
900
+ entryManifestDigest: digest(
901
+ payloadInput?.entryManifestDigest,
902
+ `${field}.payload.entryManifestDigest`,
903
+ ),
904
+ surface: text(payloadInput?.surface, `${field}.payload.surface`, 128),
905
+ entryId: text(payloadInput?.entryId, `${field}.payload.entryId`, 256),
906
+ commitSha: exactCommit(
907
+ payloadInput?.commitSha,
908
+ `${field}.payload.commitSha`,
909
+ ),
910
+ repository: text(
911
+ payloadInput?.repository,
912
+ `${field}.payload.repository`,
913
+ 256,
914
+ ),
915
+ ref: text(payloadInput?.ref, `${field}.payload.ref`, 256),
916
+ workflow: text(payloadInput?.workflow, `${field}.payload.workflow`, 256),
917
+ workflowRunId: positive(
918
+ payloadInput?.workflowRunId,
919
+ `${field}.payload.workflowRunId`,
920
+ ),
921
+ workflowRunAttempt: positive(
922
+ payloadInput?.workflowRunAttempt,
923
+ `${field}.payload.workflowRunAttempt`,
924
+ ),
925
+ workflowJob: text(
926
+ payloadInput?.workflowJob,
927
+ `${field}.payload.workflowJob`,
928
+ 128,
929
+ ),
930
+ workflowJobDatabaseId: positive(
931
+ payloadInput?.workflowJobDatabaseId,
932
+ `${field}.payload.workflowJobDatabaseId`,
933
+ ),
934
+ runner: {
935
+ registrationId: positive(
936
+ payloadInput?.runner?.registrationId,
937
+ `${field}.payload.runner.registrationId`,
938
+ ),
939
+ name: text(
940
+ payloadInput?.runner?.name,
941
+ `${field}.payload.runner.name`,
942
+ 256,
943
+ ),
944
+ labels: uniqueLabels(
945
+ payloadInput?.runner?.labels,
946
+ `${field}.payload.runner.labels`,
947
+ ),
948
+ },
949
+ hardwareIdentityDigest: digest(
950
+ payloadInput?.hardwareIdentityDigest,
951
+ `${field}.payload.hardwareIdentityDigest`,
952
+ ),
953
+ operatorIdentityDigest: digest(
954
+ payloadInput?.operatorIdentityDigest,
955
+ `${field}.payload.operatorIdentityDigest`,
956
+ ),
957
+ attester: {
958
+ identityDigest: digest(
959
+ payloadInput?.attester?.identityDigest,
960
+ `${field}.payload.attester.identityDigest`,
961
+ ),
962
+ measurementDigest: digest(
963
+ payloadInput?.attester?.measurementDigest,
964
+ `${field}.payload.attester.measurementDigest`,
965
+ ),
966
+ logAuthorityDigest: digest(
967
+ payloadInput?.attester?.logAuthorityDigest,
968
+ `${field}.payload.attester.logAuthorityDigest`,
969
+ ),
970
+ },
971
+ challenge: String(payloadInput?.challenge || ""),
972
+ nonce: String(payloadInput?.nonce || ""),
973
+ collectorStartedAt: collectorStarted.value,
974
+ collectorEndedAt: collectorEnded.value,
975
+ collectorMonotonicElapsedMs: monotonicElapsedMs,
976
+ };
977
+ payload.rawLog = normalizeRawLog(payloadInput?.rawLog, {
978
+ source,
979
+ strategy,
980
+ collectorStarted,
981
+ collectorEnded,
982
+ nowMs,
983
+ field: `${field}.payload.rawLog`,
984
+ });
985
+ if (
986
+ payload.sourceId !== source.sourceId ||
987
+ payload.platform !== source.platform ||
988
+ payload.keyId !== source.keyId ||
989
+ payload.registryDigest !== registry.registryDigest ||
990
+ payload.manifestDigest !== registry.manifestDigest ||
991
+ payload.entryManifestDigest !== entryManifestDigest ||
992
+ payload.surface !== surface ||
993
+ payload.entryId !== entryId ||
994
+ payload.commitSha !== expected.commitSha ||
995
+ payload.repository !== expected.repository ||
996
+ payload.ref !== GRAPH_PRODUCTION_SOURCE_REF ||
997
+ payload.workflow !== GRAPH_PRODUCTION_SOURCE_WORKFLOW ||
998
+ payload.workflowRunId !== expected.workflowRunId ||
999
+ payload.workflowRunAttempt !== expected.workflowRunAttempt ||
1000
+ payload.workflowJob !== GRAPH_PRODUCTION_SOURCE_JOB ||
1001
+ canonicalGraphJson(payload.runner) !== canonicalGraphJson(source.runner) ||
1002
+ payload.hardwareIdentityDigest !== source.hardwareIdentityDigest ||
1003
+ payload.operatorIdentityDigest !== source.operatorIdentityDigest ||
1004
+ canonicalGraphJson(payload.attester) !==
1005
+ canonicalGraphJson(source.attester) ||
1006
+ !CHALLENGE.test(payload.challenge) ||
1007
+ payload.challenge !== expected.challenge ||
1008
+ !CHALLENGE.test(payload.nonce)
1009
+ ) {
1010
+ throw sourceError(
1011
+ "CC_GRAPH_PRODUCTION_SOURCE_BINDING_MISMATCH",
1012
+ `${field} does not bind the exact protected source/run/runner/challenge`,
1013
+ );
1014
+ }
1015
+ const job = jobsInventory?.get(payload.workflowJobDatabaseId);
1016
+ if (
1017
+ jobsInventory &&
1018
+ (!job ||
1019
+ job.status !== "completed" ||
1020
+ job.conclusion !== "success" ||
1021
+ job.name !== `Collect signed ${payload.platform} source receipts` ||
1022
+ job.runnerId !== payload.runner.registrationId ||
1023
+ job.runnerName !== payload.runner.name ||
1024
+ !includesEveryLabel(job.labels, [
1025
+ ...GRAPH_PRODUCTION_REQUIRED_RUNNER_LABELS,
1026
+ payload.platform,
1027
+ ]) ||
1028
+ collectorStarted.milliseconds < Date.parse(source.validFrom) ||
1029
+ collectorEnded.milliseconds > Date.parse(source.validUntil) ||
1030
+ job.startedAt.milliseconds < Date.parse(source.validFrom) ||
1031
+ job.startedAt.milliseconds >= Date.parse(source.validUntil) ||
1032
+ collectorStarted.milliseconds < job.startedAt.milliseconds ||
1033
+ collectorEnded.milliseconds > job.completedAt.milliseconds)
1034
+ ) {
1035
+ throw sourceError(
1036
+ "CC_GRAPH_PRODUCTION_JOB_BINDING_MISMATCH",
1037
+ `${field} is not contained by its successful Actions source job and runner identity`,
1038
+ );
1039
+ }
1040
+ if (!BASE64URL.test(String(input?.signature || ""))) {
1041
+ throw sourceError(
1042
+ "CC_GRAPH_PRODUCTION_SOURCE_SIGNATURE_INVALID",
1043
+ `${field}.signature must be base64url`,
1044
+ );
1045
+ }
1046
+ const signature = Buffer.from(input.signature, "base64url");
1047
+ if (
1048
+ signature.length !== 64 ||
1049
+ !verify(
1050
+ null,
1051
+ graphProductionSourceReceiptSigningBytes(payload),
1052
+ ed25519PublicKey(source.publicKeySpki, `${field}.publicKey`),
1053
+ signature,
1054
+ )
1055
+ ) {
1056
+ throw sourceError(
1057
+ "CC_GRAPH_PRODUCTION_SOURCE_SIGNATURE_INVALID",
1058
+ `${field} Ed25519 signature does not verify against the pinned registry key`,
1059
+ );
1060
+ }
1061
+ const normalized = {
1062
+ payload,
1063
+ signature: input.signature,
1064
+ };
1065
+ normalized.receiptDigest = digest(
1066
+ input?.receiptDigest,
1067
+ `${field}.receiptDigest`,
1068
+ );
1069
+ if (
1070
+ normalized.receiptDigest !== graphProductionSourceReceiptDigest(normalized)
1071
+ ) {
1072
+ throw sourceError(
1073
+ "CC_GRAPH_PRODUCTION_SOURCE_RECEIPT_DIGEST_MISMATCH",
1074
+ `${field}.receiptDigest does not bind the signed receipt`,
1075
+ );
1076
+ }
1077
+ return normalized;
1078
+ }
1079
+
1080
+ function phaseEvidenceDigest(receipts, type, domain, extra = {}) {
1081
+ return graphDigest(
1082
+ {
1083
+ ...extra,
1084
+ sources: receipts.map((receipt) => ({
1085
+ sourceId: receipt.payload.sourceId,
1086
+ receiptDigest: receipt.receiptDigest,
1087
+ rawLogRoot: receipt.payload.rawLog.merkleRoot,
1088
+ eventDigest: receipt.payload.rawLog.events.find(
1089
+ (event) => event.type === type,
1090
+ ).eventDigest,
1091
+ })),
1092
+ },
1093
+ domain,
1094
+ );
1095
+ }
1096
+
1097
+ function commonPhasePayload(receipts, type) {
1098
+ const payloads = receipts.map(
1099
+ (receipt) =>
1100
+ receipt.payload.rawLog.events.find((event) => event.type === type)
1101
+ .payload,
1102
+ );
1103
+ const canonical = canonicalGraphJson(payloads[0]);
1104
+ if (payloads.some((payload) => canonicalGraphJson(payload) !== canonical)) {
1105
+ throw sourceError(
1106
+ "CC_GRAPH_PRODUCTION_SOURCE_CROSS_CHECK_FAILED",
1107
+ `${type} differs across independently signed platform receipts`,
1108
+ );
1109
+ }
1110
+ return clone(payloads[0]);
1111
+ }
1112
+
1113
+ function windowPayload(value) {
1114
+ const normalized = clone(value);
1115
+ delete normalized.monotonicDurationMs;
1116
+ return normalized;
1117
+ }
1118
+
1119
+ function withItemDigests(items, receipts, type, key, domain) {
1120
+ return items.map((item) => ({
1121
+ ...item,
1122
+ evidenceDigest: phaseEvidenceDigest(receipts, type, domain, {
1123
+ key: item[key],
1124
+ }),
1125
+ }));
1126
+ }
1127
+
1128
+ function deriveLegacyWriterEvidence(receipts, type, identity, strategy) {
1129
+ const payload = windowPayload(commonPhasePayload(receipts, type));
1130
+ payload.writerObservations = withItemDigests(
1131
+ payload.writerObservations || [],
1132
+ receipts,
1133
+ type,
1134
+ "writerFile",
1135
+ "cc.graph.production-derived-writer/v1",
1136
+ );
1137
+ payload.mutationProbes = withItemDigests(
1138
+ payload.mutationProbes || [],
1139
+ receipts,
1140
+ type,
1141
+ "mutationFunction",
1142
+ "cc.graph.production-derived-mutation/v1",
1143
+ );
1144
+ const unsigned = {
1145
+ schema:
1146
+ strategy === "retire"
1147
+ ? GRAPH_LEGACY_WRITER_OBSERVATION_SCHEMA
1148
+ : "chainlesschain.graph-entry-writer-observation/v1",
1149
+ surface: identity.surface,
1150
+ entryId: identity.entryId,
1151
+ rolloutKey: identity.rolloutKey,
1152
+ manifestDigest: identity.manifestDigest,
1153
+ commitSha: identity.commitSha,
1154
+ ...payload,
1155
+ };
1156
+ return {
1157
+ ...unsigned,
1158
+ evidenceDigest:
1159
+ strategy === "retire"
1160
+ ? graphLegacyWriterObservationDigest(unsigned)
1161
+ : graphDigest(unsigned, "cc.graph.entry-writer-observation/v1"),
1162
+ };
1163
+ }
1164
+
1165
+ function deriveRetirementEvidence(receipts, identity) {
1166
+ const payload = windowPayload(
1167
+ commonPhasePayload(receipts, "retirement_observation"),
1168
+ );
1169
+ payload.replacementJourneys = withItemDigests(
1170
+ payload.replacementJourneys || [],
1171
+ receipts,
1172
+ "retirement_observation",
1173
+ "replacementEntryId",
1174
+ "cc.graph.production-derived-replacement/v1",
1175
+ );
1176
+ payload.mutationProbes = withItemDigests(
1177
+ payload.mutationProbes || [],
1178
+ receipts,
1179
+ "retirement_observation",
1180
+ "mutationFunction",
1181
+ "cc.graph.production-derived-retirement-mutation/v1",
1182
+ );
1183
+ payload.historicalReadProbes = withItemDigests(
1184
+ payload.historicalReadProbes || [],
1185
+ receipts,
1186
+ "retirement_observation",
1187
+ "historicalReadFunction",
1188
+ "cc.graph.production-derived-historical-read/v1",
1189
+ );
1190
+ const unsigned = {
1191
+ schema: GRAPH_RETIREMENT_EVIDENCE_SCHEMA,
1192
+ surface: identity.surface,
1193
+ entryId: identity.entryId,
1194
+ rolloutKey: identity.rolloutKey,
1195
+ manifestDigest: identity.manifestDigest,
1196
+ commitSha: identity.commitSha,
1197
+ ...payload,
1198
+ };
1199
+ return {
1200
+ ...unsigned,
1201
+ evidenceDigest: graphRetirementEvidenceDigest(unsigned),
1202
+ };
1203
+ }
1204
+
1205
+ function deriveObservationSources(receipts, observationDigests) {
1206
+ return receipts.map((receipt) => ({
1207
+ platform: receipt.payload.platform,
1208
+ sourceKind: "physical_self_hosted_runner",
1209
+ sourceId: receipt.payload.sourceId,
1210
+ identityDigest: graphDigest(
1211
+ receipt.payload.runner,
1212
+ "cc.graph.production-runner-identity/v1",
1213
+ ),
1214
+ keyId: receipt.payload.keyId,
1215
+ registryDigest: receipt.payload.registryDigest,
1216
+ runnerRegistrationId: receipt.payload.runner.registrationId,
1217
+ runnerName: receipt.payload.runner.name,
1218
+ runnerLabels: [...receipt.payload.runner.labels],
1219
+ challenge: receipt.payload.challenge,
1220
+ nonce: receipt.payload.nonce,
1221
+ rawLogRoot: receipt.payload.rawLog.merkleRoot,
1222
+ receiptDigest: receipt.receiptDigest,
1223
+ authenticationDigest: graphDigest(
1224
+ {
1225
+ keyId: receipt.payload.keyId,
1226
+ receiptDigest: receipt.receiptDigest,
1227
+ signature: receipt.signature,
1228
+ },
1229
+ "cc.graph.production-source-authentication/v1",
1230
+ ),
1231
+ observationDigests: [...observationDigests].sort(),
1232
+ }));
1233
+ }
1234
+
1235
+ function deriveDurableEntry(receipts, expected, commitSha) {
1236
+ const entry = expected.entry;
1237
+ const identity = {
1238
+ surface: expected.surface.originSurface,
1239
+ entryId: entry.id,
1240
+ rolloutKey: entry.rolloutKey,
1241
+ cutoverStrategy: entry.cutoverStrategy,
1242
+ manifestDigest: graphRuntimeEntryManifestDigest(
1243
+ expected.manifest,
1244
+ expected.surface.originSurface,
1245
+ entry.id,
1246
+ ),
1247
+ commitSha,
1248
+ };
1249
+ const shadow = windowPayload(
1250
+ commonPhasePayload(receipts, "shadow_observation"),
1251
+ );
1252
+ shadow.comparisons = withItemDigests(
1253
+ shadow.comparisons || [],
1254
+ receipts,
1255
+ "shadow_observation",
1256
+ "dimension",
1257
+ "cc.graph.production-derived-shadow-comparison/v1",
1258
+ );
1259
+ shadow.evidenceDigest = phaseEvidenceDigest(
1260
+ receipts,
1261
+ "shadow_observation",
1262
+ "cc.graph.production-derived-shadow/v1",
1263
+ );
1264
+ const canary = windowPayload(
1265
+ commonPhasePayload(receipts, "canary_observation"),
1266
+ );
1267
+ canary.platformJourneys = withItemDigests(
1268
+ canary.platformJourneys || [],
1269
+ receipts,
1270
+ "canary_observation",
1271
+ "platform",
1272
+ "cc.graph.production-derived-canary-platform/v1",
1273
+ );
1274
+ canary.evidenceDigest = phaseEvidenceDigest(
1275
+ receipts,
1276
+ "canary_observation",
1277
+ "cc.graph.production-derived-canary/v1",
1278
+ );
1279
+ const rollback = commonPhasePayload(receipts, "rollback_observation");
1280
+ delete rollback.observedAt;
1281
+ rollback.drills = withItemDigests(
1282
+ rollback.drills || [],
1283
+ receipts,
1284
+ "rollback_observation",
1285
+ "transition",
1286
+ "cc.graph.production-derived-rollback-drill/v1",
1287
+ );
1288
+ rollback.evidenceDigest = phaseEvidenceDigest(
1289
+ receipts,
1290
+ "rollback_observation",
1291
+ "cc.graph.production-derived-rollback/v1",
1292
+ );
1293
+ const finalLedger = commonPhasePayload(receipts, "final_ledger_observation");
1294
+ finalLedger.evidenceDigest = phaseEvidenceDigest(
1295
+ receipts,
1296
+ "final_ledger_observation",
1297
+ "cc.graph.production-derived-final-ledger/v1",
1298
+ );
1299
+ const legacyReadOnly = deriveLegacyWriterEvidence(
1300
+ receipts,
1301
+ "legacy_writer_observation",
1302
+ identity,
1303
+ entry.cutoverStrategy,
1304
+ );
1305
+ const unsigned = {
1306
+ ...identity,
1307
+ stageSequence: [
1308
+ "shadow",
1309
+ "internal_canary",
1310
+ "opt_in_canary",
1311
+ "canonical_default",
1312
+ "legacy_read_only",
1313
+ ],
1314
+ shadow,
1315
+ canary,
1316
+ rollback,
1317
+ finalLedger,
1318
+ legacyReadOnly,
1319
+ };
1320
+ if (entry.cutoverStrategy === "retire") {
1321
+ unsigned.retirementEvidence = deriveRetirementEvidence(receipts, identity);
1322
+ }
1323
+ const observationDigests = [
1324
+ shadow.evidenceDigest,
1325
+ canary.evidenceDigest,
1326
+ rollback.evidenceDigest,
1327
+ finalLedger.evidenceDigest,
1328
+ legacyReadOnly.evidenceDigest,
1329
+ ...(unsigned.retirementEvidence
1330
+ ? [unsigned.retirementEvidence.evidenceDigest]
1331
+ : []),
1332
+ ];
1333
+ unsigned.observationSources = deriveObservationSources(
1334
+ receipts,
1335
+ observationDigests,
1336
+ );
1337
+ return {
1338
+ ...unsigned,
1339
+ evidenceDigest: graphDigest(
1340
+ unsigned,
1341
+ "cc.graph.production-cutover-entry/v1",
1342
+ ),
1343
+ };
1344
+ }
1345
+
1346
+ function deriveDisabledEntry(receipts, expected, commitSha) {
1347
+ const payload = commonPhasePayload(receipts, "disabled_probe");
1348
+ const observedAt = timestamp(
1349
+ payload?.observedAt,
1350
+ "disabledProbe.observedAt",
1351
+ ).value;
1352
+ delete payload.observedAt;
1353
+ const disablementProbeDigest = phaseEvidenceDigest(
1354
+ receipts,
1355
+ "disabled_probe",
1356
+ "cc.graph.production-derived-disabled-probe/v1",
1357
+ { observedAt },
1358
+ );
1359
+ const unsigned = {
1360
+ surface: expected.surface.originSurface,
1361
+ entryId: expected.entry.id,
1362
+ rolloutKey: expected.entry.rolloutKey,
1363
+ cutoverStrategy: "disabled",
1364
+ manifestDigest: graphRuntimeEntryManifestDigest(
1365
+ expected.manifest,
1366
+ expected.surface.originSurface,
1367
+ expected.entry.id,
1368
+ ),
1369
+ commitSha,
1370
+ ...payload,
1371
+ disablementProbeDigest,
1372
+ observationSources: deriveObservationSources(receipts, [
1373
+ disablementProbeDigest,
1374
+ ]),
1375
+ };
1376
+ return {
1377
+ ...unsigned,
1378
+ evidenceDigest: graphDigest(
1379
+ unsigned,
1380
+ "cc.graph.production-disabled-entry/v1",
1381
+ ),
1382
+ };
1383
+ }
1384
+
1385
+ function manifestEntries(manifest) {
1386
+ return manifest.surfaces.flatMap((surface) =>
1387
+ surface.entries.map((entry) => ({ manifest, surface, entry })),
1388
+ );
1389
+ }
1390
+
1391
+ export function normalizeGraphProductionSourceBundle(
1392
+ { registry: registryInput, fragments: fragmentInputs },
1393
+ {
1394
+ manifest,
1395
+ expectedCommitSha,
1396
+ expectedRepository,
1397
+ expectedRegistryDigest,
1398
+ expectedWorkflowRunId,
1399
+ expectedWorkflowRunAttempt,
1400
+ expectedChallenge,
1401
+ jobsInventory: jobsInventoryInput,
1402
+ clock = Date.now,
1403
+ },
1404
+ ) {
1405
+ const nowMs = Number(clock());
1406
+ if (!Number.isFinite(nowMs)) {
1407
+ throw sourceError(
1408
+ "CC_GRAPH_PRODUCTION_SOURCE_CLOCK_INVALID",
1409
+ "trusted clock returned an invalid time",
1410
+ );
1411
+ }
1412
+ const commitSha = exactCommit(expectedCommitSha, "expectedCommitSha");
1413
+ assertGraphProductionRuntimeSurfaceManifest(manifest);
1414
+ const manifestDigest = graphRuntimeSurfaceManifestDigest(manifest);
1415
+ const registry = normalizeGraphProductionSourceRegistry(registryInput, {
1416
+ expectedRepository,
1417
+ expectedManifestDigest: manifestDigest,
1418
+ expectedRegistryDigest,
1419
+ });
1420
+ const expected = {
1421
+ commitSha,
1422
+ repository: text(expectedRepository, "expectedRepository", 256),
1423
+ workflowRunId: positive(expectedWorkflowRunId, "expectedWorkflowRunId"),
1424
+ workflowRunAttempt: positive(
1425
+ expectedWorkflowRunAttempt,
1426
+ "expectedWorkflowRunAttempt",
1427
+ ),
1428
+ challenge: String(expectedChallenge || ""),
1429
+ };
1430
+ if (!CHALLENGE.test(expected.challenge)) {
1431
+ throw sourceError(
1432
+ "CC_GRAPH_PRODUCTION_SOURCE_BINDING_MISMATCH",
1433
+ "expected challenge must be an unpredictable 32-128 character token",
1434
+ );
1435
+ }
1436
+ const jobsInventory = normalizeJobsInventory(jobsInventoryInput, expected);
1437
+ if (!jobsInventory) {
1438
+ throw sourceError(
1439
+ "CC_GRAPH_PRODUCTION_JOB_INVENTORY_INVALID",
1440
+ "current-attempt Actions job inventory is required",
1441
+ );
1442
+ }
1443
+ const expectedEntries = manifestEntries(manifest);
1444
+ const expectedKeys = expectedEntries.map(
1445
+ ({ surface, entry }) => `${surface.originSurface}/${entry.id}`,
1446
+ );
1447
+ const fragments = Array.isArray(fragmentInputs) ? fragmentInputs : [];
1448
+ exactMembers(
1449
+ fragments.map((fragment) => `${fragment?.surface}/${fragment?.entryId}`),
1450
+ expectedKeys,
1451
+ "source fragments",
1452
+ );
1453
+ const fragmentMap = new Map(
1454
+ fragments.map((fragment) => [
1455
+ `${fragment.surface}/${fragment.entryId}`,
1456
+ fragment,
1457
+ ]),
1458
+ );
1459
+ const nonces = new Set();
1460
+ const sourceJobIds = new Map();
1461
+ const sourceBootIds = new Map();
1462
+ const normalizedFragments = [];
1463
+ const entries = [];
1464
+ const disabledEntries = [];
1465
+ for (const expectedEntry of expectedEntries) {
1466
+ const surface = expectedEntry.surface.originSurface;
1467
+ const entryId = expectedEntry.entry.id;
1468
+ const key = `${surface}/${entryId}`;
1469
+ const input = fragmentMap.get(key);
1470
+ const entryManifestDigest = graphRuntimeEntryManifestDigest(
1471
+ manifest,
1472
+ surface,
1473
+ entryId,
1474
+ );
1475
+ if (
1476
+ input?.schema !== GRAPH_PRODUCTION_SOURCE_FRAGMENT_SCHEMA ||
1477
+ input?.registryDigest !== registry.registryDigest ||
1478
+ input?.manifestDigest !== manifestDigest ||
1479
+ input?.entryManifestDigest !== entryManifestDigest
1480
+ ) {
1481
+ throw sourceError(
1482
+ "CC_GRAPH_PRODUCTION_SOURCE_FRAGMENT_INVALID",
1483
+ `${key} fragment does not bind the registry and frozen manifest`,
1484
+ );
1485
+ }
1486
+ const receiptsBySource = new Map(
1487
+ (Array.isArray(input?.receipts) ? input.receipts : []).map((receipt) => [
1488
+ receipt?.payload?.sourceId,
1489
+ receipt,
1490
+ ]),
1491
+ );
1492
+ exactMembers(
1493
+ [...receiptsBySource.keys()],
1494
+ registry.sources.map((source) => source.sourceId),
1495
+ `${key}.receipt sources`,
1496
+ );
1497
+ const receipts = registry.sources.map((source, index) => {
1498
+ const receipt = normalizeSourceReceipt(
1499
+ receiptsBySource.get(source.sourceId),
1500
+ {
1501
+ source,
1502
+ registry,
1503
+ expected,
1504
+ strategy: expectedEntry.entry.cutoverStrategy,
1505
+ surface,
1506
+ entryId,
1507
+ entryManifestDigest,
1508
+ nowMs,
1509
+ jobsInventory,
1510
+ field: `${key}.receipts[${index}]`,
1511
+ },
1512
+ );
1513
+ if (nonces.has(receipt.payload.nonce)) {
1514
+ throw sourceError(
1515
+ "CC_GRAPH_PRODUCTION_SOURCE_REPLAYED",
1516
+ `source receipt nonce was replayed: ${receipt.payload.nonce}`,
1517
+ );
1518
+ }
1519
+ nonces.add(receipt.payload.nonce);
1520
+ const priorJobId = sourceJobIds.get(source.sourceId);
1521
+ if (
1522
+ priorJobId !== undefined &&
1523
+ priorJobId !== receipt.payload.workflowJobDatabaseId
1524
+ ) {
1525
+ throw sourceError(
1526
+ "CC_GRAPH_PRODUCTION_JOB_BINDING_MISMATCH",
1527
+ `${source.sourceId} changed Actions job identity across the 23 entries`,
1528
+ );
1529
+ }
1530
+ sourceJobIds.set(source.sourceId, receipt.payload.workflowJobDatabaseId);
1531
+ const priorBootId = sourceBootIds.get(source.sourceId);
1532
+ if (
1533
+ priorBootId !== undefined &&
1534
+ priorBootId !== receipt.payload.rawLog.bootIdDigest
1535
+ ) {
1536
+ throw sourceError(
1537
+ "CC_GRAPH_PRODUCTION_SOURCE_CROSS_CHECK_FAILED",
1538
+ `${source.sourceId} changed physical boot identity across the 23 entries`,
1539
+ );
1540
+ }
1541
+ sourceBootIds.set(source.sourceId, receipt.payload.rawLog.bootIdDigest);
1542
+ return receipt;
1543
+ });
1544
+ if (
1545
+ new Set(receipts.map((receipt) => receipt.payload.rawLog.bootIdDigest))
1546
+ .size !== receipts.length ||
1547
+ new Set(receipts.map((receipt) => receipt.payload.rawLog.merkleRoot))
1548
+ .size !== receipts.length
1549
+ ) {
1550
+ throw sourceError(
1551
+ "CC_GRAPH_PRODUCTION_SOURCE_CROSS_CHECK_FAILED",
1552
+ `${key} must have independent host boot identities and raw log roots`,
1553
+ );
1554
+ }
1555
+ normalizedFragments.push({
1556
+ schema: GRAPH_PRODUCTION_SOURCE_FRAGMENT_SCHEMA,
1557
+ surface,
1558
+ entryId,
1559
+ registryDigest: registry.registryDigest,
1560
+ manifestDigest,
1561
+ entryManifestDigest,
1562
+ receipts,
1563
+ });
1564
+ const derived =
1565
+ expectedEntry.entry.cutoverStrategy === "disabled"
1566
+ ? deriveDisabledEntry(receipts, expectedEntry, commitSha)
1567
+ : deriveDurableEntry(receipts, expectedEntry, commitSha);
1568
+ if (expectedEntry.entry.cutoverStrategy === "disabled") {
1569
+ disabledEntries.push(derived);
1570
+ } else {
1571
+ entries.push(derived);
1572
+ }
1573
+ }
1574
+ const order = (left, right) =>
1575
+ `${left.surface}/${left.entryId}`.localeCompare(
1576
+ `${right.surface}/${right.entryId}`,
1577
+ );
1578
+ if (
1579
+ sourceJobIds.size !== registry.sources.length ||
1580
+ new Set(sourceJobIds.values()).size !== registry.sources.length ||
1581
+ sourceBootIds.size !== registry.sources.length ||
1582
+ new Set(sourceBootIds.values()).size !== registry.sources.length ||
1583
+ [...jobsInventory.keys()].some(
1584
+ (jobId) => !new Set(sourceJobIds.values()).has(jobId),
1585
+ )
1586
+ ) {
1587
+ throw sourceError(
1588
+ "CC_GRAPH_PRODUCTION_JOB_BINDING_MISMATCH",
1589
+ "all 23 entries must use the same three independent fixed source jobs",
1590
+ );
1591
+ }
1592
+ return Object.freeze({
1593
+ manifestDigest,
1594
+ registry,
1595
+ fragments: normalizedFragments.sort(order),
1596
+ entries: entries.sort(order),
1597
+ disabledEntries: disabledEntries.sort(order),
1598
+ });
1599
+ }