chainlesschain 0.165.2 → 0.165.4
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/README.md +151 -29
- package/package.json +2 -2
- package/src/assets/web-panel/assets/{AIOps-lS1R0Cza.js → AIOps-C__nnraW.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BmlgBPsj.js → ActionButton-CsKWHEZ-.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CNKczv1S.js → Analytics-BhIjfjWO.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-BuZGJARQ.js → AppLayout-BjuXFzWZ.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-Dm73Sl-O.js → Artifacts-k6whDkZJ.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-BGMW7A5e.js → Audit-C4FBMYRh.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BQ6M54JR.js → BackgroundAgents-C_vRrSXk.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DBzGP0bR.js → Backup-CngFZeVp.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-D1mLCoOV.js → BaseInput-B_H9EBRm.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BUfcN7p8.js → Chat-DLEC5kt7.js} +4 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-xijcWkJe.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-Du0Yg5Pi.js → Checkbox-m3ClR4xh.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-B2RXziHW.js → Codegen-CmLSFrgp.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DWhekOrR.js → Col-DFGQteHN.js} +1 -1
- package/src/assets/web-panel/assets/{Community-HQhrkon4.js → Community-EUUVORHE.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-izHNb4uz.js → Compact-2PSg0Yr7.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-BDwLy41-.js → Compliance-CcKur7gB.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DFJhLa7K.js → Cowork-DK_Fezfa.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DYY9RG7-.js → Cron-Dbw89xl7.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-rr7tJTtv.js → Crosschain-CH66h_Ec.js} +1 -1
- package/src/assets/web-panel/assets/{DID-D2sfHRLi.js → DID-D1igRb17.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-q_eGdhha.js → Dashboard-DVAsrobk.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-Cufrh2Bz.js → Dropdown-Dl7W8AQs.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-B-kdv10f.js → EmailListRenderer-D5tpaEQC.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-D21XjgfG.js → FamilyGuardDashboard-O_VQf8gq.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CAwRTLZH.js → Federation-BTVcgS9n.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-vx3daNDF.js → FormItemContext-BRnkJAIz.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CRWY2wU4.js → GenericCardRenderer-8OWSCkDj.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Q6SwauyD.js → Git-WXLYTQj2.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-BJ1q4IQZ.js → Governance-BezaUhtc.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CSWjROgD.js → Inference-CuRay3Gu.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-fzuQsWuY.js → KnowledgeGraph-DfWLDW1m.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-DVF8Mnns.js → Logs-C55y2pPY.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-hDpWdiBc.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-BQP7LOA4.js → Marketplace-Dk08Uycg.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BNlJCmNc.js → McpTools-C11fSB5V.js} +4 -4
- package/src/assets/web-panel/assets/{Memory-DLLg5WQ4.js → Memory-mr0eZc97.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-CR2skNAF.js → MobileBridge-C0iCSRbb.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-BxVavObl.js → MobileProjects-CY-xMG8Z.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-B17pgqMI.js → Mtc-Bplqc_Wc.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-4UmV5cIL.js → MtcAudit-BlzwsoSM.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DoMEXEcU.js → Multisig-BBLmTty6.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-h9DMXFX4.js → NLProgramming-DtT1uf1W.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Cihn-xX8.js → Notes-DcqQAxTK.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-BE6ZGa3R.js → NotificationSettings-cb0kWoZe.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-CnwojoSI.js → OrderTableRenderer-kw9HZQNN.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-B2vpwmjb.js → Organization-DlTlbkUN.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-C4z28R0-.js → Overflow-pp1qs-oY.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BVlFkwhW.js → P2P-BESLnyYz.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BXbEdL5B.js → PdhVaultBrowser-B-J1mGuf.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-C28xs4lp.js → Permissions-B36PeT1_.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Dgl_rBjM.js → PersonalDataHub-CTK2ad-3.js} +3 -3
- package/src/assets/web-panel/assets/{Pipeline-3QUIhrKI.js → Pipeline-zJNGXsac.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-8Gh33OZ5.js → Privacy-b9fz0Qf0.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BNmg4tZi.js → ProjectInit-DjZbZgiB.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-vgMfeCf9.js → ProjectSettings-C-hHJXlD.js} +2 -2
- package/src/assets/web-panel/assets/Projects-BC9KSDM6.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CXoOa8eX.js → Providers-Dz-CAYYU.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BhfTF0oS.js → QrScannerModal-A0yoQ3-3.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-C746HEAj.js → QuickAsk-DrFgFJNC.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BSej8Eo0.js → Recommend-KC8hF1g4.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-f3gmnlG3.js → RemoteSession-DGTPIk6u.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-CyvybpkN.js → Reputation-QmbwM7Yk.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DwV2PeYn.js → Row-BxjJylia.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-11GrzjZy.js → RssFeed-B7Wda1J6.js} +3 -3
- package/src/assets/web-panel/assets/{Search-BjKXkrAY.js → Search-_plQ_YSA.js} +1 -1
- package/src/assets/web-panel/assets/{Security-eWiFb9Yk.js → Security-CD2YJZfx.js} +2 -2
- package/src/assets/web-panel/assets/{Services-CdCsMAtt.js → Services-qQE7NmwE.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-4Q2UTKQ5.js → Skeleton-DgQqkFf7.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-v4rtYrRj.js → Skills-C4C0oFFt.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CRhsFvd_.js → Sla-Px2N2B5c.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-DN6C3lQd.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-D16zonl7.js → SyncSettings-jm5aH_m7.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-Xxozz0i_.js → Tasks-DlkJcv67.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CXJpPtCT.js → Templates-DyrTHUBs.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-XRV0L1ZF.js → Tenant-mC434PtL.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-CYuO1i_w.js → Terminal-o0_s3NgK.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BXqitWsu.js → TimelineRenderer-DAGlAXOw.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-BvPrDHIy.js → Tokens-BnulS_bs.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DhCrSTf5.js → Trigger-suQbVXYM.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DmsuZD6K.js → Trust-DbIJAPo5.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-D_yrts5C.js → UkeySign-CQNpbIHi.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-B01ihPrm.js → VideoEditing-D8aWCERk.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DpUjKa-N.js → Wallet-CuLpYfZc.js} +2 -2
- package/src/assets/web-panel/assets/{WebAuthn-CL07yON6.js → WebAuthn-CsfG8ZMc.js} +2 -2
- package/src/assets/web-panel/assets/{WorkflowEditor-CLMflnRr.js → WorkflowEditor-BYsEAZbd.js} +1 -1
- package/src/assets/web-panel/assets/{chat-DtUqe8h_.js → chat-FT4Um3-I.js} +1 -1
- package/src/assets/web-panel/assets/{colors-DPKrgVfy.js → colors-CKbS_Wlt.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BTt2GtOm.js → compact-item-CFJtzuJM.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-D_VeUc1J.js → createContext-B9kSRW6K.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DJZ0gAMu.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-1kndiylj.js → hasIn-DR-x0Xpi.js} +1 -1
- package/src/assets/web-panel/assets/{index-dVc-CXY4.js → index-7jHr_A_k.js} +1 -1
- package/src/assets/web-panel/assets/{index-DFIZ_1Sf.js → index-B3ZX81mK.js} +1 -1
- package/src/assets/web-panel/assets/{index-fk2Rjacw.js → index-B7osrXqy.js} +3 -3
- package/src/assets/web-panel/assets/{index-CHG-KcS4.js → index-BGbPnC5x.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dbbymxpj.js → index-BI5hP_qy.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaZAbw9A.js → index-BK3TiKVq.js} +1 -1
- package/src/assets/web-panel/assets/{index-CzjkKUmR.js → index-BVPU43b1.js} +1 -1
- package/src/assets/web-panel/assets/{index-BAXfRKfG.js → index-BW-t9n-q.js} +1 -1
- package/src/assets/web-panel/assets/{index-8nz_fmlj.js → index-BfFCQHL3.js} +1 -1
- package/src/assets/web-panel/assets/index-Bizb4hkw.js +1 -0
- package/src/assets/web-panel/assets/{index-DdkplL9R.js → index-Bt4fS2eL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DG2MZQ3f.js → index-BwR7yc2h.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfKBRdSS.js → index-Bzz0C1BZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-GsoUQ2M7.js → index-C2JvmYRc.js} +1 -1
- package/src/assets/web-panel/assets/{index-l1BETrDb.js → index-CGaXUsIs.js} +1 -1
- package/src/assets/web-panel/assets/{index-4bna2_SR.js → index-CJmIQ9XR.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfGl8CqG.js → index-C_enk9I_.js} +1 -1
- package/src/assets/web-panel/assets/{index-D95OFht7.js → index-Cr2FLUsD.js} +1 -1
- package/src/assets/web-panel/assets/{index-DsJCDUJG.js → index-CvrCCqJt.js} +1 -1
- package/src/assets/web-panel/assets/{index-sDoMHTNA.js → index-D4LfPvWA.js} +1 -1
- package/src/assets/web-panel/assets/{index-By5n8up9.js → index-DMGmg497.js} +1 -1
- package/src/assets/web-panel/assets/{index-ocf06EVQ.js → index-DShSkww-.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bhudc_Ez.js → index-DTxNy0ZU.js} +1 -1
- package/src/assets/web-panel/assets/{index-CX81qSM4.js → index-DW4BWjTk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BlDNVgBt.js → index-DWdqx7zI.js} +1 -1
- package/src/assets/web-panel/assets/{index-CcO04eDn.js → index-Dap9c3eD.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dwqmtk65.js → index-DcfxhKXR.js} +1 -1
- package/src/assets/web-panel/assets/{index-2eyAXE3d.js → index-DdkIhfi-.js} +1 -1
- package/src/assets/web-panel/assets/{index-brCInrzq.js → index-DeSgw-WV.js} +1 -1
- package/src/assets/web-panel/assets/{index-DJ8vXGOa.js → index-DiNUEpSQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHpBMWCh.js → index-DjEP7WkH.js} +1 -1
- package/src/assets/web-panel/assets/{index-m31Ka8hZ.js → index-DtkFqPVA.js} +1 -1
- package/src/assets/web-panel/assets/{index-CtF7EN49.js → index-KqDaCApw.js} +1 -1
- package/src/assets/web-panel/assets/index-OHSl-xCh.js +1 -0
- package/src/assets/web-panel/assets/{index-B9KvcS7R.js → index-OSzjTzGM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CauoBQkH.js → index-QBSeNVO3.js} +1 -1
- package/src/assets/web-panel/assets/{index-FzdwcSy2.js → index-WE08gMJP.js} +1 -1
- package/src/assets/web-panel/assets/{index-DxdTdBix.js → index-r2VI1Ikk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BepLZwgB.js → index-tqSEHPRT.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CvyYJd7z.js → initDefaultProps-Dd7n7VgI.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DYZkhUdz.js → motion-BwxouRw1.js} +1 -1
- package/src/assets/web-panel/assets/{move-slnJRVT1.js → move-DObN0PG9.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DVc0CKmy.js → mtc-parser-B3nOUQc7.js} +1 -1
- package/src/assets/web-panel/assets/{omit-BCXAtHCq.js → omit-o9WU2Nbl.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DBsemz_K.js → pickAttrs-CL_vvfQ_.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BRtkJ-nv.js → placementArrow-CYrM2tyK.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-Cebq7EZn.js → responsiveObserve-k_06cQ96.js} +1 -1
- package/src/assets/web-panel/assets/{slide-DT7qvId2.js → slide-CPKG0pxS.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-pQiITLkG.js → statusUtils-BDDEeLTw.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-c-19BVKQ.js → styleChecker-Cmb7RTh7.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DsS5-0bE.js → useFlexGapSupport-e2IedC4N.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CxGiMe5n.js → useFs-CE_gxVnm.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-D9btVID5.js → usePersonalDataHub-BkMIIKMz.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CUYFsFEN.js → vnode-DW886XV1.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-CXmn_35j.js → zoom-Dwo_adaf.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +3 -3
- package/src/commands/agent.js +105 -35
- package/src/commands/artifacts.js +363 -33
- package/src/commands/cowork.js +202 -36
- package/src/commands/session-budget.js +155 -0
- package/src/commands/session-location.js +948 -0
- package/src/commands/session.js +72 -46
- package/src/data/changelog.json +26 -0
- package/src/gateways/ws/artifact-protocol.js +77 -13
- package/src/gateways/ws/session-protocol.js +391 -24
- package/src/gateways/ws/ws-agent-handler.js +191 -14
- package/src/gateways/ws/ws-server.js +14 -3
- package/src/gateways/ws/ws-session-gateway.js +101 -5
- package/src/harness/jsonl-session-store.js +1210 -0
- package/src/lib/agent-team/team-runner.js +43 -13
- package/src/lib/artifact-access-ledger.js +461 -0
- package/src/lib/artifact-cleanup-ledger.js +861 -0
- package/src/lib/artifact-deletion-ledger.js +677 -0
- package/src/lib/artifact-store.js +256 -51
- package/src/lib/background-agent-keeper-protocol.js +75 -4
- package/src/lib/background-agent-launch-barrier.js +74 -0
- package/src/lib/background-agent-supervisor.js +149 -5
- package/src/lib/background-turn-bootstrap-protocol.js +4 -0
- package/src/lib/command-lifecycle-report.js +8 -4
- package/src/lib/cowork-adapter.js +9 -5
- package/src/lib/cowork-task-runner.js +20 -0
- package/src/lib/cowork-workflow.js +178 -20
- package/src/lib/direct-model-usage.js +62 -22
- package/src/lib/dynamic-workflow-runtime.js +3184 -111
- package/src/lib/execution-location-result-apply.js +339 -0
- package/src/lib/execution-location-result-artifact.js +271 -0
- package/src/lib/execution-location-result-review.js +253 -0
- package/src/lib/execution-location-result-store.js +331 -0
- package/src/lib/execution-location-result.js +617 -0
- package/src/lib/execution-location-target.js +236 -1
- package/src/lib/managed-tool-checkpoint.js +47 -4
- package/src/lib/process-execution-broker/workspace-transaction.js +3 -0
- package/src/lib/session-budget-production-root.js +376 -0
- package/src/lib/session-budget-runtime.js +109 -18
- package/src/lib/session-budget-usage.js +63 -0
- package/src/lib/session-host-snapshot.js +19 -2
- package/src/lib/session-resource-budget.js +766 -105
- package/src/lib/sub-agent-context.js +69 -22
- package/src/lib/web-ui-server.js +12 -1
- package/src/lib/with-file-lock.js +175 -26
- package/src/repl/agent-repl.js +237 -16
- package/src/runtime/agent-core.js +123 -0
- package/src/runtime/headless-runner.js +135 -46
- package/src/runtime/headless-stream.js +133 -25
- package/src/runtime/policies/agent-policy.js +10 -0
- package/src/workers/background-agent-keeper.js +112 -19
- package/src/workers/background-agent-worker.js +383 -134
- package/src/workers/background-turn-bootstrap.js +13 -8
- package/src/assets/web-panel/assets/ChatBubbleRenderer-CY5m8ZHg.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-BLif4QSg.js +0 -1
- package/src/assets/web-panel/assets/Projects-BhtAVilc.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-DLjKsDVu.js +0 -1
- package/src/assets/web-panel/assets/devWarning-Bj5nUW2v.js +0 -1
- package/src/assets/web-panel/assets/index-CPcM-eqo.js +0 -1
- package/src/assets/web-panel/assets/index-xeSyVoRe.js +0 -1
|
@@ -0,0 +1,677 @@
|
|
|
1
|
+
import fs from "node:fs";
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { createHash, randomUUID } from "node:crypto";
|
|
4
|
+
import { TextDecoder } from "node:util";
|
|
5
|
+
import { canonicalJson } from "./scheduler-kernel/contract.js";
|
|
6
|
+
import {
|
|
7
|
+
sameFileStatIdentity,
|
|
8
|
+
samePathHandleFileIdentity,
|
|
9
|
+
withTrustedFileParentSync,
|
|
10
|
+
} from "./secure-file-identity.js";
|
|
11
|
+
import { withFileLock } from "./with-file-lock.js";
|
|
12
|
+
|
|
13
|
+
export const ARTIFACT_DELETION_EVENT_SCHEMA =
|
|
14
|
+
"cc-artifact-deletion-settlement/v1";
|
|
15
|
+
export const ARTIFACT_DELETION_LEDGER_SCHEMA =
|
|
16
|
+
"cc-artifact-deletion-settlement-ledger/v1";
|
|
17
|
+
export const ARTIFACT_DELETION_RECEIPT_SCHEMA =
|
|
18
|
+
"cc-artifact-deletion-receipt/v1";
|
|
19
|
+
|
|
20
|
+
export const ARTIFACT_DELETION_CLIENTS = Object.freeze([
|
|
21
|
+
"cli",
|
|
22
|
+
"vscode",
|
|
23
|
+
"jetbrains",
|
|
24
|
+
"websocket",
|
|
25
|
+
"system",
|
|
26
|
+
]);
|
|
27
|
+
export const ARTIFACT_DELETION_REASONS = Object.freeze(["explicit", "expired"]);
|
|
28
|
+
|
|
29
|
+
const ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:@+-]{0,255}$/u;
|
|
30
|
+
const SHA256_RE = /^sha256:[a-f0-9]{64}$/u;
|
|
31
|
+
const MAX_LEDGER_BYTES = 16 * 1024 * 1024;
|
|
32
|
+
const MAX_LEDGER_EVENTS = 100_000;
|
|
33
|
+
|
|
34
|
+
function digest(domain, value) {
|
|
35
|
+
return `sha256:${createHash("sha256")
|
|
36
|
+
.update(domain, "utf8")
|
|
37
|
+
.update(canonicalJson(value, "artifactDeletionLedger"), "utf8")
|
|
38
|
+
.digest("hex")}`;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
function exactObject(value, fields, label) {
|
|
42
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
43
|
+
throw new TypeError(`${label} must be an object`);
|
|
44
|
+
}
|
|
45
|
+
const actual = Object.keys(value).sort();
|
|
46
|
+
const expected = [...fields].sort();
|
|
47
|
+
if (
|
|
48
|
+
actual.length !== expected.length ||
|
|
49
|
+
actual.some((field, index) => field !== expected[index])
|
|
50
|
+
) {
|
|
51
|
+
throw new TypeError(`${label} has an invalid schema`);
|
|
52
|
+
}
|
|
53
|
+
return value;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
function optionalDigest(value, label) {
|
|
57
|
+
if (value === null) return null;
|
|
58
|
+
const normalized = String(value || "");
|
|
59
|
+
if (!SHA256_RE.test(normalized)) throw new TypeError(`${label} is invalid`);
|
|
60
|
+
return normalized;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
function optionalId(value, label) {
|
|
64
|
+
if (value === null) return null;
|
|
65
|
+
const normalized = String(value || "");
|
|
66
|
+
if (!ID_RE.test(normalized)) throw new TypeError(`${label} is invalid`);
|
|
67
|
+
return normalized;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function safeStoredFile(value) {
|
|
71
|
+
const normalized = String(value || "");
|
|
72
|
+
if (
|
|
73
|
+
!normalized ||
|
|
74
|
+
normalized !== path.basename(normalized) ||
|
|
75
|
+
normalized.includes("..") ||
|
|
76
|
+
normalized.includes("/") ||
|
|
77
|
+
normalized.includes("\\")
|
|
78
|
+
) {
|
|
79
|
+
throw new TypeError("artifact deletion stored file is invalid");
|
|
80
|
+
}
|
|
81
|
+
return normalized;
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
export function normalizeArtifactDeletionEvent(input, previous = null) {
|
|
85
|
+
const value = exactObject(
|
|
86
|
+
input,
|
|
87
|
+
[
|
|
88
|
+
"schema",
|
|
89
|
+
"sequence",
|
|
90
|
+
"previousEventDigest",
|
|
91
|
+
"deletionId",
|
|
92
|
+
"phase",
|
|
93
|
+
"preparedEventDigest",
|
|
94
|
+
"artifactId",
|
|
95
|
+
"artifactSha256",
|
|
96
|
+
"artifactSize",
|
|
97
|
+
"recordDigest",
|
|
98
|
+
"artifactSessionId",
|
|
99
|
+
"storedFile",
|
|
100
|
+
"immutable",
|
|
101
|
+
"reason",
|
|
102
|
+
"client",
|
|
103
|
+
"indexGenerationBefore",
|
|
104
|
+
"indexGenerationAfter",
|
|
105
|
+
"managedCopyDisposition",
|
|
106
|
+
"occurredAt",
|
|
107
|
+
"eventDigest",
|
|
108
|
+
],
|
|
109
|
+
"artifact deletion event",
|
|
110
|
+
);
|
|
111
|
+
const sequence = Number(value.sequence);
|
|
112
|
+
const previousEventDigest = optionalDigest(
|
|
113
|
+
value.previousEventDigest,
|
|
114
|
+
"artifact deletion predecessor digest",
|
|
115
|
+
);
|
|
116
|
+
const deletionId = String(value.deletionId || "");
|
|
117
|
+
const phase = String(value.phase || "");
|
|
118
|
+
const preparedEventDigest = optionalDigest(
|
|
119
|
+
value.preparedEventDigest,
|
|
120
|
+
"artifact deletion prepared event digest",
|
|
121
|
+
);
|
|
122
|
+
const artifactId = String(value.artifactId || "");
|
|
123
|
+
const artifactSha256 = String(value.artifactSha256 || "");
|
|
124
|
+
const artifactSize = Number(value.artifactSize);
|
|
125
|
+
const recordDigest = optionalDigest(
|
|
126
|
+
value.recordDigest,
|
|
127
|
+
"artifact deletion record digest",
|
|
128
|
+
);
|
|
129
|
+
const artifactSessionId = optionalId(
|
|
130
|
+
value.artifactSessionId,
|
|
131
|
+
"artifact deletion session id",
|
|
132
|
+
);
|
|
133
|
+
const storedFile = safeStoredFile(value.storedFile);
|
|
134
|
+
const reason = String(value.reason || "");
|
|
135
|
+
const client = String(value.client || "");
|
|
136
|
+
const indexGenerationBefore = String(value.indexGenerationBefore || "");
|
|
137
|
+
const indexGenerationAfter = optionalDigest(
|
|
138
|
+
value.indexGenerationAfter,
|
|
139
|
+
"artifact deletion terminal index generation",
|
|
140
|
+
);
|
|
141
|
+
const managedCopyDisposition = String(value.managedCopyDisposition || "");
|
|
142
|
+
const occurredAt = String(value.occurredAt || "");
|
|
143
|
+
if (
|
|
144
|
+
value.schema !== ARTIFACT_DELETION_EVENT_SCHEMA ||
|
|
145
|
+
!Number.isSafeInteger(sequence) ||
|
|
146
|
+
sequence < 1 ||
|
|
147
|
+
!ID_RE.test(deletionId) ||
|
|
148
|
+
!["prepared", "terminal"].includes(phase) ||
|
|
149
|
+
!ID_RE.test(artifactId) ||
|
|
150
|
+
!SHA256_RE.test(artifactSha256) ||
|
|
151
|
+
!Number.isSafeInteger(artifactSize) ||
|
|
152
|
+
artifactSize < 0 ||
|
|
153
|
+
typeof value.immutable !== "boolean" ||
|
|
154
|
+
!ARTIFACT_DELETION_REASONS.includes(reason) ||
|
|
155
|
+
!ARTIFACT_DELETION_CLIENTS.includes(client) ||
|
|
156
|
+
!SHA256_RE.test(indexGenerationBefore) ||
|
|
157
|
+
!Number.isFinite(Date.parse(occurredAt)) ||
|
|
158
|
+
!SHA256_RE.test(String(value.eventDigest || ""))
|
|
159
|
+
) {
|
|
160
|
+
throw new TypeError("artifact deletion event is invalid");
|
|
161
|
+
}
|
|
162
|
+
if (
|
|
163
|
+
(phase === "prepared" &&
|
|
164
|
+
(preparedEventDigest !== null ||
|
|
165
|
+
indexGenerationAfter !== null ||
|
|
166
|
+
managedCopyDisposition !== "pending")) ||
|
|
167
|
+
(phase === "terminal" &&
|
|
168
|
+
(!preparedEventDigest ||
|
|
169
|
+
!indexGenerationAfter ||
|
|
170
|
+
!["removed", "already-absent"].includes(managedCopyDisposition)))
|
|
171
|
+
) {
|
|
172
|
+
throw new TypeError("artifact deletion phase fields are invalid");
|
|
173
|
+
}
|
|
174
|
+
if (
|
|
175
|
+
sequence !== (previous?.sequence || 0) + 1 ||
|
|
176
|
+
previousEventDigest !== (previous?.eventDigest || null)
|
|
177
|
+
) {
|
|
178
|
+
throw new Error("artifact deletion event chain is invalid");
|
|
179
|
+
}
|
|
180
|
+
const material = {
|
|
181
|
+
schema: value.schema,
|
|
182
|
+
sequence,
|
|
183
|
+
previousEventDigest,
|
|
184
|
+
deletionId,
|
|
185
|
+
phase,
|
|
186
|
+
preparedEventDigest,
|
|
187
|
+
artifactId,
|
|
188
|
+
artifactSha256,
|
|
189
|
+
artifactSize,
|
|
190
|
+
recordDigest,
|
|
191
|
+
artifactSessionId,
|
|
192
|
+
storedFile,
|
|
193
|
+
immutable: value.immutable,
|
|
194
|
+
reason,
|
|
195
|
+
client,
|
|
196
|
+
indexGenerationBefore,
|
|
197
|
+
indexGenerationAfter,
|
|
198
|
+
managedCopyDisposition,
|
|
199
|
+
occurredAt,
|
|
200
|
+
};
|
|
201
|
+
if (
|
|
202
|
+
value.eventDigest !==
|
|
203
|
+
digest("chainlesschain.artifact.deletion-settlement-event.v1\0", material)
|
|
204
|
+
) {
|
|
205
|
+
throw new Error("artifact deletion event digest is invalid");
|
|
206
|
+
}
|
|
207
|
+
return Object.freeze({ ...material, eventDigest: value.eventDigest });
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
function ledgerPath(store) {
|
|
211
|
+
return path.join(path.resolve(store.dir), "deletion-settlements.jsonl");
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
function readLedgerBytes(filePath, runtimeFs, runtime = undefined) {
|
|
215
|
+
return withTrustedFileParentSync(
|
|
216
|
+
runtimeFs,
|
|
217
|
+
filePath,
|
|
218
|
+
({ canonicalPath, parentDevice }) => {
|
|
219
|
+
let before;
|
|
220
|
+
try {
|
|
221
|
+
before = runtimeFs.lstatSync(canonicalPath, { bigint: true });
|
|
222
|
+
} catch (error) {
|
|
223
|
+
if (error?.code === "ENOENT") return Buffer.alloc(0);
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
if (
|
|
227
|
+
before.isSymbolicLink() ||
|
|
228
|
+
!before.isFile() ||
|
|
229
|
+
Number(before.nlink) !== 1 ||
|
|
230
|
+
Number(before.size) > MAX_LEDGER_BYTES
|
|
231
|
+
) {
|
|
232
|
+
throw new Error("artifact deletion ledger identity is invalid");
|
|
233
|
+
}
|
|
234
|
+
let descriptor = null;
|
|
235
|
+
try {
|
|
236
|
+
descriptor = runtimeFs.openSync(
|
|
237
|
+
canonicalPath,
|
|
238
|
+
runtimeFs.constants.O_RDONLY |
|
|
239
|
+
Number(runtimeFs.constants.O_NOFOLLOW || 0),
|
|
240
|
+
);
|
|
241
|
+
const opened = runtimeFs.fstatSync(descriptor, { bigint: true });
|
|
242
|
+
if (
|
|
243
|
+
!opened.isFile() ||
|
|
244
|
+
Number(opened.nlink) !== 1 ||
|
|
245
|
+
!samePathHandleFileIdentity(before, opened, parentDevice, runtime)
|
|
246
|
+
) {
|
|
247
|
+
throw new Error("artifact deletion ledger handle is invalid");
|
|
248
|
+
}
|
|
249
|
+
const bytes = runtimeFs.readFileSync(descriptor);
|
|
250
|
+
const after = runtimeFs.fstatSync(descriptor, { bigint: true });
|
|
251
|
+
if (
|
|
252
|
+
bytes.length > MAX_LEDGER_BYTES ||
|
|
253
|
+
Number(after.size) !== bytes.length ||
|
|
254
|
+
!sameFileStatIdentity(opened, after)
|
|
255
|
+
) {
|
|
256
|
+
throw new Error("artifact deletion ledger changed while reading");
|
|
257
|
+
}
|
|
258
|
+
return bytes;
|
|
259
|
+
} finally {
|
|
260
|
+
if (descriptor !== null) runtimeFs.closeSync(descriptor);
|
|
261
|
+
}
|
|
262
|
+
},
|
|
263
|
+
{ runtime },
|
|
264
|
+
);
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
function parseLedger(bytes) {
|
|
268
|
+
if (bytes.length === 0) return [];
|
|
269
|
+
let text;
|
|
270
|
+
try {
|
|
271
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
272
|
+
} catch {
|
|
273
|
+
throw new Error("artifact deletion ledger is not strict UTF-8");
|
|
274
|
+
}
|
|
275
|
+
if (!text.endsWith("\n")) {
|
|
276
|
+
throw new Error("artifact deletion ledger has a truncated tail");
|
|
277
|
+
}
|
|
278
|
+
const lines = text.slice(0, -1).split("\n");
|
|
279
|
+
if (lines.length > MAX_LEDGER_EVENTS) {
|
|
280
|
+
throw new Error("artifact deletion ledger exceeds its event limit");
|
|
281
|
+
}
|
|
282
|
+
const events = [];
|
|
283
|
+
for (const line of lines) {
|
|
284
|
+
let parsed;
|
|
285
|
+
try {
|
|
286
|
+
parsed = JSON.parse(line);
|
|
287
|
+
} catch {
|
|
288
|
+
throw new Error("artifact deletion ledger contains invalid JSON");
|
|
289
|
+
}
|
|
290
|
+
events.push(normalizeArtifactDeletionEvent(parsed, events.at(-1) || null));
|
|
291
|
+
}
|
|
292
|
+
return events;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
export function readArtifactDeletionLedger(store, options = {}) {
|
|
296
|
+
if (!store?.dir) {
|
|
297
|
+
throw new TypeError("artifact deletion ledger requires an ArtifactStore");
|
|
298
|
+
}
|
|
299
|
+
const events = parseLedger(
|
|
300
|
+
readLedgerBytes(ledgerPath(store), options.fs || fs, options.runtime),
|
|
301
|
+
);
|
|
302
|
+
return Object.freeze({
|
|
303
|
+
schema: ARTIFACT_DELETION_LEDGER_SCHEMA,
|
|
304
|
+
eventCount: events.length,
|
|
305
|
+
preparedCount: events.filter((event) => event.phase === "prepared").length,
|
|
306
|
+
terminalCount: events.filter((event) => event.phase === "terminal").length,
|
|
307
|
+
headDigest: events.at(-1)?.eventDigest || null,
|
|
308
|
+
events: Object.freeze(events),
|
|
309
|
+
});
|
|
310
|
+
}
|
|
311
|
+
|
|
312
|
+
function normalizeRequest(input = {}) {
|
|
313
|
+
const deletionId = String(
|
|
314
|
+
input.deletionId || `delete_${randomUUID().replaceAll("-", "")}`,
|
|
315
|
+
);
|
|
316
|
+
const artifactId = String(input.artifactId || "");
|
|
317
|
+
const reason = String(input.reason || "explicit");
|
|
318
|
+
const client = String(input.client || "cli");
|
|
319
|
+
if (
|
|
320
|
+
!ID_RE.test(deletionId) ||
|
|
321
|
+
!ID_RE.test(artifactId) ||
|
|
322
|
+
!ARTIFACT_DELETION_REASONS.includes(reason) ||
|
|
323
|
+
!ARTIFACT_DELETION_CLIENTS.includes(client)
|
|
324
|
+
) {
|
|
325
|
+
throw new TypeError("artifact deletion request is invalid");
|
|
326
|
+
}
|
|
327
|
+
return Object.freeze({ deletionId, artifactId, reason, client });
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
function indexGenerationDigest(entries) {
|
|
331
|
+
return digest("chainlesschain.artifact.index-generation.v1\0", entries);
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
function preparedArtifact(entry) {
|
|
335
|
+
const artifactId = String(entry?.id || "");
|
|
336
|
+
const sha256 = String(entry?.sha256 || "");
|
|
337
|
+
const size = Number(entry?.size);
|
|
338
|
+
if (
|
|
339
|
+
!ID_RE.test(artifactId) ||
|
|
340
|
+
!/^[a-f0-9]{64}$/u.test(sha256) ||
|
|
341
|
+
!Number.isSafeInteger(size) ||
|
|
342
|
+
size < 0
|
|
343
|
+
) {
|
|
344
|
+
throw new Error("artifact index row cannot authorize deletion");
|
|
345
|
+
}
|
|
346
|
+
return Object.freeze({
|
|
347
|
+
artifactId,
|
|
348
|
+
artifactSha256: `sha256:${sha256}`,
|
|
349
|
+
artifactSize: size,
|
|
350
|
+
recordDigest:
|
|
351
|
+
entry.recordDigest == null
|
|
352
|
+
? null
|
|
353
|
+
: optionalDigest(entry.recordDigest, "artifact record digest"),
|
|
354
|
+
artifactSessionId:
|
|
355
|
+
entry.sessionId == null
|
|
356
|
+
? null
|
|
357
|
+
: optionalId(entry.sessionId, "artifact session id"),
|
|
358
|
+
storedFile: safeStoredFile(entry.file),
|
|
359
|
+
immutable: entry.immutable === true,
|
|
360
|
+
});
|
|
361
|
+
}
|
|
362
|
+
|
|
363
|
+
function samePreparedArtifact(entry, prepared) {
|
|
364
|
+
try {
|
|
365
|
+
const current = preparedArtifact(entry);
|
|
366
|
+
return [
|
|
367
|
+
"artifactId",
|
|
368
|
+
"artifactSha256",
|
|
369
|
+
"artifactSize",
|
|
370
|
+
"recordDigest",
|
|
371
|
+
"artifactSessionId",
|
|
372
|
+
"storedFile",
|
|
373
|
+
"immutable",
|
|
374
|
+
].every((field) => current[field] === prepared[field]);
|
|
375
|
+
} catch {
|
|
376
|
+
return false;
|
|
377
|
+
}
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function sameDeletionAuthority(terminal, prepared) {
|
|
381
|
+
return [
|
|
382
|
+
"artifactId",
|
|
383
|
+
"artifactSha256",
|
|
384
|
+
"artifactSize",
|
|
385
|
+
"recordDigest",
|
|
386
|
+
"artifactSessionId",
|
|
387
|
+
"storedFile",
|
|
388
|
+
"immutable",
|
|
389
|
+
"reason",
|
|
390
|
+
"client",
|
|
391
|
+
"indexGenerationBefore",
|
|
392
|
+
].every((field) => terminal[field] === prepared[field]);
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
function appendEvent(filePath, event, expectedSize, runtimeFs) {
|
|
396
|
+
let descriptor = null;
|
|
397
|
+
try {
|
|
398
|
+
descriptor = runtimeFs.openSync(
|
|
399
|
+
filePath,
|
|
400
|
+
runtimeFs.constants.O_WRONLY |
|
|
401
|
+
runtimeFs.constants.O_APPEND |
|
|
402
|
+
runtimeFs.constants.O_CREAT |
|
|
403
|
+
Number(runtimeFs.constants.O_NOFOLLOW || 0),
|
|
404
|
+
0o600,
|
|
405
|
+
);
|
|
406
|
+
const opened = runtimeFs.fstatSync(descriptor, { bigint: true });
|
|
407
|
+
if (
|
|
408
|
+
!opened.isFile() ||
|
|
409
|
+
Number(opened.nlink) !== 1 ||
|
|
410
|
+
Number(opened.size) !== expectedSize
|
|
411
|
+
) {
|
|
412
|
+
throw new Error("artifact deletion ledger changed before append");
|
|
413
|
+
}
|
|
414
|
+
runtimeFs.writeFileSync(descriptor, `${JSON.stringify(event)}\n`, "utf8");
|
|
415
|
+
runtimeFs.fsyncSync(descriptor);
|
|
416
|
+
} finally {
|
|
417
|
+
if (descriptor !== null) runtimeFs.closeSync(descriptor);
|
|
418
|
+
}
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
function eventFor(material, previous) {
|
|
422
|
+
const candidate = {
|
|
423
|
+
...material,
|
|
424
|
+
eventDigest: digest(
|
|
425
|
+
"chainlesschain.artifact.deletion-settlement-event.v1\0",
|
|
426
|
+
material,
|
|
427
|
+
),
|
|
428
|
+
};
|
|
429
|
+
return normalizeArtifactDeletionEvent(candidate, previous);
|
|
430
|
+
}
|
|
431
|
+
|
|
432
|
+
function assertRequestBinding(event, request) {
|
|
433
|
+
if (
|
|
434
|
+
event.artifactId !== request.artifactId ||
|
|
435
|
+
event.reason !== request.reason ||
|
|
436
|
+
event.client !== request.client
|
|
437
|
+
) {
|
|
438
|
+
throw new Error("artifact deletion id is already bound to other inputs");
|
|
439
|
+
}
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
function managedPath(store, storedFile) {
|
|
443
|
+
const filesRoot = path.resolve(store.dir, "files");
|
|
444
|
+
const target = path.resolve(filesRoot, safeStoredFile(storedFile));
|
|
445
|
+
if (
|
|
446
|
+
target !== path.join(filesRoot, storedFile) ||
|
|
447
|
+
!target.startsWith(`${filesRoot}${path.sep}`)
|
|
448
|
+
) {
|
|
449
|
+
throw new Error(
|
|
450
|
+
"artifact deletion path escapes the managed files directory",
|
|
451
|
+
);
|
|
452
|
+
}
|
|
453
|
+
return target;
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function removeManagedCopy(store, prepared, runtimeFs) {
|
|
457
|
+
const target = managedPath(store, prepared.storedFile);
|
|
458
|
+
try {
|
|
459
|
+
runtimeFs.lstatSync(target);
|
|
460
|
+
} catch (error) {
|
|
461
|
+
if (error?.code === "ENOENT") return "already-absent";
|
|
462
|
+
throw error;
|
|
463
|
+
}
|
|
464
|
+
if (prepared.immutable) {
|
|
465
|
+
try {
|
|
466
|
+
runtimeFs.chmodSync(target, 0o600);
|
|
467
|
+
} catch {
|
|
468
|
+
// rm may still succeed on filesystems without chmod support.
|
|
469
|
+
}
|
|
470
|
+
}
|
|
471
|
+
runtimeFs.rmSync(target, { force: true });
|
|
472
|
+
try {
|
|
473
|
+
runtimeFs.lstatSync(target);
|
|
474
|
+
} catch (error) {
|
|
475
|
+
if (error?.code === "ENOENT") return "removed";
|
|
476
|
+
throw error;
|
|
477
|
+
}
|
|
478
|
+
throw new Error("artifact managed copy remains after deletion");
|
|
479
|
+
}
|
|
480
|
+
|
|
481
|
+
function assertManagedCopyAbsent(store, prepared, runtimeFs) {
|
|
482
|
+
const target = managedPath(store, prepared.storedFile);
|
|
483
|
+
try {
|
|
484
|
+
runtimeFs.lstatSync(target);
|
|
485
|
+
} catch (error) {
|
|
486
|
+
if (error?.code === "ENOENT") return;
|
|
487
|
+
throw error;
|
|
488
|
+
}
|
|
489
|
+
throw new Error("artifact managed copy reappeared after settlement");
|
|
490
|
+
}
|
|
491
|
+
|
|
492
|
+
function receipt(request, terminal, recorded) {
|
|
493
|
+
return Object.freeze({
|
|
494
|
+
schema: ARTIFACT_DELETION_RECEIPT_SCHEMA,
|
|
495
|
+
deletionId: request.deletionId,
|
|
496
|
+
artifactId: request.artifactId,
|
|
497
|
+
found: true,
|
|
498
|
+
settled: true,
|
|
499
|
+
recorded,
|
|
500
|
+
deletion: terminal,
|
|
501
|
+
});
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
export function settleArtifactDeletion(store, input = {}, options = {}) {
|
|
505
|
+
if (
|
|
506
|
+
!store?.dir ||
|
|
507
|
+
typeof store._withIndexLock !== "function" ||
|
|
508
|
+
typeof store._readEntries !== "function" ||
|
|
509
|
+
typeof store._rewriteUnlocked !== "function"
|
|
510
|
+
) {
|
|
511
|
+
throw new TypeError("artifact deletion requires an ArtifactStore");
|
|
512
|
+
}
|
|
513
|
+
const request = normalizeRequest(input);
|
|
514
|
+
const runtimeFs = options.fs || fs;
|
|
515
|
+
const filePath = ledgerPath(store);
|
|
516
|
+
runtimeFs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
517
|
+
try {
|
|
518
|
+
return store._withIndexLock(() =>
|
|
519
|
+
(options.withFileLock || withFileLock)(
|
|
520
|
+
filePath,
|
|
521
|
+
() => {
|
|
522
|
+
let ledgerBytes = readLedgerBytes(
|
|
523
|
+
filePath,
|
|
524
|
+
runtimeFs,
|
|
525
|
+
options.runtime,
|
|
526
|
+
);
|
|
527
|
+
const events = parseLedger(ledgerBytes);
|
|
528
|
+
const requestEvents = events.filter(
|
|
529
|
+
(event) => event.deletionId === request.deletionId,
|
|
530
|
+
);
|
|
531
|
+
if (requestEvents.length > 2) {
|
|
532
|
+
throw new Error(
|
|
533
|
+
"artifact deletion id has too many settlement events",
|
|
534
|
+
);
|
|
535
|
+
}
|
|
536
|
+
const preparedEvents = requestEvents.filter(
|
|
537
|
+
(event) => event.phase === "prepared",
|
|
538
|
+
);
|
|
539
|
+
const terminalEvents = requestEvents.filter(
|
|
540
|
+
(event) => event.phase === "terminal",
|
|
541
|
+
);
|
|
542
|
+
if (preparedEvents.length > 1 || terminalEvents.length > 1) {
|
|
543
|
+
throw new Error("artifact deletion settlement is ambiguous");
|
|
544
|
+
}
|
|
545
|
+
let prepared = preparedEvents[0] || null;
|
|
546
|
+
const terminal = terminalEvents[0] || null;
|
|
547
|
+
if (terminal) {
|
|
548
|
+
if (
|
|
549
|
+
!prepared ||
|
|
550
|
+
terminal.preparedEventDigest !== prepared.eventDigest ||
|
|
551
|
+
!sameDeletionAuthority(terminal, prepared)
|
|
552
|
+
) {
|
|
553
|
+
throw new Error(
|
|
554
|
+
"artifact deletion terminal has no unique preparation",
|
|
555
|
+
);
|
|
556
|
+
}
|
|
557
|
+
assertRequestBinding(prepared, request);
|
|
558
|
+
const currentMatches = store
|
|
559
|
+
._readEntries()
|
|
560
|
+
.filter((entry) => entry.id === request.artifactId);
|
|
561
|
+
if (currentMatches.length !== 0) {
|
|
562
|
+
throw new Error("artifact reappeared after deletion settlement");
|
|
563
|
+
}
|
|
564
|
+
assertManagedCopyAbsent(store, prepared, runtimeFs);
|
|
565
|
+
return receipt(request, terminal, false);
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
let entries = store._readEntries();
|
|
569
|
+
const matches = entries.filter(
|
|
570
|
+
(entry) => entry.id === request.artifactId,
|
|
571
|
+
);
|
|
572
|
+
if (!prepared) {
|
|
573
|
+
if (matches.length === 0) {
|
|
574
|
+
return Object.freeze({
|
|
575
|
+
schema: ARTIFACT_DELETION_RECEIPT_SCHEMA,
|
|
576
|
+
deletionId: request.deletionId,
|
|
577
|
+
artifactId: request.artifactId,
|
|
578
|
+
found: false,
|
|
579
|
+
settled: false,
|
|
580
|
+
recorded: false,
|
|
581
|
+
deletion: null,
|
|
582
|
+
});
|
|
583
|
+
}
|
|
584
|
+
if (matches.length > 1) {
|
|
585
|
+
throw new Error("artifact id is ambiguous for deletion");
|
|
586
|
+
}
|
|
587
|
+
const artifact = preparedArtifact(matches[0]);
|
|
588
|
+
const previous = events.at(-1) || null;
|
|
589
|
+
const material = {
|
|
590
|
+
schema: ARTIFACT_DELETION_EVENT_SCHEMA,
|
|
591
|
+
sequence: (previous?.sequence || 0) + 1,
|
|
592
|
+
previousEventDigest: previous?.eventDigest || null,
|
|
593
|
+
deletionId: request.deletionId,
|
|
594
|
+
phase: "prepared",
|
|
595
|
+
preparedEventDigest: null,
|
|
596
|
+
...artifact,
|
|
597
|
+
reason: request.reason,
|
|
598
|
+
client: request.client,
|
|
599
|
+
indexGenerationBefore: indexGenerationDigest(entries),
|
|
600
|
+
indexGenerationAfter: null,
|
|
601
|
+
managedCopyDisposition: "pending",
|
|
602
|
+
occurredAt: new Date(
|
|
603
|
+
typeof options.now === "function" ? options.now() : Date.now(),
|
|
604
|
+
).toISOString(),
|
|
605
|
+
};
|
|
606
|
+
prepared = eventFor(material, previous);
|
|
607
|
+
appendEvent(filePath, prepared, ledgerBytes.length, runtimeFs);
|
|
608
|
+
ledgerBytes = Buffer.concat([
|
|
609
|
+
ledgerBytes,
|
|
610
|
+
Buffer.from(`${JSON.stringify(prepared)}\n`, "utf8"),
|
|
611
|
+
]);
|
|
612
|
+
} else {
|
|
613
|
+
assertRequestBinding(prepared, request);
|
|
614
|
+
if (matches.length > 1) {
|
|
615
|
+
throw new Error(
|
|
616
|
+
"artifact id is ambiguous during deletion recovery",
|
|
617
|
+
);
|
|
618
|
+
}
|
|
619
|
+
if (
|
|
620
|
+
matches.length === 1 &&
|
|
621
|
+
!samePreparedArtifact(matches[0], prepared)
|
|
622
|
+
) {
|
|
623
|
+
throw new Error("artifact changed after deletion preparation");
|
|
624
|
+
}
|
|
625
|
+
}
|
|
626
|
+
|
|
627
|
+
if (matches.length === 1) {
|
|
628
|
+
store._rewriteUnlocked(
|
|
629
|
+
entries.filter((entry) => entry.id !== request.artifactId),
|
|
630
|
+
);
|
|
631
|
+
entries = entries.filter(
|
|
632
|
+
(entry) => entry.id !== request.artifactId,
|
|
633
|
+
);
|
|
634
|
+
}
|
|
635
|
+
const disposition = removeManagedCopy(store, prepared, runtimeFs);
|
|
636
|
+
const previous = parseLedger(ledgerBytes).at(-1) || null;
|
|
637
|
+
const terminalMaterial = {
|
|
638
|
+
schema: ARTIFACT_DELETION_EVENT_SCHEMA,
|
|
639
|
+
sequence: (previous?.sequence || 0) + 1,
|
|
640
|
+
previousEventDigest: previous?.eventDigest || null,
|
|
641
|
+
deletionId: request.deletionId,
|
|
642
|
+
phase: "terminal",
|
|
643
|
+
preparedEventDigest: prepared.eventDigest,
|
|
644
|
+
artifactId: prepared.artifactId,
|
|
645
|
+
artifactSha256: prepared.artifactSha256,
|
|
646
|
+
artifactSize: prepared.artifactSize,
|
|
647
|
+
recordDigest: prepared.recordDigest,
|
|
648
|
+
artifactSessionId: prepared.artifactSessionId,
|
|
649
|
+
storedFile: prepared.storedFile,
|
|
650
|
+
immutable: prepared.immutable,
|
|
651
|
+
reason: prepared.reason,
|
|
652
|
+
client: prepared.client,
|
|
653
|
+
indexGenerationBefore: prepared.indexGenerationBefore,
|
|
654
|
+
indexGenerationAfter: indexGenerationDigest(entries),
|
|
655
|
+
managedCopyDisposition: disposition,
|
|
656
|
+
occurredAt: new Date(
|
|
657
|
+
typeof options.now === "function" ? options.now() : Date.now(),
|
|
658
|
+
).toISOString(),
|
|
659
|
+
};
|
|
660
|
+
const settled = eventFor(terminalMaterial, previous);
|
|
661
|
+
appendEvent(filePath, settled, ledgerBytes.length, runtimeFs);
|
|
662
|
+
return receipt(request, settled, true);
|
|
663
|
+
},
|
|
664
|
+
{
|
|
665
|
+
failIfUnavailable: true,
|
|
666
|
+
timeoutMs: 30_000,
|
|
667
|
+
retryMs: 1,
|
|
668
|
+
maxRetryMs: 8,
|
|
669
|
+
retryJitterMs: 4,
|
|
670
|
+
},
|
|
671
|
+
),
|
|
672
|
+
);
|
|
673
|
+
} catch (error) {
|
|
674
|
+
error.deletionId = request.deletionId;
|
|
675
|
+
throw error;
|
|
676
|
+
}
|
|
677
|
+
}
|