chainlesschain 0.166.83 → 0.166.84
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/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-OYLnAlB5.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-DxsRk5n1.js → ActionButton-uc9kbLR6.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-D-7EvH4S.js → Analytics-BH901FG0.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-DDEWVr-d.js → AppLayout-qypKb8GL.js} +4 -4
- package/src/assets/web-panel/assets/{Artifacts-CAlJAjRB.js → Artifacts-Q-uPWyLY.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-tuaWDOAM.js → Audit-B8hMGMim.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-CIwRFJ50.js → BackgroundAgents-BSs8rJL_.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CpBepJlH.js → Backup-2YMU_nCd.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-eKguCzQc.js → BaseInput-DyMppTuT.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-CFrUfXDV.js → Chat-B5QGMaDO.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-B8ts_JnN.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-o5FEn6a1.js → Checkbox-DAvtgEGd.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-Dih8HogC.js → Codegen-BSChEVM3.js} +1 -1
- package/src/assets/web-panel/assets/{Col-D5r-6WZe.js → Col-G0zYohIP.js} +1 -1
- package/src/assets/web-panel/assets/{Community-jeQ6bGey.js → Community-CzCN0B_p.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-CEP7QsWp.js → Compact-DrIN3G8z.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-B8NydOHd.js → Compliance-CIsLa64Z.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-Bmi1zuPy.js → Cowork-CGYoafsB.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-BAL9bwSp.js → Cron-DJ1JPXuH.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-D8WUi2dS.js → Crosschain-B-IqVFJl.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CQCkYcLY.js → DID-CAAczPz_.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-DogXg6kh.js → Dashboard-Dkpel8VE.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BJBJyQJn.js → Dropdown-ETNBf8-Z.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CxPWquJx.js → EmailListRenderer-CJGDyjVD.js} +1 -1
- package/src/assets/web-panel/assets/{EvolutionSettings-BijH1x8G.js → EvolutionSettings-CL_hvVYe.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DStwf-00.js → FamilyGuardDashboard-iexOh6Dh.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-LU9_sNxC.js → Federation-DnDHvV5D.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-DNfWhj7P.js → FormItemContext-DLCDJjUp.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-DWYsPjzk.js → GenericCardRenderer-CaCgXCjS.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CA0O9ulB.js → Git-BVm-euUF.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-ECShL7BJ.js → Governance-DPnUvLWr.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-9GA2542e.js → Inference-5qCWSYKl.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-nC5mTbEH.js → KnowledgeGraph-DXdv2oEh.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-B7IqgnDy.js → Logs-BF2f7bBA.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-qNJS3ygh.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-CEr0suM8.js → Marketplace-Bp-0Y7Ng.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DhTPQKOE.js → McpTools-dm-Xdi63.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-mHRZP1w4.js → Memory-TBHK031z.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BNfX6Rdo.js → MobileBridge-BTILAlds.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-8zG8f5li.js → MobileProjects-DC0Clgzs.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-CzkV3wsn.js → Mtc-BvyWkmRw.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-DWYljJik.js → MtcAudit-D7ksLoNa.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-kHgtvZY_.js → Multisig-Dpzu7X1t.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-56uQiPiY.js → NLProgramming-DZ0ADHSB.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-DxGOZqRf.js → Notes-hIdkmRug.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-DabaCpmo.js → NotificationSettings-R3jyP6Vf.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-SzeQNlGT.js → OrderTableRenderer-d1zrFkqx.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-D9gnPpK2.js → Organization-DoA5WMcc.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-Cqp_bXm4.js → Overflow-DHjIloAQ.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-DTeK-r0G.js → P2P-2TMon_Wy.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CsjG85GT.js → PdhVaultBrowser-DEUxGSWO.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-B4ofZq3j.js → Permissions-CxKcZ-8X.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-Cvas5cYd.js → PersonalDataHub-CcYtkHqc.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-Er3wmAew.js → Pipeline-BxwOuq22.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-DMFYeZox.js → Privacy-VIx9pdsH.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-DaVaYPy0.js → ProjectInit-COjp_erK.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-dPx5q2Ac.js → ProjectSettings-x9NrZU80.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-N0JNeCui.js → Projects-DMgkw7QJ.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-BPQo_gQa.js → Providers-B_fR7Y7Y.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CD-sEsWh.js → QrScannerModal-BUvzrn3V.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-LOKRJiN_.js → QuickAsk-C0sJwLy2.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BwPXqcWy.js → Recommend-CJC7Wpce.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-BjotndxF.js → RemoteSession-B62J6ZP0.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-BlZ2gcbU.js → Reputation-4f48kVBn.js} +1 -1
- package/src/assets/web-panel/assets/{Row-D399HM-d.js → Row-EzEMaYoV.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C7H-ibnY.js → RssFeed-Dy0wD83j.js} +3 -3
- package/src/assets/web-panel/assets/{Search-CNHCVmM1.js → Search-GeVRHQND.js} +1 -1
- package/src/assets/web-panel/assets/{Security-C70eDGYY.js → Security-omLr1J7w.js} +3 -3
- package/src/assets/web-panel/assets/{Services-6xfq9G-H.js → Services-DmGPaDAs.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BbY2zeZV.js → Skeleton-CAla21Gm.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-DYtyZ7_6.js → Skills-QqEyIKOI.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-CkWcl5OH.js → Sla-Cwi15-pZ.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CiknYk2P.js → SpeechSettings-Cs8Zi1J8.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-D-8JVip4.js → SyncSettings-ipuJsDKP.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-B2KnR9XC.js → Tasks-BbVAg9Z7.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CipXkOE-.js → Templates-slUp9yXF.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-DbwgIMmx.js → Tenant-6edCSG5K.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-OtCriHEE.js → Terminal-DKq84G3h.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-B9LU5qrl.js → TimelineRenderer-Dg0Aw6se.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-B4iFVxvH.js → Tokens-D63h7qYd.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BegM2v8q.js → Trigger-F7gTP_gv.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-COkqnnFj.js → Trust-zw8Zqp8k.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-BzqKPChj.js → UkeySign-C-YBUmod.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-D-F2XL3C.js → VideoEditing-AhVxTgmC.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-22PzRY9T.js → Wallet-C58zxjIg.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-eSMq_qtz.js → WebAuthn-D-n-rlfI.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-BvdAakpq.js → WorkflowEditor-Ddc8gGD4.js} +1 -1
- package/src/assets/web-panel/assets/{chat-C6jhpF7E.js → chat-C66V_U7w.js} +1 -1
- package/src/assets/web-panel/assets/{colors-BbbW2Pfv.js → colors-BcJyVZRj.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-CVFeLTqt.js → compact-item-UgUXWcgP.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-DS0m7NZc.js → createContext-oxEKHKYP.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-BoDvrsgY.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DVouP0t6.js → hasIn-CbqzrKQO.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPS8E-J6.js → index-7lSY8B9T.js} +1 -1
- package/src/assets/web-panel/assets/{index-C4xmLgLa.js → index-B-h0Jgtj.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvCgzl0X.js → index-BMOY4PSW.js} +1 -1
- package/src/assets/web-panel/assets/{index-DR6TDz5H.js → index-BNIq0NAn.js} +1 -1
- package/src/assets/web-panel/assets/{index-SdFkCtiC.js → index-BQlGMcZ9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BrIv7C9a.js → index-BT2PZ9df.js} +1 -1
- package/src/assets/web-panel/assets/{index-DotRL126.js → index-BZ7mv_oS.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ds4wbrgk.js → index-BdFgUn1S.js} +1 -1
- package/src/assets/web-panel/assets/index-BhqRbl5P.js +1 -0
- package/src/assets/web-panel/assets/{index--AdzSlH7.js → index-Bmhw_Ler.js} +1 -1
- package/src/assets/web-panel/assets/{index-B5JRIZXi.js → index-Bp9rfr-P.js} +1 -1
- package/src/assets/web-panel/assets/{index-B8a_c_qZ.js → index-BpqJNeIj.js} +1 -1
- package/src/assets/web-panel/assets/{index-DUBkae39.js → index-BrWZmUdA.js} +1 -1
- package/src/assets/web-panel/assets/{index-BE_u-rXr.js → index-Bu1A2sXy.js} +1 -1
- package/src/assets/web-panel/assets/{index-BOOsA080.js → index-C-p9hhAz.js} +1 -1
- package/src/assets/web-panel/assets/{index-CkdYB0_g.js → index-C3j9jTfB.js} +1 -1
- package/src/assets/web-panel/assets/{index-DGYX17YW.js → index-C6X26RJu.js} +1 -1
- package/src/assets/web-panel/assets/{index-BXw6ivB8.js → index-CH13yc9x.js} +1 -1
- package/src/assets/web-panel/assets/{index-BwZnPiFd.js → index-CJluGB2F.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpTbnbUI.js → index-CM2S5TJZ.js} +1 -1
- package/src/assets/web-panel/assets/{index-eaXEQFuP.js → index-CUmYwZyf.js} +1 -1
- package/src/assets/web-panel/assets/{index-DuVjTDcW.js → index-CrG-1Kqs.js} +1 -1
- package/src/assets/web-panel/assets/{index-CIk7mg6G.js → index-Crqqbu-W.js} +1 -1
- package/src/assets/web-panel/assets/{index-Be12HoCS.js → index-CuZupgUw.js} +3 -3
- package/src/assets/web-panel/assets/{index-JeJqXbUU.js → index-D3xNhHF3.js} +1 -1
- package/src/assets/web-panel/assets/{index-BwozzG6k.js → index-D8rgeCoC.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpReoAGb.js → index-DAIuMvVR.js} +1 -1
- package/src/assets/web-panel/assets/{index-CIg4nqyp.js → index-DBEL07sa.js} +1 -1
- package/src/assets/web-panel/assets/{index-ByLsH9CL.js → index-DMbJ06sF.js} +1 -1
- package/src/assets/web-panel/assets/{index-54aHB4FL.js → index-DO-5vgTW.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaUah6TO.js → index-DR4YKlLt.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bw-ZGQCH.js → index-DRrxJygS.js} +1 -1
- package/src/assets/web-panel/assets/{index-C74emdKD.js → index-DWFNv_EG.js} +1 -1
- package/src/assets/web-panel/assets/index-DreT6MIR.js +1 -0
- package/src/assets/web-panel/assets/{index-C6q2MkIG.js → index-Ov77O9mH.js} +1 -1
- package/src/assets/web-panel/assets/{index-CwYFDMn_.js → index-RnxuL8wM.js} +1 -1
- package/src/assets/web-panel/assets/{index-BzL3LWFX.js → index-SzGgAqai.js} +1 -1
- package/src/assets/web-panel/assets/{index-BhvnNFfd.js → index-UpN_kC9R.js} +1 -1
- package/src/assets/web-panel/assets/{index-8QbwhE3i.js → index-YvNh1UdB.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-CB5cUnQ5.js → initDefaultProps-BK9ncFiF.js} +1 -1
- package/src/assets/web-panel/assets/{motion-bn_fWhQV.js → motion-BxzXi1Pe.js} +1 -1
- package/src/assets/web-panel/assets/{move-DY4-Odtu.js → move-CRmwRuah.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-B5l5mjoh.js → mtc-parser-BQq5_Znx.js} +1 -1
- package/src/assets/web-panel/assets/{omit-rJ-Ci8CW.js → omit-Ch4VNY2j.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BMwGJd6C.js → pickAttrs-CUioBhrz.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CaiQyGXG.js → placementArrow-Hj3o1CrW.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-DQTO5wDH.js → responsiveObserve-PfqJrlkI.js} +1 -1
- package/src/assets/web-panel/assets/{slide-Dg6ZDtbz.js → slide-BXcnR-Lu.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-bvjrktKc.js → statusUtils-C74a-ofo.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-C1geIU_x.js → styleChecker-Bq57I3ls.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-BzU3RJRi.js → useFlexGapSupport-ITPUjcDw.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-B7l6OwLe.js → useFs-DzPuXDIS.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BYF1L_8x.js → usePersonalDataHub-DYSgt9L5.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-6c2GVkT-.js → vnode-NPs_EJEk.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BYAY6QeD.js → zoom-DZzO6KVS.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/data/changelog.json +13 -0
- package/src/lib/permission-authority.js +43 -2
- package/src/lib/scoped-permission-store.js +229 -119
- package/src/lib/settings-authority-domain.cjs +966 -0
- package/src/lib/settings-authority-record.cjs +701 -0
- 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/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",
|
|
@@ -181,24 +234,89 @@ export class ScopedPermissionStore {
|
|
|
181
234
|
this._randomId = randomId;
|
|
182
235
|
}
|
|
183
236
|
|
|
184
|
-
|
|
185
|
-
const
|
|
186
|
-
|
|
187
|
-
|
|
237
|
+
_capture() {
|
|
238
|
+
const revision = getScopedPermissionRevision();
|
|
239
|
+
const captured = Object.freeze({
|
|
240
|
+
filePath: path.resolve(this.filePath),
|
|
241
|
+
workspace: Object.freeze({ ...this.workspace }),
|
|
242
|
+
now: this._now.bind(this),
|
|
243
|
+
randomId: this._randomId.bind(this),
|
|
244
|
+
maxRecords: this.maxRecords,
|
|
245
|
+
lock: this._lock || withFileLock,
|
|
246
|
+
});
|
|
247
|
+
if (getScopedPermissionRevision() !== revision)
|
|
248
|
+
throw scopedPermissionError(
|
|
249
|
+
"CC_SCOPED_PERMISSION_AUTHORITY_CHANGED",
|
|
250
|
+
"Scoped permission authority changed while capturing the operation",
|
|
251
|
+
);
|
|
252
|
+
return captured;
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
_read(captured = this._capture()) {
|
|
256
|
+
getScopedPermissionRevision();
|
|
257
|
+
const stored = readSecurityStore(captured.filePath, STORE_LABEL);
|
|
258
|
+
if (Object.keys(stored).length === 0) return emptyState(captured.workspace);
|
|
259
|
+
return validateState(stored, captured.workspace);
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
_mutate(captured, update) {
|
|
263
|
+
getScopedPermissionRevision(); // Reject observer reentry before the lock.
|
|
264
|
+
fs.mkdirSync(path.dirname(captured.filePath), {
|
|
265
|
+
recursive: true,
|
|
266
|
+
mode: 0o700,
|
|
267
|
+
});
|
|
268
|
+
let started = false;
|
|
269
|
+
let committed = false;
|
|
270
|
+
try {
|
|
271
|
+
captured.lock(
|
|
272
|
+
captured.filePath,
|
|
273
|
+
() => {
|
|
274
|
+
const draft = this._read(captured);
|
|
275
|
+
if (update(draft) === false) return; // Idempotent revoke does no I/O.
|
|
276
|
+
validateState(draft, captured.workspace);
|
|
277
|
+
started = true;
|
|
278
|
+
beginMutation();
|
|
279
|
+
// No draft field aliases the public instance during notification.
|
|
280
|
+
validateState(draft, captured.workspace);
|
|
281
|
+
writeSecurityStore(captured.filePath, STORE_LABEL, draft);
|
|
282
|
+
committed = true;
|
|
283
|
+
},
|
|
284
|
+
{ timeoutMs: 2000, staleMs: 30000, failIfUnavailable: true },
|
|
285
|
+
);
|
|
286
|
+
if (started) finishMutation("ready");
|
|
287
|
+
} catch (error) {
|
|
288
|
+
if (!error.commitState)
|
|
289
|
+
error.commitState = committed ? "committed" : "not-committed";
|
|
290
|
+
if (started)
|
|
291
|
+
finishMutation(
|
|
292
|
+
error.commitState === "unknown" ||
|
|
293
|
+
permissionRevision.state === "invalid"
|
|
294
|
+
? "invalid"
|
|
295
|
+
: "ready",
|
|
296
|
+
);
|
|
297
|
+
throw error;
|
|
298
|
+
}
|
|
188
299
|
}
|
|
189
300
|
|
|
190
301
|
list() {
|
|
191
|
-
const
|
|
192
|
-
const
|
|
302
|
+
const revision = getScopedPermissionRevision();
|
|
303
|
+
const captured = this._capture();
|
|
304
|
+
const state = this._read(captured);
|
|
305
|
+
const now = captured.now();
|
|
306
|
+
if (getScopedPermissionRevision() !== revision)
|
|
307
|
+
throw scopedPermissionError(
|
|
308
|
+
"CC_SCOPED_PERMISSION_AUTHORITY_CHANGED",
|
|
309
|
+
"Scoped permission authority changed while reading rules",
|
|
310
|
+
);
|
|
193
311
|
return Object.freeze({
|
|
194
312
|
schema: state.schema,
|
|
195
313
|
schemaVersion: state.schemaVersion,
|
|
196
314
|
generation: state.generation,
|
|
197
315
|
updatedAt: state.updatedAt,
|
|
198
316
|
workspace: structuredClone(state.workspace),
|
|
199
|
-
file:
|
|
317
|
+
file: captured.filePath,
|
|
200
318
|
rules: state.rules.map((record) =>
|
|
201
|
-
projectRecord(record, now,
|
|
319
|
+
projectRecord(record, now, captured.filePath),
|
|
202
320
|
),
|
|
203
321
|
});
|
|
204
322
|
}
|
|
@@ -210,12 +328,13 @@ export class ScopedPermissionStore {
|
|
|
210
328
|
reason = "",
|
|
211
329
|
expectedGeneration = null,
|
|
212
330
|
} = {}) {
|
|
331
|
+
const captured = this._capture();
|
|
213
332
|
const normalizedDecision = String(decision || "")
|
|
214
333
|
.trim()
|
|
215
334
|
.toLowerCase();
|
|
216
335
|
const normalizedRule = String(rule || "").trim();
|
|
217
336
|
const normalizedReason = String(reason || "").trim();
|
|
218
|
-
const now =
|
|
337
|
+
const now = captured.now();
|
|
219
338
|
const expiry = Number(expiresAt);
|
|
220
339
|
if (!DECISIONS.has(normalizedDecision)) {
|
|
221
340
|
throw scopedPermissionError(
|
|
@@ -243,71 +362,67 @@ export class ScopedPermissionStore {
|
|
|
243
362
|
}
|
|
244
363
|
|
|
245
364
|
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
|
-
}
|
|
365
|
+
this._mutate(captured, (draft) => {
|
|
366
|
+
const current =
|
|
367
|
+
Object.keys(draft).length === 0
|
|
368
|
+
? emptyState(captured.workspace)
|
|
369
|
+
: validateState(draft, captured.workspace);
|
|
370
|
+
if (
|
|
371
|
+
expectedGeneration !== null &&
|
|
372
|
+
Number(expectedGeneration) !== current.generation
|
|
373
|
+
) {
|
|
374
|
+
throw scopedPermissionError(
|
|
375
|
+
SCOPED_PERMISSION_ERROR_CODES.CONFLICT,
|
|
376
|
+
`Scoped permission generation changed (expected ${expectedGeneration}, current ${current.generation})`,
|
|
377
|
+
);
|
|
378
|
+
}
|
|
263
379
|
|
|
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);
|
|
380
|
+
const retained = [...current.rules];
|
|
381
|
+
while (retained.length >= captured.maxRecords) {
|
|
382
|
+
const inactiveIndex = retained.findIndex(
|
|
383
|
+
(record) => statusFor(record, now) !== "active",
|
|
384
|
+
);
|
|
385
|
+
if (inactiveIndex < 0) break;
|
|
386
|
+
retained.splice(inactiveIndex, 1);
|
|
387
|
+
}
|
|
388
|
+
if (retained.length >= captured.maxRecords) {
|
|
389
|
+
throw scopedPermissionError(
|
|
390
|
+
SCOPED_PERMISSION_ERROR_CODES.INVALID,
|
|
391
|
+
`Scoped permission store is full (${captured.maxRecords} retained rules)`,
|
|
392
|
+
);
|
|
393
|
+
}
|
|
394
|
+
const id = captured.randomId();
|
|
395
|
+
if (
|
|
396
|
+
!RULE_ID_PATTERN.test(id) ||
|
|
397
|
+
retained.some((item) => item.id === id)
|
|
398
|
+
) {
|
|
399
|
+
throw scopedPermissionError(
|
|
400
|
+
SCOPED_PERMISSION_ERROR_CODES.INVALID,
|
|
401
|
+
"Could not allocate a valid unique scoped permission id",
|
|
402
|
+
);
|
|
403
|
+
}
|
|
404
|
+
created = {
|
|
405
|
+
id,
|
|
406
|
+
decision: normalizedDecision,
|
|
407
|
+
rule: normalizedRule,
|
|
408
|
+
revision: 1,
|
|
409
|
+
createdAt: now,
|
|
410
|
+
expiresAt: expiry,
|
|
411
|
+
revokedAt: null,
|
|
412
|
+
reason: normalizedReason,
|
|
413
|
+
};
|
|
414
|
+
Object.assign(draft, current, {
|
|
415
|
+
generation: current.generation + 1,
|
|
416
|
+
updatedAt: now,
|
|
417
|
+
rules: [...retained, created],
|
|
418
|
+
});
|
|
419
|
+
validateState(draft, captured.workspace);
|
|
420
|
+
});
|
|
421
|
+
return projectRecord(created, now, captured.filePath);
|
|
308
422
|
}
|
|
309
423
|
|
|
310
424
|
revoke({ id, expectedRevision = null } = {}) {
|
|
425
|
+
const captured = this._capture();
|
|
311
426
|
const normalizedId = String(id || "").trim();
|
|
312
427
|
if (!RULE_ID_PATTERN.test(normalizedId)) {
|
|
313
428
|
throw scopedPermissionError(
|
|
@@ -315,55 +430,50 @@ export class ScopedPermissionStore {
|
|
|
315
430
|
"A valid scoped permission id is required",
|
|
316
431
|
);
|
|
317
432
|
}
|
|
318
|
-
const now =
|
|
433
|
+
const now = captured.now();
|
|
319
434
|
let revoked = null;
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
435
|
+
this._mutate(captured, (draft) => {
|
|
436
|
+
const current =
|
|
437
|
+
Object.keys(draft).length === 0
|
|
438
|
+
? emptyState(captured.workspace)
|
|
439
|
+
: validateState(draft, captured.workspace);
|
|
440
|
+
const index = current.rules.findIndex(
|
|
441
|
+
(record) => record.id === normalizedId,
|
|
442
|
+
);
|
|
443
|
+
if (index < 0) {
|
|
444
|
+
throw scopedPermissionError(
|
|
445
|
+
SCOPED_PERMISSION_ERROR_CODES.NOT_FOUND,
|
|
446
|
+
`Scoped permission rule not found: ${normalizedId}`,
|
|
330
447
|
);
|
|
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);
|
|
448
|
+
}
|
|
449
|
+
const record = current.rules[index];
|
|
450
|
+
if (
|
|
451
|
+
expectedRevision !== null &&
|
|
452
|
+
Number(expectedRevision) !== record.revision
|
|
453
|
+
) {
|
|
454
|
+
throw scopedPermissionError(
|
|
455
|
+
SCOPED_PERMISSION_ERROR_CODES.CONFLICT,
|
|
456
|
+
`Scoped permission revision changed (expected ${expectedRevision}, current ${record.revision})`,
|
|
457
|
+
);
|
|
458
|
+
}
|
|
459
|
+
if (record.revokedAt !== null) {
|
|
460
|
+
revoked = record;
|
|
461
|
+
return false;
|
|
462
|
+
}
|
|
463
|
+
revoked = {
|
|
464
|
+
...record,
|
|
465
|
+
revision: record.revision + 1,
|
|
466
|
+
revokedAt: now,
|
|
467
|
+
};
|
|
468
|
+
const rules = [...current.rules];
|
|
469
|
+
rules[index] = revoked;
|
|
470
|
+
Object.assign(draft, current, {
|
|
471
|
+
generation: current.generation + 1,
|
|
472
|
+
updatedAt: now,
|
|
473
|
+
rules,
|
|
474
|
+
});
|
|
475
|
+
validateState(draft, captured.workspace);
|
|
476
|
+
});
|
|
477
|
+
return projectRecord(revoked, now, captured.filePath);
|
|
368
478
|
}
|
|
369
479
|
}
|