chainlesschain 0.166.85 → 0.166.86
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-DZwiXAIh.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-lUMnE2Ln.js → ActionButton-aNzXoIJP.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CsvKzTot.js → Analytics-ETQk4ZXt.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-C2z1h1Pv.js → AppLayout-D5oEbeEC.js} +3 -3
- package/src/assets/web-panel/assets/{Artifacts-C5Q0ZhNb.js → Artifacts-ja48FAne.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DXnfPKsI.js → Audit-DlN0YnDt.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-DjbGcLm3.js → BackgroundAgents-CzvyNsRg.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-9bVrKvXE.js → Backup-DXgEC2MQ.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-bx5ri7BD.js → BaseInput-D9XqejLx.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CCFmeUMY.js → Chat-BTwuYUlN.js} +4 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-0anIC2Wn.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-ei7MQXe9.js → Checkbox-CLuaQbDt.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CXgBJ9iu.js → Codegen-KwQUZs8A.js} +1 -1
- package/src/assets/web-panel/assets/{Col-DMkMsizS.js → Col-BCmROkzc.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CvpB_UFR.js → Community-CXlUEHR4.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-gay5hbFX.js → Compact-Dzx-lWI2.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-dG8JQyRL.js → Compliance-CfXW82FF.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BA3xfaTW.js → Cowork-DIISTRm2.js} +4 -4
- package/src/assets/web-panel/assets/{Cron-D1_HsxyY.js → Cron-nyJ-KcFE.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-DZqoqoy5.js → Crosschain-Dtv1E4cV.js} +1 -1
- package/src/assets/web-panel/assets/{DID-Cwnr8hCO.js → DID-BCTpektS.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-CxevypiI.js → Dashboard-C1Q_m0So.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-Bs7-yWgr.js → Dropdown-BrQ5_PPL.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CcEs2Puo.js → EmailListRenderer-DjoNlONP.js} +1 -1
- package/src/assets/web-panel/assets/{EvolutionSettings-C9ipBzAQ.js → EvolutionSettings-DaGaE6bX.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-Dt6--mGn.js → FamilyGuardDashboard-nJDWBECt.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CD0JN5FF.js → Federation-DhMUY_wG.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-POypz3Xv.js → FormItemContext-CbONJPuc.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-Dv9f-0F5.js → GenericCardRenderer-CDdX4dX-.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BbTh5Atv.js → Git-DXbDVJdR.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-C71dgoir.js → Governance-DbAfAvs1.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DwSHxbJB.js → Inference-Dy-fGea2.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-Cx1oQYQZ.js → KnowledgeGraph-DAxRpGUF.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BAJWe00o.js → Logs-5L1UCR8X.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-CD8a7lHT.js → MarkdownRenderer-u0FPafvT.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-fwuY7P7Y.js → Marketplace-DUk_McDG.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-Di4693uc.js → McpTools-B6PNsZ_p.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-Cz3L-GUB.js → Memory-BUUs8j5F.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BuXKIkSb.js → MobileBridge-pXRsgyuO.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-D7v-2Hwm.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-LnACMFd0.js → Mtc-Deaj4faB.js} +6 -6
- package/src/assets/web-panel/assets/{MtcAudit-BZy7yE0t.js → MtcAudit-wUV0VUH1.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-BFG5iVrE.js → Multisig-C67IIkts.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DgXi04vJ.js → NLProgramming-BCA_CWrW.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BAj0Kgg4.js → Notes-D9eu14hQ.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-AixrsHw5.js → NotificationSettings-Cnd-J4LM.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-Dwqk3FlG.js → OrderTableRenderer-C4pHUuyZ.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-UNCBC3ux.js → Organization-4YExAbnp.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Drh8oAFT.js → Overflow-BF-wFuZz.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-bc2q0xEB.js → P2P-CdiajqCT.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BGwKxcDM.js → PdhVaultBrowser-BkjfS6Th.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DOnUxUzu.js → Permissions-DrZ4D78_.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-B3qPZkrr.js → PersonalDataHub-lDc6OXjz.js} +6 -6
- package/src/assets/web-panel/assets/{Pipeline-B6hfa4-i.js → Pipeline-DqDS9ON1.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-Bun3c35F.js → Privacy-BMekZRZu.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-CCObdzk9.js → ProjectInit-LYS7NFvJ.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-C6lKrSXY.js → ProjectSettings-CcJNHk9A.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DYcta7MZ.js → Projects-DrfAHgdF.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-BtykxKCe.js → Providers-aqsly9SR.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-D9CDyroY.js → QrScannerModal-ZWvR3zqg.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CJauCxXx.js → QuickAsk-BT5CgsvW.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BbiYZofR.js → Recommend-Cz8Dz2RN.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-BymCrgFW.js → RemoteSession-BlxPVbtQ.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BWrGpMpF.js → Reputation-B3VGBE-1.js} +1 -1
- package/src/assets/web-panel/assets/{Row-9pOYIhiW.js → Row-Bwv7vSEA.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-D3bQJ4qB.js → RssFeed-DHI085r_.js} +2 -2
- package/src/assets/web-panel/assets/{Search-BpKOOFE0.js → Search-DBler5T0.js} +1 -1
- package/src/assets/web-panel/assets/{Security-DC6C0NSz.js → Security-BZdw43NW.js} +4 -4
- package/src/assets/web-panel/assets/{Services-Lkf1046J.js → Services-Cslk5lUj.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-eCQ_SsDb.js → Skeleton-Bur-C-25.js} +1 -1
- package/src/assets/web-panel/assets/Skills-v0zgLy0X.js +1 -0
- package/src/assets/web-panel/assets/{Sla-BQbKy8XJ.js → Sla-B1OWgQ51.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-DHsRx-XA.js → SpeechSettings-CaBcEaV4.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CC281X_J.js → SyncSettings-CBbG8L6J.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-C7EjXrnN.js +1 -0
- package/src/assets/web-panel/assets/{Templates-Cr2vC0tg.js → Templates-VBBOZ1H5.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DeYO5pIG.js → Tenant-CtQTj7IF.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DBNgbfPr.js → Terminal-DXVTQjnl.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-MoxgaF3x.js → TimelineRenderer-BRIno0aN.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-CT2QfAN7.js → Tokens-uiiAfkYN.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DPUnomxs.js → Trigger-CPApN8o6.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-DnufuaTx.js → Trust-T2jKVwCE.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CJ6iOdPs.js → UkeySign-C2U_gbzP.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-xBWkH8bL.js → VideoEditing-BCdlr3Kj.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-WYV-4hMR.js → Wallet-CWnjoSc1.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DJ5Mhd9L.js → WebAuthn-CqSbYqHq.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-B__UdOjN.js → WorkflowEditor-Dr_I3jcr.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CLlmBVhp.js → chat-Cg21FKdE.js} +1 -1
- package/src/assets/web-panel/assets/{colors-B8zoPQYm.js → colors-CK85tY9e.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CvJ-dUsH.js → compact-item-m-XYPC7W.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-LAtMm8SC.js → createContext-BmiEeNvs.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CkMEODFC.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CLYvg42H.js → hasIn-CCw4KAoc.js} +1 -1
- package/src/assets/web-panel/assets/{index-B02qgCao.js → index-6jXf-Lln.js} +1 -1
- package/src/assets/web-panel/assets/{index-B6zlMzZz.js → index-8KzhlvJ9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BCzyJ1xS.js → index-B7xJkhtJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Di9vKira.js → index-B8IC9ZeZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DfOOACfd.js → index-BGUYgc96.js} +1 -1
- package/src/assets/web-panel/assets/{index-DDm9m4vC.js → index-BGb1j2z9.js} +1 -1
- package/src/assets/web-panel/assets/{index-DX0aHEHL.js → index-BHsiSoGY.js} +1 -1
- package/src/assets/web-panel/assets/{index-BUVrYDNV.js → index-BIOTRzaF.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUcE6rIa.js → index-BStyjmLG.js} +1 -1
- package/src/assets/web-panel/assets/{index-DNQUdPam.js → index-BUYyhYP6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CxJ4NPVq.js → index-BcfVHmFK.js} +1 -1
- package/src/assets/web-panel/assets/{index-4rr5jFOL.js → index-BiVPFsat.js} +1 -1
- package/src/assets/web-panel/assets/index-BjE5qaKn.js +1 -0
- package/src/assets/web-panel/assets/{index-C3uD5H7u.js → index-Br0ZNdu1.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5v7cEtm.js → index-Br9t_I1X.js} +1 -1
- package/src/assets/web-panel/assets/{index-DX99e8Xy.js → index-BtvVupUL.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGrQ2uEl.js → index-Bv90WEWa.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cpda0SAz.js → index-BvjC3t35.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cv7vwX3z.js → index-ByVvAXdl.js} +1 -1
- package/src/assets/web-panel/assets/{index-D0Z13xcZ.js → index-CBcgIWOe.js} +1 -1
- package/src/assets/web-panel/assets/{index-DYW4Z3y-.js → index-CN2xbLGz.js} +1 -1
- package/src/assets/web-panel/assets/{index-f1ptYBMr.js → index-CYEzWoyZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-C_dq-6_3.js → index-Cf5arJie.js} +1 -1
- package/src/assets/web-panel/assets/index-CsFH-2Zk.js +1 -0
- package/src/assets/web-panel/assets/{index-C2KFnUsv.js → index-D1sU5-XA.js} +1 -1
- package/src/assets/web-panel/assets/{index-B_RPWP_5.js → index-D7gS_TJl.js} +1 -1
- package/src/assets/web-panel/assets/{index-iJ6s24bA.js → index-DAlGVix-.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bu7-I2L4.js → index-DI9GJFcn.js} +1 -1
- package/src/assets/web-panel/assets/{index-C6PETyel.js → index-DIlGxS2W.js} +1 -1
- package/src/assets/web-panel/assets/{index-CVDcRQuX.js → index-DMbrl43d.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkcQJvsz.js → index-DmSgfXuz.js} +1 -1
- package/src/assets/web-panel/assets/{index-DHq2Of6z.js → index-DpIy2HzE.js} +1 -1
- package/src/assets/web-panel/assets/{index-BM2HqHdT.js → index-DxRhnsEC.js} +1 -1
- package/src/assets/web-panel/assets/{index-GR3omApz.js → index-IskAKXhx.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1hHp9_M.js → index-J81uvUMN.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpeV-6y4.js → index-OIbfsi-o.js} +1 -1
- package/src/assets/web-panel/assets/{index-oX216HRH.js → index-lhoexBDH.js} +1 -1
- package/src/assets/web-panel/assets/{index-CnLXLaEF.js → index-sWUmvT8H.js} +1 -1
- package/src/assets/web-panel/assets/{index-09YzRb6Z.js → index-yYmjkf38.js} +3 -3
- package/src/assets/web-panel/assets/{initDefaultProps-QD-koeaw.js → initDefaultProps-Cc1XOmNe.js} +1 -1
- package/src/assets/web-panel/assets/{motion-CTItLW4-.js → motion-BehsX88u.js} +1 -1
- package/src/assets/web-panel/assets/{move-CGsdTOdN.js → move-BPx2YsqY.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-C1kW_CNU.js → mtc-parser-BSFJgFQA.js} +1 -1
- package/src/assets/web-panel/assets/{omit-B4PilaYy.js → omit-DBN5KUeo.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-DBGUE12e.js → pickAttrs-BDK1yZ-9.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DZxCLjuo.js → placementArrow-C4rMmKNJ.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-CZjQiocN.js → responsiveObserve-BoeoOnxX.js} +1 -1
- package/src/assets/web-panel/assets/{slide-Btz5hh2_.js → slide-BKlr1bl9.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CfuxuLIt.js → statusUtils-Ky1apWPI.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DdO0Obo_.js → styleChecker-DxxR7uXk.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-CxftJ-Zs.js → useFlexGapSupport-DmsUqVe9.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-pVJChh6K.js → useFs-CwkG7sOg.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-wv7Q0uNE.js → usePersonalDataHub-CddgeEfF.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DVOVfAqC.js → vnode-C_-H8i4Y.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-B5tebZuT.js → zoom-aAsHUMNE.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/data/changelog.json +13 -0
- package/src/lib/context-memory-kernel/durable-memory-port.js +5 -2
- package/src/lib/context-memory-kernel/index.js +1 -0
- package/src/lib/context-memory-kernel/memory-service.js +43 -16
- package/src/lib/context-memory-kernel/runtime.js +5 -3
- package/src/lib/context-memory-kernel/segmented-memory-port.js +687 -0
- package/src/lib/packer/pack-update-applier.js +13 -8
- package/src/lib/permission-authority.js +146 -10
- 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 +79 -383
- package/src/runtime/headless-runner.js +8 -3
- package/src/runtime/headless-stream.js +10 -3
- package/src/runtime/permission-authority-host.js +137 -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/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
|
@@ -853,6 +853,11 @@ async function scheduleWindowsTransaction(ctx) {
|
|
|
853
853
|
function windowsGenerationJournalHelperSource() {
|
|
854
854
|
return String.raw`param()
|
|
855
855
|
$ErrorActionPreference = 'Stop'
|
|
856
|
+
# The journal contains only JSON primitives. Use the inbox .NET serializer and
|
|
857
|
+
# file/path APIs so each phase does not repeat PowerShell module discovery.
|
|
858
|
+
# Keep the same synchronous atomic replacement and durable flush protocol.
|
|
859
|
+
[void][Reflection.Assembly]::Load('System.Web.Extensions, Version=4.0.0.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35')
|
|
860
|
+
$Serializer = [Web.Script.Serialization.JavaScriptSerializer]::new()
|
|
856
861
|
$Action = [string]$env:JOURNAL_ACTION
|
|
857
862
|
$JournalPath = [string]$env:JOURNAL_FILE
|
|
858
863
|
$RetiredPath = [string]$env:JOURNAL_RETIRED
|
|
@@ -872,7 +877,7 @@ if (($Phase -eq 'committed') -ne ($Decision -eq 'commit')) { throw 'Invalid phas
|
|
|
872
877
|
if ($Action -eq 'write' -and $Phase -eq 'prepared') {
|
|
873
878
|
if ([IO.File]::Exists($JournalPath)) { throw 'A generation journal already exists' }
|
|
874
879
|
$Bytes = [Convert]::FromBase64String([string]$env:JOURNAL_INITIAL_BASE64)
|
|
875
|
-
$Journal = [Text.Encoding]::UTF8.GetString($Bytes)
|
|
880
|
+
$Journal = $Serializer.DeserializeObject([Text.Encoding]::UTF8.GetString($Bytes))
|
|
876
881
|
$ParentSource = @'
|
|
877
882
|
using System;
|
|
878
883
|
using System.Runtime.InteropServices;
|
|
@@ -900,10 +905,10 @@ public static class CcParentProcess {
|
|
|
900
905
|
}
|
|
901
906
|
'@
|
|
902
907
|
Add-Type -TypeDefinition $ParentSource -Language CSharp
|
|
903
|
-
$Journal
|
|
908
|
+
$Journal.Add('ownerPid', [CcParentProcess]::Get())
|
|
904
909
|
} else {
|
|
905
910
|
if (-not [IO.File]::Exists($JournalPath)) { throw 'Generation journal is missing' }
|
|
906
|
-
$Journal =
|
|
911
|
+
$Journal = $Serializer.DeserializeObject([IO.File]::ReadAllText($JournalPath, [Text.Encoding]::UTF8))
|
|
907
912
|
}
|
|
908
913
|
|
|
909
914
|
$ValidBefore = {
|
|
@@ -931,7 +936,7 @@ if ($Action -eq 'retire') {
|
|
|
931
936
|
if ([string]$Journal.phase -ne $Phase -or [string]$Journal.decision -ne $Decision) {
|
|
932
937
|
throw 'Generation journal decision changed before retirement'
|
|
933
938
|
}
|
|
934
|
-
$ReplacedPath =
|
|
939
|
+
$ReplacedPath = [IO.Path]::Combine([IO.Path]::GetDirectoryName($JournalPath), ('.chainlesschain.journal-retired-previous-' + [guid]::NewGuid().ToString('N')))
|
|
935
940
|
try {
|
|
936
941
|
if ([IO.File]::Exists($RetiredPath)) {
|
|
937
942
|
[IO.File]::Replace($JournalPath, $RetiredPath, $ReplacedPath, $true)
|
|
@@ -985,11 +990,11 @@ if ($Phase -ne 'prepared') {
|
|
|
985
990
|
$Journal.phase = $Phase
|
|
986
991
|
$Journal.decision = $Decision
|
|
987
992
|
$Journal.updatedAt = [DateTimeOffset]::UtcNow.ToString('o')
|
|
988
|
-
$Directory =
|
|
989
|
-
$StagingPath =
|
|
990
|
-
$ReplacedPath =
|
|
993
|
+
$Directory = [IO.Path]::GetDirectoryName($JournalPath)
|
|
994
|
+
$StagingPath = [IO.Path]::Combine($Directory, ('.chainlesschain.journal-' + [guid]::NewGuid().ToString('N')))
|
|
995
|
+
$ReplacedPath = [IO.Path]::Combine($Directory, ('.chainlesschain.journal-previous-' + [guid]::NewGuid().ToString('N')))
|
|
991
996
|
try {
|
|
992
|
-
$Payload = $Journal
|
|
997
|
+
$Payload = $Serializer.Serialize($Journal)
|
|
993
998
|
$PayloadBytes = [Text.UTF8Encoding]::new($false).GetBytes($Payload + [Environment]::NewLine)
|
|
994
999
|
$Stream = [IO.File]::Open($StagingPath, [IO.FileMode]::CreateNew, [IO.FileAccess]::Write, [IO.FileShare]::None)
|
|
995
1000
|
try {
|
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
/** Resolve static settings plus CLI-owned workspace-scoped permission rules. */
|
|
2
2
|
|
|
3
3
|
import settingsLoader from "./settings-loader.cjs";
|
|
4
|
+
import persistentSettings from "./settings-permission-authority.cjs";
|
|
4
5
|
import path from "node:path";
|
|
5
6
|
import {
|
|
6
7
|
ScopedPermissionStore,
|
|
7
8
|
getScopedPermissionRevision,
|
|
8
9
|
subscribeScopedPermissionRevision,
|
|
10
|
+
projectScopedPermissionObservation,
|
|
9
11
|
} from "./scoped-permission-store.js";
|
|
10
12
|
|
|
11
13
|
const {
|
|
@@ -86,16 +88,41 @@ export function loadPermissionAuthority({
|
|
|
86
88
|
env = process.env,
|
|
87
89
|
baseRules = null,
|
|
88
90
|
scopedStore = null,
|
|
91
|
+
settingsAuthority: persistentAuthority = null,
|
|
89
92
|
} = {}) {
|
|
90
93
|
const settingsRevision = getSettingsPermissionRevision();
|
|
91
94
|
const scopedRevision = baseRules ? null : getScopedPermissionRevision();
|
|
92
|
-
const
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
95
|
+
const persistent = persistentAuthority
|
|
96
|
+
? persistentSettings.readSettingsPermissionAuthority(persistentAuthority)
|
|
97
|
+
: null;
|
|
98
|
+
if (persistentAuthority) {
|
|
99
|
+
const bound =
|
|
100
|
+
persistentSettings.settingsPermissionAuthorityOptions(
|
|
101
|
+
persistentAuthority,
|
|
102
|
+
);
|
|
103
|
+
if (
|
|
104
|
+
path.resolve(cwd) !== bound.cwd ||
|
|
105
|
+
(settingsFile &&
|
|
106
|
+
path.resolve(cwd, settingsFile) !== bound.settingsFile) ||
|
|
107
|
+
(managedSettingsFile &&
|
|
108
|
+
path.resolve(managedSettingsFile) !== bound.managedSettingsFile)
|
|
109
|
+
)
|
|
110
|
+
throw new TypeError("persistent settings authority options mismatch");
|
|
111
|
+
}
|
|
112
|
+
const settingsObservation =
|
|
113
|
+
persistent?.observation ||
|
|
114
|
+
inspectSettingsSources({
|
|
115
|
+
cwd,
|
|
116
|
+
settingsFile,
|
|
117
|
+
managedSettingsFile,
|
|
118
|
+
env,
|
|
119
|
+
});
|
|
120
|
+
const loaded = persistent
|
|
121
|
+
? persistentSettings.projectSettingsPermissionObservation(
|
|
122
|
+
settingsObservation,
|
|
123
|
+
{ env },
|
|
124
|
+
)
|
|
125
|
+
: projectSettingsObservation(settingsObservation, { env });
|
|
99
126
|
const sources = baseRules ? {} : { ...loaded.sources };
|
|
100
127
|
let rules = baseRules
|
|
101
128
|
? applyManagedPermissionPolicy(baseRules, loaded.managed, sources)
|
|
@@ -105,7 +132,29 @@ export function loadPermissionAuthority({
|
|
|
105
132
|
// Explicit caller rules retain their historical replacement semantics.
|
|
106
133
|
// Managed-only policy also suppresses every user-owned scoped grant.
|
|
107
134
|
if (!baseRules) {
|
|
108
|
-
|
|
135
|
+
const bound = persistentAuthority
|
|
136
|
+
? persistentSettings.settingsPermissionAuthorityOptions(
|
|
137
|
+
persistentAuthority,
|
|
138
|
+
)
|
|
139
|
+
: null;
|
|
140
|
+
const store =
|
|
141
|
+
scopedStore ||
|
|
142
|
+
new ScopedPermissionStore({
|
|
143
|
+
cwd,
|
|
144
|
+
...(bound
|
|
145
|
+
? {
|
|
146
|
+
filePath: bound.scopedFile,
|
|
147
|
+
settingsAuthority: persistentAuthority,
|
|
148
|
+
}
|
|
149
|
+
: {}),
|
|
150
|
+
});
|
|
151
|
+
scoped = persistent
|
|
152
|
+
? projectScopedPermissionObservation(
|
|
153
|
+
store,
|
|
154
|
+
persistentAuthority,
|
|
155
|
+
persistent.scopedObservation,
|
|
156
|
+
)
|
|
157
|
+
: store.list();
|
|
109
158
|
const managedOnly =
|
|
110
159
|
loaded.managed?.allowManagedPermissionRulesOnly === true;
|
|
111
160
|
scoped = {
|
|
@@ -143,6 +192,17 @@ export function loadPermissionAuthority({
|
|
|
143
192
|
error.code = "CC_SCOPED_PERMISSION_AUTHORITY_CHANGED";
|
|
144
193
|
throw error;
|
|
145
194
|
}
|
|
195
|
+
if (
|
|
196
|
+
persistent &&
|
|
197
|
+
persistentSettings.readSettingsPermissionAuthority(persistentAuthority)
|
|
198
|
+
.snapshot !== persistent.snapshot
|
|
199
|
+
) {
|
|
200
|
+
const error = new Error(
|
|
201
|
+
"persistent settings changed while permission authority was loading",
|
|
202
|
+
);
|
|
203
|
+
error.code = "CC_SETTINGS_PERMISSION_AUTHORITY_CHANGED";
|
|
204
|
+
throw error;
|
|
205
|
+
}
|
|
146
206
|
return freezeSnapshot({
|
|
147
207
|
...loaded,
|
|
148
208
|
rules,
|
|
@@ -151,12 +211,17 @@ export function loadPermissionAuthority({
|
|
|
151
211
|
hasRules: hasRules(rules),
|
|
152
212
|
settingsRevision,
|
|
153
213
|
settingsObservation,
|
|
214
|
+
...(persistent ? { persistentSettingsRevision: persistent.snapshot } : {}),
|
|
154
215
|
...(scopedRevision ? { scopedRevision } : {}),
|
|
155
216
|
});
|
|
156
217
|
}
|
|
157
218
|
|
|
158
219
|
export function createPermissionRulesProvider(options = {}) {
|
|
159
|
-
const
|
|
220
|
+
const persistentAuthority = options.settingsAuthority ?? null;
|
|
221
|
+
const bound = persistentAuthority
|
|
222
|
+
? persistentSettings.settingsPermissionAuthorityOptions(persistentAuthority)
|
|
223
|
+
: null;
|
|
224
|
+
const cwd = path.resolve(options.cwd || bound?.cwd || process.cwd());
|
|
160
225
|
const captured = Object.freeze({
|
|
161
226
|
cwd,
|
|
162
227
|
settingsFile: options.settingsFile
|
|
@@ -172,15 +237,86 @@ export function createPermissionRulesProvider(options = {}) {
|
|
|
172
237
|
? freezeSnapshot(cloneRules(options.baseRules))
|
|
173
238
|
: null,
|
|
174
239
|
scopedStore: options.scopedStore || null,
|
|
240
|
+
settingsAuthority: persistentAuthority,
|
|
175
241
|
});
|
|
242
|
+
if (
|
|
243
|
+
bound &&
|
|
244
|
+
(cwd !== bound.cwd ||
|
|
245
|
+
(captured.settingsFile && captured.settingsFile !== bound.settingsFile) ||
|
|
246
|
+
(captured.managedSettingsFile &&
|
|
247
|
+
captured.managedSettingsFile !== bound.managedSettingsFile))
|
|
248
|
+
)
|
|
249
|
+
throw new TypeError("persistent settings authority options mismatch");
|
|
176
250
|
const provider = () => loadPermissionAuthority(captured);
|
|
251
|
+
const localAuthority = captured.baseRules
|
|
252
|
+
? settingsAuthority
|
|
253
|
+
: combinedAuthority;
|
|
177
254
|
providers.set(
|
|
178
255
|
provider,
|
|
179
|
-
|
|
256
|
+
persistentAuthority
|
|
257
|
+
? persistentProviderAuthority(persistentAuthority, localAuthority)
|
|
258
|
+
: localAuthority,
|
|
180
259
|
);
|
|
181
260
|
return Object.freeze(provider);
|
|
182
261
|
}
|
|
183
262
|
|
|
263
|
+
function persistentProviderAuthority(binding, localAuthority) {
|
|
264
|
+
let last = null;
|
|
265
|
+
function getSnapshot() {
|
|
266
|
+
const local = localAuthority.getSnapshot();
|
|
267
|
+
const durable =
|
|
268
|
+
persistentSettings.readSettingsPermissionAuthority(binding).snapshot;
|
|
269
|
+
if (last?.local !== local || last?.durable !== durable)
|
|
270
|
+
last = Object.freeze({ local, durable });
|
|
271
|
+
return last;
|
|
272
|
+
}
|
|
273
|
+
return Object.freeze({
|
|
274
|
+
getSnapshot,
|
|
275
|
+
assertWorkspace(cwd) {
|
|
276
|
+
if (
|
|
277
|
+
path.resolve(cwd) !==
|
|
278
|
+
persistentSettings.settingsPermissionAuthorityOptions(binding).cwd
|
|
279
|
+
)
|
|
280
|
+
throw Object.assign(
|
|
281
|
+
new Error("persistent permission authority workspace mismatch"),
|
|
282
|
+
{ code: "CC_SETTINGS_AUTHORITY_BINDING_CHANGED" },
|
|
283
|
+
);
|
|
284
|
+
},
|
|
285
|
+
assertWritableRoots(roots) {
|
|
286
|
+
persistentSettings.assertSettingsPermissionWritableRoots(binding, roots);
|
|
287
|
+
},
|
|
288
|
+
subscribePolicyRevision(listener) {
|
|
289
|
+
if (typeof listener !== "function")
|
|
290
|
+
throw new TypeError("permission revision listener must be a function");
|
|
291
|
+
let known = getSnapshot();
|
|
292
|
+
const remove = localAuthority.subscribePolicyRevision(listener);
|
|
293
|
+
// Persistence prevents official ABA loss between polls. Timers provide
|
|
294
|
+
// bounded observation, not a promise that another process has stopped
|
|
295
|
+
// before synchronous addRule returns.
|
|
296
|
+
const timer = setInterval(() => {
|
|
297
|
+
let next;
|
|
298
|
+
try {
|
|
299
|
+
next = getSnapshot();
|
|
300
|
+
} catch {
|
|
301
|
+
next = null;
|
|
302
|
+
}
|
|
303
|
+
if (next === known) return;
|
|
304
|
+
known = next;
|
|
305
|
+
try {
|
|
306
|
+
listener(next);
|
|
307
|
+
} catch {
|
|
308
|
+
/* isolate observers */
|
|
309
|
+
}
|
|
310
|
+
}, 100);
|
|
311
|
+
timer.unref?.();
|
|
312
|
+
return () => {
|
|
313
|
+
clearInterval(timer);
|
|
314
|
+
remove();
|
|
315
|
+
};
|
|
316
|
+
},
|
|
317
|
+
});
|
|
318
|
+
}
|
|
319
|
+
|
|
184
320
|
// A legacy callback cannot advertise a trusted revision subscription simply
|
|
185
321
|
// by copying properties onto itself. It still receives sampled revalidation.
|
|
186
322
|
export function permissionRulesProviderAuthority(provider) {
|
|
@@ -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,
|