chainlesschain 0.166.84 → 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 +17 -4
- 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-OYLnAlB5.js → AIOps-DZwiXAIh.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-uc9kbLR6.js → ActionButton-aNzXoIJP.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-BH901FG0.js → Analytics-ETQk4ZXt.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-qypKb8GL.js → AppLayout-D5oEbeEC.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-Q-uPWyLY.js → Artifacts-ja48FAne.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-B8hMGMim.js → Audit-DlN0YnDt.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BSs8rJL_.js → BackgroundAgents-CzvyNsRg.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-2YMU_nCd.js → Backup-DXgEC2MQ.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DyMppTuT.js → BaseInput-D9XqejLx.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-B5QGMaDO.js → Chat-BTwuYUlN.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-B8ts_JnN.js → ChatBubbleRenderer-0anIC2Wn.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-DAvtgEGd.js → Checkbox-CLuaQbDt.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BSChEVM3.js → Codegen-KwQUZs8A.js} +1 -1
- package/src/assets/web-panel/assets/{Col-G0zYohIP.js → Col-BCmROkzc.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CzCN0B_p.js → Community-CXlUEHR4.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-DrIN3G8z.js → Compact-Dzx-lWI2.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CIsLa64Z.js → Compliance-CfXW82FF.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CGYoafsB.js → Cowork-DIISTRm2.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-DJ1JPXuH.js → Cron-nyJ-KcFE.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-B-IqVFJl.js → Crosschain-Dtv1E4cV.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CAAczPz_.js → DID-BCTpektS.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Dkpel8VE.js → Dashboard-C1Q_m0So.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-ETNBf8-Z.js → Dropdown-BrQ5_PPL.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CJGDyjVD.js → EmailListRenderer-DjoNlONP.js} +1 -1
- package/src/assets/web-panel/assets/{EvolutionSettings-CL_hvVYe.js → EvolutionSettings-DaGaE6bX.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-iexOh6Dh.js → FamilyGuardDashboard-nJDWBECt.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DnDHvV5D.js → Federation-DhMUY_wG.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DLCDJjUp.js → FormItemContext-CbONJPuc.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CaCgXCjS.js → GenericCardRenderer-CDdX4dX-.js} +1 -1
- package/src/assets/web-panel/assets/{Git-BVm-euUF.js → Git-DXbDVJdR.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DPnUvLWr.js → Governance-DbAfAvs1.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-5qCWSYKl.js → Inference-Dy-fGea2.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DXdv2oEh.js → KnowledgeGraph-DAxRpGUF.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BF2f7bBA.js → Logs-5L1UCR8X.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-u0FPafvT.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-Bp-0Y7Ng.js → Marketplace-DUk_McDG.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-dm-Xdi63.js → McpTools-B6PNsZ_p.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-TBHK031z.js → Memory-BUUs8j5F.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BTILAlds.js → MobileBridge-pXRsgyuO.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-DC0Clgzs.js → MobileProjects-D7v-2Hwm.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BvyWkmRw.js → Mtc-Deaj4faB.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-D7ksLoNa.js → MtcAudit-wUV0VUH1.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-Dpzu7X1t.js → Multisig-C67IIkts.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DZ0ADHSB.js → NLProgramming-BCA_CWrW.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-hIdkmRug.js → Notes-D9eu14hQ.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-R3jyP6Vf.js → NotificationSettings-Cnd-J4LM.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-d1zrFkqx.js → OrderTableRenderer-C4pHUuyZ.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DoA5WMcc.js → Organization-4YExAbnp.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-DHjIloAQ.js → Overflow-BF-wFuZz.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-2TMon_Wy.js → P2P-CdiajqCT.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DEUxGSWO.js → PdhVaultBrowser-BkjfS6Th.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-CxKcZ-8X.js → Permissions-DrZ4D78_.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-CcYtkHqc.js → PersonalDataHub-lDc6OXjz.js} +6 -6
- package/src/assets/web-panel/assets/{Pipeline-BxwOuq22.js → Pipeline-DqDS9ON1.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-VIx9pdsH.js → Privacy-BMekZRZu.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-COjp_erK.js → ProjectInit-LYS7NFvJ.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-x9NrZU80.js → ProjectSettings-CcJNHk9A.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DMgkw7QJ.js → Projects-DrfAHgdF.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-B_fR7Y7Y.js → Providers-aqsly9SR.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-BUvzrn3V.js → QrScannerModal-ZWvR3zqg.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-C0sJwLy2.js → QuickAsk-BT5CgsvW.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-CJC7Wpce.js → Recommend-Cz8Dz2RN.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-B62J6ZP0.js → RemoteSession-BlxPVbtQ.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-4f48kVBn.js → Reputation-B3VGBE-1.js} +1 -1
- package/src/assets/web-panel/assets/{Row-EzEMaYoV.js → Row-Bwv7vSEA.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-Dy0wD83j.js → RssFeed-DHI085r_.js} +3 -3
- package/src/assets/web-panel/assets/{Search-GeVRHQND.js → Search-DBler5T0.js} +1 -1
- package/src/assets/web-panel/assets/{Security-omLr1J7w.js → Security-BZdw43NW.js} +3 -3
- package/src/assets/web-panel/assets/{Services-DmGPaDAs.js → Services-Cslk5lUj.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CAla21Gm.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-Cwi15-pZ.js → Sla-B1OWgQ51.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Cs8Zi1J8.js → SpeechSettings-CaBcEaV4.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-ipuJsDKP.js → SyncSettings-CBbG8L6J.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-C7EjXrnN.js +1 -0
- package/src/assets/web-panel/assets/{Templates-slUp9yXF.js → Templates-VBBOZ1H5.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-6edCSG5K.js → Tenant-CtQTj7IF.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DKq84G3h.js → Terminal-DXVTQjnl.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-Dg0Aw6se.js → TimelineRenderer-BRIno0aN.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-D63h7qYd.js → Tokens-uiiAfkYN.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-F7gTP_gv.js → Trigger-CPApN8o6.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-zw8Zqp8k.js → Trust-T2jKVwCE.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-C-YBUmod.js → UkeySign-C2U_gbzP.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-AhVxTgmC.js → VideoEditing-BCdlr3Kj.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-C58zxjIg.js → Wallet-CWnjoSc1.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-D-n-rlfI.js → WebAuthn-CqSbYqHq.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-Ddc8gGD4.js → WorkflowEditor-Dr_I3jcr.js} +1 -1
- package/src/assets/web-panel/assets/{chat-C66V_U7w.js → chat-Cg21FKdE.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BcJyVZRj.js → colors-CK85tY9e.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-UgUXWcgP.js → compact-item-m-XYPC7W.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-oxEKHKYP.js → createContext-BmiEeNvs.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-CkMEODFC.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CbqzrKQO.js → hasIn-CCw4KAoc.js} +1 -1
- package/src/assets/web-panel/assets/{index-CH13yc9x.js → index-6jXf-Lln.js} +1 -1
- package/src/assets/web-panel/assets/{index-BdFgUn1S.js → index-8KzhlvJ9.js} +1 -1
- package/src/assets/web-panel/assets/{index-D3xNhHF3.js → index-B7xJkhtJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DAIuMvVR.js → index-B8IC9ZeZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CrG-1Kqs.js → index-BGUYgc96.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bu1A2sXy.js → index-BGb1j2z9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BQlGMcZ9.js → index-BHsiSoGY.js} +1 -1
- package/src/assets/web-panel/assets/{index-DWFNv_EG.js → index-BIOTRzaF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BrWZmUdA.js → index-BStyjmLG.js} +1 -1
- package/src/assets/web-panel/assets/{index-C-p9hhAz.js → index-BUYyhYP6.js} +1 -1
- package/src/assets/web-panel/assets/{index-CM2S5TJZ.js → index-BcfVHmFK.js} +1 -1
- package/src/assets/web-panel/assets/{index-7lSY8B9T.js → index-BiVPFsat.js} +1 -1
- package/src/assets/web-panel/assets/index-BjE5qaKn.js +1 -0
- package/src/assets/web-panel/assets/{index-B-h0Jgtj.js → index-Br0ZNdu1.js} +1 -1
- package/src/assets/web-panel/assets/{index-D8rgeCoC.js → index-Br9t_I1X.js} +1 -1
- package/src/assets/web-panel/assets/{index-DRrxJygS.js → index-BtvVupUL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DO-5vgTW.js → index-Bv90WEWa.js} +1 -1
- package/src/assets/web-panel/assets/{index-DBEL07sa.js → index-BvjC3t35.js} +1 -1
- package/src/assets/web-panel/assets/{index-UpN_kC9R.js → index-ByVvAXdl.js} +1 -1
- package/src/assets/web-panel/assets/{index-SzGgAqai.js → index-CBcgIWOe.js} +1 -1
- package/src/assets/web-panel/assets/{index-C3j9jTfB.js → index-CN2xbLGz.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bmhw_Ler.js → index-CYEzWoyZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Crqqbu-W.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-BMOY4PSW.js → index-D1sU5-XA.js} +1 -1
- package/src/assets/web-panel/assets/{index-BT2PZ9df.js → index-D7gS_TJl.js} +1 -1
- package/src/assets/web-panel/assets/{index-YvNh1UdB.js → index-DAlGVix-.js} +1 -1
- package/src/assets/web-panel/assets/{index-BZ7mv_oS.js → index-DI9GJFcn.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ov77O9mH.js → index-DIlGxS2W.js} +1 -1
- package/src/assets/web-panel/assets/{index-CJluGB2F.js → index-DMbrl43d.js} +1 -1
- package/src/assets/web-panel/assets/{index-C6X26RJu.js → index-DmSgfXuz.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMbJ06sF.js → index-DpIy2HzE.js} +1 -1
- package/src/assets/web-panel/assets/{index-CUmYwZyf.js → index-DxRhnsEC.js} +1 -1
- package/src/assets/web-panel/assets/{index-BNIq0NAn.js → index-IskAKXhx.js} +1 -1
- package/src/assets/web-panel/assets/{index-DR4YKlLt.js → index-J81uvUMN.js} +1 -1
- package/src/assets/web-panel/assets/{index-RnxuL8wM.js → index-OIbfsi-o.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bp9rfr-P.js → index-lhoexBDH.js} +1 -1
- package/src/assets/web-panel/assets/{index-BpqJNeIj.js → index-sWUmvT8H.js} +1 -1
- package/src/assets/web-panel/assets/{index-CuZupgUw.js → index-yYmjkf38.js} +3 -3
- package/src/assets/web-panel/assets/{initDefaultProps-BK9ncFiF.js → initDefaultProps-Cc1XOmNe.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BxzXi1Pe.js → motion-BehsX88u.js} +1 -1
- package/src/assets/web-panel/assets/{move-CRmwRuah.js → move-BPx2YsqY.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BQq5_Znx.js → mtc-parser-BSFJgFQA.js} +1 -1
- package/src/assets/web-panel/assets/{omit-Ch4VNY2j.js → omit-DBN5KUeo.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-CUioBhrz.js → pickAttrs-BDK1yZ-9.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-Hj3o1CrW.js → placementArrow-C4rMmKNJ.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-PfqJrlkI.js → responsiveObserve-BoeoOnxX.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BXcnR-Lu.js → slide-BKlr1bl9.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-C74a-ofo.js → statusUtils-Ky1apWPI.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-Bq57I3ls.js → styleChecker-DxxR7uXk.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-ITPUjcDw.js → useFlexGapSupport-DmsUqVe9.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-DzPuXDIS.js → useFs-CwkG7sOg.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-DYSgt9L5.js → usePersonalDataHub-CddgeEfF.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-NPs_EJEk.js → vnode-C_-H8i4Y.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DZzO6KVS.js → zoom-aAsHUMNE.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/permissions.js +27 -2
- package/src/data/changelog.json +26 -0
- package/src/lib/background-interaction-resolver.js +4 -0
- package/src/lib/context-memory-kernel/durable-memory-port.js +71 -15
- 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 +41 -17
- package/src/lib/packer/pkg-config-generator.js +119 -2
- package/src/lib/packer/pkg-runner.js +7 -0
- package/src/lib/packer/pkg-targets.js +83 -0
- package/src/lib/packer/web-panel-builder.js +7 -0
- package/src/lib/permission-authority.js +157 -13
- package/src/lib/process-execution-broker/index.js +11 -0
- package/src/lib/process-execution-broker/linux-subreaper-helper.js +87 -13
- package/src/lib/process-execution-broker/packer-build-policy.js +31 -0
- package/src/lib/process-execution-broker/platform-sandbox.js +11 -3
- package/src/lib/sandbox-egress-authority-monitor.js +62 -5
- package/src/lib/scoped-permission-store.js +119 -6
- package/src/lib/settings-authority-domain.cjs +37 -2
- package/src/lib/settings-loader.cjs +112 -42
- 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/MarkdownRenderer-qNJS3ygh.js +0 -1
- package/src/assets/web-panel/assets/Skills-QqEyIKOI.js +0 -1
- package/src/assets/web-panel/assets/Tasks-BbVAg9Z7.js +0 -1
- package/src/assets/web-panel/assets/devWarning-BoDvrsgY.js +0 -1
- package/src/assets/web-panel/assets/index-BhqRbl5P.js +0 -1
- package/src/assets/web-panel/assets/index-DreT6MIR.js +0 -1
|
@@ -86,11 +86,13 @@ import {
|
|
|
86
86
|
mcpStdioCapsuleNativeCodePolicyDigest,
|
|
87
87
|
} from "../mcp-stdio-native-code-policy.js";
|
|
88
88
|
|
|
89
|
+
import { isPackerBuildPolicy } from "./packer-build-policy.js";
|
|
89
90
|
const SUPPORTED_SANDBOX_BOUNDARIES = new Set(Object.values(SANDBOX_BOUNDARIES));
|
|
90
91
|
const SUPPORTED_SANDBOX_PROFILES = new Set([
|
|
91
92
|
"default",
|
|
92
93
|
"strict",
|
|
93
94
|
"network-only",
|
|
95
|
+
"build",
|
|
94
96
|
]);
|
|
95
97
|
// Broker-private admission state. Public runtime-probe fields are audit
|
|
96
98
|
// evidence, not authority to invoke a privileged post-spawn closure.
|
|
@@ -1590,6 +1592,15 @@ class ProcessExecutionBroker extends EventEmitter {
|
|
|
1590
1592
|
}
|
|
1591
1593
|
|
|
1592
1594
|
const profile = rawPolicy?.profile ?? null;
|
|
1595
|
+
if (
|
|
1596
|
+
profile === "build" &&
|
|
1597
|
+
!isPackerBuildPolicy(rawPolicy, options, launch)
|
|
1598
|
+
) {
|
|
1599
|
+
throw this._sandboxBoundaryError(
|
|
1600
|
+
"untrusted_build_sandbox_policy",
|
|
1601
|
+
"The build CPU budget requires an original packer policy for this origin and scope",
|
|
1602
|
+
);
|
|
1603
|
+
}
|
|
1593
1604
|
if (profile !== null && !SUPPORTED_SANDBOX_PROFILES.has(profile)) {
|
|
1594
1605
|
throw this._sandboxBoundaryError(
|
|
1595
1606
|
"invalid_sandbox_profile",
|
|
@@ -67,10 +67,77 @@ function compilerPath() {
|
|
|
67
67
|
return compiler;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
function isPkgRuntime() {
|
|
71
|
+
if (!process.pkg) return false;
|
|
72
|
+
// The location is derived only from this module and pkg's bootstrap, never
|
|
73
|
+
// from cwd, executable siblings, environment variables or caller paths.
|
|
74
|
+
if (
|
|
75
|
+
typeof process.pkg.defaultEntrypoint !== "string" ||
|
|
76
|
+
!process.pkg.defaultEntrypoint.startsWith("/snapshot/") ||
|
|
77
|
+
!fileURLToPath(import.meta.url).startsWith("/snapshot/")
|
|
78
|
+
)
|
|
79
|
+
throw failure("invalid-standalone-runtime");
|
|
80
|
+
return true;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
function readPkgAsset(file, maximum) {
|
|
84
|
+
// pkg's snapshot descriptors refer to /dev/null in the kernel and are read
|
|
85
|
+
// through its immutable payload reader. They are NOT native directory FDs.
|
|
86
|
+
// Reject real files mounted under a snapshot-looking pathname as well as
|
|
87
|
+
// symlink aliases; a pathname prefix alone never establishes this identity.
|
|
88
|
+
if (fs.realpathSync(file) !== file || fs.lstatSync(file).isSymbolicLink())
|
|
89
|
+
throw failure("invalid-standalone-asset");
|
|
90
|
+
const fd = fs.openSync(file, fs.constants.O_RDONLY);
|
|
91
|
+
try {
|
|
92
|
+
if (fs.readlinkSync(`/proc/self/fd/${fd}`) !== "/dev/null")
|
|
93
|
+
throw failure("standalone-asset-not-embedded");
|
|
94
|
+
const stat = fs.fstatSync(fd);
|
|
95
|
+
if (
|
|
96
|
+
!stat.isFile() ||
|
|
97
|
+
stat.size < 1 ||
|
|
98
|
+
stat.size > maximum ||
|
|
99
|
+
stat.nlink !== 0 ||
|
|
100
|
+
stat.ino !== 0 ||
|
|
101
|
+
stat.dev !== 0
|
|
102
|
+
)
|
|
103
|
+
throw failure("invalid-standalone-asset");
|
|
104
|
+
// readFileSync(path) reads the embedded payload directly. pkg readSync(fd)
|
|
105
|
+
// can instead read a decompressed external cache when compression is on.
|
|
106
|
+
// Never trust those cache bytes as supervision executable content.
|
|
107
|
+
const bytes = fs.readFileSync(file);
|
|
108
|
+
if (
|
|
109
|
+
!Buffer.isBuffer(bytes) ||
|
|
110
|
+
bytes.length !== stat.size ||
|
|
111
|
+
bytes.length > maximum
|
|
112
|
+
)
|
|
113
|
+
throw failure("invalid-standalone-asset");
|
|
114
|
+
return bytes;
|
|
115
|
+
} finally {
|
|
116
|
+
fs.closeSync(fd);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
70
120
|
function packagedImage() {
|
|
71
121
|
const root = fileURLToPath(
|
|
72
122
|
new URL("../../assets/linux-subreaper/", import.meta.url),
|
|
73
123
|
);
|
|
124
|
+
if (isPkgRuntime()) {
|
|
125
|
+
const directory = path.join(root, `linux-${process.arch}`);
|
|
126
|
+
const manifest = readPkgAsset(
|
|
127
|
+
path.join(directory, "manifest.json"),
|
|
128
|
+
MAX_SUBREAPER_MANIFEST_BYTES,
|
|
129
|
+
);
|
|
130
|
+
const bytes = readPkgAsset(
|
|
131
|
+
path.join(directory, "supervisor"),
|
|
132
|
+
MAX_IMAGE_BYTES,
|
|
133
|
+
);
|
|
134
|
+
return {
|
|
135
|
+
bytes,
|
|
136
|
+
identity: validateLinuxSubreaperArtifact(manifest, bytes, {
|
|
137
|
+
arch: process.arch,
|
|
138
|
+
}),
|
|
139
|
+
};
|
|
140
|
+
}
|
|
74
141
|
// Only source checkouts with no packaged directory may use the development
|
|
75
142
|
// compiler path. A partial/corrupt installed payload must never fall back.
|
|
76
143
|
try {
|
|
@@ -135,23 +202,28 @@ function compileImage(spawnSync) {
|
|
|
135
202
|
const source = fileURLToPath(
|
|
136
203
|
new URL("./linux-subreaper-supervisor.c", import.meta.url),
|
|
137
204
|
);
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
205
|
+
const packed = isPkgRuntime();
|
|
206
|
+
let sourceSnapshot;
|
|
207
|
+
if (packed) {
|
|
208
|
+
sourceSnapshot = readPkgAsset(source, MAX_SOURCE_BYTES);
|
|
209
|
+
} else {
|
|
210
|
+
sourceFd = fs.openSync(
|
|
211
|
+
source,
|
|
212
|
+
fs.constants.O_RDONLY | fs.constants.O_NOFOLLOW,
|
|
213
|
+
);
|
|
214
|
+
const snapshot = readDescriptor(sourceFd, MAX_SOURCE_BYTES);
|
|
215
|
+
if (snapshot.stat.nlink !== 1n) throw failure("source-digest-mismatch");
|
|
216
|
+
sourceSnapshot = snapshot.bytes;
|
|
217
|
+
fs.closeSync(sourceFd);
|
|
218
|
+
sourceFd = null;
|
|
219
|
+
}
|
|
143
220
|
// npm/git may transport text with CRLF. Compile the exact canonical bytes
|
|
144
221
|
// that the source digest commits to, passed on stdin rather than reopened.
|
|
145
222
|
const sourceBytes = Buffer.from(
|
|
146
|
-
|
|
223
|
+
sourceSnapshot.toString("utf8").replace(/\r\n/g, "\n"),
|
|
147
224
|
);
|
|
148
|
-
if (
|
|
149
|
-
snapshot.stat.nlink !== 1n ||
|
|
150
|
-
sha(sourceBytes) !== LINUX_SUBREAPER_SOURCE_DIGEST
|
|
151
|
-
)
|
|
225
|
+
if (sha(sourceBytes) !== LINUX_SUBREAPER_SOURCE_DIGEST)
|
|
152
226
|
throw failure("source-digest-mismatch");
|
|
153
|
-
fs.closeSync(sourceFd);
|
|
154
|
-
sourceFd = null;
|
|
155
227
|
const packaged = packagedImage();
|
|
156
228
|
const compiler = packaged ? null : compilerPath();
|
|
157
229
|
temporaryRoot = fs.mkdtempSync(
|
|
@@ -253,7 +325,9 @@ function compileImage(spawnSync) {
|
|
|
253
325
|
// Do not expose compiler stdout/stderr, paths, environment, or native argv.
|
|
254
326
|
throw failure(
|
|
255
327
|
error.code === "ENOENT"
|
|
256
|
-
?
|
|
328
|
+
? process.pkg
|
|
329
|
+
? "standalone-asset-missing"
|
|
330
|
+
: "system-compiler-or-source-missing"
|
|
257
331
|
: "native-build-setup-failed",
|
|
258
332
|
);
|
|
259
333
|
} finally {
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
// In-process identity, not a JSON profile name, admits the packer's larger
|
|
2
|
+
// finite CPU budget. This grants no process permission: the broker's ordinary
|
|
3
|
+
// deny/prompt checks and strict-mode override still apply.
|
|
4
|
+
const policies = new WeakMap();
|
|
5
|
+
const origins = new Set(["packer:pkg", "packer:web-panel-build"]);
|
|
6
|
+
|
|
7
|
+
export const PACKER_BUILD_TIMEOUT_MS = 900_000;
|
|
8
|
+
|
|
9
|
+
export function createPackerBuildPolicy(origin) {
|
|
10
|
+
if (!origins.has(origin)) throw new Error("Unknown packer build origin");
|
|
11
|
+
const policy = Object.freeze({ profile: "build" });
|
|
12
|
+
policies.set(policy, origin);
|
|
13
|
+
return policy;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export function isPackerBuildPolicy(policy, options, launch = {}) {
|
|
17
|
+
const { origin, scope, shell, detached, timeout, killSignal } = options;
|
|
18
|
+
return (
|
|
19
|
+
scope === "pack" &&
|
|
20
|
+
origins.has(origin) &&
|
|
21
|
+
policies.get(policy) === origin &&
|
|
22
|
+
launch.sync === true &&
|
|
23
|
+
launch.pty !== true &&
|
|
24
|
+
shell === false &&
|
|
25
|
+
(detached === undefined || detached === false) &&
|
|
26
|
+
Number.isSafeInteger(timeout) &&
|
|
27
|
+
timeout > 0 &&
|
|
28
|
+
timeout <= PACKER_BUILD_TIMEOUT_MS &&
|
|
29
|
+
killSignal === "SIGKILL"
|
|
30
|
+
);
|
|
31
|
+
}
|
|
@@ -2128,7 +2128,7 @@ export function applyMacSandbox(
|
|
|
2128
2128
|
// non-strict mode records the unavailable primitive and executes the
|
|
2129
2129
|
// original invocation, while strict mode fails closed. Explicit strict or
|
|
2130
2130
|
// network-only profiles still opt in to the sandbox-exec wrapper below.
|
|
2131
|
-
if ((sandboxOpts.profileName || "default")
|
|
2131
|
+
if (["default", "build"].includes(sandboxOpts.profileName || "default")) {
|
|
2132
2132
|
return createSandboxPlan({
|
|
2133
2133
|
...base,
|
|
2134
2134
|
reason: "macos_default_profile_requires_explicit_policy",
|
|
@@ -9969,8 +9969,16 @@ export function applySandbox(
|
|
|
9969
9969
|
};
|
|
9970
9970
|
|
|
9971
9971
|
const profile = {
|
|
9972
|
-
...(
|
|
9973
|
-
|
|
9972
|
+
...(profileName === "build"
|
|
9973
|
+
? {
|
|
9974
|
+
...profiles.default,
|
|
9975
|
+
limits: { ...profiles.default.limits, cpu: 600 },
|
|
9976
|
+
}
|
|
9977
|
+
: profiles[profileName] || profiles.default),
|
|
9978
|
+
profileName:
|
|
9979
|
+
profileName === "build" || profiles[profileName]
|
|
9980
|
+
? profileName
|
|
9981
|
+
: "default",
|
|
9974
9982
|
requiredBoundaries: sandboxRequest.requiredBoundaries,
|
|
9975
9983
|
sync: sandboxRequest.sync,
|
|
9976
9984
|
pty: sandboxRequest.pty,
|
|
@@ -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.
|
|
@@ -143,7 +145,7 @@ function validateRecord(record, seenIds) {
|
|
|
143
145
|
seenIds.has(record.id) ||
|
|
144
146
|
!DECISIONS.has(record.decision) ||
|
|
145
147
|
!parseRule(record.rule) ||
|
|
146
|
-
!Number.
|
|
148
|
+
!Number.isSafeInteger(record.revision) ||
|
|
147
149
|
record.revision < 1 ||
|
|
148
150
|
!Number.isFinite(record.createdAt) ||
|
|
149
151
|
!Number.isFinite(record.expiresAt) ||
|
|
@@ -165,7 +167,7 @@ function validateState(value, workspace) {
|
|
|
165
167
|
!value ||
|
|
166
168
|
value.schema !== STATE_SCHEMA ||
|
|
167
169
|
value.schemaVersion !== STATE_SCHEMA_VERSION ||
|
|
168
|
-
!Number.
|
|
170
|
+
!Number.isSafeInteger(value.generation) ||
|
|
169
171
|
value.generation < 0 ||
|
|
170
172
|
!value.workspace ||
|
|
171
173
|
value.workspace.id !== workspace.id ||
|
|
@@ -188,6 +190,16 @@ function statusFor(record, now) {
|
|
|
188
190
|
return "active";
|
|
189
191
|
}
|
|
190
192
|
|
|
193
|
+
function nextVersion(value, label) {
|
|
194
|
+
if (value === Number.MAX_SAFE_INTEGER) {
|
|
195
|
+
throw scopedPermissionError(
|
|
196
|
+
SCOPED_PERMISSION_ERROR_CODES.INVALID,
|
|
197
|
+
`Scoped permission ${label} is exhausted`,
|
|
198
|
+
);
|
|
199
|
+
}
|
|
200
|
+
return value + 1;
|
|
201
|
+
}
|
|
202
|
+
|
|
191
203
|
function projectRecord(record, now, filePath) {
|
|
192
204
|
return Object.freeze({
|
|
193
205
|
...structuredClone(record),
|
|
@@ -206,6 +218,7 @@ export class ScopedPermissionStore {
|
|
|
206
218
|
maxRecords = DEFAULT_MAX_RECORDS,
|
|
207
219
|
lock = undefined,
|
|
208
220
|
randomId = () => `spr_${randomUUID().replaceAll("-", "")}`,
|
|
221
|
+
settingsAuthority = null,
|
|
209
222
|
} = {}) {
|
|
210
223
|
this.workspace = workspaceBinding(cwd);
|
|
211
224
|
this.filePath = path.resolve(
|
|
@@ -232,6 +245,17 @@ export class ScopedPermissionStore {
|
|
|
232
245
|
);
|
|
233
246
|
this._lock = lock;
|
|
234
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
|
+
}
|
|
235
259
|
}
|
|
236
260
|
|
|
237
261
|
_capture() {
|
|
@@ -243,7 +267,22 @@ export class ScopedPermissionStore {
|
|
|
243
267
|
randomId: this._randomId.bind(this),
|
|
244
268
|
maxRecords: this.maxRecords,
|
|
245
269
|
lock: this._lock || withFileLock,
|
|
270
|
+
settingsAuthority: persistentBindings.get(this) || null,
|
|
246
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
|
+
}
|
|
247
286
|
if (getScopedPermissionRevision() !== revision)
|
|
248
287
|
throw scopedPermissionError(
|
|
249
288
|
"CC_SCOPED_PERMISSION_AUTHORITY_CHANGED",
|
|
@@ -254,13 +293,54 @@ export class ScopedPermissionStore {
|
|
|
254
293
|
|
|
255
294
|
_read(captured = this._capture()) {
|
|
256
295
|
getScopedPermissionRevision();
|
|
257
|
-
const stored =
|
|
296
|
+
const stored = captured.settingsAuthority
|
|
297
|
+
? persistentSettings.readSettingsPermissionAuthority(
|
|
298
|
+
captured.settingsAuthority,
|
|
299
|
+
).scopedObservation.settings || {}
|
|
300
|
+
: readSecurityStore(captured.filePath, STORE_LABEL);
|
|
258
301
|
if (Object.keys(stored).length === 0) return emptyState(captured.workspace);
|
|
259
302
|
return validateState(stored, captured.workspace);
|
|
260
303
|
}
|
|
261
304
|
|
|
262
305
|
_mutate(captured, update) {
|
|
263
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
|
+
}
|
|
264
344
|
fs.mkdirSync(path.dirname(captured.filePath), {
|
|
265
345
|
recursive: true,
|
|
266
346
|
mode: 0o700,
|
|
@@ -412,7 +492,7 @@ export class ScopedPermissionStore {
|
|
|
412
492
|
reason: normalizedReason,
|
|
413
493
|
};
|
|
414
494
|
Object.assign(draft, current, {
|
|
415
|
-
generation: current.generation
|
|
495
|
+
generation: nextVersion(current.generation, "generation"),
|
|
416
496
|
updatedAt: now,
|
|
417
497
|
rules: [...retained, created],
|
|
418
498
|
});
|
|
@@ -462,13 +542,13 @@ export class ScopedPermissionStore {
|
|
|
462
542
|
}
|
|
463
543
|
revoked = {
|
|
464
544
|
...record,
|
|
465
|
-
revision: record.revision
|
|
545
|
+
revision: nextVersion(record.revision, "revision"),
|
|
466
546
|
revokedAt: now,
|
|
467
547
|
};
|
|
468
548
|
const rules = [...current.rules];
|
|
469
549
|
rules[index] = revoked;
|
|
470
550
|
Object.assign(draft, current, {
|
|
471
|
-
generation: current.generation
|
|
551
|
+
generation: nextVersion(current.generation, "generation"),
|
|
472
552
|
updatedAt: now,
|
|
473
553
|
rules,
|
|
474
554
|
});
|
|
@@ -477,3 +557,36 @@ export class ScopedPermissionStore {
|
|
|
477
557
|
return projectRecord(revoked, now, captured.filePath);
|
|
478
558
|
}
|
|
479
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,
|