chainlesschain 0.166.85 → 0.166.87
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 +8 -0
- package/package.json +3 -2
- package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
- package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-Cgo7OFpi.js → AIOps-DdQjhXg9.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-lUMnE2Ln.js → ActionButton-CuwA3hPA.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CsvKzTot.js → Analytics-DTVXJSAC.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-C2z1h1Pv.js → AppLayout-j06JBfEQ.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-C5Q0ZhNb.js → Artifacts-BFAK8LEW.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DXnfPKsI.js → Audit-BiGxRpOf.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DjbGcLm3.js → BackgroundAgents-B6uHaPkZ.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-9bVrKvXE.js → Backup-BNAesB8q.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-bx5ri7BD.js → BaseInput-BOuetfCd.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CCFmeUMY.js → Chat-cVzo3m_D.js} +4 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DZ2Ys-CI.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-ei7MQXe9.js → Checkbox-BaCTS-H8.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CXgBJ9iu.js → Codegen-CBE8NiYh.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DMkMsizS.js → Col-C9wLiUTN.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CvpB_UFR.js → Community-jdClBSIp.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-gay5hbFX.js → Compact-BIjauIlM.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-dG8JQyRL.js → Compliance-CZ-AoaNj.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BA3xfaTW.js → Cowork-BbTKtDS4.js} +4 -4
- package/src/assets/web-panel/assets/{Cron-D1_HsxyY.js → Cron-ImKdtwNi.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DZqoqoy5.js → Crosschain-B54GSHf5.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Cwnr8hCO.js → DID-BQt5WUzs.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CxevypiI.js → Dashboard--gNOffif.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-Bs7-yWgr.js → Dropdown-342A5YJw.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CcEs2Puo.js → EmailListRenderer-CtIZ3ZHd.js} +1 -1
- package/src/assets/web-panel/assets/{EvolutionSettings-C9ipBzAQ.js → EvolutionSettings-CyZPoTio.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-Dt6--mGn.js → FamilyGuardDashboard-B1MCkFEu.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CD0JN5FF.js → Federation-DZWoERUy.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-POypz3Xv.js → FormItemContext-Onmb0mp3.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-Dv9f-0F5.js → GenericCardRenderer-u1qhNNc7.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BbTh5Atv.js → Git-BEowR1C5.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-C71dgoir.js → Governance-BiYd3AAg.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DwSHxbJB.js → Inference-CsC4Vjh5.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-Cx1oQYQZ.js → KnowledgeGraph-BXA7ikPM.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BAJWe00o.js → Logs-C9WoplQm.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-CD8a7lHT.js → MarkdownRenderer-DecEsXsy.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-fwuY7P7Y.js → Marketplace-CCoJBfRL.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Di4693uc.js → McpTools-CzaOWBoA.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-Cz3L-GUB.js → Memory-BPwh7u1-.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BuXKIkSb.js → MobileBridge-Bk47v6z2.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-D-CY--Sb.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-LnACMFd0.js → Mtc-ExVyAGlW.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-BZy7yE0t.js → MtcAudit-Rrw10csE.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-BFG5iVrE.js → Multisig-8TxxwxZZ.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DgXi04vJ.js → NLProgramming-CNckjtnY.js} +1 -1
- package/src/assets/web-panel/assets/Notes-C4j6cZTu.js +7 -0
- package/src/assets/web-panel/assets/{NotificationSettings-AixrsHw5.js → NotificationSettings-CchU8rWd.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-ZJBoyJ16.js +1 -0
- package/src/assets/web-panel/assets/{Organization-UNCBC3ux.js → Organization-F0Je7EdZ.js} +2 -2
- package/src/assets/web-panel/assets/{Overflow-Drh8oAFT.js → Overflow-B2k-JA3u.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-bc2q0xEB.js → P2P-yui9Xxe6.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BGwKxcDM.js → PdhVaultBrowser-CegM7hT6.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DOnUxUzu.js → Permissions-CSFL0uvh.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-B3qPZkrr.js → PersonalDataHub-BrMcd-vr.js} +6 -6
- package/src/assets/web-panel/assets/{Pipeline-B6hfa4-i.js → Pipeline-fzrliaVP.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-Bun3c35F.js → Privacy-BrkUjJ_g.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CCObdzk9.js → ProjectInit-CF_QCsW9.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-C6lKrSXY.js → ProjectSettings-Duwrd8aC.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DYcta7MZ.js → Projects-B0dLVXzU.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-BtykxKCe.js → Providers-Db8iS1xQ.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-D9CDyroY.js → QrScannerModal-CHbNzEQs.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CJauCxXx.js → QuickAsk-CIESMfeR.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BbiYZofR.js → Recommend-BTMbCMtR.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-BymCrgFW.js → RemoteSession-DavyK6J-.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-BWrGpMpF.js → Reputation-BFKtMelA.js} +1 -1
- package/src/assets/web-panel/assets/{Row-9pOYIhiW.js → Row-BI9AZUvw.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-D3bQJ4qB.js → RssFeed-1nwXt8NY.js} +2 -2
- package/src/assets/web-panel/assets/{Search-BpKOOFE0.js → Search-Ck8PDYbk.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DC6C0NSz.js → Security-BgDqpCbq.js} +4 -4
- package/src/assets/web-panel/assets/{Services-Lkf1046J.js → Services-DF8anXm4.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-eCQ_SsDb.js → Skeleton-CoHyY4ZX.js} +1 -1
- package/src/assets/web-panel/assets/Skills-dOaD4Ztl.js +1 -0
- package/src/assets/web-panel/assets/{Sla-BQbKy8XJ.js → Sla-XuOawvQY.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-DHsRx-XA.js → SpeechSettings-DFJ7nAiu.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CC281X_J.js → SyncSettings-B2xYj-Pi.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-D_SzNTLi.js +1 -0
- package/src/assets/web-panel/assets/{Templates-Cr2vC0tg.js → Templates-BXFmKh7W.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DeYO5pIG.js → Tenant-CRg2AXbd.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DBNgbfPr.js → Terminal-DQgxnuep.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-MoxgaF3x.js → TimelineRenderer-BrPQhQVc.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CT2QfAN7.js → Tokens-RByy4USy.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DPUnomxs.js → Trigger-pE3y8u73.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DnufuaTx.js → Trust-DhID9C33.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CJ6iOdPs.js → UkeySign-CPxEPNBH.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-xBWkH8bL.js → VideoEditing-ZD8DUxeQ.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-WYV-4hMR.js → Wallet-Br9JREEd.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DJ5Mhd9L.js → WebAuthn-Cn-YacJg.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-B__UdOjN.js → WorkflowEditor-BTu0oqMp.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CLlmBVhp.js → chat-CEjws_QF.js} +1 -1
- package/src/assets/web-panel/assets/{colors-B8zoPQYm.js → colors-Bv2RNebW.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CvJ-dUsH.js → compact-item-gSc3EofP.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-LAtMm8SC.js → createContext-kyFDaqWC.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Dcc8pILJ.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CLYvg42H.js → hasIn-BuMWcjhx.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cv7vwX3z.js → index--nrQmk8X.js} +1 -1
- package/src/assets/web-panel/assets/index-05NB95_V.js +1 -0
- package/src/assets/web-panel/assets/{index-BUVrYDNV.js → index-42PTBO7a.js} +1 -1
- package/src/assets/web-panel/assets/{index-B6zlMzZz.js → index-BCS5j4SQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-oX216HRH.js → index-BSSIJ-rv.js} +1 -1
- package/src/assets/web-panel/assets/{index-B02qgCao.js → index-BUOCcXou.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_RPWP_5.js → index-Bg5jis0q.js} +1 -1
- package/src/assets/web-panel/assets/{index-DX99e8Xy.js → index-C-LPpwe9.js} +1 -1
- package/src/assets/web-panel/assets/{index-DDm9m4vC.js → index-C56Drzol.js} +1 -1
- package/src/assets/web-panel/assets/{index-DHq2Of6z.js → index-C7Jo20JB.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_dq-6_3.js → index-C9U9SjcT.js} +1 -1
- package/src/assets/web-panel/assets/{index-D0Z13xcZ.js → index-CF3TjDNz.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1hHp9_M.js → index-CMS3Mk93.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfOOACfd.js → index-CNt2X-aK.js} +1 -1
- package/src/assets/web-panel/assets/index-Cb6q8wlI.js +1 -0
- package/src/assets/web-panel/assets/{index-DYW4Z3y-.js → index-Cj4RA7oA.js} +1 -1
- package/src/assets/web-panel/assets/{index-C2KFnUsv.js → index-CkfvP2dN.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkcQJvsz.js → index-Cm8K8Evn.js} +1 -1
- package/src/assets/web-panel/assets/{index-CVDcRQuX.js → index-CvSuM_CB.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCzyJ1xS.js → index-Czo_hw97.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cpda0SAz.js → index-D3m3EXbL.js} +1 -1
- package/src/assets/web-panel/assets/{index-iJ6s24bA.js → index-DR561Sqc.js} +1 -1
- package/src/assets/web-panel/assets/{index-Di9vKira.js → index-DRa4R7vv.js} +1 -1
- package/src/assets/web-panel/assets/{index-09YzRb6Z.js → index-DYKSuD02.js} +3 -3
- package/src/assets/web-panel/assets/{index-C5v7cEtm.js → index-DYcPWVN9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGrQ2uEl.js → index-DbVUA5l3.js} +1 -1
- package/src/assets/web-panel/assets/{index-4rr5jFOL.js → index-DbkyTMj-.js} +1 -1
- package/src/assets/web-panel/assets/{index-GR3omApz.js → index-Dej4AGOz.js} +1 -1
- package/src/assets/web-panel/assets/{index-f1ptYBMr.js → index-EpxWlmip.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3uD5H7u.js → index-GpvWtvw3.js} +1 -1
- package/src/assets/web-panel/assets/{index-C6PETyel.js → index-NQ0c4hJ1.js} +1 -1
- package/src/assets/web-panel/assets/{index-CnLXLaEF.js → index-OjZUHz5z.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpeV-6y4.js → index-U-ytt84A.js} +1 -1
- package/src/assets/web-panel/assets/{index-DNQUdPam.js → index-_0bfDTDu.js} +1 -1
- package/src/assets/web-panel/assets/{index-BM2HqHdT.js → index-jmMAqBAm.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bu7-I2L4.js → index-nLemnBZ2.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUcE6rIa.js → index-q65c1kp8.js} +1 -1
- package/src/assets/web-panel/assets/{index-DX0aHEHL.js → index-sJ1F5Txa.js} +1 -1
- package/src/assets/web-panel/assets/{index-CxJ4NPVq.js → index-xfS_WO9b.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-QD-koeaw.js → initDefaultProps-B-wqiSu7.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CTItLW4-.js → motion-BK4_B4MD.js} +1 -1
- package/src/assets/web-panel/assets/{move-CGsdTOdN.js → move-BU2TgDDO.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-C1kW_CNU.js → mtc-parser-DA3E4_EX.js} +1 -1
- package/src/assets/web-panel/assets/{omit-B4PilaYy.js → omit-CaHz1e_q.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DBGUE12e.js → pickAttrs-NXZ19amf.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DZxCLjuo.js → placementArrow-BCXI9AHO.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CZjQiocN.js → responsiveObserve-DdlZ9tLk.js} +1 -1
- package/src/assets/web-panel/assets/{slide-Btz5hh2_.js → slide-DCZm_Vc4.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CfuxuLIt.js → statusUtils-CuEULKQr.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DdO0Obo_.js → styleChecker-Bo_UXG8x.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-CxftJ-Zs.js → useFlexGapSupport-B9PaTcFH.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-pVJChh6K.js → useFs-DwSTGdVR.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-wv7Q0uNE.js → usePersonalDataHub-FswSxnDC.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DVOVfAqC.js → vnode-EUztvYTj.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-B5tebZuT.js → zoom-D41MAgV-.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/eval.js +118 -18
- package/src/commands/memory.js +34 -15
- package/src/commands/sandbox.js +9 -0
- package/src/data/changelog.json +26 -0
- package/src/lib/causal-observability.js +6 -45
- package/src/lib/context-memory-kernel/durable-memory-port.js +54 -12
- package/src/lib/context-memory-kernel/index.js +1 -0
- package/src/lib/context-memory-kernel/memory-query-index.js +278 -0
- package/src/lib/context-memory-kernel/memory-service.js +60 -18
- package/src/lib/context-memory-kernel/runtime.js +5 -3
- package/src/lib/context-memory-kernel/segmented-memory-port.js +955 -0
- package/src/lib/eval/outcomes.js +20 -10
- package/src/lib/eval/runner.js +49 -5
- package/src/lib/eval/tasks.js +8 -3
- package/src/lib/eval/verify01-contracts.js +349 -0
- package/src/lib/eval/verify01-execution.js +605 -0
- package/src/lib/execution-support.js +58 -0
- package/src/lib/llm-pricing.js +98 -11
- package/src/lib/model-catalog-review.js +116 -0
- package/src/lib/model-context-catalog.js +23 -1
- package/src/lib/openai-responses.js +39 -0
- package/src/lib/packer/pack-update-applier.js +13 -8
- package/src/lib/permission-authority.js +146 -10
- package/src/lib/process-execution-broker/index.js +8 -0
- package/src/lib/process-execution-broker/linux-subreaper-process.js +24 -2
- package/src/lib/process-execution-broker/process-ownership-journal.js +168 -7
- package/src/lib/process-execution-broker/process-ownership-quarantine.js +56 -1
- package/src/lib/process-execution-broker/process-recovery-cgroup.js +272 -0
- package/src/lib/sandbox-egress-authority-monitor.js +62 -5
- package/src/lib/scoped-permission-store.js +104 -1
- package/src/lib/settings-authority-domain.cjs +37 -2
- package/src/lib/settings-loader.cjs +67 -34
- package/src/lib/settings-permission-authority.cjs +373 -0
- package/src/runtime/agent-core.js +88 -407
- package/src/runtime/headless-runner.js +8 -3
- package/src/runtime/headless-stream.js +10 -3
- package/src/runtime/permission-authority-host.js +151 -0
- package/src/runtime/provider-stream-state.js +379 -0
- package/src/assets/web-panel/assets/ChatBubbleRenderer-ClqsIfTT.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-Bk-lK4Um.js +0 -1
- package/src/assets/web-panel/assets/Notes-BAj0Kgg4.js +0 -7
- package/src/assets/web-panel/assets/OrderTableRenderer-Dwqk3FlG.js +0 -1
- package/src/assets/web-panel/assets/Skills-BvDSQ3-d.js +0 -1
- package/src/assets/web-panel/assets/Tasks-CHHSWHom.js +0 -1
- package/src/assets/web-panel/assets/devWarning-DgZ4fdGI.js +0 -1
- package/src/assets/web-panel/assets/index-jLEdASOV.js +0 -1
- package/src/assets/web-panel/assets/index-jesGo4rl.js +0 -1
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import { randomUUID } from "node:crypto";
|
|
2
|
+
|
|
1
3
|
/**
|
|
2
4
|
* Recheck the live shell authority while a Docker egress session exists. This
|
|
3
5
|
* uses bounded polling as a fallback. Trusted policy owners also call revoke()
|
|
@@ -9,19 +11,35 @@ export function createDockerEgressAuthorityMonitor({
|
|
|
9
11
|
abortProxy,
|
|
10
12
|
intervalMs = 500,
|
|
11
13
|
checkTimeoutMs = 5_000,
|
|
14
|
+
sessionId = null,
|
|
15
|
+
policyVersion = null,
|
|
12
16
|
}) {
|
|
13
17
|
if (typeof revalidate !== "function" || typeof abortProxy !== "function")
|
|
14
18
|
throw new TypeError("Docker egress authority callbacks are required");
|
|
19
|
+
if (
|
|
20
|
+
(sessionId !== null && typeof sessionId !== "string") ||
|
|
21
|
+
(policyVersion !== null && typeof policyVersion !== "string")
|
|
22
|
+
)
|
|
23
|
+
throw new TypeError(
|
|
24
|
+
"Docker egress acknowledgement identities must be strings",
|
|
25
|
+
);
|
|
26
|
+
const stopIdentity = Object.freeze({
|
|
27
|
+
receiverId: randomUUID(),
|
|
28
|
+
sessionId,
|
|
29
|
+
policyVersion,
|
|
30
|
+
});
|
|
15
31
|
let revoked = null;
|
|
16
32
|
let session = null;
|
|
17
33
|
let timer = null;
|
|
18
34
|
let checking = null;
|
|
19
35
|
let stopped = false;
|
|
20
36
|
const cleanupTasks = new Set();
|
|
37
|
+
let proxyStopped = false;
|
|
38
|
+
let sessionStopped = false;
|
|
21
39
|
|
|
22
|
-
function trackCleanup(operation) {
|
|
40
|
+
function trackCleanup(operation, onStopped) {
|
|
23
41
|
try {
|
|
24
|
-
const task = Promise.resolve(operation());
|
|
42
|
+
const task = Promise.resolve(operation()).then(onStopped);
|
|
25
43
|
cleanupTasks.add(task);
|
|
26
44
|
void task.catch((error) => {
|
|
27
45
|
if (revoked && !revoked.cleanupError) revoked.cleanupError = error;
|
|
@@ -41,8 +59,18 @@ export function createDockerEgressAuthorityMonitor({
|
|
|
41
59
|
if (!revoked.code) revoked.code = "CC_DOCKER_EGRESS_AUTHORITY_CHANGED";
|
|
42
60
|
clearTimeout(timer);
|
|
43
61
|
// Latch first, then cut the broker before closing the target containers.
|
|
44
|
-
trackCleanup(abortProxy)
|
|
45
|
-
|
|
62
|
+
trackCleanup(abortProxy, () => {
|
|
63
|
+
proxyStopped = true;
|
|
64
|
+
});
|
|
65
|
+
if (session) {
|
|
66
|
+
const closing = session;
|
|
67
|
+
trackCleanup(
|
|
68
|
+
() => closing.close(),
|
|
69
|
+
() => {
|
|
70
|
+
if (session === closing) sessionStopped = true;
|
|
71
|
+
},
|
|
72
|
+
);
|
|
73
|
+
}
|
|
46
74
|
return revoked;
|
|
47
75
|
}
|
|
48
76
|
|
|
@@ -51,9 +79,17 @@ export function createDockerEgressAuthorityMonitor({
|
|
|
51
79
|
}
|
|
52
80
|
|
|
53
81
|
function attachSession(value) {
|
|
82
|
+
if (session)
|
|
83
|
+
throw new TypeError("Docker egress monitor session is already attached");
|
|
54
84
|
session = value;
|
|
85
|
+
sessionStopped = false;
|
|
55
86
|
if (revoked) {
|
|
56
|
-
trackCleanup(
|
|
87
|
+
trackCleanup(
|
|
88
|
+
() => value.close(),
|
|
89
|
+
() => {
|
|
90
|
+
if (session === value) sessionStopped = true;
|
|
91
|
+
},
|
|
92
|
+
);
|
|
57
93
|
throw revoked;
|
|
58
94
|
}
|
|
59
95
|
}
|
|
@@ -112,6 +148,7 @@ export function createDockerEgressAuthorityMonitor({
|
|
|
112
148
|
}
|
|
113
149
|
|
|
114
150
|
return {
|
|
151
|
+
stopIdentity,
|
|
115
152
|
get revocationError() {
|
|
116
153
|
return revoked;
|
|
117
154
|
},
|
|
@@ -134,5 +171,25 @@ export function createDockerEgressAuthorityMonitor({
|
|
|
134
171
|
async awaitCleanup() {
|
|
135
172
|
await Promise.allSettled([...cleanupTasks]);
|
|
136
173
|
},
|
|
174
|
+
getStopAcknowledgement() {
|
|
175
|
+
// A synchronous writer commit is never a stop ACK. Only the receiving
|
|
176
|
+
// runtime, after stopping checks and awaiting both teardown operations,
|
|
177
|
+
// can report this acknowledgement. Cleanup failures never mint one.
|
|
178
|
+
if (
|
|
179
|
+
!stopped ||
|
|
180
|
+
!revoked ||
|
|
181
|
+
revoked.cleanupError ||
|
|
182
|
+
cleanupTasks.size ||
|
|
183
|
+
!proxyStopped ||
|
|
184
|
+
!sessionStopped
|
|
185
|
+
)
|
|
186
|
+
return null;
|
|
187
|
+
return Object.freeze({
|
|
188
|
+
schema: "chainlesschain.egress-stop-ack/v1",
|
|
189
|
+
...stopIdentity,
|
|
190
|
+
proxyStopped: true,
|
|
191
|
+
sessionStopped: true,
|
|
192
|
+
});
|
|
193
|
+
},
|
|
137
194
|
};
|
|
138
195
|
}
|
|
@@ -11,6 +11,7 @@ import path from "node:path";
|
|
|
11
11
|
import { createHash, randomUUID } from "node:crypto";
|
|
12
12
|
import projectRoot from "./project-root.cjs";
|
|
13
13
|
import permissionRules from "./permission-rules.cjs";
|
|
14
|
+
import persistentSettings from "./settings-permission-authority.cjs";
|
|
14
15
|
import { getMachineSecurityAnchorDir } from "./paths.js";
|
|
15
16
|
import {
|
|
16
17
|
readSecurityStore,
|
|
@@ -27,6 +28,7 @@ const STORE_LABEL = "scoped permission";
|
|
|
27
28
|
const RULE_ID_PATTERN = /^spr_[0-9a-f]{32}$/;
|
|
28
29
|
const DECISIONS = new Set(["allow", "ask", "deny"]);
|
|
29
30
|
const DEFAULT_MAX_RECORDS = 1000;
|
|
31
|
+
const persistentBindings = new WeakMap();
|
|
30
32
|
|
|
31
33
|
// Official mutations revoke every provider in this module instance. Path
|
|
32
34
|
// matching alone misses aliases and files that did not exist at admission.
|
|
@@ -216,6 +218,7 @@ export class ScopedPermissionStore {
|
|
|
216
218
|
maxRecords = DEFAULT_MAX_RECORDS,
|
|
217
219
|
lock = undefined,
|
|
218
220
|
randomId = () => `spr_${randomUUID().replaceAll("-", "")}`,
|
|
221
|
+
settingsAuthority = null,
|
|
219
222
|
} = {}) {
|
|
220
223
|
this.workspace = workspaceBinding(cwd);
|
|
221
224
|
this.filePath = path.resolve(
|
|
@@ -242,6 +245,17 @@ export class ScopedPermissionStore {
|
|
|
242
245
|
);
|
|
243
246
|
this._lock = lock;
|
|
244
247
|
this._randomId = randomId;
|
|
248
|
+
if (settingsAuthority) {
|
|
249
|
+
const bound =
|
|
250
|
+
persistentSettings.settingsPermissionAuthorityOptions(
|
|
251
|
+
settingsAuthority,
|
|
252
|
+
);
|
|
253
|
+
if (path.resolve(cwd) !== bound.cwd || this.filePath !== bound.scopedFile)
|
|
254
|
+
throw new TypeError(
|
|
255
|
+
"persistent scoped permission authority options mismatch",
|
|
256
|
+
);
|
|
257
|
+
persistentBindings.set(this, settingsAuthority);
|
|
258
|
+
}
|
|
245
259
|
}
|
|
246
260
|
|
|
247
261
|
_capture() {
|
|
@@ -253,7 +267,22 @@ export class ScopedPermissionStore {
|
|
|
253
267
|
randomId: this._randomId.bind(this),
|
|
254
268
|
maxRecords: this.maxRecords,
|
|
255
269
|
lock: this._lock || withFileLock,
|
|
270
|
+
settingsAuthority: persistentBindings.get(this) || null,
|
|
256
271
|
});
|
|
272
|
+
if (captured.settingsAuthority) {
|
|
273
|
+
const bound = persistentSettings.settingsPermissionAuthorityOptions(
|
|
274
|
+
captured.settingsAuthority,
|
|
275
|
+
);
|
|
276
|
+
const workspace = workspaceBinding(bound.cwd);
|
|
277
|
+
if (
|
|
278
|
+
captured.filePath !== bound.scopedFile ||
|
|
279
|
+
captured.workspace.id !== workspace.id ||
|
|
280
|
+
captured.workspace.root !== workspace.root
|
|
281
|
+
)
|
|
282
|
+
throw new TypeError(
|
|
283
|
+
"persistent scoped permission authority options mismatch",
|
|
284
|
+
);
|
|
285
|
+
}
|
|
257
286
|
if (getScopedPermissionRevision() !== revision)
|
|
258
287
|
throw scopedPermissionError(
|
|
259
288
|
"CC_SCOPED_PERMISSION_AUTHORITY_CHANGED",
|
|
@@ -264,13 +293,54 @@ export class ScopedPermissionStore {
|
|
|
264
293
|
|
|
265
294
|
_read(captured = this._capture()) {
|
|
266
295
|
getScopedPermissionRevision();
|
|
267
|
-
const stored =
|
|
296
|
+
const stored = captured.settingsAuthority
|
|
297
|
+
? persistentSettings.readSettingsPermissionAuthority(
|
|
298
|
+
captured.settingsAuthority,
|
|
299
|
+
).scopedObservation.settings || {}
|
|
300
|
+
: readSecurityStore(captured.filePath, STORE_LABEL);
|
|
268
301
|
if (Object.keys(stored).length === 0) return emptyState(captured.workspace);
|
|
269
302
|
return validateState(stored, captured.workspace);
|
|
270
303
|
}
|
|
271
304
|
|
|
272
305
|
_mutate(captured, update) {
|
|
273
306
|
getScopedPermissionRevision(); // Reject observer reentry before the lock.
|
|
307
|
+
if (captured.settingsAuthority) {
|
|
308
|
+
let started = false;
|
|
309
|
+
try {
|
|
310
|
+
persistentSettings.mutateSettingsPermissionSource(
|
|
311
|
+
captured.settingsAuthority,
|
|
312
|
+
{
|
|
313
|
+
logicalPath: captured.filePath,
|
|
314
|
+
prepare(draft) {
|
|
315
|
+
if (Object.keys(draft).length === 0)
|
|
316
|
+
Object.assign(draft, emptyState(captured.workspace));
|
|
317
|
+
validateState(draft, captured.workspace);
|
|
318
|
+
if (update(draft) === false) return false;
|
|
319
|
+
validateState(draft, captured.workspace);
|
|
320
|
+
},
|
|
321
|
+
revokeLocal() {
|
|
322
|
+
started = true;
|
|
323
|
+
beginMutation();
|
|
324
|
+
},
|
|
325
|
+
replace(file, draft) {
|
|
326
|
+
validateState(draft, captured.workspace);
|
|
327
|
+
writeSecurityStore(file, STORE_LABEL, draft);
|
|
328
|
+
},
|
|
329
|
+
},
|
|
330
|
+
);
|
|
331
|
+
if (started) finishMutation("ready");
|
|
332
|
+
} catch (error) {
|
|
333
|
+
if (started)
|
|
334
|
+
finishMutation(
|
|
335
|
+
error.commitState === "unknown" ||
|
|
336
|
+
permissionRevision.state === "invalid"
|
|
337
|
+
? "invalid"
|
|
338
|
+
: "ready",
|
|
339
|
+
);
|
|
340
|
+
throw error;
|
|
341
|
+
}
|
|
342
|
+
return;
|
|
343
|
+
}
|
|
274
344
|
fs.mkdirSync(path.dirname(captured.filePath), {
|
|
275
345
|
recursive: true,
|
|
276
346
|
mode: 0o700,
|
|
@@ -487,3 +557,36 @@ export class ScopedPermissionStore {
|
|
|
487
557
|
return projectRecord(revoked, now, captured.filePath);
|
|
488
558
|
}
|
|
489
559
|
}
|
|
560
|
+
|
|
561
|
+
// Only the production provider uses a scoped observation already validated in
|
|
562
|
+
// the same durable read as settings. A store cannot substitute another source.
|
|
563
|
+
export function projectScopedPermissionObservation(
|
|
564
|
+
store,
|
|
565
|
+
binding,
|
|
566
|
+
observation,
|
|
567
|
+
) {
|
|
568
|
+
if (persistentBindings.get(store) !== binding)
|
|
569
|
+
throw new TypeError(
|
|
570
|
+
"scoped store must use the provider's persistent authority",
|
|
571
|
+
);
|
|
572
|
+
const captured = ScopedPermissionStore.prototype._capture.call(store);
|
|
573
|
+
if (observation.logicalPath !== captured.filePath)
|
|
574
|
+
throw new TypeError("persistent scoped permission source mismatch");
|
|
575
|
+
const stored = observation.settings || {};
|
|
576
|
+
const state =
|
|
577
|
+
Object.keys(stored).length === 0
|
|
578
|
+
? emptyState(captured.workspace)
|
|
579
|
+
: validateState(stored, captured.workspace);
|
|
580
|
+
const now = captured.now();
|
|
581
|
+
return Object.freeze({
|
|
582
|
+
schema: state.schema,
|
|
583
|
+
schemaVersion: state.schemaVersion,
|
|
584
|
+
generation: state.generation,
|
|
585
|
+
updatedAt: state.updatedAt,
|
|
586
|
+
workspace: structuredClone(state.workspace),
|
|
587
|
+
file: captured.filePath,
|
|
588
|
+
rules: state.rules.map((record) =>
|
|
589
|
+
projectRecord(record, now, captured.filePath),
|
|
590
|
+
),
|
|
591
|
+
});
|
|
592
|
+
}
|
|
@@ -727,12 +727,24 @@ function transitionSettingsAuthority(
|
|
|
727
727
|
replace,
|
|
728
728
|
revokeLocal,
|
|
729
729
|
transactionId = randomUUID(),
|
|
730
|
+
expectedSnapshot = null,
|
|
731
|
+
localRevision = null,
|
|
730
732
|
} = {},
|
|
731
733
|
) {
|
|
732
734
|
const state = stateFor(handle);
|
|
733
735
|
// Snapshot caller data before any callback. These are trusted host APIs, but
|
|
734
736
|
// mutable intents must not turn an already-validated no-op into a mutation.
|
|
735
737
|
intent = plainCopy(intent);
|
|
738
|
+
if (expectedSnapshot !== null) {
|
|
739
|
+
expectedSnapshot = plainCopy(expectedSnapshot);
|
|
740
|
+
shape(expectedSnapshot, [
|
|
741
|
+
"domainId",
|
|
742
|
+
"epoch",
|
|
743
|
+
"generation",
|
|
744
|
+
"digest",
|
|
745
|
+
"state",
|
|
746
|
+
]);
|
|
747
|
+
}
|
|
736
748
|
requireSynchronous(observeContexts);
|
|
737
749
|
requireSynchronous(revokeLocal);
|
|
738
750
|
if (intent.kind === "write") requireSynchronous(replace);
|
|
@@ -743,7 +755,15 @@ function transitionSettingsAuthority(
|
|
|
743
755
|
try {
|
|
744
756
|
return locked(state, () => {
|
|
745
757
|
const perform = () => {
|
|
746
|
-
const current = readSettingsAuthority(handle, {
|
|
758
|
+
const current = readSettingsAuthority(handle, {
|
|
759
|
+
observeContexts,
|
|
760
|
+
localRevision,
|
|
761
|
+
});
|
|
762
|
+
if (
|
|
763
|
+
expectedSnapshot !== null &&
|
|
764
|
+
!sameData(current.snapshot, expectedSnapshot)
|
|
765
|
+
)
|
|
766
|
+
refuse("CC_SETTINGS_AUTHORITY_CONFLICT");
|
|
747
767
|
if (
|
|
748
768
|
intent.kind === "register" &&
|
|
749
769
|
current.ledger.contexts.some(
|
|
@@ -841,7 +861,15 @@ function transitionSettingsAuthority(
|
|
|
841
861
|
};
|
|
842
862
|
};
|
|
843
863
|
if (intent.kind !== "write") return perform();
|
|
844
|
-
const admission = readSettingsAuthority(handle, {
|
|
864
|
+
const admission = readSettingsAuthority(handle, {
|
|
865
|
+
observeContexts,
|
|
866
|
+
localRevision,
|
|
867
|
+
});
|
|
868
|
+
if (
|
|
869
|
+
expectedSnapshot !== null &&
|
|
870
|
+
!sameData(admission.snapshot, expectedSnapshot)
|
|
871
|
+
)
|
|
872
|
+
refuse("CC_SETTINGS_AUTHORITY_CONFLICT");
|
|
845
873
|
records.prepareTransition({
|
|
846
874
|
ledger: admission.ledger,
|
|
847
875
|
transactionId,
|
|
@@ -878,6 +906,12 @@ function transitionSettingsAuthority(
|
|
|
878
906
|
}
|
|
879
907
|
}
|
|
880
908
|
|
|
909
|
+
// Official read/modify/write adapters take an initial snapshot under the same
|
|
910
|
+
// authority lock. The later transition MUST still compare expectedSnapshot.
|
|
911
|
+
function readSettingsAuthorityForUpdate(handle, options) {
|
|
912
|
+
return locked(stateFor(handle), () => readSettingsAuthority(handle, options));
|
|
913
|
+
}
|
|
914
|
+
|
|
881
915
|
function recoverSettingsAuthority(
|
|
882
916
|
handle,
|
|
883
917
|
{ transactionId, observeContexts, outcome } = {},
|
|
@@ -954,6 +988,7 @@ function recoverSettingsAuthority(
|
|
|
954
988
|
}
|
|
955
989
|
|
|
956
990
|
module.exports = {
|
|
991
|
+
readSettingsAuthorityForUpdate,
|
|
957
992
|
DOMAIN_SCHEMA,
|
|
958
993
|
initializeSettingsAuthorityDomain,
|
|
959
994
|
pinSettingsAuthorityDomain,
|
|
@@ -146,9 +146,11 @@ function accrete(target, sources, arr, file, kind) {
|
|
|
146
146
|
}
|
|
147
147
|
|
|
148
148
|
/** The ordered list of candidate settings files for a cwd. */
|
|
149
|
-
function settingsPaths(cwd, explicitFile) {
|
|
149
|
+
function settingsPaths(cwd, explicitFile, userSettingsFile = null) {
|
|
150
150
|
const home = _deps.homedir();
|
|
151
|
-
const list = [
|
|
151
|
+
const list = [
|
|
152
|
+
userSettingsFile || path.join(home, ".claude", "settings.json"),
|
|
153
|
+
];
|
|
152
154
|
// When run from a subdirectory, the project-root `.claude` sits BELOW cwd's
|
|
153
155
|
// own (closest wins) but ABOVE the user layer — so its rules apply yet a
|
|
154
156
|
// cwd-local settings file still overrides them. Null when cwd IS the root.
|
|
@@ -171,7 +173,7 @@ function settingsPaths(cwd, explicitFile) {
|
|
|
171
173
|
function inspectSettingsSources(opts = {}) {
|
|
172
174
|
const cwd = path.resolve(opts.cwd || process.cwd());
|
|
173
175
|
const candidates = [
|
|
174
|
-
...settingsPaths(cwd, opts.settingsFile),
|
|
176
|
+
...settingsPaths(cwd, opts.settingsFile, opts.userSettingsFile),
|
|
175
177
|
managedSettingsPath(opts),
|
|
176
178
|
];
|
|
177
179
|
const observation = Object.freeze({
|
|
@@ -390,7 +392,38 @@ function scopeFile(cwd, scope) {
|
|
|
390
392
|
* @returns {{ file:string, added:boolean }} added=false → already present.
|
|
391
393
|
* @throws if the target file exists but is malformed JSON (refuse to clobber).
|
|
392
394
|
*/
|
|
393
|
-
function
|
|
395
|
+
function appendPermissionRule(data, file, kind, rule) {
|
|
396
|
+
if (
|
|
397
|
+
Object.hasOwn(data, "permissions") &&
|
|
398
|
+
(!data.permissions ||
|
|
399
|
+
typeof data.permissions !== "object" ||
|
|
400
|
+
Array.isArray(data.permissions))
|
|
401
|
+
)
|
|
402
|
+
throw new TypeError(`refusing to overwrite malformed permissions: ${file}`);
|
|
403
|
+
data.permissions ??= {};
|
|
404
|
+
for (const decision of KINDS) {
|
|
405
|
+
if (
|
|
406
|
+
Object.hasOwn(data.permissions, decision) &&
|
|
407
|
+
(!Array.isArray(data.permissions[decision]) ||
|
|
408
|
+
data.permissions[decision].some((entry) => typeof entry !== "string"))
|
|
409
|
+
)
|
|
410
|
+
throw new TypeError(
|
|
411
|
+
`refusing to overwrite malformed permission rules: ${file}`,
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
data.permissions[kind] ??= [];
|
|
415
|
+
if (data.permissions[kind].includes(rule)) return false;
|
|
416
|
+
data.permissions[kind].push(rule);
|
|
417
|
+
return true;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
function addRule({
|
|
421
|
+
cwd = process.cwd(),
|
|
422
|
+
kind,
|
|
423
|
+
rule,
|
|
424
|
+
scope = "project",
|
|
425
|
+
settingsAuthority = null,
|
|
426
|
+
} = {}) {
|
|
394
427
|
const entryCwd = process.cwd();
|
|
395
428
|
const absoluteCwd = path.resolve(entryCwd, cwd);
|
|
396
429
|
// Check before lock acquisition: notification callbacks may reenter here.
|
|
@@ -404,11 +437,38 @@ function addRule({ cwd = process.cwd(), kind, rule, scope = "project" } = {}) {
|
|
|
404
437
|
if (!["project", "local", "user"].includes(scope)) {
|
|
405
438
|
throw new TypeError("scope must be project | local | user");
|
|
406
439
|
}
|
|
407
|
-
|
|
408
|
-
|
|
440
|
+
let file = path.resolve(entryCwd, scopeFile(absoluteCwd, scope));
|
|
441
|
+
if (settingsAuthority !== null) {
|
|
442
|
+
const authority = require("./settings-permission-authority.cjs");
|
|
443
|
+
const bound =
|
|
444
|
+
authority.settingsPermissionAuthorityOptions(settingsAuthority);
|
|
445
|
+
if (absoluteCwd !== bound.cwd)
|
|
446
|
+
throw new TypeError("settings authority cwd mismatch");
|
|
447
|
+
if (scope === "user") file = bound.userSettingsFile;
|
|
448
|
+
} else _deps.fs.mkdirSync(path.dirname(file), { recursive: true });
|
|
409
449
|
let began = false;
|
|
410
450
|
let committed = false;
|
|
411
451
|
try {
|
|
452
|
+
if (settingsAuthority !== null) {
|
|
453
|
+
const authority = require("./settings-permission-authority.cjs");
|
|
454
|
+
const result = authority.mutateSettingsPermissionSource(
|
|
455
|
+
settingsAuthority,
|
|
456
|
+
{
|
|
457
|
+
logicalPath: file,
|
|
458
|
+
prepare: (data) => appendPermissionRule(data, file, kind, rule),
|
|
459
|
+
revokeLocal() {
|
|
460
|
+
beginSettingsMutation();
|
|
461
|
+
began = true;
|
|
462
|
+
},
|
|
463
|
+
replace(physicalPath, data) {
|
|
464
|
+
_deps.writeSecurityStore(physicalPath, "settings", data);
|
|
465
|
+
committed = true;
|
|
466
|
+
},
|
|
467
|
+
},
|
|
468
|
+
);
|
|
469
|
+
if (began) finishSettingsMutation("ready");
|
|
470
|
+
return { file, added: result.changed };
|
|
471
|
+
}
|
|
412
472
|
const result = _deps.withFileLock(
|
|
413
473
|
file,
|
|
414
474
|
() => {
|
|
@@ -423,35 +483,8 @@ function addRule({ cwd = process.cwd(), kind, rule, scope = "project" } = {}) {
|
|
|
423
483
|
);
|
|
424
484
|
}
|
|
425
485
|
const data = _deps.readSecurityStore(file, "settings");
|
|
426
|
-
if (
|
|
427
|
-
Object.hasOwn(data, "permissions") &&
|
|
428
|
-
(!data.permissions ||
|
|
429
|
-
typeof data.permissions !== "object" ||
|
|
430
|
-
Array.isArray(data.permissions))
|
|
431
|
-
) {
|
|
432
|
-
throw new TypeError(
|
|
433
|
-
`refusing to overwrite malformed permissions: ${file}`,
|
|
434
|
-
);
|
|
435
|
-
}
|
|
436
|
-
data.permissions ??= {};
|
|
437
|
-
// Refuse to hide a damaged rule list, including a different decision.
|
|
438
|
-
for (const decision of KINDS) {
|
|
439
|
-
if (
|
|
440
|
-
Object.hasOwn(data.permissions, decision) &&
|
|
441
|
-
(!Array.isArray(data.permissions[decision]) ||
|
|
442
|
-
data.permissions[decision].some(
|
|
443
|
-
(entry) => typeof entry !== "string",
|
|
444
|
-
))
|
|
445
|
-
) {
|
|
446
|
-
throw new TypeError(
|
|
447
|
-
`refusing to overwrite malformed permission rules: ${file}`,
|
|
448
|
-
);
|
|
449
|
-
}
|
|
450
|
-
}
|
|
451
|
-
data.permissions[kind] ??= [];
|
|
452
|
-
if (data.permissions[kind].includes(rule))
|
|
486
|
+
if (!appendPermissionRule(data, file, kind, rule))
|
|
453
487
|
return { file, added: false };
|
|
454
|
-
data.permissions[kind].push(rule);
|
|
455
488
|
beginSettingsMutation();
|
|
456
489
|
began = true;
|
|
457
490
|
_deps.writeSecurityStore(file, "settings", data);
|