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
|
@@ -0,0 +1,701 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Pure records for the cooperative settings-authority protocol. This module
|
|
5
|
+
* performs no I/O, enrollment, locking, recovery, or permission admission.
|
|
6
|
+
* Callers must publish guard -> prepared -> settings -> ready durably, then
|
|
7
|
+
* remove the guard. Construction of a record is not evidence of persistence.
|
|
8
|
+
*
|
|
9
|
+
* A write changes one physical source, including every registered alias.
|
|
10
|
+
* Parent materialization is deliberately unsupported: a changed parent binding
|
|
11
|
+
* cannot be accepted merely because the resulting content has the right hash.
|
|
12
|
+
*/
|
|
13
|
+
const path = require("node:path");
|
|
14
|
+
const { createHash } = require("node:crypto");
|
|
15
|
+
const { types } = require("node:util");
|
|
16
|
+
|
|
17
|
+
const LEDGER_SCHEMA = "chainlesschain.settings-authority/v1";
|
|
18
|
+
const GUARD_SCHEMA = "chainlesschain.settings-authority-guard/v1";
|
|
19
|
+
const LIMITS = Object.freeze({
|
|
20
|
+
contexts: 64,
|
|
21
|
+
sourcesPerContext: 64,
|
|
22
|
+
sources: 1024,
|
|
23
|
+
discoveryPaths: 64,
|
|
24
|
+
pathBytes: 4096,
|
|
25
|
+
recordBytes: 4 * 1024 * 1024,
|
|
26
|
+
sourceBytes: 1024 * 1024,
|
|
27
|
+
});
|
|
28
|
+
const SOURCE_KEYS = [
|
|
29
|
+
"logicalPath",
|
|
30
|
+
"physicalPath",
|
|
31
|
+
"nearestExistingParent",
|
|
32
|
+
"remainingPath",
|
|
33
|
+
"parentIdentity",
|
|
34
|
+
"exists",
|
|
35
|
+
"byteLength",
|
|
36
|
+
"digest",
|
|
37
|
+
"fileIdentity",
|
|
38
|
+
];
|
|
39
|
+
const FILE_FIELDS = [
|
|
40
|
+
"dev",
|
|
41
|
+
"ino",
|
|
42
|
+
"mode",
|
|
43
|
+
"nlink",
|
|
44
|
+
"size",
|
|
45
|
+
"mtimeNs",
|
|
46
|
+
"ctimeNs",
|
|
47
|
+
];
|
|
48
|
+
|
|
49
|
+
function fail(code = "CC_SETTINGS_AUTHORITY_RECORD_INVALID") {
|
|
50
|
+
const error = new Error(
|
|
51
|
+
"Settings authority record is invalid or inconsistent",
|
|
52
|
+
);
|
|
53
|
+
error.name = "SettingsAuthorityRecordError";
|
|
54
|
+
error.code = code;
|
|
55
|
+
throw error;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Reject accessors/Proxy/toJSON before any property read. Never invoke caller
|
|
59
|
+
// code during validation; limits apply before recursive copying/stringifying.
|
|
60
|
+
function copyData(value) {
|
|
61
|
+
let nodes = 0;
|
|
62
|
+
let stringBytes = 0;
|
|
63
|
+
const ancestors = new Set();
|
|
64
|
+
function visit(input, depth) {
|
|
65
|
+
if (++nodes > 100000 || depth > 24) fail();
|
|
66
|
+
if (input === null || typeof input === "boolean") return input;
|
|
67
|
+
if (typeof input === "string") {
|
|
68
|
+
stringBytes += Buffer.byteLength(input);
|
|
69
|
+
if (stringBytes > LIMITS.recordBytes) fail();
|
|
70
|
+
return input;
|
|
71
|
+
}
|
|
72
|
+
if (typeof input === "number") {
|
|
73
|
+
if (!Number.isSafeInteger(input)) fail();
|
|
74
|
+
return input;
|
|
75
|
+
}
|
|
76
|
+
if (!input || typeof input !== "object" || types.isProxy(input)) fail();
|
|
77
|
+
if (ancestors.has(input)) fail();
|
|
78
|
+
const array = Array.isArray(input);
|
|
79
|
+
const prototype = Object.getPrototypeOf(input);
|
|
80
|
+
if (
|
|
81
|
+
array
|
|
82
|
+
? prototype !== Array.prototype
|
|
83
|
+
: prototype !== Object.prototype && prototype !== null
|
|
84
|
+
)
|
|
85
|
+
fail();
|
|
86
|
+
const descriptors = Object.getOwnPropertyDescriptors(input);
|
|
87
|
+
const keys = Reflect.ownKeys(descriptors);
|
|
88
|
+
if (keys.some((key) => typeof key !== "string")) fail();
|
|
89
|
+
if (array && input.length > 4096) fail();
|
|
90
|
+
const output = array ? [] : {};
|
|
91
|
+
ancestors.add(input);
|
|
92
|
+
for (const key of keys) {
|
|
93
|
+
if (array && key === "length") continue;
|
|
94
|
+
const descriptor = descriptors[key];
|
|
95
|
+
if (
|
|
96
|
+
!Object.hasOwn(descriptor, "value") ||
|
|
97
|
+
!descriptor.enumerable ||
|
|
98
|
+
key === "__proto__"
|
|
99
|
+
)
|
|
100
|
+
fail();
|
|
101
|
+
if (
|
|
102
|
+
array &&
|
|
103
|
+
(!/^(0|[1-9][0-9]*)$/.test(key) || Number(key) >= input.length)
|
|
104
|
+
)
|
|
105
|
+
fail();
|
|
106
|
+
Object.defineProperty(output, key, {
|
|
107
|
+
value: visit(descriptor.value, depth + 1),
|
|
108
|
+
enumerable: true,
|
|
109
|
+
configurable: true,
|
|
110
|
+
writable: true,
|
|
111
|
+
});
|
|
112
|
+
}
|
|
113
|
+
ancestors.delete(input);
|
|
114
|
+
if (
|
|
115
|
+
array &&
|
|
116
|
+
(output.length !== input.length || keys.length !== input.length + 1)
|
|
117
|
+
)
|
|
118
|
+
fail();
|
|
119
|
+
return output;
|
|
120
|
+
}
|
|
121
|
+
const result = visit(value, 0);
|
|
122
|
+
if (Buffer.byteLength(JSON.stringify(result)) > LIMITS.recordBytes) fail();
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function freeze(value) {
|
|
127
|
+
if (value && typeof value === "object") {
|
|
128
|
+
for (const child of Object.values(value)) freeze(child);
|
|
129
|
+
Object.freeze(value);
|
|
130
|
+
}
|
|
131
|
+
return value;
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
function shape(value, keys) {
|
|
135
|
+
if (!value || typeof value !== "object" || Array.isArray(value)) fail();
|
|
136
|
+
const actual = Object.keys(value);
|
|
137
|
+
if (
|
|
138
|
+
actual.length !== keys.length ||
|
|
139
|
+
keys.some((key) => !Object.hasOwn(value, key))
|
|
140
|
+
)
|
|
141
|
+
fail();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
function list(value, max, min = 0) {
|
|
145
|
+
if (!Array.isArray(value) || value.length < min || value.length > max) fail();
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
function identifier(value) {
|
|
149
|
+
if (
|
|
150
|
+
typeof value !== "string" ||
|
|
151
|
+
!/^[A-Za-z0-9][A-Za-z0-9._:-]{0,127}$/.test(value)
|
|
152
|
+
)
|
|
153
|
+
fail();
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
function generation(value) {
|
|
157
|
+
if (!Number.isSafeInteger(value) || value < 0) fail();
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
function hash(value) {
|
|
161
|
+
if (typeof value !== "string" || !/^[a-f0-9]{64}$/.test(value)) fail();
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
function platformName(value) {
|
|
165
|
+
if (value === "win32") return value;
|
|
166
|
+
if (
|
|
167
|
+
["posix", "linux", "darwin", "freebsd", "openbsd", "aix", "sunos"].includes(
|
|
168
|
+
value,
|
|
169
|
+
)
|
|
170
|
+
)
|
|
171
|
+
return "posix";
|
|
172
|
+
fail();
|
|
173
|
+
}
|
|
174
|
+
|
|
175
|
+
function normalizedPath(value, platform, absolute = true) {
|
|
176
|
+
if (
|
|
177
|
+
typeof value !== "string" ||
|
|
178
|
+
!value ||
|
|
179
|
+
value.includes("\0") ||
|
|
180
|
+
Buffer.byteLength(value) > LIMITS.pathBytes
|
|
181
|
+
)
|
|
182
|
+
fail();
|
|
183
|
+
const api = platform === "win32" ? path.win32 : path.posix;
|
|
184
|
+
if (absolute) {
|
|
185
|
+
if (!api.isAbsolute(value)) fail();
|
|
186
|
+
if (
|
|
187
|
+
platform === "win32" &&
|
|
188
|
+
!/^(?:[a-z]:[\\/]|[\\/]{2}[^\\/]+[\\/][^\\/]+[\\/])/i.test(value)
|
|
189
|
+
)
|
|
190
|
+
fail();
|
|
191
|
+
} else if (
|
|
192
|
+
api.isAbsolute(value) ||
|
|
193
|
+
(platform === "win32" && /^[a-z]:/i.test(value))
|
|
194
|
+
)
|
|
195
|
+
fail();
|
|
196
|
+
let result = api.normalize(value);
|
|
197
|
+
if (platform === "win32") {
|
|
198
|
+
if (result.startsWith("\\\\?\\") || result.startsWith("\\\\.\\")) fail();
|
|
199
|
+
const segments = result
|
|
200
|
+
.replace(/^[a-z]:/i, "")
|
|
201
|
+
.split("\\")
|
|
202
|
+
.filter(Boolean);
|
|
203
|
+
if (
|
|
204
|
+
segments.some(
|
|
205
|
+
(segment) => /[.: ]$/.test(segment) || segment.includes(":"),
|
|
206
|
+
)
|
|
207
|
+
)
|
|
208
|
+
fail();
|
|
209
|
+
result = result.toLowerCase();
|
|
210
|
+
}
|
|
211
|
+
if (
|
|
212
|
+
!absolute &&
|
|
213
|
+
(result === "." || result === ".." || result.startsWith(`..${api.sep}`))
|
|
214
|
+
)
|
|
215
|
+
fail();
|
|
216
|
+
return result;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
function identity(value, fields) {
|
|
220
|
+
shape(value, fields);
|
|
221
|
+
for (const field of fields) {
|
|
222
|
+
const signed = field === "mtimeNs" || field === "ctimeNs";
|
|
223
|
+
if (
|
|
224
|
+
typeof value[field] !== "string" ||
|
|
225
|
+
value[field].length > 40 ||
|
|
226
|
+
!(signed ? /^(?:0|-?[1-9][0-9]*)$/ : /^(?:0|[1-9][0-9]*)$/).test(
|
|
227
|
+
value[field],
|
|
228
|
+
)
|
|
229
|
+
)
|
|
230
|
+
fail();
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
function content(value) {
|
|
235
|
+
if (
|
|
236
|
+
typeof value.exists !== "boolean" ||
|
|
237
|
+
!Number.isSafeInteger(value.byteLength) ||
|
|
238
|
+
value.byteLength < 0 ||
|
|
239
|
+
value.byteLength > LIMITS.sourceBytes
|
|
240
|
+
)
|
|
241
|
+
fail();
|
|
242
|
+
if (value.exists) hash(value.digest);
|
|
243
|
+
else if (value.byteLength !== 0 || value.digest !== null) fail();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
function validateSource(value, platform) {
|
|
247
|
+
shape(value, SOURCE_KEYS);
|
|
248
|
+
for (const key of ["logicalPath", "physicalPath", "nearestExistingParent"])
|
|
249
|
+
if (normalizedPath(value[key], platform) !== value[key]) fail();
|
|
250
|
+
if (
|
|
251
|
+
normalizedPath(value.remainingPath, platform, false) !== value.remainingPath
|
|
252
|
+
)
|
|
253
|
+
fail();
|
|
254
|
+
const api = platform === "win32" ? path.win32 : path.posix;
|
|
255
|
+
if (
|
|
256
|
+
api.join(value.nearestExistingParent, value.remainingPath) !==
|
|
257
|
+
value.physicalPath
|
|
258
|
+
)
|
|
259
|
+
fail();
|
|
260
|
+
identity(value.parentIdentity, ["dev", "ino"]);
|
|
261
|
+
content(value);
|
|
262
|
+
if (value.exists) {
|
|
263
|
+
identity(value.fileIdentity, FILE_FIELDS);
|
|
264
|
+
if (
|
|
265
|
+
value.fileIdentity.nlink !== "1" ||
|
|
266
|
+
BigInt(value.fileIdentity.size) !== BigInt(value.byteLength) ||
|
|
267
|
+
(BigInt(value.fileIdentity.mode) & 0o170000n) !== 0o100000n ||
|
|
268
|
+
api.dirname(value.physicalPath) !== value.nearestExistingParent
|
|
269
|
+
)
|
|
270
|
+
fail();
|
|
271
|
+
} else if (value.fileIdentity !== null) fail();
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
function validateManifest(manifest, platform) {
|
|
275
|
+
shape(manifest, ["contextId", "discovery", "sources"]);
|
|
276
|
+
identifier(manifest.contextId);
|
|
277
|
+
list(manifest.discovery, LIMITS.discoveryPaths, 1);
|
|
278
|
+
for (const item of manifest.discovery)
|
|
279
|
+
if (normalizedPath(item, platform) !== item) fail();
|
|
280
|
+
list(manifest.sources, LIMITS.sourcesPerContext, 1);
|
|
281
|
+
for (const source of manifest.sources) validateSource(source, platform);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
function canonical(value) {
|
|
285
|
+
if (Array.isArray(value)) return value.map(canonical);
|
|
286
|
+
if (!value || typeof value !== "object") return value;
|
|
287
|
+
return Object.fromEntries(
|
|
288
|
+
Object.keys(value)
|
|
289
|
+
.sort()
|
|
290
|
+
.map((key) => [key, canonical(value[key])]),
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
|
|
294
|
+
function equal(a, b) {
|
|
295
|
+
return JSON.stringify(canonical(a)) === JSON.stringify(canonical(b));
|
|
296
|
+
}
|
|
297
|
+
|
|
298
|
+
function digest(value) {
|
|
299
|
+
return createHash("sha256")
|
|
300
|
+
.update(JSON.stringify(canonical(value)))
|
|
301
|
+
.digest("hex");
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
function headOf(source) {
|
|
305
|
+
const head = { ...source };
|
|
306
|
+
delete head.logicalPath;
|
|
307
|
+
return head;
|
|
308
|
+
}
|
|
309
|
+
|
|
310
|
+
function inverseHeads(contexts, platform) {
|
|
311
|
+
list(contexts, LIMITS.contexts);
|
|
312
|
+
const ids = new Set();
|
|
313
|
+
const logical = new Map();
|
|
314
|
+
const heads = new Map();
|
|
315
|
+
const inodes = new Map();
|
|
316
|
+
let sourceCount = 0;
|
|
317
|
+
for (const manifest of contexts) {
|
|
318
|
+
validateManifest(manifest, platform);
|
|
319
|
+
if (ids.has(manifest.contextId)) fail();
|
|
320
|
+
ids.add(manifest.contextId);
|
|
321
|
+
sourceCount += manifest.sources.length;
|
|
322
|
+
if (sourceCount > LIMITS.sources) fail();
|
|
323
|
+
for (const source of manifest.sources) {
|
|
324
|
+
const previous = logical.get(source.logicalPath);
|
|
325
|
+
if (previous && !equal(previous, source))
|
|
326
|
+
fail("CC_SETTINGS_AUTHORITY_BINDING_CHANGED");
|
|
327
|
+
logical.set(source.logicalPath, source);
|
|
328
|
+
const head = headOf(source);
|
|
329
|
+
const known = heads.get(source.physicalPath);
|
|
330
|
+
if (known && !equal(known, head))
|
|
331
|
+
fail("CC_SETTINGS_AUTHORITY_BINDING_CHANGED");
|
|
332
|
+
heads.set(source.physicalPath, head);
|
|
333
|
+
if (source.exists) {
|
|
334
|
+
const inode = `${source.fileIdentity.dev}:${source.fileIdentity.ino}`;
|
|
335
|
+
const previousPath = inodes.get(inode);
|
|
336
|
+
// A final-component alias that canonical parent resolution missed must
|
|
337
|
+
// not create a second head. Reject it instead of inventing equivalence.
|
|
338
|
+
if (previousPath && previousPath !== source.physicalPath)
|
|
339
|
+
fail("CC_SETTINGS_AUTHORITY_BINDING_CHANGED");
|
|
340
|
+
inodes.set(inode, source.physicalPath);
|
|
341
|
+
}
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
return [...heads.values()].sort((a, b) =>
|
|
345
|
+
a.physicalPath < b.physicalPath
|
|
346
|
+
? -1
|
|
347
|
+
: a.physicalPath > b.physicalPath
|
|
348
|
+
? 1
|
|
349
|
+
: 0,
|
|
350
|
+
);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
function readyRecord({ platform, epoch, generation: revision, contexts }) {
|
|
354
|
+
return {
|
|
355
|
+
schema: LEDGER_SCHEMA,
|
|
356
|
+
platform,
|
|
357
|
+
epoch,
|
|
358
|
+
generation: revision,
|
|
359
|
+
phase: "ready",
|
|
360
|
+
transactionId: null,
|
|
361
|
+
transition: null,
|
|
362
|
+
contexts,
|
|
363
|
+
physicalHeads: inverseHeads(contexts, platform),
|
|
364
|
+
};
|
|
365
|
+
}
|
|
366
|
+
|
|
367
|
+
function validateIntent(intent, platform) {
|
|
368
|
+
if (intent?.kind === "register") {
|
|
369
|
+
shape(intent, ["kind", "manifest"]);
|
|
370
|
+
validateManifest(intent.manifest, platform);
|
|
371
|
+
} else if (intent?.kind === "write") {
|
|
372
|
+
shape(intent, ["kind", "physicalPath", "after"]);
|
|
373
|
+
if (normalizedPath(intent.physicalPath, platform) !== intent.physicalPath)
|
|
374
|
+
fail();
|
|
375
|
+
shape(intent.after, ["exists", "byteLength", "digest"]);
|
|
376
|
+
content(intent.after);
|
|
377
|
+
} else fail();
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
function checkIntentAgainstBefore(before, intent) {
|
|
381
|
+
if (intent.kind === "write") {
|
|
382
|
+
const target = before.physicalHeads.find(
|
|
383
|
+
(head) => head.physicalPath === intent.physicalPath,
|
|
384
|
+
);
|
|
385
|
+
if (!target) fail("CC_SETTINGS_AUTHORITY_SOURCE_UNREGISTERED");
|
|
386
|
+
const api = before.platform === "win32" ? path.win32 : path.posix;
|
|
387
|
+
if (api.dirname(target.physicalPath) !== target.nearestExistingParent)
|
|
388
|
+
fail("CC_SETTINGS_AUTHORITY_PARENT_MATERIALIZATION_UNSUPPORTED");
|
|
389
|
+
} else {
|
|
390
|
+
if (
|
|
391
|
+
before.contexts.some(
|
|
392
|
+
(context) => context.contextId === intent.manifest.contextId,
|
|
393
|
+
)
|
|
394
|
+
)
|
|
395
|
+
fail("CC_SETTINGS_AUTHORITY_CONTEXT_EXISTS");
|
|
396
|
+
// Combining with all existing contexts verifies every logical and inverse
|
|
397
|
+
// physical binding before an alias or a new project may be enrolled.
|
|
398
|
+
inverseHeads([...before.contexts, intent.manifest], before.platform);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
|
|
402
|
+
function beforeOf(prepared) {
|
|
403
|
+
return readyRecord({
|
|
404
|
+
platform: prepared.platform,
|
|
405
|
+
epoch: prepared.epoch,
|
|
406
|
+
generation: prepared.transition.fromGeneration,
|
|
407
|
+
contexts: prepared.contexts,
|
|
408
|
+
});
|
|
409
|
+
}
|
|
410
|
+
|
|
411
|
+
function validateLedgerData(record) {
|
|
412
|
+
shape(record, [
|
|
413
|
+
"schema",
|
|
414
|
+
"platform",
|
|
415
|
+
"epoch",
|
|
416
|
+
"generation",
|
|
417
|
+
"phase",
|
|
418
|
+
"transactionId",
|
|
419
|
+
"transition",
|
|
420
|
+
"contexts",
|
|
421
|
+
"physicalHeads",
|
|
422
|
+
]);
|
|
423
|
+
if (
|
|
424
|
+
record.schema !== LEDGER_SCHEMA ||
|
|
425
|
+
!["posix", "win32"].includes(record.platform)
|
|
426
|
+
)
|
|
427
|
+
fail();
|
|
428
|
+
identifier(record.epoch);
|
|
429
|
+
generation(record.generation);
|
|
430
|
+
if (
|
|
431
|
+
!equal(inverseHeads(record.contexts, record.platform), record.physicalHeads)
|
|
432
|
+
)
|
|
433
|
+
fail("CC_SETTINGS_AUTHORITY_BINDING_CHANGED");
|
|
434
|
+
if (record.phase === "ready") {
|
|
435
|
+
if (record.transactionId !== null || record.transition !== null) fail();
|
|
436
|
+
} else if (record.phase === "prepared") {
|
|
437
|
+
identifier(record.transactionId);
|
|
438
|
+
shape(record.transition, ["fromGeneration", "beforeDigest", "intent"]);
|
|
439
|
+
generation(record.transition.fromGeneration);
|
|
440
|
+
if (
|
|
441
|
+
record.generation <= 0 ||
|
|
442
|
+
record.transition.fromGeneration !== record.generation - 1
|
|
443
|
+
)
|
|
444
|
+
fail();
|
|
445
|
+
hash(record.transition.beforeDigest);
|
|
446
|
+
validateIntent(record.transition.intent, record.platform);
|
|
447
|
+
const before = beforeOf(record);
|
|
448
|
+
if (digest(before) !== record.transition.beforeDigest)
|
|
449
|
+
fail("CC_SETTINGS_AUTHORITY_TRANSACTION_MISMATCH");
|
|
450
|
+
checkIntentAgainstBefore(before, record.transition.intent);
|
|
451
|
+
} else fail();
|
|
452
|
+
return record;
|
|
453
|
+
}
|
|
454
|
+
|
|
455
|
+
function validateLedger({ record }) {
|
|
456
|
+
return freeze(validateLedgerData(copyData(record)));
|
|
457
|
+
}
|
|
458
|
+
|
|
459
|
+
function requireReady(record) {
|
|
460
|
+
if (record.phase !== "ready") fail("CC_SETTINGS_AUTHORITY_NOT_READY");
|
|
461
|
+
}
|
|
462
|
+
|
|
463
|
+
function createManifest({
|
|
464
|
+
contextId,
|
|
465
|
+
discovery,
|
|
466
|
+
sources,
|
|
467
|
+
platform = process.platform,
|
|
468
|
+
}) {
|
|
469
|
+
const normalizedPlatform = platformName(platform);
|
|
470
|
+
// Observer settings may be a large nested object. Deliberately select only
|
|
471
|
+
// data descriptors of the binding fields, without reading/invoking settings.
|
|
472
|
+
if (!Array.isArray(sources) || types.isProxy(sources)) fail();
|
|
473
|
+
const selected = [];
|
|
474
|
+
if (sources.length > LIMITS.sourcesPerContext) fail();
|
|
475
|
+
for (let index = 0; index < sources.length; index++) {
|
|
476
|
+
const itemDescriptor = Object.getOwnPropertyDescriptor(
|
|
477
|
+
sources,
|
|
478
|
+
String(index),
|
|
479
|
+
);
|
|
480
|
+
if (!itemDescriptor || !Object.hasOwn(itemDescriptor, "value")) fail();
|
|
481
|
+
const source = itemDescriptor.value;
|
|
482
|
+
if (!source || typeof source !== "object" || types.isProxy(source)) fail();
|
|
483
|
+
const data = {};
|
|
484
|
+
for (const key of SOURCE_KEYS) {
|
|
485
|
+
const descriptor = Object.getOwnPropertyDescriptor(source, key);
|
|
486
|
+
if (!descriptor || !Object.hasOwn(descriptor, "value")) fail();
|
|
487
|
+
data[key] = descriptor.value;
|
|
488
|
+
}
|
|
489
|
+
selected.push(data);
|
|
490
|
+
}
|
|
491
|
+
const manifest = copyData({ contextId, discovery, sources: selected });
|
|
492
|
+
list(manifest.discovery, LIMITS.discoveryPaths, 1);
|
|
493
|
+
manifest.discovery = manifest.discovery.map((item) =>
|
|
494
|
+
normalizedPath(item, normalizedPlatform),
|
|
495
|
+
);
|
|
496
|
+
for (const source of manifest.sources) {
|
|
497
|
+
for (const key of ["logicalPath", "physicalPath", "nearestExistingParent"])
|
|
498
|
+
source[key] = normalizedPath(source[key], normalizedPlatform);
|
|
499
|
+
source.remainingPath = normalizedPath(
|
|
500
|
+
source.remainingPath,
|
|
501
|
+
normalizedPlatform,
|
|
502
|
+
false,
|
|
503
|
+
);
|
|
504
|
+
}
|
|
505
|
+
validateManifest(manifest, normalizedPlatform);
|
|
506
|
+
inverseHeads([manifest], normalizedPlatform);
|
|
507
|
+
return freeze(manifest);
|
|
508
|
+
}
|
|
509
|
+
|
|
510
|
+
function createInitialLedger({
|
|
511
|
+
epoch,
|
|
512
|
+
platform = process.platform,
|
|
513
|
+
contexts = [],
|
|
514
|
+
}) {
|
|
515
|
+
const data = copyData({ epoch, contexts });
|
|
516
|
+
return validateLedger({
|
|
517
|
+
record: readyRecord({
|
|
518
|
+
platform: platformName(platform),
|
|
519
|
+
epoch: data.epoch,
|
|
520
|
+
generation: 0,
|
|
521
|
+
contexts: data.contexts,
|
|
522
|
+
}),
|
|
523
|
+
});
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
function recordDigest({ record }) {
|
|
527
|
+
return digest(validateLedger({ record }));
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function assertContextMatches({ ledger, manifest }) {
|
|
531
|
+
const current = validateLedger({ record: ledger });
|
|
532
|
+
requireReady(current);
|
|
533
|
+
const observed = copyData(manifest);
|
|
534
|
+
validateManifest(observed, current.platform);
|
|
535
|
+
const known = current.contexts.find(
|
|
536
|
+
(context) => context.contextId === observed.contextId,
|
|
537
|
+
);
|
|
538
|
+
if (!known) fail("CC_SETTINGS_AUTHORITY_CONTEXT_UNREGISTERED");
|
|
539
|
+
if (!equal(known, observed)) fail("CC_SETTINGS_AUTHORITY_BINDING_CHANGED");
|
|
540
|
+
return true;
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
function guardOf(prepared) {
|
|
544
|
+
return {
|
|
545
|
+
schema: GUARD_SCHEMA,
|
|
546
|
+
platform: prepared.platform,
|
|
547
|
+
epoch: prepared.epoch,
|
|
548
|
+
generation: prepared.generation,
|
|
549
|
+
transactionId: prepared.transactionId,
|
|
550
|
+
beforeDigest: prepared.transition.beforeDigest,
|
|
551
|
+
intent: prepared.transition.intent,
|
|
552
|
+
};
|
|
553
|
+
}
|
|
554
|
+
|
|
555
|
+
function validateGuard({ guard, prepared = null }) {
|
|
556
|
+
const data = copyData(guard);
|
|
557
|
+
shape(data, [
|
|
558
|
+
"schema",
|
|
559
|
+
"platform",
|
|
560
|
+
"epoch",
|
|
561
|
+
"generation",
|
|
562
|
+
"transactionId",
|
|
563
|
+
"beforeDigest",
|
|
564
|
+
"intent",
|
|
565
|
+
]);
|
|
566
|
+
if (
|
|
567
|
+
data.schema !== GUARD_SCHEMA ||
|
|
568
|
+
!["posix", "win32"].includes(data.platform)
|
|
569
|
+
)
|
|
570
|
+
fail();
|
|
571
|
+
identifier(data.epoch);
|
|
572
|
+
identifier(data.transactionId);
|
|
573
|
+
generation(data.generation);
|
|
574
|
+
if (!data.generation) fail();
|
|
575
|
+
hash(data.beforeDigest);
|
|
576
|
+
validateIntent(data.intent, data.platform);
|
|
577
|
+
if (prepared !== null) {
|
|
578
|
+
const record = validateLedger({ record: prepared });
|
|
579
|
+
if (record.phase !== "prepared" || !equal(data, guardOf(record)))
|
|
580
|
+
fail("CC_SETTINGS_AUTHORITY_TRANSACTION_MISMATCH");
|
|
581
|
+
}
|
|
582
|
+
return freeze(data);
|
|
583
|
+
}
|
|
584
|
+
|
|
585
|
+
function prepareTransition({ ledger, transactionId, intent }) {
|
|
586
|
+
const before = validateLedger({ record: ledger });
|
|
587
|
+
requireReady(before);
|
|
588
|
+
identifier(transactionId);
|
|
589
|
+
if (before.generation === Number.MAX_SAFE_INTEGER)
|
|
590
|
+
fail("CC_SETTINGS_AUTHORITY_GENERATION_EXHAUSTED");
|
|
591
|
+
const plan = copyData(intent);
|
|
592
|
+
validateIntent(plan, before.platform);
|
|
593
|
+
checkIntentAgainstBefore(before, plan);
|
|
594
|
+
const prepared = validateLedger({
|
|
595
|
+
record: {
|
|
596
|
+
...before,
|
|
597
|
+
phase: "prepared",
|
|
598
|
+
generation: before.generation + 1,
|
|
599
|
+
transactionId,
|
|
600
|
+
transition: {
|
|
601
|
+
fromGeneration: before.generation,
|
|
602
|
+
beforeDigest: digest(before),
|
|
603
|
+
intent: plan,
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
});
|
|
607
|
+
return freeze({
|
|
608
|
+
prepared,
|
|
609
|
+
guard: validateGuard({ guard: guardOf(prepared), prepared }),
|
|
610
|
+
});
|
|
611
|
+
}
|
|
612
|
+
|
|
613
|
+
function registerContext({ ledger, manifest, transactionId }) {
|
|
614
|
+
return prepareTransition({
|
|
615
|
+
ledger,
|
|
616
|
+
transactionId,
|
|
617
|
+
intent: { kind: "register", manifest },
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
|
|
621
|
+
function sameBinding(left, right) {
|
|
622
|
+
return [
|
|
623
|
+
"logicalPath",
|
|
624
|
+
"physicalPath",
|
|
625
|
+
"nearestExistingParent",
|
|
626
|
+
"remainingPath",
|
|
627
|
+
"parentIdentity",
|
|
628
|
+
].every((key) => equal(left[key], right[key]));
|
|
629
|
+
}
|
|
630
|
+
|
|
631
|
+
function settleTransition({ prepared, guard, contexts, outcome }) {
|
|
632
|
+
const pending = validateLedger({ record: prepared });
|
|
633
|
+
if (pending.phase !== "prepared") fail("CC_SETTINGS_AUTHORITY_NOT_PREPARED");
|
|
634
|
+
validateGuard({ guard, prepared: pending });
|
|
635
|
+
if (!["before", "after"].includes(outcome)) fail();
|
|
636
|
+
const observed = copyData(contexts);
|
|
637
|
+
inverseHeads(observed, pending.platform);
|
|
638
|
+
const intent = pending.transition.intent;
|
|
639
|
+
const expectedContexts =
|
|
640
|
+
intent.kind === "register" && outcome === "after"
|
|
641
|
+
? [...pending.contexts, intent.manifest]
|
|
642
|
+
: pending.contexts;
|
|
643
|
+
if (observed.length !== expectedContexts.length)
|
|
644
|
+
fail("CC_SETTINGS_AUTHORITY_BINDING_CHANGED");
|
|
645
|
+
for (let i = 0; i < expectedContexts.length; i++) {
|
|
646
|
+
const old = expectedContexts[i];
|
|
647
|
+
const next = observed[i];
|
|
648
|
+
if (
|
|
649
|
+
old.contextId !== next.contextId ||
|
|
650
|
+
!equal(old.discovery, next.discovery) ||
|
|
651
|
+
old.sources.length !== next.sources.length
|
|
652
|
+
)
|
|
653
|
+
fail("CC_SETTINGS_AUTHORITY_BINDING_CHANGED");
|
|
654
|
+
for (let j = 0; j < old.sources.length; j++) {
|
|
655
|
+
const prior = old.sources[j];
|
|
656
|
+
const source = next.sources[j];
|
|
657
|
+
if (
|
|
658
|
+
intent.kind !== "write" ||
|
|
659
|
+
prior.physicalPath !== intent.physicalPath
|
|
660
|
+
) {
|
|
661
|
+
if (!equal(prior, source))
|
|
662
|
+
fail("CC_SETTINGS_AUTHORITY_BINDING_CHANGED");
|
|
663
|
+
continue;
|
|
664
|
+
}
|
|
665
|
+
if (!sameBinding(prior, source))
|
|
666
|
+
fail("CC_SETTINGS_AUTHORITY_BINDING_CHANGED");
|
|
667
|
+
const wanted = outcome === "after" ? intent.after : prior;
|
|
668
|
+
if (
|
|
669
|
+
!["exists", "byteLength", "digest"].every(
|
|
670
|
+
(key) => source[key] === wanted[key],
|
|
671
|
+
)
|
|
672
|
+
)
|
|
673
|
+
fail("CC_SETTINGS_AUTHORITY_CONTENT_MISMATCH");
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
// Both completion and verified-old recovery consume the prepared generation.
|
|
677
|
+
// Never retain a pending transaction in a ready record or decrement revision.
|
|
678
|
+
return validateLedger({
|
|
679
|
+
record: readyRecord({
|
|
680
|
+
platform: pending.platform,
|
|
681
|
+
epoch: pending.epoch,
|
|
682
|
+
generation: pending.generation,
|
|
683
|
+
contexts: observed,
|
|
684
|
+
}),
|
|
685
|
+
});
|
|
686
|
+
}
|
|
687
|
+
|
|
688
|
+
module.exports = {
|
|
689
|
+
LEDGER_SCHEMA,
|
|
690
|
+
GUARD_SCHEMA,
|
|
691
|
+
LIMITS,
|
|
692
|
+
createManifest,
|
|
693
|
+
createInitialLedger,
|
|
694
|
+
validateLedger,
|
|
695
|
+
validateGuard,
|
|
696
|
+
recordDigest,
|
|
697
|
+
assertContextMatches,
|
|
698
|
+
prepareTransition,
|
|
699
|
+
registerContext,
|
|
700
|
+
settleTransition,
|
|
701
|
+
};
|
|
@@ -146,6 +146,7 @@ function inspect(file, fs, maxBytes) {
|
|
|
146
146
|
exists: false,
|
|
147
147
|
byteLength: 0,
|
|
148
148
|
digest: null,
|
|
149
|
+
fileIdentity: null,
|
|
149
150
|
settings: null,
|
|
150
151
|
});
|
|
151
152
|
}
|
|
@@ -201,6 +202,13 @@ function inspect(file, fs, maxBytes) {
|
|
|
201
202
|
exists: true,
|
|
202
203
|
byteLength: length,
|
|
203
204
|
digest: createHash("sha256").update(bytes).digest("hex"),
|
|
205
|
+
// Preserve identity from the final fstat on the descriptor whose bytes
|
|
206
|
+
// were parsed and hashed, never a separate path-based stat projection.
|
|
207
|
+
fileIdentity: Object.freeze(
|
|
208
|
+
Object.fromEntries(
|
|
209
|
+
FILE_FIELDS.map((field) => [field, String(after[field])]),
|
|
210
|
+
),
|
|
211
|
+
),
|
|
204
212
|
settings: freezeJson(settings),
|
|
205
213
|
});
|
|
206
214
|
}
|