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.
- package/package.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-BN25cNOD.js → AIOps-C8SyFhOo.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Da9IP-m5.js → ActionButton-Dup5gFK0.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-D7hkEvQm.js → Analytics-8FyOWA7V.js} +2 -2
- package/src/assets/web-panel/assets/{AppLayout-CCV4Xnbc.js → AppLayout-DBzdU9dg.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-CVSnVJeA.js → Artifacts-C_0Z8gNy.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CuYdw5Ag.js → Audit-Cf2a-58m.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DZCnK-Tf.js → BackgroundAgents-M7w-2zgB.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-cV9FdpTy.js → Backup-CCTHQXoL.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BRnv6YSp.js → BaseInput-DjXpeFwJ.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-Bw5w87P4.js → Chat-cAvchsJU.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-C0GjtUAw.js → ChatBubbleRenderer-DvEvxlZv.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DaoTqcdt.js → Checkbox-DKs6qvOn.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-B4dYcEIi.js → Codegen-DlkA7b9X.js} +1 -1
- package/src/assets/web-panel/assets/{Col-mAhuOeuD.js → Col-CkfdKhyJ.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BK6KEdBk.js → Community-iNXMHzj6.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CCglmNu5.js → Compact-DQxPEd09.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-Cm8S4of7.js → Compliance-CSKvMCjs.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Dhs1QLMe.js → Cowork-CkWqazpD.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-CLlhmXWk.js → Cron-k6W8IzhB.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BnHVbaNs.js → Crosschain-C8MS1OuY.js} +1 -1
- package/src/assets/web-panel/assets/{DID-UzQP9-0O.js → DID-DJuCcgiQ.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CBdlPClz.js → Dashboard-LeThKzp3.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-axIRPdq1.js → Dropdown-BvxwgpE4.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DUalW5rx.js → EmailListRenderer-n3e_a1M3.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-Bl4W9luY.js → FamilyGuardDashboard-509W6W6v.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-C1yP-uEu.js → Federation-xgZReQm0.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Xh6Ig5q8.js → FormItemContext-jZOhKcQx.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-C2cz3bt2.js → GenericCardRenderer-Jo0ULzHv.js} +1 -1
- package/src/assets/web-panel/assets/{Git-C_z63ixg.js → Git-BsRcyWQP.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BYqB33_3.js → Governance-JlpMYTAk.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CEULuArm.js → Inference-CUEz872-.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-fgMsu-0H.js → KnowledgeGraph-DgrvvEDU.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DO9Hdcec.js → Logs-Df-KY4U3.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-BEPDcSwz.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-B8zyLiFe.js → Marketplace-kPZDTOeI.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BLAN2mWW.js → McpTools-CRRzPqNX.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-CrQcFM7u.js → Memory-D6pNSkg9.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DVnyiiU-.js → MobileBridge-g-pvtwRC.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-qjCME5OD.js → MobileProjects-Bzayfrfe.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-4JE3C3It.js → Mtc-B7S3W-XL.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-CjBuak9I.js → MtcAudit-B8OMEWVb.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-BtPcYQcC.js → Multisig-CowRp7GH.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-Cx653_Ba.js → NLProgramming-BkQNyi4c.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CkuD3YzS.js → Notes-Cm7gtkKz.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BgNgU7HH.js → NotificationSettings-CbPTVh9m.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-BTC8W0a-.js → OrderTableRenderer-Dswi26mJ.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-Dmd4Hjxz.js → Organization-BkjQ9yy7.js} +2 -2
- package/src/assets/web-panel/assets/{Overflow-C_5DA-G0.js → Overflow-DmRFXP7Y.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-uwslV4XC.js → P2P-DQE1toAu.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CuNgwVnX.js → PdhVaultBrowser-BujyoDRG.js} +4 -4
- package/src/assets/web-panel/assets/{Permissions-CJayVX9H.js → Permissions-xSjfOaiU.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Cb05H5go.js → PersonalDataHub-B9ICLqVP.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-BZlGKapZ.js → Pipeline-CUNwyN9E.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BIzZWYzd.js → Privacy-SOoCricX.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-vEYYyQXQ.js → ProjectInit-CSaFul0F.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-MZqTy5CC.js → ProjectSettings-CueiB_oe.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-CRARUOl8.js → Projects-BOeXvU7y.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-80VAK8PX.js → Providers-CTqf_31h.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CzRSoIwK.js → QrScannerModal-C8ZP31E9.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BpwQ2n4C.js → QuickAsk-CPgn4713.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BC03TZ79.js → Recommend-8_e0zw0i.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-DE59-jaa.js → RemoteSession-iT7lbGqw.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-DOHmeWyA.js → Reputation-B4q9SUOz.js} +1 -1
- package/src/assets/web-panel/assets/{Row-hc5RZl1D.js → Row-CA_nASyL.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Cv5uMP7E.js → RssFeed-Cwb0E3yv.js} +3 -3
- package/src/assets/web-panel/assets/{Search-qohu7Ug0.js → Search-D9iVsOxJ.js} +1 -1
- package/src/assets/web-panel/assets/{Security-drCPFWV5.js → Security-DPLbYchG.js} +4 -4
- package/src/assets/web-panel/assets/{Services-BAEgUeRy.js → Services-DGMVL_LF.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CkJkKBlo.js → Skeleton-B_6kym0D.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-bMuKEpez.js → Skills-Ds6r0dXB.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-DxWQVHRC.js → Sla-BLHAbBhb.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-BpS8idmV.js → SpeechSettings-CZuddd14.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BPBLlW5v.js → SyncSettings-CdBq7oSl.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-C4SLEeUh.js → Tasks-DfT60Nyl.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-r-wfU39U.js → Templates-RLfsKy2V.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-C7vjRmLN.js → Tenant-Cw8xCNLE.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DdzeFCV2.js → Terminal-BOUkNu4f.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CYyWN2KD.js → TimelineRenderer-CgGZWaRm.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-D8A9osUg.js → Tokens-CpwjVyir.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DBvrNlyS.js → Trigger-DL21H-eb.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DBVAGc7o.js → Trust-B3ffFclW.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-C1vQUCRJ.js → UkeySign-CkBeb5aI.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BKs4dah4.js → VideoEditing-U2iVaE3r.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-ByIuj3lD.js → Wallet-CQk2wzYd.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DYlAFCm9.js → WebAuthn-DYIuIYzT.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-Dwyadd0c.js → WorkflowEditor-tg1c2mZQ.js} +1 -1
- package/src/assets/web-panel/assets/{chat-Bm2f3s08.js → chat-CtL61spq.js} +1 -1
- package/src/assets/web-panel/assets/{colors-C8ORSh7V.js → colors-DKpbeo8s.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-1iVlnsM-.js → compact-item-gPZsrswt.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CmqNyUSL.js → createContext-CfHlwzVr.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DdqnMhzi.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-rUMaX3Ul.js → hasIn-MV47g_6X.js} +1 -1
- package/src/assets/web-panel/assets/{index-CCEyz58d.js → index-6G0ETFTW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BVtnizyC.js → index-BBw52sEl.js} +1 -1
- package/src/assets/web-panel/assets/{index-BIvM-uAX.js → index-BN-wxSQ8.js} +1 -1
- package/src/assets/web-panel/assets/{index-DvedhJeM.js → index-BOyGUuBE.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCOhTILd.js → index-BQp5M0s1.js} +1 -1
- package/src/assets/web-panel/assets/index-BcK8o4ke.js +1 -0
- package/src/assets/web-panel/assets/{index-DVJ1uKf4.js → index-BjlPD7GO.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQL9r73Y.js → index-C4lF-f_z.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5RfRciU.js → index-C6nC_DyY.js} +1 -1
- package/src/assets/web-panel/assets/{index-DgUE51Pz.js → index-C9RF4ieu.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkFvuAex.js → index-C9gtY4nG.js} +3 -3
- package/src/assets/web-panel/assets/{index-C-J5-pGH.js → index-CB1HnbNK.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bf1PlIHp.js → index-CRv_uMLV.js} +1 -1
- package/src/assets/web-panel/assets/{index-tqTx5wXo.js → index-C_B9LiKs.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bgw4Y2rU.js → index-Cjv0kKCU.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJxtPFOh.js → index-CmM9jqwe.js} +1 -1
- package/src/assets/web-panel/assets/{index-pHWA7bmM.js → index-CmPJpLoD.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dsve5Gkv.js → index-CzfxTSPW.js} +1 -1
- package/src/assets/web-panel/assets/{index-B59OWAs7.js → index-D1XB5-t5.js} +1 -1
- package/src/assets/web-panel/assets/{index-BK-RY0-7.js → index-DGtEBodr.js} +1 -1
- package/src/assets/web-panel/assets/{index-BC32_0xT.js → index-DIeJmlAA.js} +1 -1
- package/src/assets/web-panel/assets/{index-OcbzdRpT.js → index-DMMvlLp9.js} +1 -1
- package/src/assets/web-panel/assets/{index-geQinnns.js → index-DU4rZpiK.js} +1 -1
- package/src/assets/web-panel/assets/{index-D98FXb0w.js → index-D_Pl1GnW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BC8Wb6cy.js → index-Dd19vRwy.js} +1 -1
- package/src/assets/web-panel/assets/{index-fPK6GFEg.js → index-Ddcpj1El.js} +1 -1
- package/src/assets/web-panel/assets/{index-CfiHyVyl.js → index-Df9sWKpl.js} +1 -1
- package/src/assets/web-panel/assets/{index-C-ZIHi42.js → index-DfLyHCWi.js} +1 -1
- package/src/assets/web-panel/assets/{index-DDHk911Q.js → index-DnZ_zS1M.js} +1 -1
- package/src/assets/web-panel/assets/{index-BFniLMn9.js → index-DsH-i_3d.js} +1 -1
- package/src/assets/web-panel/assets/index-DvbAjyYI.js +1 -0
- package/src/assets/web-panel/assets/{index-Dcck8QfW.js → index-ExAGPAnX.js} +1 -1
- package/src/assets/web-panel/assets/{index-DwMdWKiF.js → index-Rb7CRMw9.js} +1 -1
- package/src/assets/web-panel/assets/{index-reUHhuz8.js → index-Zvkt9xqi.js} +1 -1
- package/src/assets/web-panel/assets/{index-BgwLNxmr.js → index-bSFrAszw.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3B1S-mW.js → index-fYcS7NS0.js} +1 -1
- package/src/assets/web-panel/assets/{index-CTfEmKK0.js → index-srBc9U_s.js} +1 -1
- package/src/assets/web-panel/assets/{index-B53c3diB.js → index-xUlgG0Xe.js} +1 -1
- package/src/assets/web-panel/assets/{index-B8BrB0Ls.js → index-yxxXlqBA.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CuA-cpkL.js → initDefaultProps-BeOZoSZ4.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CimmE_9b.js → motion-BXE8VMKx.js} +1 -1
- package/src/assets/web-panel/assets/{move-DlGlrltz.js → move-I9sdvPGX.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-CK6BJQ07.js → mtc-parser-d25zrJd0.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BKjBfZfB.js → omit-D71V_T4e.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-B2chVYw9.js → pickAttrs-BNZKpIAR.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BW8aLJJ3.js → placementArrow-jOcgxTTz.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-dXlPl66S.js → responsiveObserve-pta_O8AU.js} +1 -1
- package/src/assets/web-panel/assets/{slide-hSa89S4G.js → slide-Dlq6om7z.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-DM6KLt-s.js → statusUtils-BnkO_yRv.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-C9MQAK15.js → styleChecker-p8NfWmcG.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-BpQDpP0z.js → useFlexGapSupport-KcjZZ5U7.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-C7rtRViA.js → useFs-gqonDMWB.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-Dm15xafq.js → usePersonalDataHub-jIIfE4go.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-mEiEytw4.js → vnode-Dyo7fBrM.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DzPxflhY.js → zoom-6l8BMg9W.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +2 -2
- package/src/commands/agent.js +49 -27
- package/src/commands/record-replay.js +772 -0
- package/src/commands/skill.js +3 -0
- package/src/data/changelog.json +13 -0
- package/src/lib/agent-sandbox.js +4 -8
- package/src/lib/agent-team/team-message-tools.js +23 -5
- package/src/lib/process-execution-broker/credential-transport.js +31 -0
- package/src/lib/process-execution-broker/index.js +74 -0
- package/src/lib/process-execution-broker/windows-sandbox-helper.dll +0 -0
- package/src/lib/process-execution-broker/windows-sandbox-helper.exe +0 -0
- package/src/lib/process-execution-broker/windows-sandbox.cs +7 -3
- package/src/lib/record-replay/browser-target-policy.js +406 -0
- package/src/lib/record-replay/index.js +4 -0
- package/src/lib/record-replay/playwright-ui-driver.js +83 -20
- package/src/lib/record-replay/playwright-ui-recorder.js +611 -0
- package/src/lib/record-replay/recorded-skill-package.js +486 -0
- package/src/lib/record-replay/recorded-skill-store.js +1166 -0
- package/src/lib/record-replay/skill-recorder.js +634 -64
- package/src/runtime/headless-runner.js +72 -51
- package/src/assets/web-panel/assets/MarkdownRenderer-BkrK04xW.js +0 -1
- package/src/assets/web-panel/assets/devWarning-B69RCDDH.js +0 -1
- package/src/assets/web-panel/assets/index-9biMQ-tw.js +0 -1
- 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
|
-
|
|
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
|
|
79
|
-
(
|
|
80
|
-
|
|
81
|
-
|
|
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
|
-
|
|
114
|
-
|
|
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 (
|
|
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
|
-
|
|
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(
|
|
166
|
-
|
|
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
|
-
|
|
177
|
-
|
|
178
|
-
|
|
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:
|
|
721
|
+
schema: DRAFT_SCHEMA,
|
|
200
722
|
name: safeName,
|
|
201
|
-
description:
|
|
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:
|
|
213
|
-
|
|
214
|
-
),
|
|
215
|
-
environment: environmentBinding(environment),
|
|
734
|
+
failureConditions: safeFailureConditions,
|
|
735
|
+
environment: environmentBinding(safeEnvironment),
|
|
216
736
|
};
|
|
217
|
-
return
|
|
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
|
-
|
|
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([...
|
|
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:
|
|
762
|
+
draftDigest: verifiedDraft.draftDigest,
|
|
248
763
|
approvedCapabilities: approved,
|
|
249
764
|
acceptedFailureConditions: true,
|
|
250
765
|
};
|
|
251
|
-
return
|
|
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
|
-
|
|
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
|
|
859
|
+
"recorded skill replay requires the exact reviewed browser network policy",
|
|
322
860
|
);
|
|
323
861
|
}
|
|
324
|
-
if (
|
|
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
|
|
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
|
-
|
|
896
|
+
verifiedSkill.parameters.map((parameter) => [parameter.name, parameter]),
|
|
347
897
|
);
|
|
348
|
-
for (const parameter of
|
|
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
|
|
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:
|
|
377
|
-
skillDigest:
|
|
378
|
-
environmentDigest:
|
|
946
|
+
schema: REPORT_SCHEMA,
|
|
947
|
+
skillDigest: verifiedSkill.draftDigest,
|
|
948
|
+
environmentDigest: verifiedSkill.environment.digest,
|
|
379
949
|
status: "succeeded",
|
|
380
950
|
receipts,
|
|
381
951
|
};
|
|
382
|
-
return
|
|
952
|
+
return deepFreeze({
|
|
383
953
|
...report,
|
|
384
954
|
replayDigest: digest(report, "cc.record-replay.report/v1"),
|
|
385
955
|
});
|