chainlesschain 0.166.13 → 0.166.14

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 (173) hide show
  1. package/package.json +1 -1
  2. package/src/assets/web-panel/assets/{AIOps-BN25cNOD.js → AIOps-C8SyFhOo.js} +1 -1
  3. package/src/assets/web-panel/assets/{ActionButton-Da9IP-m5.js → ActionButton-Dup5gFK0.js} +1 -1
  4. package/src/assets/web-panel/assets/{Analytics-D7hkEvQm.js → Analytics-8FyOWA7V.js} +2 -2
  5. package/src/assets/web-panel/assets/{AppLayout-CCV4Xnbc.js → AppLayout-DBzdU9dg.js} +5 -5
  6. package/src/assets/web-panel/assets/{Artifacts-CVSnVJeA.js → Artifacts-C_0Z8gNy.js} +1 -1
  7. package/src/assets/web-panel/assets/{Audit-CuYdw5Ag.js → Audit-Cf2a-58m.js} +1 -1
  8. package/src/assets/web-panel/assets/{BackgroundAgents-DZCnK-Tf.js → BackgroundAgents-M7w-2zgB.js} +1 -1
  9. package/src/assets/web-panel/assets/{Backup-cV9FdpTy.js → Backup-CCTHQXoL.js} +1 -1
  10. package/src/assets/web-panel/assets/{BaseInput-BRnv6YSp.js → BaseInput-DjXpeFwJ.js} +1 -1
  11. package/src/assets/web-panel/assets/{Chat-Bw5w87P4.js → Chat-cAvchsJU.js} +5 -5
  12. package/src/assets/web-panel/assets/{ChatBubbleRenderer-C0GjtUAw.js → ChatBubbleRenderer-DvEvxlZv.js} +1 -1
  13. package/src/assets/web-panel/assets/{Checkbox-DaoTqcdt.js → Checkbox-DKs6qvOn.js} +1 -1
  14. package/src/assets/web-panel/assets/{Codegen-B4dYcEIi.js → Codegen-DlkA7b9X.js} +1 -1
  15. package/src/assets/web-panel/assets/{Col-mAhuOeuD.js → Col-CkfdKhyJ.js} +1 -1
  16. package/src/assets/web-panel/assets/{Community-BK6KEdBk.js → Community-iNXMHzj6.js} +1 -1
  17. package/src/assets/web-panel/assets/{Compact-CCglmNu5.js → Compact-DQxPEd09.js} +1 -1
  18. package/src/assets/web-panel/assets/{Compliance-Cm8S4of7.js → Compliance-CSKvMCjs.js} +1 -1
  19. package/src/assets/web-panel/assets/{Cowork-Dhs1QLMe.js → Cowork-CkWqazpD.js} +2 -2
  20. package/src/assets/web-panel/assets/{Cron-CLlhmXWk.js → Cron-k6W8IzhB.js} +2 -2
  21. package/src/assets/web-panel/assets/{Crosschain-BnHVbaNs.js → Crosschain-C8MS1OuY.js} +1 -1
  22. package/src/assets/web-panel/assets/{DID-UzQP9-0O.js → DID-DJuCcgiQ.js} +2 -2
  23. package/src/assets/web-panel/assets/{Dashboard-CBdlPClz.js → Dashboard-LeThKzp3.js} +2 -2
  24. package/src/assets/web-panel/assets/{Dropdown-axIRPdq1.js → Dropdown-BvxwgpE4.js} +1 -1
  25. package/src/assets/web-panel/assets/{EmailListRenderer-DUalW5rx.js → EmailListRenderer-n3e_a1M3.js} +1 -1
  26. package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bl4W9luY.js → FamilyGuardDashboard-509W6W6v.js} +1 -1
  27. package/src/assets/web-panel/assets/{Federation-C1yP-uEu.js → Federation-xgZReQm0.js} +1 -1
  28. package/src/assets/web-panel/assets/{FormItemContext-Xh6Ig5q8.js → FormItemContext-jZOhKcQx.js} +1 -1
  29. package/src/assets/web-panel/assets/{GenericCardRenderer-C2cz3bt2.js → GenericCardRenderer-Jo0ULzHv.js} +1 -1
  30. package/src/assets/web-panel/assets/{Git-C_z63ixg.js → Git-BsRcyWQP.js} +2 -2
  31. package/src/assets/web-panel/assets/{Governance-BYqB33_3.js → Governance-JlpMYTAk.js} +1 -1
  32. package/src/assets/web-panel/assets/{Inference-CEULuArm.js → Inference-CUEz872-.js} +1 -1
  33. package/src/assets/web-panel/assets/{KnowledgeGraph-fgMsu-0H.js → KnowledgeGraph-DgrvvEDU.js} +1 -1
  34. package/src/assets/web-panel/assets/{Logs-DO9Hdcec.js → Logs-Df-KY4U3.js} +2 -2
  35. package/src/assets/web-panel/assets/MarkdownRenderer-BEPDcSwz.js +1 -0
  36. package/src/assets/web-panel/assets/{Marketplace-B8zyLiFe.js → Marketplace-kPZDTOeI.js} +1 -1
  37. package/src/assets/web-panel/assets/{McpTools-BLAN2mWW.js → McpTools-CRRzPqNX.js} +3 -3
  38. package/src/assets/web-panel/assets/{Memory-CrQcFM7u.js → Memory-D6pNSkg9.js} +2 -2
  39. package/src/assets/web-panel/assets/{MobileBridge-DVnyiiU-.js → MobileBridge-g-pvtwRC.js} +1 -1
  40. package/src/assets/web-panel/assets/{MobileProjects-qjCME5OD.js → MobileProjects-Bzayfrfe.js} +1 -1
  41. package/src/assets/web-panel/assets/{Mtc-4JE3C3It.js → Mtc-B7S3W-XL.js} +2 -2
  42. package/src/assets/web-panel/assets/{MtcAudit-CjBuak9I.js → MtcAudit-B8OMEWVb.js} +5 -5
  43. package/src/assets/web-panel/assets/{Multisig-BtPcYQcC.js → Multisig-CowRp7GH.js} +3 -3
  44. package/src/assets/web-panel/assets/{NLProgramming-Cx653_Ba.js → NLProgramming-BkQNyi4c.js} +1 -1
  45. package/src/assets/web-panel/assets/{Notes-CkuD3YzS.js → Notes-Cm7gtkKz.js} +3 -3
  46. package/src/assets/web-panel/assets/{NotificationSettings-BgNgU7HH.js → NotificationSettings-CbPTVh9m.js} +1 -1
  47. package/src/assets/web-panel/assets/{OrderTableRenderer-BTC8W0a-.js → OrderTableRenderer-Dswi26mJ.js} +1 -1
  48. package/src/assets/web-panel/assets/{Organization-Dmd4Hjxz.js → Organization-BkjQ9yy7.js} +2 -2
  49. package/src/assets/web-panel/assets/{Overflow-C_5DA-G0.js → Overflow-DmRFXP7Y.js} +1 -1
  50. package/src/assets/web-panel/assets/{P2P-uwslV4XC.js → P2P-DQE1toAu.js} +2 -2
  51. package/src/assets/web-panel/assets/{PdhVaultBrowser-CuNgwVnX.js → PdhVaultBrowser-BujyoDRG.js} +4 -4
  52. package/src/assets/web-panel/assets/{Permissions-CJayVX9H.js → Permissions-xSjfOaiU.js} +4 -4
  53. package/src/assets/web-panel/assets/{PersonalDataHub-Cb05H5go.js → PersonalDataHub-B9ICLqVP.js} +4 -4
  54. package/src/assets/web-panel/assets/{Pipeline-BZlGKapZ.js → Pipeline-CUNwyN9E.js} +1 -1
  55. package/src/assets/web-panel/assets/{Privacy-BIzZWYzd.js → Privacy-SOoCricX.js} +1 -1
  56. package/src/assets/web-panel/assets/{ProjectInit-vEYYyQXQ.js → ProjectInit-CSaFul0F.js} +2 -2
  57. package/src/assets/web-panel/assets/{ProjectSettings-MZqTy5CC.js → ProjectSettings-CueiB_oe.js} +2 -2
  58. package/src/assets/web-panel/assets/{Projects-CRARUOl8.js → Projects-BOeXvU7y.js} +1 -1
  59. package/src/assets/web-panel/assets/{Providers-80VAK8PX.js → Providers-CTqf_31h.js} +1 -1
  60. package/src/assets/web-panel/assets/{QrScannerModal-CzRSoIwK.js → QrScannerModal-C8ZP31E9.js} +1 -1
  61. package/src/assets/web-panel/assets/{QuickAsk-BpwQ2n4C.js → QuickAsk-CPgn4713.js} +1 -1
  62. package/src/assets/web-panel/assets/{Recommend-BC03TZ79.js → Recommend-8_e0zw0i.js} +1 -1
  63. package/src/assets/web-panel/assets/{RemoteSession-DE59-jaa.js → RemoteSession-iT7lbGqw.js} +3 -3
  64. package/src/assets/web-panel/assets/{Reputation-DOHmeWyA.js → Reputation-B4q9SUOz.js} +1 -1
  65. package/src/assets/web-panel/assets/{Row-hc5RZl1D.js → Row-CA_nASyL.js} +1 -1
  66. package/src/assets/web-panel/assets/{RssFeed-Cv5uMP7E.js → RssFeed-Cwb0E3yv.js} +3 -3
  67. package/src/assets/web-panel/assets/{Search-qohu7Ug0.js → Search-D9iVsOxJ.js} +1 -1
  68. package/src/assets/web-panel/assets/{Security-drCPFWV5.js → Security-DPLbYchG.js} +4 -4
  69. package/src/assets/web-panel/assets/{Services-BAEgUeRy.js → Services-DGMVL_LF.js} +2 -2
  70. package/src/assets/web-panel/assets/{Skeleton-CkJkKBlo.js → Skeleton-B_6kym0D.js} +1 -1
  71. package/src/assets/web-panel/assets/{Skills-bMuKEpez.js → Skills-Ds6r0dXB.js} +1 -1
  72. package/src/assets/web-panel/assets/{Sla-DxWQVHRC.js → Sla-BLHAbBhb.js} +1 -1
  73. package/src/assets/web-panel/assets/{SpeechSettings-BpS8idmV.js → SpeechSettings-CZuddd14.js} +1 -1
  74. package/src/assets/web-panel/assets/{SyncSettings-BPBLlW5v.js → SyncSettings-CdBq7oSl.js} +2 -2
  75. package/src/assets/web-panel/assets/{Tasks-C4SLEeUh.js → Tasks-DfT60Nyl.js} +1 -1
  76. package/src/assets/web-panel/assets/{Templates-r-wfU39U.js → Templates-RLfsKy2V.js} +1 -1
  77. package/src/assets/web-panel/assets/{Tenant-C7vjRmLN.js → Tenant-Cw8xCNLE.js} +1 -1
  78. package/src/assets/web-panel/assets/{Terminal-DdzeFCV2.js → Terminal-BOUkNu4f.js} +2 -2
  79. package/src/assets/web-panel/assets/{TimelineRenderer-CYyWN2KD.js → TimelineRenderer-CgGZWaRm.js} +1 -1
  80. package/src/assets/web-panel/assets/{Tokens-D8A9osUg.js → Tokens-CpwjVyir.js} +1 -1
  81. package/src/assets/web-panel/assets/{Trigger-DBvrNlyS.js → Trigger-DL21H-eb.js} +1 -1
  82. package/src/assets/web-panel/assets/{Trust-DBVAGc7o.js → Trust-B3ffFclW.js} +1 -1
  83. package/src/assets/web-panel/assets/{UkeySign-C1vQUCRJ.js → UkeySign-CkBeb5aI.js} +1 -1
  84. package/src/assets/web-panel/assets/{VideoEditing-BKs4dah4.js → VideoEditing-U2iVaE3r.js} +1 -1
  85. package/src/assets/web-panel/assets/{Wallet-ByIuj3lD.js → Wallet-CQk2wzYd.js} +4 -4
  86. package/src/assets/web-panel/assets/{WebAuthn-DYlAFCm9.js → WebAuthn-DYIuIYzT.js} +5 -5
  87. package/src/assets/web-panel/assets/{WorkflowEditor-Dwyadd0c.js → WorkflowEditor-tg1c2mZQ.js} +1 -1
  88. package/src/assets/web-panel/assets/{chat-Bm2f3s08.js → chat-CtL61spq.js} +1 -1
  89. package/src/assets/web-panel/assets/{colors-C8ORSh7V.js → colors-DKpbeo8s.js} +1 -1
  90. package/src/assets/web-panel/assets/{compact-item-1iVlnsM-.js → compact-item-gPZsrswt.js} +1 -1
  91. package/src/assets/web-panel/assets/{createContext-CmqNyUSL.js → createContext-CfHlwzVr.js} +1 -1
  92. package/src/assets/web-panel/assets/devWarning-DdqnMhzi.js +1 -0
  93. package/src/assets/web-panel/assets/{hasIn-rUMaX3Ul.js → hasIn-MV47g_6X.js} +1 -1
  94. package/src/assets/web-panel/assets/{index-CCEyz58d.js → index-6G0ETFTW.js} +1 -1
  95. package/src/assets/web-panel/assets/{index-BVtnizyC.js → index-BBw52sEl.js} +1 -1
  96. package/src/assets/web-panel/assets/{index-BIvM-uAX.js → index-BN-wxSQ8.js} +1 -1
  97. package/src/assets/web-panel/assets/{index-DvedhJeM.js → index-BOyGUuBE.js} +1 -1
  98. package/src/assets/web-panel/assets/{index-BCOhTILd.js → index-BQp5M0s1.js} +1 -1
  99. package/src/assets/web-panel/assets/index-BcK8o4ke.js +1 -0
  100. package/src/assets/web-panel/assets/{index-DVJ1uKf4.js → index-BjlPD7GO.js} +1 -1
  101. package/src/assets/web-panel/assets/{index-BQL9r73Y.js → index-C4lF-f_z.js} +1 -1
  102. package/src/assets/web-panel/assets/{index-C5RfRciU.js → index-C6nC_DyY.js} +1 -1
  103. package/src/assets/web-panel/assets/{index-DgUE51Pz.js → index-C9RF4ieu.js} +1 -1
  104. package/src/assets/web-panel/assets/{index-CkFvuAex.js → index-C9gtY4nG.js} +3 -3
  105. package/src/assets/web-panel/assets/{index-C-J5-pGH.js → index-CB1HnbNK.js} +1 -1
  106. package/src/assets/web-panel/assets/{index-Bf1PlIHp.js → index-CRv_uMLV.js} +1 -1
  107. package/src/assets/web-panel/assets/{index-tqTx5wXo.js → index-C_B9LiKs.js} +1 -1
  108. package/src/assets/web-panel/assets/{index-Bgw4Y2rU.js → index-Cjv0kKCU.js} +1 -1
  109. package/src/assets/web-panel/assets/{index-DJxtPFOh.js → index-CmM9jqwe.js} +1 -1
  110. package/src/assets/web-panel/assets/{index-pHWA7bmM.js → index-CmPJpLoD.js} +1 -1
  111. package/src/assets/web-panel/assets/{index-Dsve5Gkv.js → index-CzfxTSPW.js} +1 -1
  112. package/src/assets/web-panel/assets/{index-B59OWAs7.js → index-D1XB5-t5.js} +1 -1
  113. package/src/assets/web-panel/assets/{index-BK-RY0-7.js → index-DGtEBodr.js} +1 -1
  114. package/src/assets/web-panel/assets/{index-BC32_0xT.js → index-DIeJmlAA.js} +1 -1
  115. package/src/assets/web-panel/assets/{index-OcbzdRpT.js → index-DMMvlLp9.js} +1 -1
  116. package/src/assets/web-panel/assets/{index-geQinnns.js → index-DU4rZpiK.js} +1 -1
  117. package/src/assets/web-panel/assets/{index-D98FXb0w.js → index-D_Pl1GnW.js} +1 -1
  118. package/src/assets/web-panel/assets/{index-BC8Wb6cy.js → index-Dd19vRwy.js} +1 -1
  119. package/src/assets/web-panel/assets/{index-fPK6GFEg.js → index-Ddcpj1El.js} +1 -1
  120. package/src/assets/web-panel/assets/{index-CfiHyVyl.js → index-Df9sWKpl.js} +1 -1
  121. package/src/assets/web-panel/assets/{index-C-ZIHi42.js → index-DfLyHCWi.js} +1 -1
  122. package/src/assets/web-panel/assets/{index-DDHk911Q.js → index-DnZ_zS1M.js} +1 -1
  123. package/src/assets/web-panel/assets/{index-BFniLMn9.js → index-DsH-i_3d.js} +1 -1
  124. package/src/assets/web-panel/assets/index-DvbAjyYI.js +1 -0
  125. package/src/assets/web-panel/assets/{index-Dcck8QfW.js → index-ExAGPAnX.js} +1 -1
  126. package/src/assets/web-panel/assets/{index-DwMdWKiF.js → index-Rb7CRMw9.js} +1 -1
  127. package/src/assets/web-panel/assets/{index-reUHhuz8.js → index-Zvkt9xqi.js} +1 -1
  128. package/src/assets/web-panel/assets/{index-BgwLNxmr.js → index-bSFrAszw.js} +1 -1
  129. package/src/assets/web-panel/assets/{index-C3B1S-mW.js → index-fYcS7NS0.js} +1 -1
  130. package/src/assets/web-panel/assets/{index-CTfEmKK0.js → index-srBc9U_s.js} +1 -1
  131. package/src/assets/web-panel/assets/{index-B53c3diB.js → index-xUlgG0Xe.js} +1 -1
  132. package/src/assets/web-panel/assets/{index-B8BrB0Ls.js → index-yxxXlqBA.js} +1 -1
  133. package/src/assets/web-panel/assets/{initDefaultProps-CuA-cpkL.js → initDefaultProps-BeOZoSZ4.js} +1 -1
  134. package/src/assets/web-panel/assets/{motion-CimmE_9b.js → motion-BXE8VMKx.js} +1 -1
  135. package/src/assets/web-panel/assets/{move-DlGlrltz.js → move-I9sdvPGX.js} +1 -1
  136. package/src/assets/web-panel/assets/{mtc-parser-CK6BJQ07.js → mtc-parser-d25zrJd0.js} +1 -1
  137. package/src/assets/web-panel/assets/{omit-BKjBfZfB.js → omit-D71V_T4e.js} +1 -1
  138. package/src/assets/web-panel/assets/{pickAttrs-B2chVYw9.js → pickAttrs-BNZKpIAR.js} +1 -1
  139. package/src/assets/web-panel/assets/{placementArrow-BW8aLJJ3.js → placementArrow-jOcgxTTz.js} +1 -1
  140. package/src/assets/web-panel/assets/{responsiveObserve-dXlPl66S.js → responsiveObserve-pta_O8AU.js} +1 -1
  141. package/src/assets/web-panel/assets/{slide-hSa89S4G.js → slide-Dlq6om7z.js} +1 -1
  142. package/src/assets/web-panel/assets/{statusUtils-DM6KLt-s.js → statusUtils-BnkO_yRv.js} +1 -1
  143. package/src/assets/web-panel/assets/{styleChecker-C9MQAK15.js → styleChecker-p8NfWmcG.js} +1 -1
  144. package/src/assets/web-panel/assets/{useFlexGapSupport-BpQDpP0z.js → useFlexGapSupport-KcjZZ5U7.js} +1 -1
  145. package/src/assets/web-panel/assets/{useFs-C7rtRViA.js → useFs-gqonDMWB.js} +1 -1
  146. package/src/assets/web-panel/assets/{usePersonalDataHub-Dm15xafq.js → usePersonalDataHub-jIIfE4go.js} +1 -1
  147. package/src/assets/web-panel/assets/{vnode-mEiEytw4.js → vnode-Dyo7fBrM.js} +1 -1
  148. package/src/assets/web-panel/assets/{zoom-DzPxflhY.js → zoom-6l8BMg9W.js} +1 -1
  149. package/src/assets/web-panel/index.html +1 -1
  150. package/src/command-help-index.json +2 -2
  151. package/src/commands/agent.js +49 -27
  152. package/src/commands/record-replay.js +772 -0
  153. package/src/commands/skill.js +3 -0
  154. package/src/data/changelog.json +13 -0
  155. package/src/lib/agent-sandbox.js +4 -8
  156. package/src/lib/agent-team/team-message-tools.js +23 -5
  157. package/src/lib/process-execution-broker/credential-transport.js +31 -0
  158. package/src/lib/process-execution-broker/index.js +74 -0
  159. package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
  160. package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
  161. package/src/lib/process-execution-broker/windows-sandbox.cs +7 -3
  162. package/src/lib/record-replay/browser-target-policy.js +406 -0
  163. package/src/lib/record-replay/index.js +4 -0
  164. package/src/lib/record-replay/playwright-ui-driver.js +83 -20
  165. package/src/lib/record-replay/playwright-ui-recorder.js +611 -0
  166. package/src/lib/record-replay/recorded-skill-package.js +486 -0
  167. package/src/lib/record-replay/recorded-skill-store.js +1166 -0
  168. package/src/lib/record-replay/skill-recorder.js +634 -64
  169. package/src/runtime/headless-runner.js +72 -51
  170. package/src/assets/web-panel/assets/MarkdownRenderer-BkrK04xW.js +0 -1
  171. package/src/assets/web-panel/assets/devWarning-B69RCDDH.js +0 -1
  172. package/src/assets/web-panel/assets/index-9biMQ-tw.js +0 -1
  173. package/src/assets/web-panel/assets/index-COie87kl.js +0 -1
