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
|
@@ -402,8 +402,9 @@ export class TeamRunner {
|
|
|
402
402
|
this._setState(holder, "shutdown", { reason: "fatal-error" });
|
|
403
403
|
return;
|
|
404
404
|
}
|
|
405
|
-
|
|
406
|
-
|
|
405
|
+
const sessionStopReason = this._sessionBudgetStopReason();
|
|
406
|
+
if (sessionStopReason) {
|
|
407
|
+
const reason = sessionStopReason;
|
|
407
408
|
if (!this._budgetStopped) {
|
|
408
409
|
this._budgetStopped = true;
|
|
409
410
|
this._emit("run:budget-exhausted", { reason });
|
|
@@ -606,9 +607,7 @@ export class TeamRunner {
|
|
|
606
607
|
// Preserve an explicit human interruption so `_execute` can settle it
|
|
607
608
|
// fail-closed for adjudication instead of silently abandoning it.
|
|
608
609
|
const preparedBudgetReason = this.budget?.reason?.();
|
|
609
|
-
const preparedSessionReason = this.
|
|
610
|
-
? this.sessionBudget.reason?.() || "session-aborted"
|
|
611
|
-
: null;
|
|
610
|
+
const preparedSessionReason = this._sessionBudgetStopReason();
|
|
612
611
|
if (
|
|
613
612
|
!claim.interruption &&
|
|
614
613
|
!claim.coordinatorAbort &&
|
|
@@ -718,6 +717,7 @@ export class TeamRunner {
|
|
|
718
717
|
sessionWork,
|
|
719
718
|
sessionBudgetSettled: false,
|
|
720
719
|
sessionBudgetUsageHandledByExecutor: false,
|
|
720
|
+
sessionBudgetUsageUnknown: false,
|
|
721
721
|
sessionBudgetView: null,
|
|
722
722
|
unregisterSessionAbortable: null,
|
|
723
723
|
interruption: null,
|
|
@@ -908,13 +908,18 @@ export class TeamRunner {
|
|
|
908
908
|
if (typeof value !== "function") return value;
|
|
909
909
|
if (methods.has(property)) return methods.get(property);
|
|
910
910
|
const method =
|
|
911
|
-
property === "recordUsage"
|
|
911
|
+
property === "recordUsage" || property === "markUsageUnknown"
|
|
912
912
|
? (...args) => {
|
|
913
|
+
const result = value.apply(target, args);
|
|
913
914
|
// Usage charged through the inherited authority is already in
|
|
914
915
|
// the global ledger. Remember that ownership so the task's
|
|
915
|
-
// returned aggregate is not charged a second time.
|
|
916
|
+
// returned aggregate is not charged a second time. Unknown
|
|
917
|
+
// usage must also prevent this task becoming a phantom success.
|
|
916
918
|
claim.sessionBudgetUsageHandledByExecutor = true;
|
|
917
|
-
|
|
919
|
+
if (property === "markUsageUnknown") {
|
|
920
|
+
claim.sessionBudgetUsageUnknown = true;
|
|
921
|
+
}
|
|
922
|
+
return result;
|
|
918
923
|
}
|
|
919
924
|
: value.bind(target);
|
|
920
925
|
methods.set(property, method);
|
|
@@ -927,10 +932,25 @@ export class TeamRunner {
|
|
|
927
932
|
return claim.sessionBudgetView;
|
|
928
933
|
}
|
|
929
934
|
|
|
935
|
+
_sessionBudgetStopReason() {
|
|
936
|
+
if (!this.sessionBudget) return null;
|
|
937
|
+
const reason = this.sessionBudget.reason?.() || null;
|
|
938
|
+
if (this.sessionBudget.signal?.aborted) {
|
|
939
|
+
return reason || "session-aborted";
|
|
940
|
+
}
|
|
941
|
+
return reason === "recovery-required" ? reason : null;
|
|
942
|
+
}
|
|
943
|
+
|
|
930
944
|
_recordSessionUsage(claim, source) {
|
|
931
945
|
if (!this.sessionBudget || claim.sessionBudgetSettled) return;
|
|
932
946
|
claim.sessionBudgetSettled = true;
|
|
933
947
|
if (claim.sessionBudgetUsageHandledByExecutor) return;
|
|
948
|
+
if (
|
|
949
|
+
!source?.usage &&
|
|
950
|
+
(!Array.isArray(source?.usageRecords) || source.usageRecords.length === 0)
|
|
951
|
+
) {
|
|
952
|
+
return;
|
|
953
|
+
}
|
|
934
954
|
this.sessionBudget.recordUsage({
|
|
935
955
|
usage: source?.usage || null,
|
|
936
956
|
usageRecords: source?.usageRecords || null,
|
|
@@ -941,6 +961,10 @@ export class TeamRunner {
|
|
|
941
961
|
|
|
942
962
|
_sessionBudgetFailure(executionResult = null) {
|
|
943
963
|
const sharedReason = this.sessionBudget?.signal?.reason;
|
|
964
|
+
const budgetReason =
|
|
965
|
+
sharedReason?.budgetReason ||
|
|
966
|
+
this.sessionBudget?.reason?.() ||
|
|
967
|
+
"session-aborted";
|
|
944
968
|
// Never attach per-task usage to the shared AbortSignal reason: every
|
|
945
969
|
// sibling observes that same Error object and would then re-record the
|
|
946
970
|
// triggering task's usage as its own failure.
|
|
@@ -949,9 +973,12 @@ export class TeamRunner {
|
|
|
949
973
|
`Session budget exhausted: ${this.sessionBudget?.reason?.() || "session-aborted"}`,
|
|
950
974
|
sharedReason instanceof Error ? { cause: sharedReason } : undefined,
|
|
951
975
|
);
|
|
952
|
-
failure.code =
|
|
953
|
-
|
|
954
|
-
|
|
976
|
+
failure.code =
|
|
977
|
+
sharedReason?.code ||
|
|
978
|
+
(budgetReason === "recovery-required"
|
|
979
|
+
? "CC_SESSION_BUDGET_USAGE_UNKNOWN"
|
|
980
|
+
: "ERR_SESSION_RESOURCE_BUDGET");
|
|
981
|
+
failure.budgetReason = budgetReason;
|
|
955
982
|
failure.retryable = false;
|
|
956
983
|
if (executionResult && typeof executionResult === "object") {
|
|
957
984
|
failure.usage = executionResult.usage || null;
|
|
@@ -1160,7 +1187,7 @@ export class TeamRunner {
|
|
|
1160
1187
|
// Synchronous observers above (`onEvent`, mailbox, tracing) may abort the
|
|
1161
1188
|
// shared authority after the worker's post-beforeTask check. This is the
|
|
1162
1189
|
// final fail-closed fence immediately before executor side effects.
|
|
1163
|
-
if (this.
|
|
1190
|
+
if (this._sessionBudgetStopReason()) {
|
|
1164
1191
|
throw this._sessionBudgetFailure();
|
|
1165
1192
|
}
|
|
1166
1193
|
const result = await this.runTask({
|
|
@@ -1186,7 +1213,10 @@ export class TeamRunner {
|
|
|
1186
1213
|
throw claim.coordinatorAbort;
|
|
1187
1214
|
}
|
|
1188
1215
|
this._recordSessionUsage(claim, result);
|
|
1189
|
-
if (
|
|
1216
|
+
if (
|
|
1217
|
+
claim.sessionBudgetUsageUnknown ||
|
|
1218
|
+
this.sessionBudget?.signal?.aborted
|
|
1219
|
+
) {
|
|
1190
1220
|
throw this._sessionBudgetFailure(result);
|
|
1191
1221
|
}
|
|
1192
1222
|
// Executors are allowed to return after observing (or even ignoring) an
|
|
@@ -0,0 +1,461 @@
|
|
|
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_ACCESS_EVENT_SCHEMA = "cc-artifact-content-access/v1";
|
|
14
|
+
export const ARTIFACT_ACCESS_LEDGER_SCHEMA =
|
|
15
|
+
"cc-artifact-content-access-ledger/v1";
|
|
16
|
+
|
|
17
|
+
export const ARTIFACT_ACCESS_CLIENTS = Object.freeze([
|
|
18
|
+
"cli",
|
|
19
|
+
"vscode",
|
|
20
|
+
"jetbrains",
|
|
21
|
+
"websocket",
|
|
22
|
+
"web",
|
|
23
|
+
]);
|
|
24
|
+
export const ARTIFACT_ACCESS_ACTIONS = Object.freeze([
|
|
25
|
+
"open",
|
|
26
|
+
"preview",
|
|
27
|
+
"download",
|
|
28
|
+
"copy-path",
|
|
29
|
+
"reveal",
|
|
30
|
+
"open-external",
|
|
31
|
+
]);
|
|
32
|
+
|
|
33
|
+
const ACCESS_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._:@+-]{0,255}$/u;
|
|
34
|
+
const ARTIFACT_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,255}$/u;
|
|
35
|
+
const SHA256_RE = /^sha256:[a-f0-9]{64}$/u;
|
|
36
|
+
const MAX_LEDGER_BYTES = 16 * 1024 * 1024;
|
|
37
|
+
const MAX_LEDGER_EVENTS = 100_000;
|
|
38
|
+
|
|
39
|
+
function digest(domain, value) {
|
|
40
|
+
return `sha256:${createHash("sha256")
|
|
41
|
+
.update(domain, "utf8")
|
|
42
|
+
.update(canonicalJson(value, "artifactAccessLedger"), "utf8")
|
|
43
|
+
.digest("hex")}`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
function exactObject(value, fields, label) {
|
|
47
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) {
|
|
48
|
+
throw new TypeError(`${label} must be an object`);
|
|
49
|
+
}
|
|
50
|
+
const actual = Object.keys(value).sort();
|
|
51
|
+
const expected = [...fields].sort();
|
|
52
|
+
if (
|
|
53
|
+
actual.length !== expected.length ||
|
|
54
|
+
actual.some((field, index) => field !== expected[index])
|
|
55
|
+
) {
|
|
56
|
+
throw new TypeError(`${label} has an invalid schema`);
|
|
57
|
+
}
|
|
58
|
+
return value;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeOptionalDigest(value, label) {
|
|
62
|
+
if (value === null) return null;
|
|
63
|
+
const normalized = String(value || "");
|
|
64
|
+
if (!SHA256_RE.test(normalized)) {
|
|
65
|
+
throw new TypeError(`${label} is invalid`);
|
|
66
|
+
}
|
|
67
|
+
return normalized;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function normalizeOptionalId(value, label) {
|
|
71
|
+
if (value === null) return null;
|
|
72
|
+
const normalized = String(value || "");
|
|
73
|
+
if (!ACCESS_ID_RE.test(normalized)) {
|
|
74
|
+
throw new TypeError(`${label} is invalid`);
|
|
75
|
+
}
|
|
76
|
+
return normalized;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
export function normalizeArtifactAccessEvent(input, previous = null) {
|
|
80
|
+
const value = exactObject(
|
|
81
|
+
input,
|
|
82
|
+
[
|
|
83
|
+
"schema",
|
|
84
|
+
"sequence",
|
|
85
|
+
"previousEventDigest",
|
|
86
|
+
"accessId",
|
|
87
|
+
"artifactId",
|
|
88
|
+
"artifactSha256",
|
|
89
|
+
"recordDigest",
|
|
90
|
+
"artifactSessionId",
|
|
91
|
+
"client",
|
|
92
|
+
"action",
|
|
93
|
+
"authorizedAt",
|
|
94
|
+
"authorization",
|
|
95
|
+
"eventDigest",
|
|
96
|
+
],
|
|
97
|
+
"artifact access event",
|
|
98
|
+
);
|
|
99
|
+
const sequence = Number(value.sequence);
|
|
100
|
+
const accessId = String(value.accessId || "");
|
|
101
|
+
const artifactId = String(value.artifactId || "");
|
|
102
|
+
const artifactSha256 = String(value.artifactSha256 || "");
|
|
103
|
+
const client = String(value.client || "");
|
|
104
|
+
const action = String(value.action || "");
|
|
105
|
+
const authorizedAt = String(value.authorizedAt || "");
|
|
106
|
+
const previousEventDigest = normalizeOptionalDigest(
|
|
107
|
+
value.previousEventDigest,
|
|
108
|
+
"artifact access predecessor digest",
|
|
109
|
+
);
|
|
110
|
+
const recordDigest = normalizeOptionalDigest(
|
|
111
|
+
value.recordDigest,
|
|
112
|
+
"artifact record digest",
|
|
113
|
+
);
|
|
114
|
+
const artifactSessionId = normalizeOptionalId(
|
|
115
|
+
value.artifactSessionId,
|
|
116
|
+
"artifact session id",
|
|
117
|
+
);
|
|
118
|
+
if (
|
|
119
|
+
value.schema !== ARTIFACT_ACCESS_EVENT_SCHEMA ||
|
|
120
|
+
!Number.isSafeInteger(sequence) ||
|
|
121
|
+
sequence < 1 ||
|
|
122
|
+
!ACCESS_ID_RE.test(accessId) ||
|
|
123
|
+
!ARTIFACT_ID_RE.test(artifactId) ||
|
|
124
|
+
!SHA256_RE.test(artifactSha256) ||
|
|
125
|
+
!ARTIFACT_ACCESS_CLIENTS.includes(client) ||
|
|
126
|
+
!ARTIFACT_ACCESS_ACTIONS.includes(action) ||
|
|
127
|
+
!Number.isFinite(Date.parse(authorizedAt)) ||
|
|
128
|
+
value.authorization !== "current-artifact-index-and-byte-readback" ||
|
|
129
|
+
!SHA256_RE.test(String(value.eventDigest || ""))
|
|
130
|
+
) {
|
|
131
|
+
throw new TypeError("artifact access event is invalid");
|
|
132
|
+
}
|
|
133
|
+
if (
|
|
134
|
+
sequence !== (previous?.sequence || 0) + 1 ||
|
|
135
|
+
previousEventDigest !== (previous?.eventDigest || null)
|
|
136
|
+
) {
|
|
137
|
+
throw new Error("artifact access event chain is invalid");
|
|
138
|
+
}
|
|
139
|
+
const material = {
|
|
140
|
+
schema: value.schema,
|
|
141
|
+
sequence,
|
|
142
|
+
previousEventDigest,
|
|
143
|
+
accessId,
|
|
144
|
+
artifactId,
|
|
145
|
+
artifactSha256,
|
|
146
|
+
recordDigest,
|
|
147
|
+
artifactSessionId,
|
|
148
|
+
client,
|
|
149
|
+
action,
|
|
150
|
+
authorizedAt,
|
|
151
|
+
authorization: value.authorization,
|
|
152
|
+
};
|
|
153
|
+
if (
|
|
154
|
+
value.eventDigest !==
|
|
155
|
+
digest("chainlesschain.artifact.content-access-event.v1\0", material)
|
|
156
|
+
) {
|
|
157
|
+
throw new Error("artifact access event digest is invalid");
|
|
158
|
+
}
|
|
159
|
+
return Object.freeze({ ...material, eventDigest: value.eventDigest });
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
function ledgerPath(store) {
|
|
163
|
+
return path.join(path.resolve(store.dir), "content-access.jsonl");
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
function readLedgerBytes(filePath, runtimeFs, runtime = undefined) {
|
|
167
|
+
return withTrustedFileParentSync(
|
|
168
|
+
runtimeFs,
|
|
169
|
+
filePath,
|
|
170
|
+
({ canonicalPath, parentDevice }) => {
|
|
171
|
+
let before;
|
|
172
|
+
try {
|
|
173
|
+
before = runtimeFs.lstatSync(canonicalPath, { bigint: true });
|
|
174
|
+
} catch (error) {
|
|
175
|
+
if (error?.code === "ENOENT") return Buffer.alloc(0);
|
|
176
|
+
throw error;
|
|
177
|
+
}
|
|
178
|
+
if (
|
|
179
|
+
before.isSymbolicLink() ||
|
|
180
|
+
!before.isFile() ||
|
|
181
|
+
Number(before.nlink) !== 1 ||
|
|
182
|
+
Number(before.size) > MAX_LEDGER_BYTES
|
|
183
|
+
) {
|
|
184
|
+
throw new Error("artifact access ledger identity is invalid");
|
|
185
|
+
}
|
|
186
|
+
let descriptor = null;
|
|
187
|
+
try {
|
|
188
|
+
descriptor = runtimeFs.openSync(
|
|
189
|
+
canonicalPath,
|
|
190
|
+
runtimeFs.constants.O_RDONLY |
|
|
191
|
+
Number(runtimeFs.constants.O_NOFOLLOW || 0),
|
|
192
|
+
);
|
|
193
|
+
const opened = runtimeFs.fstatSync(descriptor, { bigint: true });
|
|
194
|
+
if (
|
|
195
|
+
!opened.isFile() ||
|
|
196
|
+
Number(opened.nlink) !== 1 ||
|
|
197
|
+
!samePathHandleFileIdentity(before, opened, parentDevice, runtime)
|
|
198
|
+
) {
|
|
199
|
+
throw new Error("artifact access ledger handle is invalid");
|
|
200
|
+
}
|
|
201
|
+
const bytes = runtimeFs.readFileSync(descriptor);
|
|
202
|
+
const after = runtimeFs.fstatSync(descriptor, { bigint: true });
|
|
203
|
+
if (
|
|
204
|
+
bytes.length > MAX_LEDGER_BYTES ||
|
|
205
|
+
Number(after.size) !== bytes.length ||
|
|
206
|
+
!sameFileStatIdentity(opened, after)
|
|
207
|
+
) {
|
|
208
|
+
throw new Error("artifact access ledger changed while reading");
|
|
209
|
+
}
|
|
210
|
+
return bytes;
|
|
211
|
+
} finally {
|
|
212
|
+
if (descriptor !== null) runtimeFs.closeSync(descriptor);
|
|
213
|
+
}
|
|
214
|
+
},
|
|
215
|
+
{ runtime },
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function parseLedger(bytes) {
|
|
220
|
+
if (bytes.length === 0) return [];
|
|
221
|
+
let text;
|
|
222
|
+
try {
|
|
223
|
+
text = new TextDecoder("utf-8", { fatal: true }).decode(bytes);
|
|
224
|
+
} catch {
|
|
225
|
+
throw new Error("artifact access ledger is not strict UTF-8");
|
|
226
|
+
}
|
|
227
|
+
if (!text.endsWith("\n")) {
|
|
228
|
+
throw new Error("artifact access ledger has a truncated tail");
|
|
229
|
+
}
|
|
230
|
+
const lines = text.slice(0, -1).split("\n");
|
|
231
|
+
if (lines.length > MAX_LEDGER_EVENTS) {
|
|
232
|
+
throw new Error("artifact access ledger exceeds its event limit");
|
|
233
|
+
}
|
|
234
|
+
const events = [];
|
|
235
|
+
for (const line of lines) {
|
|
236
|
+
let parsed;
|
|
237
|
+
try {
|
|
238
|
+
parsed = JSON.parse(line);
|
|
239
|
+
} catch {
|
|
240
|
+
throw new Error("artifact access ledger contains invalid JSON");
|
|
241
|
+
}
|
|
242
|
+
events.push(normalizeArtifactAccessEvent(parsed, events.at(-1) || null));
|
|
243
|
+
}
|
|
244
|
+
return events;
|
|
245
|
+
}
|
|
246
|
+
|
|
247
|
+
export function readArtifactAccessLedger(store, options = {}) {
|
|
248
|
+
if (!store?.dir || typeof store.list !== "function") {
|
|
249
|
+
throw new TypeError("artifact access ledger requires an ArtifactStore");
|
|
250
|
+
}
|
|
251
|
+
const runtimeFs = options.fs || fs;
|
|
252
|
+
const events = parseLedger(
|
|
253
|
+
readLedgerBytes(ledgerPath(store), runtimeFs, options.runtime),
|
|
254
|
+
);
|
|
255
|
+
return Object.freeze({
|
|
256
|
+
schema: ARTIFACT_ACCESS_LEDGER_SCHEMA,
|
|
257
|
+
eventCount: events.length,
|
|
258
|
+
headDigest: events.at(-1)?.eventDigest || null,
|
|
259
|
+
events: Object.freeze(events),
|
|
260
|
+
});
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
function normalizeAccessRequest(input = {}) {
|
|
264
|
+
const accessId = String(
|
|
265
|
+
input.accessId || `access_${randomUUID().replaceAll("-", "")}`,
|
|
266
|
+
);
|
|
267
|
+
const artifactId = String(input.artifactId || "");
|
|
268
|
+
const client = String(input.client || "");
|
|
269
|
+
const action = String(input.action || "");
|
|
270
|
+
if (
|
|
271
|
+
!ACCESS_ID_RE.test(accessId) ||
|
|
272
|
+
!ARTIFACT_ID_RE.test(artifactId) ||
|
|
273
|
+
!ARTIFACT_ACCESS_CLIENTS.includes(client) ||
|
|
274
|
+
!ARTIFACT_ACCESS_ACTIONS.includes(action)
|
|
275
|
+
) {
|
|
276
|
+
throw new TypeError("artifact content access request is invalid");
|
|
277
|
+
}
|
|
278
|
+
return Object.freeze({ accessId, artifactId, client, action });
|
|
279
|
+
}
|
|
280
|
+
|
|
281
|
+
function readCurrentArtifact(store, entries, artifactId, runtimeFs = fs) {
|
|
282
|
+
const matches = entries.filter((entry) => entry.id === artifactId);
|
|
283
|
+
if (matches.length !== 1) {
|
|
284
|
+
throw new Error(
|
|
285
|
+
matches.length === 0 ? "artifact not found" : "artifact id is ambiguous",
|
|
286
|
+
);
|
|
287
|
+
}
|
|
288
|
+
const entry = matches[0];
|
|
289
|
+
if (
|
|
290
|
+
!entry.file ||
|
|
291
|
+
entry.file !== path.basename(entry.file) ||
|
|
292
|
+
entry.file.includes("..")
|
|
293
|
+
) {
|
|
294
|
+
throw new Error("artifact index row has an unsafe stored filename");
|
|
295
|
+
}
|
|
296
|
+
const storedPath = store.storedPath(entry);
|
|
297
|
+
const filesRoot = path.resolve(store.dir, "files");
|
|
298
|
+
if (
|
|
299
|
+
path.resolve(storedPath) !== path.join(filesRoot, entry.file) ||
|
|
300
|
+
!path.resolve(storedPath).startsWith(`${filesRoot}${path.sep}`)
|
|
301
|
+
) {
|
|
302
|
+
throw new Error("artifact stored path escapes the managed files directory");
|
|
303
|
+
}
|
|
304
|
+
const storedStat = runtimeFs.lstatSync(storedPath, { bigint: true });
|
|
305
|
+
if (
|
|
306
|
+
storedStat.isSymbolicLink() ||
|
|
307
|
+
!storedStat.isFile() ||
|
|
308
|
+
Number(storedStat.nlink) !== 1 ||
|
|
309
|
+
Number(storedStat.size) !== Number(entry.size)
|
|
310
|
+
) {
|
|
311
|
+
throw new Error("artifact stored byte identity is invalid");
|
|
312
|
+
}
|
|
313
|
+
const integrity = store.verifyIntegrity(entry);
|
|
314
|
+
if (
|
|
315
|
+
integrity.ok !== true ||
|
|
316
|
+
integrity.actualSha256 !== entry.sha256 ||
|
|
317
|
+
!/^[a-f0-9]{64}$/u.test(String(entry.sha256 || "")) ||
|
|
318
|
+
(entry.recordDigest != null &&
|
|
319
|
+
!SHA256_RE.test(String(entry.recordDigest))) ||
|
|
320
|
+
(entry.sessionId != null && !ACCESS_ID_RE.test(String(entry.sessionId)))
|
|
321
|
+
) {
|
|
322
|
+
throw new Error("artifact current byte readback failed");
|
|
323
|
+
}
|
|
324
|
+
return Object.freeze({ entry, storedPath, integrity });
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
function appendEvent(filePath, event, expectedSize, runtimeFs) {
|
|
328
|
+
let descriptor = null;
|
|
329
|
+
try {
|
|
330
|
+
descriptor = runtimeFs.openSync(
|
|
331
|
+
filePath,
|
|
332
|
+
runtimeFs.constants.O_WRONLY |
|
|
333
|
+
runtimeFs.constants.O_APPEND |
|
|
334
|
+
runtimeFs.constants.O_CREAT |
|
|
335
|
+
Number(runtimeFs.constants.O_NOFOLLOW || 0),
|
|
336
|
+
0o600,
|
|
337
|
+
);
|
|
338
|
+
const opened = runtimeFs.fstatSync(descriptor, { bigint: true });
|
|
339
|
+
if (
|
|
340
|
+
!opened.isFile() ||
|
|
341
|
+
Number(opened.nlink) !== 1 ||
|
|
342
|
+
Number(opened.size) !== expectedSize
|
|
343
|
+
) {
|
|
344
|
+
throw new Error("artifact access ledger changed before append");
|
|
345
|
+
}
|
|
346
|
+
runtimeFs.writeFileSync(descriptor, `${JSON.stringify(event)}\n`, "utf8");
|
|
347
|
+
runtimeFs.fsyncSync(descriptor);
|
|
348
|
+
} finally {
|
|
349
|
+
if (descriptor !== null) runtimeFs.closeSync(descriptor);
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
export function authorizeArtifactContentAccess(
|
|
354
|
+
store,
|
|
355
|
+
input = {},
|
|
356
|
+
options = {},
|
|
357
|
+
) {
|
|
358
|
+
if (!store?.dir || typeof store.verifyIntegrity !== "function") {
|
|
359
|
+
throw new TypeError("artifact content access requires an ArtifactStore");
|
|
360
|
+
}
|
|
361
|
+
if (typeof store.withIndexSnapshot !== "function") {
|
|
362
|
+
throw new TypeError(
|
|
363
|
+
"artifact content access requires a locked ArtifactStore index snapshot",
|
|
364
|
+
);
|
|
365
|
+
}
|
|
366
|
+
const request = normalizeAccessRequest(input);
|
|
367
|
+
const runtimeFs = options.fs || fs;
|
|
368
|
+
const filePath = ledgerPath(store);
|
|
369
|
+
runtimeFs.mkdirSync(path.dirname(filePath), { recursive: true, mode: 0o700 });
|
|
370
|
+
return store.withIndexSnapshot((entries) =>
|
|
371
|
+
(options.withFileLock || withFileLock)(
|
|
372
|
+
filePath,
|
|
373
|
+
() => {
|
|
374
|
+
const ledgerBytes = readLedgerBytes(
|
|
375
|
+
filePath,
|
|
376
|
+
runtimeFs,
|
|
377
|
+
options.runtime,
|
|
378
|
+
);
|
|
379
|
+
const events = parseLedger(ledgerBytes);
|
|
380
|
+
const prior = events.find(
|
|
381
|
+
(event) => event.accessId === request.accessId,
|
|
382
|
+
);
|
|
383
|
+
if (prior) {
|
|
384
|
+
if (
|
|
385
|
+
prior.artifactId !== request.artifactId ||
|
|
386
|
+
prior.client !== request.client ||
|
|
387
|
+
prior.action !== request.action
|
|
388
|
+
) {
|
|
389
|
+
throw new Error(
|
|
390
|
+
"artifact access id is already bound to other inputs",
|
|
391
|
+
);
|
|
392
|
+
}
|
|
393
|
+
const current = readCurrentArtifact(
|
|
394
|
+
store,
|
|
395
|
+
entries,
|
|
396
|
+
request.artifactId,
|
|
397
|
+
runtimeFs,
|
|
398
|
+
);
|
|
399
|
+
if (
|
|
400
|
+
prior.artifactSha256 !== `sha256:${current.entry.sha256}` ||
|
|
401
|
+
prior.recordDigest !== (current.entry.recordDigest || null) ||
|
|
402
|
+
prior.artifactSessionId !== (current.entry.sessionId || null)
|
|
403
|
+
) {
|
|
404
|
+
throw new Error("artifact changed after access authorization");
|
|
405
|
+
}
|
|
406
|
+
return Object.freeze({
|
|
407
|
+
access: prior,
|
|
408
|
+
recorded: false,
|
|
409
|
+
storedPath: current.storedPath,
|
|
410
|
+
integrity: Object.freeze({ ...current.integrity }),
|
|
411
|
+
});
|
|
412
|
+
}
|
|
413
|
+
const current = readCurrentArtifact(
|
|
414
|
+
store,
|
|
415
|
+
entries,
|
|
416
|
+
request.artifactId,
|
|
417
|
+
runtimeFs,
|
|
418
|
+
);
|
|
419
|
+
const previous = events.at(-1) || null;
|
|
420
|
+
const material = {
|
|
421
|
+
schema: ARTIFACT_ACCESS_EVENT_SCHEMA,
|
|
422
|
+
sequence: (previous?.sequence || 0) + 1,
|
|
423
|
+
previousEventDigest: previous?.eventDigest || null,
|
|
424
|
+
accessId: request.accessId,
|
|
425
|
+
artifactId: request.artifactId,
|
|
426
|
+
artifactSha256: `sha256:${current.entry.sha256}`,
|
|
427
|
+
recordDigest: current.entry.recordDigest || null,
|
|
428
|
+
artifactSessionId: current.entry.sessionId || null,
|
|
429
|
+
client: request.client,
|
|
430
|
+
action: request.action,
|
|
431
|
+
authorizedAt: new Date(
|
|
432
|
+
typeof options.now === "function" ? options.now() : Date.now(),
|
|
433
|
+
).toISOString(),
|
|
434
|
+
authorization: "current-artifact-index-and-byte-readback",
|
|
435
|
+
};
|
|
436
|
+
const candidateEvent = {
|
|
437
|
+
...material,
|
|
438
|
+
eventDigest: digest(
|
|
439
|
+
"chainlesschain.artifact.content-access-event.v1\0",
|
|
440
|
+
material,
|
|
441
|
+
),
|
|
442
|
+
};
|
|
443
|
+
const event = normalizeArtifactAccessEvent(candidateEvent, previous);
|
|
444
|
+
appendEvent(filePath, event, ledgerBytes.length, runtimeFs);
|
|
445
|
+
return Object.freeze({
|
|
446
|
+
access: event,
|
|
447
|
+
recorded: true,
|
|
448
|
+
storedPath: current.storedPath,
|
|
449
|
+
integrity: Object.freeze({ ...current.integrity }),
|
|
450
|
+
});
|
|
451
|
+
},
|
|
452
|
+
{
|
|
453
|
+
failIfUnavailable: true,
|
|
454
|
+
timeoutMs: 30_000,
|
|
455
|
+
retryMs: 1,
|
|
456
|
+
maxRetryMs: 8,
|
|
457
|
+
retryJitterMs: 4,
|
|
458
|
+
},
|
|
459
|
+
),
|
|
460
|
+
);
|
|
461
|
+
}
|