chainlesschain 0.166.83 → 0.166.85
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 +9 -4
- package/package.json +1 -1
- 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-D5a4i3N7.js → AIOps-Cgo7OFpi.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DxsRk5n1.js → ActionButton-lUMnE2Ln.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-D-7EvH4S.js → Analytics-CsvKzTot.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DDEWVr-d.js → AppLayout-C2z1h1Pv.js} +3 -3
- package/src/assets/web-panel/assets/{Artifacts-CAlJAjRB.js → Artifacts-C5Q0ZhNb.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-tuaWDOAM.js → Audit-DXnfPKsI.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-CIwRFJ50.js → BackgroundAgents-DjbGcLm3.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CpBepJlH.js → Backup-9bVrKvXE.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-eKguCzQc.js → BaseInput-bx5ri7BD.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CFrUfXDV.js → Chat-CCFmeUMY.js} +4 -4
- package/src/assets/web-panel/assets/ChatBubbleRenderer-ClqsIfTT.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-o5FEn6a1.js → Checkbox-ei7MQXe9.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-Dih8HogC.js → Codegen-CXgBJ9iu.js} +1 -1
- package/src/assets/web-panel/assets/{Col-D5r-6WZe.js → Col-DMkMsizS.js} +1 -1
- package/src/assets/web-panel/assets/{Community-jeQ6bGey.js → Community-CvpB_UFR.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CEP7QsWp.js → Compact-gay5hbFX.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-B8NydOHd.js → Compliance-dG8JQyRL.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Bmi1zuPy.js → Cowork-BA3xfaTW.js} +4 -4
- package/src/assets/web-panel/assets/{Cron-BAL9bwSp.js → Cron-D1_HsxyY.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-D8WUi2dS.js → Crosschain-DZqoqoy5.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CQCkYcLY.js → DID-Cwnr8hCO.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DogXg6kh.js → Dashboard-CxevypiI.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BJBJyQJn.js → Dropdown-Bs7-yWgr.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CxPWquJx.js → EmailListRenderer-CcEs2Puo.js} +1 -1
- package/src/assets/web-panel/assets/{EvolutionSettings-BijH1x8G.js → EvolutionSettings-C9ipBzAQ.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DStwf-00.js → FamilyGuardDashboard-Dt6--mGn.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-LU9_sNxC.js → Federation-CD0JN5FF.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DNfWhj7P.js → FormItemContext-POypz3Xv.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DWYsPjzk.js → GenericCardRenderer-Dv9f-0F5.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CA0O9ulB.js → Git-BbTh5Atv.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-ECShL7BJ.js → Governance-C71dgoir.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-9GA2542e.js → Inference-DwSHxbJB.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-nC5mTbEH.js → KnowledgeGraph-Cx1oQYQZ.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-B7IqgnDy.js → Logs-BAJWe00o.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-CD8a7lHT.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-CEr0suM8.js → Marketplace-fwuY7P7Y.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DhTPQKOE.js → McpTools-Di4693uc.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-mHRZP1w4.js → Memory-Cz3L-GUB.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BNfX6Rdo.js → MobileBridge-BuXKIkSb.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-Bk-lK4Um.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-CzkV3wsn.js → Mtc-LnACMFd0.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-DWYljJik.js → MtcAudit-BZy7yE0t.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-kHgtvZY_.js → Multisig-BFG5iVrE.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-56uQiPiY.js → NLProgramming-DgXi04vJ.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DxGOZqRf.js → Notes-BAj0Kgg4.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-DabaCpmo.js → NotificationSettings-AixrsHw5.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-SzeQNlGT.js → OrderTableRenderer-Dwqk3FlG.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-D9gnPpK2.js → Organization-UNCBC3ux.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Cqp_bXm4.js → Overflow-Drh8oAFT.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DTeK-r0G.js → P2P-bc2q0xEB.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CsjG85GT.js → PdhVaultBrowser-BGwKxcDM.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-B4ofZq3j.js → Permissions-DOnUxUzu.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-Cvas5cYd.js → PersonalDataHub-B3qPZkrr.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-Er3wmAew.js → Pipeline-B6hfa4-i.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DMFYeZox.js → Privacy-Bun3c35F.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DaVaYPy0.js → ProjectInit-CCObdzk9.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-dPx5q2Ac.js → ProjectSettings-C6lKrSXY.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-N0JNeCui.js → Projects-DYcta7MZ.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-BPQo_gQa.js → Providers-BtykxKCe.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CD-sEsWh.js → QrScannerModal-D9CDyroY.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-LOKRJiN_.js → QuickAsk-CJauCxXx.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BwPXqcWy.js → Recommend-BbiYZofR.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-BjotndxF.js → RemoteSession-BymCrgFW.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-BlZ2gcbU.js → Reputation-BWrGpMpF.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D399HM-d.js → Row-9pOYIhiW.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C7H-ibnY.js → RssFeed-D3bQJ4qB.js} +2 -2
- package/src/assets/web-panel/assets/{Search-CNHCVmM1.js → Search-BpKOOFE0.js} +1 -1
- package/src/assets/web-panel/assets/{Security-C70eDGYY.js → Security-DC6C0NSz.js} +4 -4
- package/src/assets/web-panel/assets/{Services-6xfq9G-H.js → Services-Lkf1046J.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BbY2zeZV.js → Skeleton-eCQ_SsDb.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DYtyZ7_6.js → Skills-BvDSQ3-d.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CkWcl5OH.js → Sla-BQbKy8XJ.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CiknYk2P.js → SpeechSettings-DHsRx-XA.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-D-8JVip4.js → SyncSettings-CC281X_J.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-B2KnR9XC.js → Tasks-CHHSWHom.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CipXkOE-.js → Templates-Cr2vC0tg.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DbwgIMmx.js → Tenant-DeYO5pIG.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-OtCriHEE.js → Terminal-DBNgbfPr.js} +2 -2
- package/src/assets/web-panel/assets/TimelineRenderer-MoxgaF3x.js +1 -0
- package/src/assets/web-panel/assets/{Tokens-B4iFVxvH.js → Tokens-CT2QfAN7.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BegM2v8q.js → Trigger-DPUnomxs.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-COkqnnFj.js → Trust-DnufuaTx.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-BzqKPChj.js → UkeySign-CJ6iOdPs.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-D-F2XL3C.js → VideoEditing-xBWkH8bL.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-22PzRY9T.js → Wallet-WYV-4hMR.js} +3 -3
- package/src/assets/web-panel/assets/{WebAuthn-eSMq_qtz.js → WebAuthn-DJ5Mhd9L.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-BvdAakpq.js → WorkflowEditor-B__UdOjN.js} +1 -1
- package/src/assets/web-panel/assets/{chat-C6jhpF7E.js → chat-CLlmBVhp.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BbbW2Pfv.js → colors-B8zoPQYm.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CVFeLTqt.js → compact-item-CvJ-dUsH.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DS0m7NZc.js → createContext-LAtMm8SC.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-DgZ4fdGI.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DVouP0t6.js → hasIn-CLYvg42H.js} +1 -1
- package/src/assets/web-panel/assets/{index-Be12HoCS.js → index-09YzRb6Z.js} +3 -3
- package/src/assets/web-panel/assets/{index-CPS8E-J6.js → index-4rr5jFOL.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXw6ivB8.js → index-B02qgCao.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaUah6TO.js → index-B1hHp9_M.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ds4wbrgk.js → index-B6zlMzZz.js} +1 -1
- package/src/assets/web-panel/assets/{index-JeJqXbUU.js → index-BCzyJ1xS.js} +1 -1
- package/src/assets/web-panel/assets/{index-54aHB4FL.js → index-BGrQ2uEl.js} +1 -1
- package/src/assets/web-panel/assets/{index-eaXEQFuP.js → index-BM2HqHdT.js} +1 -1
- package/src/assets/web-panel/assets/{index-C74emdKD.js → index-BUVrYDNV.js} +1 -1
- package/src/assets/web-panel/assets/{index-BrIv7C9a.js → index-B_RPWP_5.js} +1 -1
- package/src/assets/web-panel/assets/{index-DGYX17YW.js → index-BkcQJvsz.js} +1 -1
- package/src/assets/web-panel/assets/{index-DotRL126.js → index-Bu7-I2L4.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvCgzl0X.js → index-C2KFnUsv.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4xmLgLa.js → index-C3uD5H7u.js} +1 -1
- package/src/assets/web-panel/assets/{index-BwozzG6k.js → index-C5v7cEtm.js} +1 -1
- package/src/assets/web-panel/assets/{index-C6q2MkIG.js → index-C6PETyel.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUBkae39.js → index-CUcE6rIa.js} +1 -1
- package/src/assets/web-panel/assets/{index-BwZnPiFd.js → index-CVDcRQuX.js} +1 -1
- package/src/assets/web-panel/assets/{index-CIk7mg6G.js → index-C_dq-6_3.js} +1 -1
- package/src/assets/web-panel/assets/{index-B8a_c_qZ.js → index-CnLXLaEF.js} +1 -1
- package/src/assets/web-panel/assets/{index-CIg4nqyp.js → index-Cpda0SAz.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhvnNFfd.js → index-Cv7vwX3z.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpTbnbUI.js → index-CxJ4NPVq.js} +1 -1
- package/src/assets/web-panel/assets/{index-BzL3LWFX.js → index-D0Z13xcZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-BE_u-rXr.js → index-DDm9m4vC.js} +1 -1
- package/src/assets/web-panel/assets/{index-ByLsH9CL.js → index-DHq2Of6z.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOOsA080.js → index-DNQUdPam.js} +1 -1
- package/src/assets/web-panel/assets/{index-SdFkCtiC.js → index-DX0aHEHL.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bw-ZGQCH.js → index-DX99e8Xy.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkdYB0_g.js → index-DYW4Z3y-.js} +1 -1
- package/src/assets/web-panel/assets/{index-DuVjTDcW.js → index-DfOOACfd.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpReoAGb.js → index-Di9vKira.js} +1 -1
- package/src/assets/web-panel/assets/{index-CwYFDMn_.js → index-DpeV-6y4.js} +1 -1
- package/src/assets/web-panel/assets/{index-DR6TDz5H.js → index-GR3omApz.js} +1 -1
- package/src/assets/web-panel/assets/{index--AdzSlH7.js → index-f1ptYBMr.js} +1 -1
- package/src/assets/web-panel/assets/{index-8QbwhE3i.js → index-iJ6s24bA.js} +1 -1
- package/src/assets/web-panel/assets/index-jLEdASOV.js +1 -0
- package/src/assets/web-panel/assets/index-jesGo4rl.js +1 -0
- package/src/assets/web-panel/assets/{index-B5JRIZXi.js → index-oX216HRH.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CB5cUnQ5.js → initDefaultProps-QD-koeaw.js} +1 -1
- package/src/assets/web-panel/assets/{motion-bn_fWhQV.js → motion-CTItLW4-.js} +1 -1
- package/src/assets/web-panel/assets/{move-DY4-Odtu.js → move-CGsdTOdN.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-B5l5mjoh.js → mtc-parser-C1kW_CNU.js} +1 -1
- package/src/assets/web-panel/assets/{omit-rJ-Ci8CW.js → omit-B4PilaYy.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BMwGJd6C.js → pickAttrs-DBGUE12e.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CaiQyGXG.js → placementArrow-DZxCLjuo.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DQTO5wDH.js → responsiveObserve-CZjQiocN.js} +1 -1
- package/src/assets/web-panel/assets/{slide-Dg6ZDtbz.js → slide-Btz5hh2_.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-bvjrktKc.js → statusUtils-CfuxuLIt.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-C1geIU_x.js → styleChecker-DdO0Obo_.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-BzU3RJRi.js → useFlexGapSupport-CxftJ-Zs.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-B7l6OwLe.js → useFs-pVJChh6K.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BYF1L_8x.js → usePersonalDataHub-wv7Q0uNE.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-6c2GVkT-.js → vnode-DVOVfAqC.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BYAY6QeD.js → zoom-B5tebZuT.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 +68 -15
- package/src/lib/packer/pack-update-applier.js +28 -9
- 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 +56 -7
- 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/scoped-permission-store.js +241 -121
- package/src/lib/settings-authority-domain.cjs +966 -0
- package/src/lib/settings-authority-record.cjs +701 -0
- package/src/lib/settings-loader.cjs +45 -8
- package/src/lib/settings-source-observation.cjs +8 -0
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DaRtY87l.js +0 -1
- package/src/assets/web-panel/assets/MarkdownRenderer-BqlqrJkY.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-8zG8f5li.js +0 -1
- package/src/assets/web-panel/assets/TimelineRenderer-B9LU5qrl.js +0 -1
- package/src/assets/web-panel/assets/devWarning-D50gzPS9.js +0 -1
- package/src/assets/web-panel/assets/index-CgensV-n.js +0 -1
- package/src/assets/web-panel/assets/index-Ctkf06Z_.js +0 -1
|
@@ -13,9 +13,10 @@ import projectRoot from "./project-root.cjs";
|
|
|
13
13
|
import permissionRules from "./permission-rules.cjs";
|
|
14
14
|
import { getMachineSecurityAnchorDir } from "./paths.js";
|
|
15
15
|
import {
|
|
16
|
-
mutateSecurityStore,
|
|
17
16
|
readSecurityStore,
|
|
17
|
+
writeSecurityStore,
|
|
18
18
|
} from "./durable-security-store.js";
|
|
19
|
+
import { withFileLock } from "./with-file-lock.js";
|
|
19
20
|
|
|
20
21
|
const { findGitProjectRoot } = projectRoot;
|
|
21
22
|
const { parseRule } = permissionRules;
|
|
@@ -27,6 +28,58 @@ const RULE_ID_PATTERN = /^spr_[0-9a-f]{32}$/;
|
|
|
27
28
|
const DECISIONS = new Set(["allow", "ask", "deny"]);
|
|
28
29
|
const DEFAULT_MAX_RECORDS = 1000;
|
|
29
30
|
|
|
31
|
+
// Official mutations revoke every provider in this module instance. Path
|
|
32
|
+
// matching alone misses aliases and files that did not exist at admission.
|
|
33
|
+
// This is an in-process event owner, not a cross-process notification journal.
|
|
34
|
+
const ownerId = randomUUID();
|
|
35
|
+
let permissionRevision = Object.freeze({
|
|
36
|
+
ownerId,
|
|
37
|
+
revision: 0,
|
|
38
|
+
state: "ready",
|
|
39
|
+
});
|
|
40
|
+
const revisionListeners = new Set();
|
|
41
|
+
|
|
42
|
+
export function getScopedPermissionRevision() {
|
|
43
|
+
if (permissionRevision.state !== "ready") {
|
|
44
|
+
throw scopedPermissionError(
|
|
45
|
+
"CC_SCOPED_PERMISSION_AUTHORITY_UNAVAILABLE",
|
|
46
|
+
"Scoped permission authority is unavailable",
|
|
47
|
+
);
|
|
48
|
+
}
|
|
49
|
+
return permissionRevision;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export function subscribeScopedPermissionRevision(listener) {
|
|
53
|
+
if (typeof listener !== "function")
|
|
54
|
+
throw new TypeError(
|
|
55
|
+
"scoped permission revision listener must be a function",
|
|
56
|
+
);
|
|
57
|
+
revisionListeners.add(listener);
|
|
58
|
+
return () => revisionListeners.delete(listener);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function beginMutation() {
|
|
62
|
+
getScopedPermissionRevision();
|
|
63
|
+
const revision = permissionRevision.revision + 1;
|
|
64
|
+
permissionRevision = Object.freeze({
|
|
65
|
+
ownerId,
|
|
66
|
+
revision,
|
|
67
|
+
state: Number.isSafeInteger(revision) ? "mutating" : "invalid",
|
|
68
|
+
});
|
|
69
|
+
for (const listener of [...revisionListeners]) {
|
|
70
|
+
try {
|
|
71
|
+
listener(permissionRevision);
|
|
72
|
+
} catch {
|
|
73
|
+
// One observer must not prevent the other shells from being revoked.
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
if (permissionRevision.state === "invalid") getScopedPermissionRevision();
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
function finishMutation(state) {
|
|
80
|
+
permissionRevision = Object.freeze({ ...permissionRevision, state });
|
|
81
|
+
}
|
|
82
|
+
|
|
30
83
|
export const SCOPED_PERMISSION_ERROR_CODES = Object.freeze({
|
|
31
84
|
INVALID: "CC_SCOPED_PERMISSION_INVALID",
|
|
32
85
|
CONFLICT: "CC_SCOPED_PERMISSION_CONFLICT",
|
|
@@ -90,7 +143,7 @@ function validateRecord(record, seenIds) {
|
|
|
90
143
|
seenIds.has(record.id) ||
|
|
91
144
|
!DECISIONS.has(record.decision) ||
|
|
92
145
|
!parseRule(record.rule) ||
|
|
93
|
-
!Number.
|
|
146
|
+
!Number.isSafeInteger(record.revision) ||
|
|
94
147
|
record.revision < 1 ||
|
|
95
148
|
!Number.isFinite(record.createdAt) ||
|
|
96
149
|
!Number.isFinite(record.expiresAt) ||
|
|
@@ -112,7 +165,7 @@ function validateState(value, workspace) {
|
|
|
112
165
|
!value ||
|
|
113
166
|
value.schema !== STATE_SCHEMA ||
|
|
114
167
|
value.schemaVersion !== STATE_SCHEMA_VERSION ||
|
|
115
|
-
!Number.
|
|
168
|
+
!Number.isSafeInteger(value.generation) ||
|
|
116
169
|
value.generation < 0 ||
|
|
117
170
|
!value.workspace ||
|
|
118
171
|
value.workspace.id !== workspace.id ||
|
|
@@ -135,6 +188,16 @@ function statusFor(record, now) {
|
|
|
135
188
|
return "active";
|
|
136
189
|
}
|
|
137
190
|
|
|
191
|
+
function nextVersion(value, label) {
|
|
192
|
+
if (value === Number.MAX_SAFE_INTEGER) {
|
|
193
|
+
throw scopedPermissionError(
|
|
194
|
+
SCOPED_PERMISSION_ERROR_CODES.INVALID,
|
|
195
|
+
`Scoped permission ${label} is exhausted`,
|
|
196
|
+
);
|
|
197
|
+
}
|
|
198
|
+
return value + 1;
|
|
199
|
+
}
|
|
200
|
+
|
|
138
201
|
function projectRecord(record, now, filePath) {
|
|
139
202
|
return Object.freeze({
|
|
140
203
|
...structuredClone(record),
|
|
@@ -181,24 +244,89 @@ export class ScopedPermissionStore {
|
|
|
181
244
|
this._randomId = randomId;
|
|
182
245
|
}
|
|
183
246
|
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
247
|
+
_capture() {
|
|
248
|
+
const revision = getScopedPermissionRevision();
|
|
249
|
+
const captured = Object.freeze({
|
|
250
|
+
filePath: path.resolve(this.filePath),
|
|
251
|
+
workspace: Object.freeze({ ...this.workspace }),
|
|
252
|
+
now: this._now.bind(this),
|
|
253
|
+
randomId: this._randomId.bind(this),
|
|
254
|
+
maxRecords: this.maxRecords,
|
|
255
|
+
lock: this._lock || withFileLock,
|
|
256
|
+
});
|
|
257
|
+
if (getScopedPermissionRevision() !== revision)
|
|
258
|
+
throw scopedPermissionError(
|
|
259
|
+
"CC_SCOPED_PERMISSION_AUTHORITY_CHANGED",
|
|
260
|
+
"Scoped permission authority changed while capturing the operation",
|
|
261
|
+
);
|
|
262
|
+
return captured;
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
_read(captured = this._capture()) {
|
|
266
|
+
getScopedPermissionRevision();
|
|
267
|
+
const stored = readSecurityStore(captured.filePath, STORE_LABEL);
|
|
268
|
+
if (Object.keys(stored).length === 0) return emptyState(captured.workspace);
|
|
269
|
+
return validateState(stored, captured.workspace);
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
_mutate(captured, update) {
|
|
273
|
+
getScopedPermissionRevision(); // Reject observer reentry before the lock.
|
|
274
|
+
fs.mkdirSync(path.dirname(captured.filePath), {
|
|
275
|
+
recursive: true,
|
|
276
|
+
mode: 0o700,
|
|
277
|
+
});
|
|
278
|
+
let started = false;
|
|
279
|
+
let committed = false;
|
|
280
|
+
try {
|
|
281
|
+
captured.lock(
|
|
282
|
+
captured.filePath,
|
|
283
|
+
() => {
|
|
284
|
+
const draft = this._read(captured);
|
|
285
|
+
if (update(draft) === false) return; // Idempotent revoke does no I/O.
|
|
286
|
+
validateState(draft, captured.workspace);
|
|
287
|
+
started = true;
|
|
288
|
+
beginMutation();
|
|
289
|
+
// No draft field aliases the public instance during notification.
|
|
290
|
+
validateState(draft, captured.workspace);
|
|
291
|
+
writeSecurityStore(captured.filePath, STORE_LABEL, draft);
|
|
292
|
+
committed = true;
|
|
293
|
+
},
|
|
294
|
+
{ timeoutMs: 2000, staleMs: 30000, failIfUnavailable: true },
|
|
295
|
+
);
|
|
296
|
+
if (started) finishMutation("ready");
|
|
297
|
+
} catch (error) {
|
|
298
|
+
if (!error.commitState)
|
|
299
|
+
error.commitState = committed ? "committed" : "not-committed";
|
|
300
|
+
if (started)
|
|
301
|
+
finishMutation(
|
|
302
|
+
error.commitState === "unknown" ||
|
|
303
|
+
permissionRevision.state === "invalid"
|
|
304
|
+
? "invalid"
|
|
305
|
+
: "ready",
|
|
306
|
+
);
|
|
307
|
+
throw error;
|
|
308
|
+
}
|
|
188
309
|
}
|
|
189
310
|
|
|
190
311
|
list() {
|
|
191
|
-
const
|
|
192
|
-
const
|
|
312
|
+
const revision = getScopedPermissionRevision();
|
|
313
|
+
const captured = this._capture();
|
|
314
|
+
const state = this._read(captured);
|
|
315
|
+
const now = captured.now();
|
|
316
|
+
if (getScopedPermissionRevision() !== revision)
|
|
317
|
+
throw scopedPermissionError(
|
|
318
|
+
"CC_SCOPED_PERMISSION_AUTHORITY_CHANGED",
|
|
319
|
+
"Scoped permission authority changed while reading rules",
|
|
320
|
+
);
|
|
193
321
|
return Object.freeze({
|
|
194
322
|
schema: state.schema,
|
|
195
323
|
schemaVersion: state.schemaVersion,
|
|
196
324
|
generation: state.generation,
|
|
197
325
|
updatedAt: state.updatedAt,
|
|
198
326
|
workspace: structuredClone(state.workspace),
|
|
199
|
-
file:
|
|
327
|
+
file: captured.filePath,
|
|
200
328
|
rules: state.rules.map((record) =>
|
|
201
|
-
projectRecord(record, now,
|
|
329
|
+
projectRecord(record, now, captured.filePath),
|
|
202
330
|
),
|
|
203
331
|
});
|
|
204
332
|
}
|
|
@@ -210,12 +338,13 @@ export class ScopedPermissionStore {
|
|
|
210
338
|
reason = "",
|
|
211
339
|
expectedGeneration = null,
|
|
212
340
|
} = {}) {
|
|
341
|
+
const captured = this._capture();
|
|
213
342
|
const normalizedDecision = String(decision || "")
|
|
214
343
|
.trim()
|
|
215
344
|
.toLowerCase();
|
|
216
345
|
const normalizedRule = String(rule || "").trim();
|
|
217
346
|
const normalizedReason = String(reason || "").trim();
|
|
218
|
-
const now =
|
|
347
|
+
const now = captured.now();
|
|
219
348
|
const expiry = Number(expiresAt);
|
|
220
349
|
if (!DECISIONS.has(normalizedDecision)) {
|
|
221
350
|
throw scopedPermissionError(
|
|
@@ -243,71 +372,67 @@ export class ScopedPermissionStore {
|
|
|
243
372
|
}
|
|
244
373
|
|
|
245
374
|
let created = null;
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
`Scoped permission generation changed (expected ${expectedGeneration}, current ${current.generation})`,
|
|
261
|
-
);
|
|
262
|
-
}
|
|
375
|
+
this._mutate(captured, (draft) => {
|
|
376
|
+
const current =
|
|
377
|
+
Object.keys(draft).length === 0
|
|
378
|
+
? emptyState(captured.workspace)
|
|
379
|
+
: validateState(draft, captured.workspace);
|
|
380
|
+
if (
|
|
381
|
+
expectedGeneration !== null &&
|
|
382
|
+
Number(expectedGeneration) !== current.generation
|
|
383
|
+
) {
|
|
384
|
+
throw scopedPermissionError(
|
|
385
|
+
SCOPED_PERMISSION_ERROR_CODES.CONFLICT,
|
|
386
|
+
`Scoped permission generation changed (expected ${expectedGeneration}, current ${current.generation})`,
|
|
387
|
+
);
|
|
388
|
+
}
|
|
263
389
|
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
);
|
|
307
|
-
return projectRecord(created, now, this.filePath);
|
|
390
|
+
const retained = [...current.rules];
|
|
391
|
+
while (retained.length >= captured.maxRecords) {
|
|
392
|
+
const inactiveIndex = retained.findIndex(
|
|
393
|
+
(record) => statusFor(record, now) !== "active",
|
|
394
|
+
);
|
|
395
|
+
if (inactiveIndex < 0) break;
|
|
396
|
+
retained.splice(inactiveIndex, 1);
|
|
397
|
+
}
|
|
398
|
+
if (retained.length >= captured.maxRecords) {
|
|
399
|
+
throw scopedPermissionError(
|
|
400
|
+
SCOPED_PERMISSION_ERROR_CODES.INVALID,
|
|
401
|
+
`Scoped permission store is full (${captured.maxRecords} retained rules)`,
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
const id = captured.randomId();
|
|
405
|
+
if (
|
|
406
|
+
!RULE_ID_PATTERN.test(id) ||
|
|
407
|
+
retained.some((item) => item.id === id)
|
|
408
|
+
) {
|
|
409
|
+
throw scopedPermissionError(
|
|
410
|
+
SCOPED_PERMISSION_ERROR_CODES.INVALID,
|
|
411
|
+
"Could not allocate a valid unique scoped permission id",
|
|
412
|
+
);
|
|
413
|
+
}
|
|
414
|
+
created = {
|
|
415
|
+
id,
|
|
416
|
+
decision: normalizedDecision,
|
|
417
|
+
rule: normalizedRule,
|
|
418
|
+
revision: 1,
|
|
419
|
+
createdAt: now,
|
|
420
|
+
expiresAt: expiry,
|
|
421
|
+
revokedAt: null,
|
|
422
|
+
reason: normalizedReason,
|
|
423
|
+
};
|
|
424
|
+
Object.assign(draft, current, {
|
|
425
|
+
generation: nextVersion(current.generation, "generation"),
|
|
426
|
+
updatedAt: now,
|
|
427
|
+
rules: [...retained, created],
|
|
428
|
+
});
|
|
429
|
+
validateState(draft, captured.workspace);
|
|
430
|
+
});
|
|
431
|
+
return projectRecord(created, now, captured.filePath);
|
|
308
432
|
}
|
|
309
433
|
|
|
310
434
|
revoke({ id, expectedRevision = null } = {}) {
|
|
435
|
+
const captured = this._capture();
|
|
311
436
|
const normalizedId = String(id || "").trim();
|
|
312
437
|
if (!RULE_ID_PATTERN.test(normalizedId)) {
|
|
313
438
|
throw scopedPermissionError(
|
|
@@ -315,55 +440,50 @@ export class ScopedPermissionStore {
|
|
|
315
440
|
"A valid scoped permission id is required",
|
|
316
441
|
);
|
|
317
442
|
}
|
|
318
|
-
const now =
|
|
443
|
+
const now = captured.now();
|
|
319
444
|
let revoked = null;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
445
|
+
this._mutate(captured, (draft) => {
|
|
446
|
+
const current =
|
|
447
|
+
Object.keys(draft).length === 0
|
|
448
|
+
? emptyState(captured.workspace)
|
|
449
|
+
: validateState(draft, captured.workspace);
|
|
450
|
+
const index = current.rules.findIndex(
|
|
451
|
+
(record) => record.id === normalizedId,
|
|
452
|
+
);
|
|
453
|
+
if (index < 0) {
|
|
454
|
+
throw scopedPermissionError(
|
|
455
|
+
SCOPED_PERMISSION_ERROR_CODES.NOT_FOUND,
|
|
456
|
+
`Scoped permission rule not found: ${normalizedId}`,
|
|
330
457
|
);
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
expectedRevision
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
346
|
-
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
rules,
|
|
362
|
-
});
|
|
363
|
-
validateState(draft, this.workspace);
|
|
364
|
-
},
|
|
365
|
-
this._lock ? { lock: this._lock } : undefined,
|
|
366
|
-
);
|
|
367
|
-
return projectRecord(revoked, now, this.filePath);
|
|
458
|
+
}
|
|
459
|
+
const record = current.rules[index];
|
|
460
|
+
if (
|
|
461
|
+
expectedRevision !== null &&
|
|
462
|
+
Number(expectedRevision) !== record.revision
|
|
463
|
+
) {
|
|
464
|
+
throw scopedPermissionError(
|
|
465
|
+
SCOPED_PERMISSION_ERROR_CODES.CONFLICT,
|
|
466
|
+
`Scoped permission revision changed (expected ${expectedRevision}, current ${record.revision})`,
|
|
467
|
+
);
|
|
468
|
+
}
|
|
469
|
+
if (record.revokedAt !== null) {
|
|
470
|
+
revoked = record;
|
|
471
|
+
return false;
|
|
472
|
+
}
|
|
473
|
+
revoked = {
|
|
474
|
+
...record,
|
|
475
|
+
revision: nextVersion(record.revision, "revision"),
|
|
476
|
+
revokedAt: now,
|
|
477
|
+
};
|
|
478
|
+
const rules = [...current.rules];
|
|
479
|
+
rules[index] = revoked;
|
|
480
|
+
Object.assign(draft, current, {
|
|
481
|
+
generation: nextVersion(current.generation, "generation"),
|
|
482
|
+
updatedAt: now,
|
|
483
|
+
rules,
|
|
484
|
+
});
|
|
485
|
+
validateState(draft, captured.workspace);
|
|
486
|
+
});
|
|
487
|
+
return projectRecord(revoked, now, captured.filePath);
|
|
368
488
|
}
|
|
369
489
|
}
|