@@ -1,4 +1,5 @@
1
1
  import { createHash } from "node:crypto";
2
+ import { createRecordedSkillNetworkPolicy } from "./browser-target-policy.js";
2
3
 
3
4
  const SAFE_ACTION_CAPABILITIES = Object.freeze({
4
5
  observe: "ui.observe",
@@ -25,6 +26,19 @@ const VOLATILE_PATTERNS = Object.freeze([
25
26
  /(?:^|[\\/])(?:tmp|temp)(?:[\\/]|$)/iu,
26
27
  ]);
27
28
  const PARAMETER_PATTERN = /^\$\{parameter\.([A-Za-z][A-Za-z0-9_]*)\}$/u;
29
+ const EMBEDDED_PARAMETER_PATTERN = /\$\{parameter\.([A-Za-z][A-Za-z0-9_]*)\}/gu;
30
+ const DRAFT_SCHEMA = "chainlesschain.recorded-skill-draft/v1";
31
+ const REPORT_SCHEMA = "chainlesschain.recorded-skill-replay/v1";
32
+ const MAX_ACTIONS = 256;
33
+ const MAX_FAILURE_CONDITIONS = 64;
34
+ const MAX_EVIDENCE_BYTES = 256 * 1024;
35
+ const ACTION_KEYS = Object.freeze({
36
+ observe: Object.freeze(new Set(["kind", "target"])),
37
+ click: Object.freeze(new Set(["kind", "target"])),
38
+ type: Object.freeze(new Set(["kind", "target", "value"])),
39
+ select: Object.freeze(new Set(["kind", "target", "value"])),
40
+ assert: Object.freeze(new Set(["kind", "target", "value"])),
41
+ });
28
42
 
29
43
  function canonicalValue(value) {
30
44
  if (Array.isArray(value)) return value.map(canonicalValue);
@@ -44,8 +58,49 @@ function digest(value, domain) {
44
58
  .digest("hex")}`;
45
59
  }
46
60
 
47
- function clone(value) {
48
- return value == null ? value : JSON.parse(JSON.stringify(value));
61
+ function clone(value, label = "value") {
62
+ if (value == null) return value;
63
+ let serialized;
64
+ try {
65
+ serialized = JSON.stringify(value);
66
+ } catch {
67
+ throw recorderError(
68
+ "CC_REPLAY_INVALID_ARGUMENT",
69
+ `${label} must be JSON serializable`,
70
+ );
71
+ }
72
+ if (serialized === undefined) {
73
+ throw recorderError(
74
+ "CC_REPLAY_INVALID_ARGUMENT",
75
+ `${label} must be JSON serializable`,
76
+ );
77
+ }
78
+ return JSON.parse(serialized);
79
+ }
80
+
81
+ function deepFreeze(value) {
82
+ if (!value || typeof value !== "object" || Object.isFrozen(value)) {
83
+ return value;
84
+ }
85
+ for (const child of Object.values(value)) deepFreeze(child);
86
+ return Object.freeze(value);
87
+ }
88
+
89
+ function assertPlainObject(value, label) {
90
+ if (!value || typeof value !== "object" || Array.isArray(value)) {
91
+ throw recorderError(
92
+ "CC_REPLAY_INVALID_ARGUMENT",
93
+ `${label} must be a JSON object`,
94
+ );
95
+ }
96
+ return value;
97
+ }
98
+
99
+ function assertExactKeys(value, allowed, code, label) {
100
+ const unexpected = Object.keys(value).filter((key) => !allowed.has(key));
101
+ if (unexpected.length > 0) {
102
+ throw recorderError(code, `${label} contains unsupported fields`);
103
+ }
49
104
  }
50
105
 
51
106
  function recorderError(code, message, details = {}) {
@@ -75,11 +130,13 @@ function walkStrings(value, visit, path = "#") {
75
130
 
76
131
  function substituteCaptured(value, bindings) {
77
132
  if (typeof value === "string") {
78
- return bindings.reduce(
79
- (current, binding) =>
80
- current.split(binding.value).join(`\${parameter.${binding.name}}`),
81
- value,
82
- );
133
+ return [...bindings]
134
+ .sort((left, right) => right.value.length - left.value.length)
135
+ .reduce(
136
+ (current, binding) =>
137
+ current.split(binding.value).join(`\${parameter.${binding.name}}`),
138
+ value,
139
+ );
83
140
  }
84
141
  if (Array.isArray(value)) {
85
142
  return value.map((item) => substituteCaptured(item, bindings));
@@ -93,7 +150,7 @@ function substituteCaptured(value, bindings) {
93
150
  );
94
151
  }
95
152
 
96
- function scanRecordedValue(value) {
153
+ export function scanRecordedValue(value) {
97
154
  const findings = [];
98
155
  walkStrings(value, (text, path) => {
99
156
  if (PARAMETER_PATTERN.test(text)) return;
@@ -110,8 +167,9 @@ function scanRecordedValue(value) {
110
167
 
111
168
  function environmentBinding(environment) {
112
169
  const value = canonicalValue(environment || {});
113
- return Object.freeze({
114
- requirements: Object.freeze(clone(value)),
170
+ assertPlainObject(value, "environment");
171
+ return deepFreeze({
172
+ requirements: clone(value, "environment"),
115
173
  digest: digest(value, "cc.record-replay.environment/v1"),
116
174
  });
117
175
  }
@@ -124,6 +182,456 @@ function assertIdentifier(value, label) {
124
182
  return text;
125
183
  }
126
184
 
185
+ function normalizeDescription(value) {
186
+ if (typeof value !== "string") {
187
+ throw recorderError(
188
+ "CC_REPLAY_INVALID_ARGUMENT",
189
+ "description must be a string",
190
+ );
191
+ }
192
+ return value.slice(0, 2048);
193
+ }
194
+
195
+ function normalizeFailureConditions(values) {
196
+ if (!Array.isArray(values) || values.length > MAX_FAILURE_CONDITIONS) {
197
+ throw recorderError(
198
+ "CC_REPLAY_INVALID_ARGUMENT",
199
+ `failureConditions must contain at most ${MAX_FAILURE_CONDITIONS} entries`,
200
+ );
201
+ }
202
+ return values.map((value) => {
203
+ if (typeof value !== "string" || !value.trim()) {
204
+ throw recorderError(
205
+ "CC_REPLAY_INVALID_ARGUMENT",
206
+ "failure conditions must be non-empty strings",
207
+ );
208
+ }
209
+ return value.slice(0, 512);
210
+ });
211
+ }
212
+
213
+ function normalizeAction(action, index) {
214
+ assertPlainObject(action, `action ${index}`);
215
+ const kind = String(action.kind || "");
216
+ const capability = SAFE_ACTION_CAPABILITIES[kind];
217
+ if (!capability) {
218
+ throw recorderError(
219
+ "CC_REPLAY_UNSAFE_ACTION",
220
+ `recorded action kind is not in the low-risk allowlist: ${kind}`,
221
+ );
222
+ }
223
+ assertExactKeys(
224
+ action,
225
+ ACTION_KEYS[kind],
226
+ "CC_REPLAY_UNSAFE_ACTION",
227
+ `action ${index}`,
228
+ );
229
+ if (
230
+ typeof action.target !== "string" ||
231
+ !action.target ||
232
+ action.target.length > 1_024
233
+ ) {
234
+ throw recorderError(
235
+ "CC_REPLAY_INVALID_ARGUMENT",
236
+ `action ${index} target must be a non-empty string no longer than 1024 characters`,
237
+ );
238
+ }
239
+ if (["type", "select"].includes(kind) && typeof action.value !== "string") {
240
+ throw recorderError(
241
+ "CC_REPLAY_INVALID_ARGUMENT",
242
+ `action ${index} ${kind} value must be a string`,
243
+ );
244
+ }
245
+ if (kind === "type" && action.value.length > 8_192) {
246
+ throw recorderError(
247
+ "CC_REPLAY_INVALID_ARGUMENT",
248
+ `action ${index} type value exceeds 8192 characters`,
249
+ );
250
+ }
251
+ if (kind === "select" && action.value.length > 1_024) {
252
+ throw recorderError(
253
+ "CC_REPLAY_INVALID_ARGUMENT",
254
+ `action ${index} select value exceeds 1024 characters`,
255
+ );
256
+ }
257
+ if (
258
+ kind === "assert" &&
259
+ "value" in action &&
260
+ typeof action.value !== "string"
261
+ ) {
262
+ throw recorderError(
263
+ "CC_REPLAY_INVALID_ARGUMENT",
264
+ `action ${index} assert value must be a string`,
265
+ );
266
+ }
267
+ return {
268
+ id: `action-${index + 1}`,
269
+ ...action,
270
+ kind,
271
+ requiredCapability: capability,
272
+ };
273
+ }
274
+
275
+ function parameterNamesIn(value) {
276
+ const names = new Set();
277
+ walkStrings(value, (text) => {
278
+ EMBEDDED_PARAMETER_PATTERN.lastIndex = 0;
279
+ for (const match of text.matchAll(EMBEDDED_PARAMETER_PATTERN)) {
280
+ names.add(match[1]);
281
+ }
282
+ });
283
+ return names;
284
+ }
285
+
286
+ function draftBodyFrom(value) {
287
+ return {
288
+ schema: value.schema,
289
+ name: value.name,
290
+ description: value.description,
291
+ status: "draft",
292
+ actions: value.actions,
293
+ parameters: value.parameters,
294
+ capabilityManifest: value.capabilityManifest,
295
+ failureConditions: value.failureConditions,
296
+ environment: value.environment,
297
+ };
298
+ }
299
+
300
+ function integrityError(code, message) {
301
+ return recorderError(code, message);
302
+ }
303
+
304
+ /**
305
+ * Validate and re-materialize a draft received from an external or persisted
306
+ * boundary. Every digest and derived field is recomputed; callers never trust
307
+ * a deserialized object's frozen state.
308
+ */
309
+ export function validateRecordedSkillDraft(value) {
310
+ assertPlainObject(value, "recorded skill draft");
311
+ assertExactKeys(
312
+ value,
313
+ new Set([
314
+ "schema",
315
+ "name",
316
+ "description",
317
+ "status",
318
+ "actions",
319
+ "parameters",
320
+ "capabilityManifest",
321
+ "failureConditions",
322
+ "environment",
323
+ "draftDigest",
324
+ ]),
325
+ "CC_REPLAY_DRAFT_INVALID",
326
+ "recorded skill draft",
327
+ );
328
+ if (value.schema !== DRAFT_SCHEMA || value.status !== "draft") {
329
+ throw integrityError(
330
+ "CC_REPLAY_DRAFT_INVALID",
331
+ "recorded skill draft schema or status is invalid",
332
+ );
333
+ }
334
+ const name = assertIdentifier(value.name, "skill name");
335
+ const description = normalizeDescription(value.description);
336
+ if (
337
+ !Array.isArray(value.actions) ||
338
+ value.actions.length < 1 ||
339
+ value.actions.length > MAX_ACTIONS
340
+ ) {
341
+ throw integrityError(
342
+ "CC_REPLAY_DRAFT_INVALID",
343
+ `recorded skill draft must contain between 1 and ${MAX_ACTIONS} actions`,
344
+ );
345
+ }
346
+ const actions = value.actions.map((action, index) => {
347
+ assertPlainObject(action, `action ${index}`);
348
+ assertExactKeys(
349
+ action,
350
+ new Set(["id", "kind", "target", "value", "requiredCapability"]),
351
+ "CC_REPLAY_DRAFT_INVALID",
352
+ `action ${index}`,
353
+ );
354
+ const { id, requiredCapability, ...source } = action;
355
+ const normalized = normalizeAction(source, index);
356
+ if (
357
+ id !== normalized.id ||
358
+ requiredCapability !== normalized.requiredCapability
359
+ ) {
360
+ throw integrityError(
361
+ "CC_REPLAY_DRAFT_INTEGRITY",
362
+ "recorded skill action identity or capability was modified",
363
+ );
364
+ }
365
+ return normalized;
366
+ });
367
+ if (!Array.isArray(value.parameters)) {
368
+ throw integrityError(
369
+ "CC_REPLAY_DRAFT_INVALID",
370
+ "recorded skill parameters must be an array",
371
+ );
372
+ }
373
+ const parameterNames = new Set();
374
+ const parameters = value.parameters.map((parameter) => {
375
+ assertPlainObject(parameter, "recorded skill parameter");
376
+ assertExactKeys(
377
+ parameter,
378
+ new Set(["name", "sensitive", "required"]),
379
+ "CC_REPLAY_DRAFT_INVALID",
380
+ "recorded skill parameter",
381
+ );
382
+ const parameterName = assertIdentifier(parameter.name, "parameter name");
383
+ if (parameterNames.has(parameterName)) {
384
+ throw integrityError(
385
+ "CC_REPLAY_DRAFT_INVALID",
386
+ "recorded skill parameter names must be unique",
387
+ );
388
+ }
389
+ parameterNames.add(parameterName);
390
+ if (
391
+ typeof parameter.sensitive !== "boolean" ||
392
+ typeof parameter.required !== "boolean"
393
+ ) {
394
+ throw integrityError(
395
+ "CC_REPLAY_DRAFT_INVALID",
396
+ "recorded skill parameter flags must be boolean",
397
+ );
398
+ }
399
+ return {
400
+ name: parameterName,
401
+ sensitive: parameter.sensitive,
402
+ required: parameter.required,
403
+ };
404
+ });
405
+ const referencedParameters = parameterNamesIn(actions);
406
+ if (
407
+ [...referencedParameters].some((name) => !parameterNames.has(name)) ||
408
+ [...parameterNames].some((name) => !referencedParameters.has(name))
409
+ ) {
410
+ throw integrityError(
411
+ "CC_REPLAY_DRAFT_INVALID",
412
+ "recorded skill parameters must exactly match action placeholders",
413
+ );
414
+ }
415
+ const expectedCapabilities = [
416
+ ...new Set(actions.map((action) => action.requiredCapability)),
417
+ ].sort();
418
+ if (
419
+ !Array.isArray(value.capabilityManifest) ||
420
+ JSON.stringify(value.capabilityManifest) !==
421
+ JSON.stringify(expectedCapabilities)
422
+ ) {
423
+ throw integrityError(
424
+ "CC_REPLAY_DRAFT_INTEGRITY",
425
+ "recorded skill capability manifest was modified",
426
+ );
427
+ }
428
+ const failureConditions = normalizeFailureConditions(value.failureConditions);
429
+ assertPlainObject(value.environment, "recorded skill environment binding");
430
+ assertExactKeys(
431
+ value.environment,
432
+ new Set(["requirements", "digest"]),
433
+ "CC_REPLAY_DRAFT_INVALID",
434
+ "recorded skill environment binding",
435
+ );
436
+ const environment = environmentBinding(value.environment.requirements);
437
+ if (environment.digest !== value.environment.digest) {
438
+ throw integrityError(
439
+ "CC_REPLAY_DRAFT_INTEGRITY",
440
+ "recorded skill environment binding was modified",
441
+ );
442
+ }
443
+ const scanned = { description, actions, failureConditions, environment };
444
+ const findings = scanRecordedValue(scanned);
445
+ if (findings.length > 0) {
446
+ throw recorderError(
447
+ "CC_REPLAY_SENSITIVE_OR_VOLATILE_DATA",
448
+ "recording contains secret, PII, or volatile data that must be parameterized",
449
+ { findings },
450
+ );
451
+ }
452
+ const body = {
453
+ schema: DRAFT_SCHEMA,
454
+ name,
455
+ description,
456
+ status: "draft",
457
+ actions,
458
+ parameters,
459
+ capabilityManifest: expectedCapabilities,
460
+ failureConditions,
461
+ environment,
462
+ };
463
+ const expectedDigest = digest(body, "cc.record-replay.skill-draft/v1");
464
+ if (value.draftDigest !== expectedDigest) {
465
+ throw integrityError(
466
+ "CC_REPLAY_DRAFT_INTEGRITY",
467
+ "recorded skill draft digest does not match its reviewed content",
468
+ );
469
+ }
470
+ return deepFreeze({ ...clone(body), draftDigest: expectedDigest });
471
+ }
472
+
473
+ /** Validate an approved skill and re-bind approval to the verified draft. */
474
+ export function validateReviewedRecordedSkill(value) {
475
+ assertPlainObject(value, "reviewed recorded skill");
476
+ assertExactKeys(
477
+ value,
478
+ new Set([
479
+ "schema",
480
+ "name",
481
+ "description",
482
+ "status",
483
+ "actions",
484
+ "parameters",
485
+ "capabilityManifest",
486
+ "failureConditions",
487
+ "environment",
488
+ "draftDigest",
489
+ "review",
490
+ "approvalDigest",
491
+ ]),
492
+ "CC_REPLAY_APPROVAL_INVALID",
493
+ "reviewed recorded skill",
494
+ );
495
+ if (value.status !== "approved") {
496
+ throw integrityError(
497
+ "CC_REPLAY_APPROVAL_INVALID",
498
+ "recorded skill is not approved",
499
+ );
500
+ }
501
+ const verifiedDraft = validateRecordedSkillDraft({
502
+ ...draftBodyFrom(value),
503
+ draftDigest: value.draftDigest,
504
+ });
505
+ assertPlainObject(value.review, "recorded skill review");
506
+ assertExactKeys(
507
+ value.review,
508
+ new Set([
509
+ "reviewerId",
510
+ "draftDigest",
511
+ "approvedCapabilities",
512
+ "acceptedFailureConditions",
513
+ ]),
514
+ "CC_REPLAY_APPROVAL_INVALID",
515
+ "recorded skill review",
516
+ );
517
+ const review = {
518
+ reviewerId: assertIdentifier(value.review.reviewerId, "reviewerId"),
519
+ draftDigest: value.review.draftDigest,
520
+ approvedCapabilities: [
521
+ ...new Set(value.review.approvedCapabilities || []),
522
+ ].sort(),
523
+ acceptedFailureConditions: value.review.acceptedFailureConditions,
524
+ };
525
+ if (
526
+ review.draftDigest !== verifiedDraft.draftDigest ||
527
+ review.acceptedFailureConditions !== true ||
528
+ JSON.stringify(review.approvedCapabilities) !==
529
+ JSON.stringify(verifiedDraft.capabilityManifest)
530
+ ) {
531
+ throw integrityError(
532
+ "CC_REPLAY_APPROVAL_INVALID",
533
+ "recorded skill approval does not match the verified draft",
534
+ );
535
+ }
536
+ const approvalDigest = digest(review, "cc.record-replay.review/v1");
537
+ if (value.approvalDigest !== approvalDigest) {
538
+ throw integrityError(
539
+ "CC_REPLAY_APPROVAL_INVALID",
540
+ "recorded skill approval digest was modified",
541
+ );
542
+ }
543
+ return deepFreeze({
544
+ ...clone(verifiedDraft),
545
+ status: "approved",
546
+ review,
547
+ approvalDigest,
548
+ });
549
+ }
550
+
551
+ /** Validate a persisted replay report and optionally bind it to one skill. */
552
+ export function validateRecordedSkillReplayReport(value, { skill } = {}) {
553
+ assertPlainObject(value, "recorded skill replay report");
554
+ assertExactKeys(
555
+ value,
556
+ new Set([
557
+ "schema",
558
+ "skillDigest",
559
+ "environmentDigest",
560
+ "status",
561
+ "receipts",
562
+ "replayDigest",
563
+ ]),
564
+ "CC_REPLAY_REPORT_INVALID",
565
+ "recorded skill replay report",
566
+ );
567
+ if (value.schema !== REPORT_SCHEMA || value.status !== "succeeded") {
568
+ throw integrityError(
569
+ "CC_REPLAY_REPORT_INVALID",
570
+ "recorded skill replay report schema or status is invalid",
571
+ );
572
+ }
573
+ if (!Array.isArray(value.receipts) || value.receipts.length > MAX_ACTIONS) {
574
+ throw integrityError(
575
+ "CC_REPLAY_REPORT_INVALID",
576
+ "recorded skill replay receipts are invalid",
577
+ );
578
+ }
579
+ const receipts = value.receipts.map((receipt, index) => {
580
+ assertPlainObject(receipt, "recorded skill replay receipt");
581
+ assertExactKeys(
582
+ receipt,
583
+ new Set(["actionId", "evidenceDigest"]),
584
+ "CC_REPLAY_REPORT_INVALID",
585
+ "recorded skill replay receipt",
586
+ );
587
+ if (
588
+ receipt.actionId !== `action-${index + 1}` ||
589
+ !/^sha256:[a-f0-9]{64}$/u.test(String(receipt.evidenceDigest || ""))
590
+ ) {
591
+ throw integrityError(
592
+ "CC_REPLAY_REPORT_INVALID",
593
+ "recorded skill replay receipt identity or digest is invalid",
594
+ );
595
+ }
596
+ return {
597
+ actionId: receipt.actionId,
598
+ evidenceDigest: receipt.evidenceDigest,
599
+ };
600
+ });
601
+ const report = {
602
+ schema: REPORT_SCHEMA,
603
+ skillDigest: String(value.skillDigest || ""),
604
+ environmentDigest: String(value.environmentDigest || ""),
605
+ status: "succeeded",
606
+ receipts,
607
+ };
608
+ const replayDigest = digest(report, "cc.record-replay.report/v1");
609
+ if (
610
+ value.replayDigest !== replayDigest ||
611
+ !/^sha256:[a-f0-9]{64}$/u.test(report.skillDigest) ||
612
+ !/^sha256:[a-f0-9]{64}$/u.test(report.environmentDigest)
613
+ ) {
614
+ throw integrityError(
615
+ "CC_REPLAY_REPORT_INVALID",
616
+ "recorded skill replay report digest is invalid",
617
+ );
618
+ }
619
+ if (skill) {
620
+ const verifiedSkill = validateReviewedRecordedSkill(skill);
621
+ if (
622
+ report.skillDigest !== verifiedSkill.draftDigest ||
623
+ report.environmentDigest !== verifiedSkill.environment.digest ||
624
+ receipts.length !== verifiedSkill.actions.length
625
+ ) {
626
+ throw integrityError(
627
+ "CC_REPLAY_REPORT_INVALID",
628
+ "recorded skill replay report does not match the approved skill",
629
+ );
630
+ }
631
+ }
632
+ return deepFreeze({ ...report, replayDigest });
633
+ }
634
+
127
635
  export function createRecordedSkillDraft({
128
636
  name,
129
637
  description = "",
@@ -133,14 +641,29 @@ export function createRecordedSkillDraft({
133
641
  failureConditions = [],
134
642
  } = {}) {
135
643
  const safeName = assertIdentifier(name, "skill name");
136
- if (!Array.isArray(actions) || actions.length === 0 || actions.length > 256) {
644
+ if (
645
+ !Array.isArray(actions) ||
646
+ actions.length === 0 ||
647
+ actions.length > MAX_ACTIONS
648
+ ) {
137
649
  throw recorderError(
138
650
  "CC_REPLAY_INVALID_ARGUMENT",
139
- "recording requires between 1 and 256 actions",
651
+ `recording requires between 1 and ${MAX_ACTIONS} actions`,
140
652
  );
141
653
  }
654
+ const safeDescription = normalizeDescription(description);
655
+ const safeFailureConditions = normalizeFailureConditions(failureConditions);
656
+ const safeEnvironment = clone(environment, "environment");
657
+ assertPlainObject(safeEnvironment, "environment");
142
658
  const names = new Set();
143
659
  const bindings = parameterBindings.map((binding) => {
660
+ assertPlainObject(binding, "parameter binding");
661
+ assertExactKeys(
662
+ binding,
663
+ new Set(["name", "value", "sensitive", "required"]),
664
+ "CC_REPLAY_INVALID_ARGUMENT",
665
+ "parameter binding",
666
+ );
144
667
  const parameterName = assertIdentifier(binding?.name, "parameter name");
145
668
  if (names.has(parameterName)) {
146
669
  throw recorderError(
@@ -162,8 +685,16 @@ export function createRecordedSkillDraft({
162
685
  required: binding.required !== false,
163
686
  };
164
687
  });
165
- const sanitizedActions = substituteCaptured(clone(actions), bindings);
166
- const findings = scanRecordedValue(sanitizedActions);
688
+ const sanitizedActions = substituteCaptured(
689
+ clone(actions, "actions"),
690
+ bindings,
691
+ );
692
+ const findings = scanRecordedValue({
693
+ description: safeDescription,
694
+ actions: sanitizedActions,
695
+ environment: safeEnvironment,
696
+ failureConditions: safeFailureConditions,
697
+ });
167
698
  if (findings.length) {
168
699
  throw recorderError(
169
700
  "CC_REPLAY_SENSITIVE_OR_VOLATILE_DATA",
@@ -173,32 +704,23 @@ export function createRecordedSkillDraft({
173
704
  }
174
705
  const requiredCapabilities = new Set();
175
706
  const normalizedActions = sanitizedActions.map((action, index) => {
176
- if (!action || typeof action !== "object" || Array.isArray(action)) {
177
- throw recorderError(
178
- "CC_REPLAY_INVALID_ARGUMENT",
179
- `action ${index} must be an object`,
180
- );
181
- }
182
- const kind = String(action.kind || "");
183
- const capability = SAFE_ACTION_CAPABILITIES[kind];
184
- if (!capability) {
185
- throw recorderError(
186
- "CC_REPLAY_UNSAFE_ACTION",
187
- `recorded action kind is not in the low-risk prototype allowlist: ${kind}`,
188
- );
189
- }
190
- requiredCapabilities.add(capability);
191
- return {
192
- id: `action-${index + 1}`,
193
- ...action,
194
- kind,
195
- requiredCapability: capability,
196
- };
707
+ const normalized = normalizeAction(action, index);
708
+ requiredCapabilities.add(normalized.requiredCapability);
709
+ return normalized;
197
710
  });
711
+ const referencedParameters = parameterNamesIn(normalizedActions);
712
+ if (
713
+ [...names].some((parameterName) => !referencedParameters.has(parameterName))
714
+ ) {
715
+ throw recorderError(
716
+ "CC_REPLAY_INVALID_ARGUMENT",
717
+ "every parameter binding must replace at least one recorded action value",
718
+ );
719
+ }
198
720
  const body = {
199
- schema: "chainlesschain.recorded-skill-draft/v1",
721
+ schema: DRAFT_SCHEMA,
200
722
  name: safeName,
201
- description: String(description).slice(0, 2048),
723
+ description: safeDescription,
202
724
  status: "draft",
203
725
  actions: normalizedActions,
204
726
  parameters: bindings.map(
@@ -209,13 +731,11 @@ export function createRecordedSkillDraft({
209
731
  }),
210
732
  ),
211
733
  capabilityManifest: [...requiredCapabilities].sort(),
212
- failureConditions: failureConditions.map((value) =>
213
- String(value).slice(0, 512),
214
- ),
215
- environment: environmentBinding(environment),
734
+ failureConditions: safeFailureConditions,
735
+ environment: environmentBinding(safeEnvironment),
216
736
  };
217
- return Object.freeze({
218
- ...clone(body),
737
+ return deepFreeze({
738
+ ...clone(body, "recorded skill draft"),
219
739
  draftDigest: digest(body, "cc.record-replay.skill-draft/v1"),
220
740
  });
221
741
  }
@@ -224,17 +744,12 @@ export function reviewRecordedSkillDraft(
224
744
  draft,
225
745
  { reviewerId, approvedCapabilities, acceptedFailureConditions = false } = {},
226
746
  ) {
227
- if (draft?.status !== "draft" || !draft?.draftDigest) {
228
- throw recorderError(
229
- "CC_REPLAY_DRAFT_INVALID",
230
- "a valid skill draft is required",
231
- );
232
- }
747
+ const verifiedDraft = validateRecordedSkillDraft(draft);
233
748
  const reviewer = assertIdentifier(reviewerId, "reviewerId");
234
749
  const approved = [...new Set(approvedCapabilities || [])].sort();
235
750
  if (
236
751
  JSON.stringify(approved) !==
237
- JSON.stringify([...draft.capabilityManifest].sort()) ||
752
+ JSON.stringify([...verifiedDraft.capabilityManifest].sort()) ||
238
753
  acceptedFailureConditions !== true
239
754
  ) {
240
755
  throw recorderError(
@@ -244,12 +759,12 @@ export function reviewRecordedSkillDraft(
244
759
  }
245
760
  const review = {
246
761
  reviewerId: reviewer,
247
- draftDigest: draft.draftDigest,
762
+ draftDigest: verifiedDraft.draftDigest,
248
763
  approvedCapabilities: approved,
249
764
  acceptedFailureConditions: true,
250
765
  };
251
- return Object.freeze({
252
- ...clone(draft),
766
+ return deepFreeze({
767
+ ...clone(verifiedDraft, "recorded skill draft"),
253
768
  status: "approved",
254
769
  review,
255
770
  approvalDigest: digest(review, "cc.record-replay.review/v1"),
@@ -315,26 +830,61 @@ export async function replayRecordedSkill(
315
830
  "recorded skill must be explicitly reviewed before replay",
316
831
  );
317
832
  }
318
- if (isolation?.sandboxed !== true || isolation?.network !== "deny") {
833
+ const verifiedSkill = validateReviewedRecordedSkill(skill);
834
+ let replayNetworkPolicy;
835
+ let reviewedNetworkPolicy;
836
+ try {
837
+ replayNetworkPolicy = createRecordedSkillNetworkPolicy({
838
+ mode: isolation?.network,
839
+ allowedOrigins: isolation?.allowedOrigins || [],
840
+ });
841
+ const reviewed = verifiedSkill.environment.requirements.networkPolicy;
842
+ reviewedNetworkPolicy = reviewed
843
+ ? createRecordedSkillNetworkPolicy({
844
+ mode: reviewed.mode,
845
+ allowedOrigins: reviewed.allowedOrigins || [],
846
+ })
847
+ : createRecordedSkillNetworkPolicy({ mode: "deny" });
848
+ } catch {
849
+ replayNetworkPolicy = null;
850
+ reviewedNetworkPolicy = null;
851
+ }
852
+ if (
853
+ isolation?.sandboxed !== true ||
854
+ !replayNetworkPolicy ||
855
+ replayNetworkPolicy.digest !== reviewedNetworkPolicy?.digest
856
+ ) {
319
857
  throw recorderError(
320
858
  "CC_REPLAY_ISOLATION_REQUIRED",
321
- "recorded skill replay requires a sandbox with network denied",
859
+ "recorded skill replay requires the exact reviewed browser network policy",
322
860
  );
323
861
  }
324
- if (environmentBinding(environment).digest !== skill.environment.digest) {
862
+ if (
863
+ environmentBinding(environment).digest !== verifiedSkill.environment.digest
864
+ ) {
325
865
  throw recorderError(
326
866
  "CC_REPLAY_ENVIRONMENT_DRIFT",
327
867
  "replay environment does not match the reviewed recording",
328
868
  );
329
869
  }
870
+ assertPlainObject(inputs, "replay inputs");
330
871
  if (typeof executor?.execute !== "function") {
331
872
  throw recorderError(
332
873
  "CC_REPLAY_EXECUTOR_INVALID",
333
874
  "a replay executor is required",
334
875
  );
335
876
  }
877
+ if (
878
+ replayNetworkPolicy.mode === "allowlist" &&
879
+ executor.networkPolicyDigest !== replayNetworkPolicy.digest
880
+ ) {
881
+ throw recorderError(
882
+ "CC_REPLAY_ISOLATION_REQUIRED",
883
+ "allowlisted browser replay requires an executor bound to the reviewed network policy",
884
+ );
885
+ }
336
886
  const executorCapabilities = new Set(executor.capabilities || []);
337
- for (const capability of skill.capabilityManifest) {
887
+ for (const capability of verifiedSkill.capabilityManifest) {
338
888
  if (!executorCapabilities.has(capability)) {
339
889
  throw recorderError(
340
890
  "CC_REPLAY_CAPABILITY_DENIED",
@@ -343,9 +893,9 @@ export async function replayRecordedSkill(
343
893
  }
344
894
  }
345
895
  const definitions = new Map(
346
- skill.parameters.map((parameter) => [parameter.name, parameter]),
896
+ verifiedSkill.parameters.map((parameter) => [parameter.name, parameter]),
347
897
  );
348
- for (const parameter of skill.parameters) {
898
+ for (const parameter of verifiedSkill.parameters) {
349
899
  if (parameter.required && !(parameter.name in inputs)) {
350
900
  throw recorderError(
351
901
  "CC_REPLAY_PARAMETER_MISSING",
@@ -354,7 +904,7 @@ export async function replayRecordedSkill(
354
904
  }
355
905
  }
356
906
  const receipts = [];
357
- for (const action of skill.actions) {
907
+ for (const action of verifiedSkill.actions) {
358
908
  const expanded = expandParameters(action, inputs, definitions);
359
909
  const result = await executor.execute(expanded, {
360
910
  isolation: clone(isolation),
@@ -367,19 +917,39 @@ export async function replayRecordedSkill(
367
917
  { actionId: action.id },
368
918
  );
369
919
  }
920
+ let serializedEvidence;
921
+ try {
922
+ serializedEvidence = JSON.stringify(canonicalValue(result.evidence));
923
+ } catch {
924
+ throw recorderError(
925
+ "CC_REPLAY_ACTION_FAILED",
926
+ `replay action returned invalid terminal evidence: ${action.id}`,
927
+ { actionId: action.id },
928
+ );
929
+ }
930
+ if (
931
+ typeof serializedEvidence !== "string" ||
932
+ Buffer.byteLength(serializedEvidence, "utf8") > MAX_EVIDENCE_BYTES
933
+ ) {
934
+ throw recorderError(
935
+ "CC_REPLAY_ACTION_FAILED",
936
+ `replay action returned oversized terminal evidence: ${action.id}`,
937
+ { actionId: action.id },
938
+ );
939
+ }
370
940
  receipts.push({
371
941
  actionId: action.id,
372
942
  evidenceDigest: digest(result.evidence, "cc.record-replay.evidence/v1"),
373
943
  });
374
944
  }
375
945
  const report = {
376
- schema: "chainlesschain.recorded-skill-replay/v1",
377
- skillDigest: skill.draftDigest,
378
- environmentDigest: skill.environment.digest,
946
+ schema: REPORT_SCHEMA,
947
+ skillDigest: verifiedSkill.draftDigest,
948
+ environmentDigest: verifiedSkill.environment.digest,
379
949
  status: "succeeded",
380
950
  receipts,
381
951
  };
382
- return Object.freeze({
952
+ return deepFreeze({
383
953
  ...report,
384
954
  replayDigest: digest(report, "cc.record-replay.report/v1"),
385
955
  });