chainlesschain 0.166.91 → 0.166.93
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -17
- package/package.json +3 -3
- 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-DgV7xb8F.js → AIOps-oo9rE0gh.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CgANpkKu.js → ActionButton-CVJVVv4i.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DnvpX7RB.js → Analytics-CUXuwmia.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-C2bHccBM.js → AppLayout-iM4PvCuY.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BIhijC9g.js → Artifacts-DKUIJbf9.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CKRjWGdp.js → Audit-C4_SzFN2.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-CblYtgBE.js → BackgroundAgents-C3427uy-.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-Cq35b78a.js → Backup-BpEQQiog.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-DcB7NoRz.js → BaseInput-C_exQomZ.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-tc3b-s7i.js → Chat-DzpkLtWa.js} +5 -5
- package/src/assets/web-panel/assets/ChatBubbleRenderer-DX4XmTWm.js +1 -0
- package/src/assets/web-panel/assets/{Checkbox-BLD5bH5u.js → Checkbox-CxAC1CU8.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-BLOsoazN.js → Codegen-wDFAGCLp.js} +1 -1
- package/src/assets/web-panel/assets/{Col-Bdr7JwIx.js → Col-BvwirhMf.js} +1 -1
- package/src/assets/web-panel/assets/{Community-C84BsWE0.js → Community-BJuA7paY.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-Dzdwd04z.js → Compact-DzV35_u2.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CCG-R0EV.js → Compliance-CIDPPvr9.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BYj72SK0.js → Cowork-DyTMxP3N.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-B6iKAwOf.js → Cron-PcNLjMFx.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-BJ-oDC6u.js → Crosschain-B_O7ck6Z.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BnMckGLr.js → DID-CII35Me1.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Cyje-gCO.js → Dashboard-DU56fkNz.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BpNlenjK.js → Dropdown-DHC8HVeJ.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-BhonO7Nv.js → EmailListRenderer-oUOHFNsD.js} +1 -1
- package/src/assets/web-panel/assets/{EvolutionSettings-DVoNU-Id.js → EvolutionSettings-D7RCAWNl.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-tdn-KC67.js → FamilyGuardDashboard-CGONPZxC.js} +1 -1
- package/src/assets/web-panel/assets/{Federation--EEVrHhz.js → Federation-BbDlPWcl.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Cy8zr0-4.js → FormItemContext-BBJbFQQT.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BuUhpgFi.js → GenericCardRenderer-iuxAero4.js} +1 -1
- package/src/assets/web-panel/assets/{Git-Ctd3E5Pe.js → Git-Dw0prdY5.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-Ce7dV6aU.js → Governance-DOGqi_uV.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-CD1vExIH.js → Inference-BY5oUGGj.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-Dn64OdQ7.js → KnowledgeGraph-CcE2EVYX.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-PmElVRrT.js → Logs-DyfN4R5w.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-BFiVKf5t.js → MarkdownRenderer-Bj62Aq0M.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-OW4jTXwx.js → Marketplace-CuuuAXUh.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BEq2Ib4x.js → McpTools-948Fithv.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-Can_P2oK.js → Memory-D4PINvyy.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-DJ8R-dn3.js → MobileBridge-ECKMaLzX.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-B7kV9GOt.js → MobileProjects-CB5MAcvD.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-BfxamolS.js → Mtc-BRzIM3HJ.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-DHtuv7av.js → MtcAudit-CGeAXJGn.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-DLkLpvxc.js → Multisig-C_qJ-8K-.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-u4XtJTIt.js → NLProgramming-DAEyCn7T.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-BwuuPJLS.js → Notes-DmD3MEec.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-HEeu-E9v.js → NotificationSettings-Cv4Q3VC9.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-BNlpaFjH.js +1 -0
- package/src/assets/web-panel/assets/{Organization-Yh-czs3b.js → Organization-DqAFdz6p.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-DmE8mYVM.js → Overflow-8Ye9cIcE.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CWVck8WN.js → P2P-755SfHoJ.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DgFqekod.js → PdhVaultBrowser-CgZLiucX.js} +4 -4
- package/src/assets/web-panel/assets/{Permissions-BtJBVOHV.js → Permissions-C-vFF_rL.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-6VBObXzd.js → PersonalDataHub-Diz1oXM3.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-D222FmqY.js → Pipeline-D4kIzsZG.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-Bi9FMikp.js → Privacy-QKLkSAUH.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Bo52H47H.js → ProjectInit-I0S9o_RT.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-BPkesfOc.js → ProjectSettings-DdgV3-Ho.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-GynohSFX.js → Projects-BJHHegtj.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-DVdMg04n.js → Providers-uVa1zfbS.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DqJCb595.js → QrScannerModal-YLVpOChR.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-J7loQbXi.js → QuickAsk-535AYH61.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-BI5aURVc.js → Recommend-C_P6eNSw.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-BN2sqGvz.js → RemoteSession-EXH1SbNj.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-BGOND-Fi.js → Reputation-Cxc7d3eU.js} +1 -1
- package/src/assets/web-panel/assets/{Row-77dfwaUk.js → Row-EDqJy-wS.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-FtBAxS3g.js → RssFeed-C03Xeg9_.js} +2 -2
- package/src/assets/web-panel/assets/{Search-BaYVLFb7.js → Search-DsIga9DK.js} +1 -1
- package/src/assets/web-panel/assets/{Security-LE3Vv6ab.js → Security-DgArMFc-.js} +3 -3
- package/src/assets/web-panel/assets/{Services-C5pP-Whz.js → Services-MooDpHBP.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-CLqKUVsF.js → Skeleton-B10P1Dr6.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-BPjN0SX5.js → Skills-DHLHEkLl.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-14jD70kg.js → Sla-D4RT7d4g.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Dr9ij42W.js → SpeechSettings-D8u6F-LV.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-BzLHP7B8.js → SyncSettings-4nvNzRq8.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-YqV9b0rV.js → Tasks-CmCb8zNO.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CxxAE4PK.js → Templates-CzESIa21.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CRb3PcKJ.js → Tenant-Ch_N3p4d.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-CTzPpBaP.js → Terminal-BZccUf_X.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DjFUj_hy.js → TimelineRenderer-FrKcNy_3.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Bbb8tPvx.js → Tokens-4xvLpXfD.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CSeWGM0W.js → Trigger-84UjyxXd.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-JgESMBo3.js → Trust-M6UtHnji.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CiObLk0T.js → UkeySign-L_xEgtCg.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-CFEZaQ2x.js → VideoEditing-BLJCX9ru.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-DR8ZGDNn.js → Wallet-DwL6uJCj.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DxZukvu7.js → WebAuthn-Bmn9pdzz.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-CKgSZQwn.js → WorkflowEditor-DSsZ5xG1.js} +1 -1
- package/src/assets/web-panel/assets/{chat-CTxEQE66.js → chat--FGNUaa8.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CAjKMUu3.js → colors-DeuwNdqX.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BXuz49at.js → compact-item-I4XKxRFU.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CFN_Hwr9.js → createContext-RoD2p5Ba.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-ctnml4IW.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-BwoSBSfP.js → hasIn-BsvyddUM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CB3kVd9c.js → index-0tRU1Sv-.js} +1 -1
- package/src/assets/web-panel/assets/{index-o9DpkZKf.js → index-4xHc2WjT.js} +1 -1
- package/src/assets/web-panel/assets/{index-BmQITjMU.js → index-B8-eGLN9.js} +1 -1
- package/src/assets/web-panel/assets/{index-DY-gBSm8.js → index-BS_uqSTa.js} +1 -1
- package/src/assets/web-panel/assets/{index-F2E5RCwz.js → index-BYEVBlCl.js} +1 -1
- package/src/assets/web-panel/assets/{index-CF9lWi1o.js → index-BcBM065G.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bu2P6u8m.js → index-BnrL4s-3.js} +1 -1
- package/src/assets/web-panel/assets/{index-Dv7V2kg6.js → index-BrZPb0On.js} +1 -1
- package/src/assets/web-panel/assets/index-C7PN8mh1.js +1 -0
- package/src/assets/web-panel/assets/{index-DViK2bkv.js → index-CBazK4-7.js} +1 -1
- package/src/assets/web-panel/assets/{index-BmXpdcHI.js → index-CPS3zv1c.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bqv_Cray.js → index-CSrZiiAs.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7kp-zRE.js → index-CVwZMt9Z.js} +1 -1
- package/src/assets/web-panel/assets/{index-B0QbLlWn.js → index-CdgKZYgm.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjF5BOZx.js → index-CesTu01s.js} +1 -1
- package/src/assets/web-panel/assets/{index-CSmFby3Q.js → index-CiHlSuMl.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2o_BBjg.js → index-CsLVac9n.js} +1 -1
- package/src/assets/web-panel/assets/{index-CehdwJ_Q.js → index-D1ONyBTW.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bj4hIiLW.js → index-D4IZsKbF.js} +1 -1
- package/src/assets/web-panel/assets/{index-_ZtCyUdz.js → index-D4P4vpmv.js} +1 -1
- package/src/assets/web-panel/assets/{index-B9yR7zqr.js → index-D6J8dz7X.js} +1 -1
- package/src/assets/web-panel/assets/{index-u9_DM3ZC.js → index-DChy2sHE.js} +1 -1
- package/src/assets/web-panel/assets/{index-C8er4W9u.js → index-DKUCl_Lk.js} +1 -1
- package/src/assets/web-panel/assets/{index-BK4ftQ4d.js → index-DRluW3_r.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2tEnBPx.js → index-DWUmhNO-.js} +1 -1
- package/src/assets/web-panel/assets/{index-IY9bd5az.js → index-DXnpVGG3.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cp5y7lX_.js → index-DYOpAS6-.js} +1 -1
- package/src/assets/web-panel/assets/{index-T3MTE6jB.js → index-DiH-3cR2.js} +1 -1
- package/src/assets/web-panel/assets/{index-DxunxaTw.js → index-DlEfwgzr.js} +1 -1
- package/src/assets/web-panel/assets/index-KnbBJAO-.js +1 -0
- package/src/assets/web-panel/assets/{index-DTX9CJPx.js → index-R4St_zjR.js} +1 -1
- package/src/assets/web-panel/assets/{index-Tz5dheZa.js → index-ScgoDU9V.js} +1 -1
- package/src/assets/web-panel/assets/{index-DmkbAADP.js → index-UWdT_lty.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bw9s2d3c.js → index-VAlor744.js} +1 -1
- package/src/assets/web-panel/assets/{index-CN53dE_u.js → index-Wco_uvRo.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpdDnZX_.js → index-ZgFED_Zd.js} +1 -1
- package/src/assets/web-panel/assets/{index-IahILygB.js → index-jaHeti4i.js} +3 -3
- package/src/assets/web-panel/assets/{index-Cgk0YEiq.js → index-rJDabtw4.js} +1 -1
- package/src/assets/web-panel/assets/{index-t2Ch_JKn.js → index-vRiGKIP0.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-BoiGJDoO.js → initDefaultProps-BHT0PacY.js} +1 -1
- package/src/assets/web-panel/assets/{motion-5eL4E0pA.js → motion-CBYsMtsC.js} +1 -1
- package/src/assets/web-panel/assets/{move-DjggDi_1.js → move-DnHfFV4K.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-Ce8Vo2T3.js → mtc-parser-DfGkRYrx.js} +1 -1
- package/src/assets/web-panel/assets/{omit-D1yOBaO2.js → omit-SjLcPQA9.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-8NLceQe8.js → pickAttrs-CCZHF4ep.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CsIdBRmQ.js → placementArrow-DjCGn2sJ.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-GM5NE8En.js → responsiveObserve-BCFCl2on.js} +1 -1
- package/src/assets/web-panel/assets/{slide-44yj3u49.js → slide-DsfmuIZu.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-B8tqtWcX.js → statusUtils-hPKuJUia.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-NhDPBbyB.js → styleChecker-DOXKH7Kf.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-C8Qz1GtN.js → useFlexGapSupport-B_G7yqio.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-ClUv4kih.js → useFs-BXOTOm2l.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BwkOVEh6.js → usePersonalDataHub-CRvie3g7.js} +1 -1
- package/src/assets/web-panel/assets/{vnode--gMF33R0.js → vnode-D9_zMmww.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-DXg3TqOP.js → zoom-C4tW03qI.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/data/changelog.json +52 -0
- package/src/lib/artifact-store.js +51 -3
- package/src/lib/bounded-artifact-file-read.js +131 -0
- package/src/lib/evolution/evolution-artifact-ports.js +235 -129
- package/src/lib/evolution/evolution-eval-cohort-enrollment.js +713 -47
- package/src/lib/evolution/evolution-eval-gate.js +146 -0
- package/src/lib/evolution/evolution-eval-launch-admission.js +168 -90
- package/src/lib/evolution/evolution-eval-ledger-capture.js +50 -0
- package/src/lib/evolution/evolution-eval-readonly-audit.js +106 -0
- package/src/lib/evolution/evolution-ledger-file-backend.js +20 -0
- package/src/lib/evolution/evolution-ledger-ports.js +59 -27
- package/src/lib/evolution/evolution-ledger-v2-journal.js +55 -18
- package/src/lib/evolution/evolution-workbench-file-resources.js +19 -1
- package/src/lib/evolution/rrsi-cohort-registration.js +275 -0
- package/src/lib/evolution/rrsi-contracts.js +742 -0
- package/src/lib/evolution/rrsi-data.js +196 -0
- package/src/lib/evolution/rrsi-evaluation-adapter.js +275 -0
- package/src/lib/evolution/rrsi-evaluation-variants.js +267 -0
- package/src/lib/evolution/rrsi-group-statistics.js +553 -0
- package/src/lib/evolution/rrsi-history-ledger-adapter.js +3286 -0
- package/src/lib/evolution/rrsi-installation-route-contracts.js +275 -0
- package/src/lib/evolution/rrsi-installation-route-reader.js +282 -0
- package/src/lib/evolution/rrsi-native-batch-evidence.js +520 -0
- package/src/lib/evolution/rrsi-native-eval-row-collector.js +538 -0
- package/src/lib/evolution/rrsi-native-evaluation-batch.js +547 -0
- package/src/lib/evolution/rrsi-native-evaluation-plan.js +431 -0
- package/src/lib/evolution/rrsi-native-group-statistics-plan.js +121 -0
- package/src/lib/evolution/rrsi-native-group-statistics.js +482 -0
- package/src/lib/evolution/rrsi-native-quality-receipt-contracts.js +321 -0
- package/src/lib/evolution/rrsi-native-quality-receipt.js +256 -0
- package/src/lib/evolution/rrsi-native-statistics-protocol.js +330 -0
- package/src/lib/evolution/rrsi-observed-text-index.js +978 -0
- package/src/lib/evolution/rrsi-parent-binding.js +290 -0
- package/src/lib/evolution/rrsi-pm-execution-bridge.js +500 -0
- package/src/lib/evolution/rrsi-pm-training-mapping.js +134 -0
- package/src/lib/evolution/rrsi-preparation-contracts.js +213 -0
- package/src/lib/evolution/rrsi-registry-history-binding.js +193 -0
- package/src/lib/evolution/rrsi-registry-runtime-policy.js +648 -0
- package/src/lib/evolution/rrsi-registry-store-policy.js +1271 -0
- package/src/lib/evolution/rrsi-runtime-composition.js +79 -0
- package/src/lib/evolution/rrsi-selector.js +434 -0
- package/src/lib/evolution/rrsi-statistics-simulation.js +354 -0
- package/src/lib/evolution/rrsi-tenant-index-anchor-contracts.js +223 -0
- package/src/lib/evolution/rrsi-tenant-index-anchor-history.js +157 -0
- package/src/lib/evolution/skill-candidate-registry.js +216 -29
- package/src/lib/evolution/skill-registry-maintenance.js +80 -0
- package/src/lib/evolution/skill-registry-writer-control.js +138 -0
- package/src/lib/evolution/skill-release-registry.js +414 -169
- package/src/lib/formal-quality-eval-runtime.js +2 -14
- package/src/lib/goal-store.js +225 -16
- package/src/lib/process-execution-broker/windows-native-evaluator-capabilities.js +291 -30
- package/src/lib/record-replay/skill-recorder.js +38 -3
- package/src/lib/remote-read-loop-guard.js +227 -24
- package/src/lib/scheduler-kernel/authority-resolver.js +12 -163
- package/src/lib/scheduler-kernel/contract.js +27 -414
- package/src/lib/scheduler-kernel/runtime.js +21 -823
- package/src/lib/scheduler-kernel/service.js +11 -328
- package/src/lib/scheduler-kernel/source-locator-path.js +7 -142
- package/src/lib/scheduler-kernel/store.js +67 -5931
- package/src/lib/secure-fs.js +16 -1314
- package/src/lib/task-progress-tracker.js +6 -1
- package/src/lib/with-file-lock.js +122 -20
- package/src/runtime/agent-core.js +71 -23
- package/src/assets/web-panel/assets/ChatBubbleRenderer-fcc_Tvqu.js +0 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-BtvN-Qwh.js +0 -1
- package/src/assets/web-panel/assets/devWarning-CfB8DxT2.js +0 -1
- package/src/assets/web-panel/assets/index-CCsRfd0k.js +0 -1
- package/src/assets/web-panel/assets/index-CsmiZIQ4.js +0 -1
|
@@ -0,0 +1,1271 @@
|
|
|
1
|
+
/** Authenticated fresh store provisioning, before any Registry is constructed.
|
|
2
|
+
* This installs a writer floor, not content-origin or business admission.
|
|
3
|
+
* Existing stores and ambiguous partial publication are never adopted.
|
|
4
|
+
*/
|
|
5
|
+
import { randomUUID } from "node:crypto";
|
|
6
|
+
import fs from "node:fs";
|
|
7
|
+
import path from "node:path";
|
|
8
|
+
import { isProxy } from "node:util/types";
|
|
9
|
+
import {
|
|
10
|
+
repairPrivatePath,
|
|
11
|
+
ensurePrivateFile,
|
|
12
|
+
inspectPrivatePath,
|
|
13
|
+
} from "../secure-fs.js";
|
|
14
|
+
import { withFileLock } from "../with-file-lock.js";
|
|
15
|
+
import { withEvolutionFileIdentity } from "./evolution-file-identity.js";
|
|
16
|
+
import { readBoundedDescriptor } from "./bounded-descriptor-read.js";
|
|
17
|
+
import {
|
|
18
|
+
captureEvolutionLedgerFileBackend,
|
|
19
|
+
captureEvolutionLedgerFileBackendBinding,
|
|
20
|
+
} from "./evolution-ledger-file-backend.js";
|
|
21
|
+
import { isEvolutionLedgerV2Journal } from "./evolution-ledger-v2-journal.js";
|
|
22
|
+
import {
|
|
23
|
+
EVOLUTION_LEDGER_MAX_EVENTS,
|
|
24
|
+
EVOLUTION_LEDGER_DOMAIN_EVENT_SCHEMA,
|
|
25
|
+
EVOLUTION_ARTIFACT_RESOLUTION_SCHEMA,
|
|
26
|
+
EVOLUTION_ARTIFACT_REF_SCHEMA,
|
|
27
|
+
} from "./evolution-ledger.js";
|
|
28
|
+
import {
|
|
29
|
+
EvolutionArtifactPorts,
|
|
30
|
+
EVOLUTION_DURABLE_ARTIFACT_RECORD_SCHEMA,
|
|
31
|
+
captureEvolutionLedgerArtifactResolverBinding,
|
|
32
|
+
} from "./evolution-artifact-ports.js";
|
|
33
|
+
import { deriveSkillCandidateTenantKey } from "./skill-candidate-registry.js";
|
|
34
|
+
import { deriveSkillReleaseTenantKey } from "./skill-release-registry.js";
|
|
35
|
+
import { captureSkillReleaseOperationReader } from "./evolution-ledger-ports.js";
|
|
36
|
+
import { captureRrsiRegistryRuntimePolicy } from "./rrsi-registry-runtime-policy.js";
|
|
37
|
+
import {
|
|
38
|
+
snapshotRrsiData,
|
|
39
|
+
rrsiExact,
|
|
40
|
+
rrsiId,
|
|
41
|
+
rrsiHash,
|
|
42
|
+
rrsiCanonical,
|
|
43
|
+
rrsiEnvelope,
|
|
44
|
+
freezeRrsiData,
|
|
45
|
+
rrsiFail,
|
|
46
|
+
} from "./rrsi-data.js";
|
|
47
|
+
|
|
48
|
+
export const RRSI_REGISTRY_STORE_POLICY_SCHEMA =
|
|
49
|
+
"chainlesschain.rrsi-registry-store-policy/v1";
|
|
50
|
+
export const RRSI_REGISTRY_STORE_POLICY_EVENT_TYPE =
|
|
51
|
+
"rrsi.registry-store-policy.committed";
|
|
52
|
+
export const RRSI_REGISTRY_STORE_POLICY_ARTIFACT_TYPE =
|
|
53
|
+
"skill-registry-store-policy";
|
|
54
|
+
export const RRSI_REGISTRY_STORE_POLICY_HOLD_CODE =
|
|
55
|
+
"CC_RRSI_REGISTRY_STORE_POLICY_HOLD";
|
|
56
|
+
export const RRSI_REGISTRY_STORE_WRITER_FLOOR = 2;
|
|
57
|
+
const EVENT_SCHEMA = "chainlesschain.rrsi-registry-store-policy-event/v1";
|
|
58
|
+
const PREPARED_SCHEMA = "chainlesschain.rrsi-registry-store-prepared/v1";
|
|
59
|
+
const PHASES = ["prepared", "markers-installed", "committed"];
|
|
60
|
+
const POLICIES = new WeakMap();
|
|
61
|
+
const COMPONENT_BINDINGS = new WeakMap();
|
|
62
|
+
const ORIGIN_POLICY = freezeRrsiData({
|
|
63
|
+
schema: "chainlesschain.rrsi-registry-origin-policy/v1",
|
|
64
|
+
revision: 1,
|
|
65
|
+
writerFloor: RRSI_REGISTRY_STORE_WRITER_FLOOR,
|
|
66
|
+
unknownContent: "hold",
|
|
67
|
+
contentKey: "tenant-and-content-digest",
|
|
68
|
+
originAdmissionImplemented: false,
|
|
69
|
+
});
|
|
70
|
+
const ORIGIN_POLICY_DIGEST = rrsiHash(ORIGIN_POLICY.schema, ORIGIN_POLICY);
|
|
71
|
+
function hold(message, cause) {
|
|
72
|
+
try {
|
|
73
|
+
rrsiFail(message, RRSI_REGISTRY_STORE_POLICY_HOLD_CODE);
|
|
74
|
+
} catch (error) {
|
|
75
|
+
if (cause) error.cause = cause;
|
|
76
|
+
throw error;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
const canonicalPath = (value) =>
|
|
80
|
+
process.platform === "win32" ? value.toLowerCase() : value;
|
|
81
|
+
const physicalIdentity = (stat) => `${String(stat.dev)}:${String(stat.ino)}`;
|
|
82
|
+
const headFields = [
|
|
83
|
+
"ledgerId",
|
|
84
|
+
"identityDigest",
|
|
85
|
+
"epoch",
|
|
86
|
+
"sequence",
|
|
87
|
+
"headDigest",
|
|
88
|
+
];
|
|
89
|
+
const headData = (head) =>
|
|
90
|
+
Object.fromEntries(headFields.map((key) => [key, head[key]]));
|
|
91
|
+
const journalIdentity = (head) =>
|
|
92
|
+
Object.fromEntries(headFields.slice(0, 3).map((key) => [key, head[key]]));
|
|
93
|
+
const equal = (left, right) => rrsiCanonical(left) === rrsiCanonical(right);
|
|
94
|
+
const bytesFor = (value) => Buffer.from(`${rrsiCanonical(value)}\n`, "utf8");
|
|
95
|
+
function ownOptions(input, names) {
|
|
96
|
+
if (
|
|
97
|
+
!input ||
|
|
98
|
+
isProxy(input) ||
|
|
99
|
+
Object.getPrototypeOf(input) !== Object.prototype ||
|
|
100
|
+
Reflect.ownKeys(input).length !== names.length
|
|
101
|
+
)
|
|
102
|
+
rrsiFail("store policy requires plain own composition fields");
|
|
103
|
+
return Object.fromEntries(
|
|
104
|
+
names.map((name) => {
|
|
105
|
+
const field = Object.getOwnPropertyDescriptor(input, name);
|
|
106
|
+
if (!field?.enumerable || !("value" in field))
|
|
107
|
+
rrsiFail("store policy cannot use option accessors");
|
|
108
|
+
return [name, field.value];
|
|
109
|
+
}),
|
|
110
|
+
);
|
|
111
|
+
}
|
|
112
|
+
function directory(value) {
|
|
113
|
+
// NTFS file IDs can exceed Number's exact integer range. Preserve the full
|
|
114
|
+
// identity so distinct directories cannot collide after numeric rounding.
|
|
115
|
+
const stat = fs.lstatSync(value, { bigint: true });
|
|
116
|
+
if (
|
|
117
|
+
!stat.isDirectory() ||
|
|
118
|
+
stat.isSymbolicLink() ||
|
|
119
|
+
canonicalPath(path.resolve(fs.realpathSync.native(value))) !==
|
|
120
|
+
canonicalPath(value)
|
|
121
|
+
)
|
|
122
|
+
hold("store directory is not a canonical physical directory");
|
|
123
|
+
return { path: value, identity: physicalIdentity(stat) };
|
|
124
|
+
}
|
|
125
|
+
function assertDirectory(expected) {
|
|
126
|
+
if (!equal(directory(expected.path), expected))
|
|
127
|
+
hold("store directory identity changed");
|
|
128
|
+
}
|
|
129
|
+
function assertPrivateParent(expected) {
|
|
130
|
+
assertDirectory(expected);
|
|
131
|
+
const permission = inspectPrivatePath(expected.path);
|
|
132
|
+
if (permission?.ok !== true || permission.exists !== true)
|
|
133
|
+
hold(
|
|
134
|
+
"provisioning parent must have verified owner-only permissions",
|
|
135
|
+
new Error(
|
|
136
|
+
permission?.error ||
|
|
137
|
+
"owner-only permission inspection was not successful",
|
|
138
|
+
),
|
|
139
|
+
);
|
|
140
|
+
assertDirectory(expected);
|
|
141
|
+
}
|
|
142
|
+
function syncDirectory(value) {
|
|
143
|
+
let fd;
|
|
144
|
+
try {
|
|
145
|
+
fd = fs.openSync(value, "r");
|
|
146
|
+
fs.fsyncSync(fd);
|
|
147
|
+
return true;
|
|
148
|
+
} catch (error) {
|
|
149
|
+
// Node cannot flush directory handles on these Windows filesystems. The
|
|
150
|
+
// authenticated journal and reopen checks do not imply power-loss proof.
|
|
151
|
+
if (
|
|
152
|
+
process.platform !== "win32" ||
|
|
153
|
+
!["EACCES", "EINVAL", "EISDIR", "EPERM"].includes(error?.code)
|
|
154
|
+
)
|
|
155
|
+
throw error;
|
|
156
|
+
return false;
|
|
157
|
+
} finally {
|
|
158
|
+
if (fd !== undefined) fs.closeSync(fd);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
function readMarker(file, linkCount = 1) {
|
|
162
|
+
return withEvolutionFileIdentity(fs, file, (samePathHandle) => {
|
|
163
|
+
const before = fs.lstatSync(file, { bigint: true });
|
|
164
|
+
const safe = (stat) =>
|
|
165
|
+
stat.isFile() &&
|
|
166
|
+
!stat.isSymbolicLink() &&
|
|
167
|
+
Number(stat.nlink) === linkCount &&
|
|
168
|
+
stat.size > 0 &&
|
|
169
|
+
stat.size <= 4096;
|
|
170
|
+
if (
|
|
171
|
+
!safe(before) ||
|
|
172
|
+
canonicalPath(fs.realpathSync.native(file)) !== canonicalPath(file)
|
|
173
|
+
)
|
|
174
|
+
hold("store marker is not a bounded regular file");
|
|
175
|
+
const fd = fs.openSync(
|
|
176
|
+
file,
|
|
177
|
+
fs.constants.O_RDONLY | (fs.constants.O_NOFOLLOW || 0),
|
|
178
|
+
);
|
|
179
|
+
try {
|
|
180
|
+
const opened = fs.fstatSync(fd, { bigint: true });
|
|
181
|
+
if (!safe(opened) || !samePathHandle(before, opened))
|
|
182
|
+
hold("store marker changed while opening");
|
|
183
|
+
const bytes = readBoundedDescriptor(fs, fd, Number(before.size), 4096);
|
|
184
|
+
const after = fs.lstatSync(file, { bigint: true });
|
|
185
|
+
if (
|
|
186
|
+
!samePathHandle(after, fs.fstatSync(fd, { bigint: true })) ||
|
|
187
|
+
!equal(
|
|
188
|
+
[
|
|
189
|
+
physicalIdentity(before),
|
|
190
|
+
String(before.size),
|
|
191
|
+
String(before.mtimeNs),
|
|
192
|
+
String(before.ctimeNs),
|
|
193
|
+
],
|
|
194
|
+
[
|
|
195
|
+
physicalIdentity(after),
|
|
196
|
+
String(after.size),
|
|
197
|
+
String(after.mtimeNs),
|
|
198
|
+
String(after.ctimeNs),
|
|
199
|
+
],
|
|
200
|
+
) ||
|
|
201
|
+
!safe(after)
|
|
202
|
+
)
|
|
203
|
+
hold("store marker changed while reading");
|
|
204
|
+
return { bytes, identity: physicalIdentity(after) };
|
|
205
|
+
} finally {
|
|
206
|
+
fs.closeSync(fd);
|
|
207
|
+
}
|
|
208
|
+
});
|
|
209
|
+
}
|
|
210
|
+
function copyRef(value) {
|
|
211
|
+
const ref = Object.fromEntries(
|
|
212
|
+
["schema", "ref", "digest"].map((key) => {
|
|
213
|
+
const field = Object.getOwnPropertyDescriptor(value, key);
|
|
214
|
+
if (!field?.enumerable || !("value" in field))
|
|
215
|
+
hold("policy artifact reference differs");
|
|
216
|
+
return [key, field.value];
|
|
217
|
+
}),
|
|
218
|
+
);
|
|
219
|
+
if (ref.schema !== EVOLUTION_ARTIFACT_REF_SCHEMA)
|
|
220
|
+
hold("policy artifact reference schema differs");
|
|
221
|
+
return ref;
|
|
222
|
+
}
|
|
223
|
+
function storePaths(parent, namespaceId, tenantId) {
|
|
224
|
+
const namespace = path.join(parent, namespaceId);
|
|
225
|
+
const stores = Object.fromEntries(
|
|
226
|
+
["candidate", "release"].map((name) => {
|
|
227
|
+
const tenantKey =
|
|
228
|
+
name === "candidate"
|
|
229
|
+
? deriveSkillCandidateTenantKey(tenantId)
|
|
230
|
+
: deriveSkillReleaseTenantKey(tenantId);
|
|
231
|
+
const baseDir = path.join(namespace, name);
|
|
232
|
+
return [
|
|
233
|
+
name,
|
|
234
|
+
{
|
|
235
|
+
baseDir,
|
|
236
|
+
tenantKey,
|
|
237
|
+
rootDir: path.join(baseDir, "tenants", tenantKey),
|
|
238
|
+
},
|
|
239
|
+
];
|
|
240
|
+
}),
|
|
241
|
+
);
|
|
242
|
+
const paths = [namespace];
|
|
243
|
+
for (const name of ["candidate", "release"])
|
|
244
|
+
paths.push(
|
|
245
|
+
stores[name].baseDir,
|
|
246
|
+
path.dirname(stores[name].rootDir),
|
|
247
|
+
stores[name].rootDir,
|
|
248
|
+
);
|
|
249
|
+
return { namespace, stores, paths };
|
|
250
|
+
}
|
|
251
|
+
function markerFor(prepared, component) {
|
|
252
|
+
const store = prepared[component],
|
|
253
|
+
peer = prepared[component === "candidate" ? "release" : "candidate"];
|
|
254
|
+
const core = {
|
|
255
|
+
schema: `chainlesschain.skill-${component}-tenant-marker/v2`,
|
|
256
|
+
component: `skill-${component}-registry`,
|
|
257
|
+
tenantId: prepared.tenantId,
|
|
258
|
+
tenantKey: store.tenantKey,
|
|
259
|
+
storeId: store.storeId,
|
|
260
|
+
peerStoreId: peer.storeId,
|
|
261
|
+
namespaceId: prepared.namespaceId,
|
|
262
|
+
operationId: prepared.operationId,
|
|
263
|
+
journalIdentity: prepared.journalIdentity,
|
|
264
|
+
storeEpoch: 1,
|
|
265
|
+
writerFloor: RRSI_REGISTRY_STORE_WRITER_FLOOR,
|
|
266
|
+
originPolicyDigest: ORIGIN_POLICY_DIGEST,
|
|
267
|
+
provisionDigest: rrsiHash(PREPARED_SCHEMA, prepared),
|
|
268
|
+
};
|
|
269
|
+
return { ...core, markerDigest: rrsiHash(core.schema, core) };
|
|
270
|
+
}
|
|
271
|
+
function normalizePrepared(value, descriptor, identity) {
|
|
272
|
+
rrsiExact(
|
|
273
|
+
value,
|
|
274
|
+
[
|
|
275
|
+
"operationId",
|
|
276
|
+
"tenantId",
|
|
277
|
+
"namespaceId",
|
|
278
|
+
"parent",
|
|
279
|
+
"directories",
|
|
280
|
+
"candidate",
|
|
281
|
+
"release",
|
|
282
|
+
"journalIdentity",
|
|
283
|
+
"originPolicy",
|
|
284
|
+
"bootstrapDirectoryFsyncVerified",
|
|
285
|
+
],
|
|
286
|
+
"prepared store",
|
|
287
|
+
);
|
|
288
|
+
rrsiId(value.operationId, "provision operation ID");
|
|
289
|
+
if (
|
|
290
|
+
value.tenantId !== descriptor.tenantId ||
|
|
291
|
+
!equal(value.journalIdentity, identity) ||
|
|
292
|
+
!equal(value.originPolicy, ORIGIN_POLICY) ||
|
|
293
|
+
typeof value.bootstrapDirectoryFsyncVerified !== "boolean" ||
|
|
294
|
+
!/^pair\.[a-f0-9-]{36}$/u.test(value.namespaceId)
|
|
295
|
+
)
|
|
296
|
+
hold("prepared store identity or policy differs");
|
|
297
|
+
const uuid =
|
|
298
|
+
/^[a-f0-9]{8}-[a-f0-9]{4}-4[a-f0-9]{3}-[89ab][a-f0-9]{3}-[a-f0-9]{12}$/u;
|
|
299
|
+
if (!uuid.test(value.namespaceId.slice(5)))
|
|
300
|
+
hold("prepared namespace ID is invalid");
|
|
301
|
+
const validateDirectory = (entry) => {
|
|
302
|
+
rrsiExact(entry, ["path", "identity"], "prepared directory");
|
|
303
|
+
if (
|
|
304
|
+
typeof entry.path !== "string" ||
|
|
305
|
+
path.resolve(entry.path) !== entry.path ||
|
|
306
|
+
!/^[0-9]+:[0-9]+$/u.test(entry.identity)
|
|
307
|
+
)
|
|
308
|
+
hold("prepared directory identity is invalid");
|
|
309
|
+
};
|
|
310
|
+
validateDirectory(value.parent);
|
|
311
|
+
const expected = storePaths(
|
|
312
|
+
value.parent.path,
|
|
313
|
+
value.namespaceId,
|
|
314
|
+
value.tenantId,
|
|
315
|
+
);
|
|
316
|
+
if (
|
|
317
|
+
!Array.isArray(value.directories) ||
|
|
318
|
+
value.directories.length !== expected.paths.length
|
|
319
|
+
)
|
|
320
|
+
hold("prepared directory inventory differs");
|
|
321
|
+
value.directories.forEach((entry, index) => {
|
|
322
|
+
validateDirectory(entry);
|
|
323
|
+
if (entry.path !== expected.paths[index])
|
|
324
|
+
hold("prepared directory path differs");
|
|
325
|
+
});
|
|
326
|
+
if (
|
|
327
|
+
new Set([
|
|
328
|
+
value.parent.identity,
|
|
329
|
+
...value.directories.map((entry) => entry.identity),
|
|
330
|
+
]).size !== 8
|
|
331
|
+
)
|
|
332
|
+
hold("prepared directories are not independent");
|
|
333
|
+
for (const name of ["candidate", "release"]) {
|
|
334
|
+
rrsiExact(
|
|
335
|
+
value[name],
|
|
336
|
+
["baseDir", "rootDir", "tenantKey", "storeId"],
|
|
337
|
+
"prepared store component",
|
|
338
|
+
);
|
|
339
|
+
if (
|
|
340
|
+
!equal(value[name], {
|
|
341
|
+
...expected.stores[name],
|
|
342
|
+
storeId: value[name].storeId,
|
|
343
|
+
}) ||
|
|
344
|
+
typeof value[name].storeId !== "string" ||
|
|
345
|
+
!value[name].storeId.startsWith("store.") ||
|
|
346
|
+
!uuid.test(value[name].storeId.slice(6))
|
|
347
|
+
)
|
|
348
|
+
hold("prepared component binding differs");
|
|
349
|
+
}
|
|
350
|
+
if (value.candidate.storeId === value.release.storeId)
|
|
351
|
+
hold("store identities must be independent");
|
|
352
|
+
for (const name of ["candidate", "release"])
|
|
353
|
+
if (bytesFor(markerFor(value, name)).length > 4096)
|
|
354
|
+
hold("store marker exceeds byte limit");
|
|
355
|
+
return value;
|
|
356
|
+
}
|
|
357
|
+
function assertLayout(prepared, markerPresence, ownTemporary = null) {
|
|
358
|
+
assertDirectory(prepared.parent);
|
|
359
|
+
prepared.directories.forEach(assertDirectory);
|
|
360
|
+
const roots = [prepared.candidate.rootDir, prepared.release.rootDir];
|
|
361
|
+
for (const entry of prepared.directories) {
|
|
362
|
+
const children = prepared.directories
|
|
363
|
+
.filter((other) => path.dirname(other.path) === entry.path)
|
|
364
|
+
.map((other) => path.basename(other.path));
|
|
365
|
+
const rootIndex = roots.indexOf(entry.path);
|
|
366
|
+
if (rootIndex !== -1 && markerPresence[rootIndex])
|
|
367
|
+
children.push("_tenant.json");
|
|
368
|
+
if (ownTemporary && path.dirname(ownTemporary) === entry.path)
|
|
369
|
+
children.push(path.basename(ownTemporary));
|
|
370
|
+
const actual = fs.readdirSync(entry.path);
|
|
371
|
+
if (actual.length > 8 || !equal(actual.sort(), children.sort()))
|
|
372
|
+
hold(
|
|
373
|
+
"fresh store bootstrap inventory changed or contains publication debris",
|
|
374
|
+
);
|
|
375
|
+
}
|
|
376
|
+
prepared.directories.forEach(assertDirectory);
|
|
377
|
+
}
|
|
378
|
+
function assertImmutableBoundary(prepared) {
|
|
379
|
+
assertDirectory(prepared.parent);
|
|
380
|
+
prepared.directories.forEach(assertDirectory);
|
|
381
|
+
}
|
|
382
|
+
function markerProof(prepared, boundaryOnly = false) {
|
|
383
|
+
const check = () =>
|
|
384
|
+
boundaryOnly
|
|
385
|
+
? assertImmutableBoundary(prepared)
|
|
386
|
+
: assertLayout(prepared, [true, true]);
|
|
387
|
+
check();
|
|
388
|
+
const markers = Object.fromEntries(
|
|
389
|
+
["candidate", "release"].map((name) => {
|
|
390
|
+
const expected = markerFor(prepared, name);
|
|
391
|
+
const stored = readMarker(
|
|
392
|
+
path.join(prepared[name].rootDir, "_tenant.json"),
|
|
393
|
+
);
|
|
394
|
+
if (!stored.bytes.equals(bytesFor(expected)))
|
|
395
|
+
hold("store marker bytes or writer floor differ");
|
|
396
|
+
return [
|
|
397
|
+
name,
|
|
398
|
+
{ markerDigest: expected.markerDigest, identity: stored.identity },
|
|
399
|
+
];
|
|
400
|
+
}),
|
|
401
|
+
);
|
|
402
|
+
check();
|
|
403
|
+
return markers;
|
|
404
|
+
}
|
|
405
|
+
|
|
406
|
+
export function createRrsiRegistryStorePolicy(input) {
|
|
407
|
+
const options = ownOptions(input, [
|
|
408
|
+
"backend",
|
|
409
|
+
"artifactPorts",
|
|
410
|
+
"ledgerArtifactResolver",
|
|
411
|
+
"descriptor",
|
|
412
|
+
]);
|
|
413
|
+
const backend = captureEvolutionLedgerFileBackend(options.backend);
|
|
414
|
+
const binding = captureEvolutionLedgerFileBackendBinding(backend);
|
|
415
|
+
const ledger = backend.ledger;
|
|
416
|
+
if (!isEvolutionLedgerV2Journal(ledger) || !binding.matchesLedger(ledger))
|
|
417
|
+
rrsiFail("store policy requires the genuine original retained v2 journal");
|
|
418
|
+
const resolver = options.ledgerArtifactResolver;
|
|
419
|
+
const resolverBinding =
|
|
420
|
+
captureEvolutionLedgerArtifactResolverBinding(resolver);
|
|
421
|
+
if (
|
|
422
|
+
!binding.matchesArtifactResolver(resolver) ||
|
|
423
|
+
!resolverBinding.matchesArtifactPorts(options.artifactPorts) ||
|
|
424
|
+
isProxy(options.artifactPorts) ||
|
|
425
|
+
Object.getPrototypeOf(options.artifactPorts) !==
|
|
426
|
+
EvolutionArtifactPorts.prototype
|
|
427
|
+
)
|
|
428
|
+
rrsiFail("store policy requires the original artifact ports and resolver");
|
|
429
|
+
const configured = snapshotRrsiData(options.descriptor);
|
|
430
|
+
rrsiExact(
|
|
431
|
+
configured,
|
|
432
|
+
["tenantId", "artifactTenantId", "audience", "purpose"],
|
|
433
|
+
"store policy descriptor",
|
|
434
|
+
);
|
|
435
|
+
Object.entries(configured).forEach(([key, value]) => rrsiId(value, key));
|
|
436
|
+
if (
|
|
437
|
+
configured.purpose !== "evolution-ledger" ||
|
|
438
|
+
configured.tenantId !== ledger.descriptor.tenantId ||
|
|
439
|
+
configured.artifactTenantId !== ledger.descriptor.artifactTenantId ||
|
|
440
|
+
configured.audience !== ledger.descriptor.audience ||
|
|
441
|
+
configured.artifactTenantId !== resolverBinding.tenantId ||
|
|
442
|
+
configured.audience !== resolverBinding.audience ||
|
|
443
|
+
configured.purpose !== resolverBinding.purpose
|
|
444
|
+
)
|
|
445
|
+
rrsiFail("store policy journal and artifact scope differ");
|
|
446
|
+
const descriptor = freezeRrsiData({
|
|
447
|
+
...configured,
|
|
448
|
+
schema: RRSI_REGISTRY_STORE_POLICY_SCHEMA,
|
|
449
|
+
scopeId: `registry-policy.${rrsiHash(RRSI_REGISTRY_STORE_POLICY_SCHEMA, configured).slice(7)}`,
|
|
450
|
+
writerFloor: RRSI_REGISTRY_STORE_WRITER_FLOOR,
|
|
451
|
+
originPolicyDigest: ORIGIN_POLICY_DIGEST,
|
|
452
|
+
existingStoreUpgradeImplemented: false,
|
|
453
|
+
originAdmissionImplemented: false,
|
|
454
|
+
grantsMutationOrPromotionAuthority: false,
|
|
455
|
+
});
|
|
456
|
+
const methods = Object.fromEntries(
|
|
457
|
+
["read", "verify", "appendDomainEvent"].map((name) => {
|
|
458
|
+
const method = Object.getOwnPropertyDescriptor(ledger, name)?.value;
|
|
459
|
+
if (typeof method !== "function" || !Object.isFrozen(ledger))
|
|
460
|
+
rrsiFail("store policy journal method is missing or mutable");
|
|
461
|
+
return [name, (...args) => Reflect.apply(method, ledger, args)];
|
|
462
|
+
}),
|
|
463
|
+
);
|
|
464
|
+
const lockTarget = path.join(
|
|
465
|
+
backend.descriptor.authorityRootDir,
|
|
466
|
+
"rrsi-registry-store-policy-operations",
|
|
467
|
+
);
|
|
468
|
+
let active = false;
|
|
469
|
+
const locked = (operation) => {
|
|
470
|
+
if (active) hold("store policy operation is already active");
|
|
471
|
+
active = true;
|
|
472
|
+
try {
|
|
473
|
+
return withFileLock(
|
|
474
|
+
lockTarget,
|
|
475
|
+
(context) => {
|
|
476
|
+
if (context.locked !== true)
|
|
477
|
+
hold("store policy lock was not acquired");
|
|
478
|
+
context.assertOwnership();
|
|
479
|
+
const result = operation(context.assertOwnership);
|
|
480
|
+
context.assertOwnership();
|
|
481
|
+
return result;
|
|
482
|
+
},
|
|
483
|
+
{ failIfUnavailable: true, timeoutMs: 0 },
|
|
484
|
+
);
|
|
485
|
+
} finally {
|
|
486
|
+
active = false;
|
|
487
|
+
}
|
|
488
|
+
};
|
|
489
|
+
const eventId = (operationId, phase) =>
|
|
490
|
+
`registry-policy.${rrsiHash(EVENT_SCHEMA, { operationId, phase }).slice(7)}`;
|
|
491
|
+
function resolveRecord(ref, identity) {
|
|
492
|
+
const resolution = Reflect.apply(resolver, undefined, [
|
|
493
|
+
{
|
|
494
|
+
ledgerId: identity.ledgerId,
|
|
495
|
+
epoch: identity.epoch,
|
|
496
|
+
ref: copyRef(ref),
|
|
497
|
+
tenantId: descriptor.artifactTenantId,
|
|
498
|
+
},
|
|
499
|
+
]);
|
|
500
|
+
if (
|
|
501
|
+
resolution?.schema !== EVOLUTION_ARTIFACT_RESOLUTION_SCHEMA ||
|
|
502
|
+
resolution.authenticated !== true ||
|
|
503
|
+
resolution.found !== true ||
|
|
504
|
+
resolution.ref !== ref.ref ||
|
|
505
|
+
resolution.digest !== ref.digest ||
|
|
506
|
+
!Buffer.isBuffer(resolution.bytes) ||
|
|
507
|
+
resolution.bytes.length > 1024 * 1024
|
|
508
|
+
)
|
|
509
|
+
hold("store policy artifact cannot be authenticated");
|
|
510
|
+
const stored = snapshotRrsiData(
|
|
511
|
+
JSON.parse(
|
|
512
|
+
new TextDecoder("utf-8", { fatal: true }).decode(resolution.bytes),
|
|
513
|
+
),
|
|
514
|
+
);
|
|
515
|
+
if (
|
|
516
|
+
stored.schema !== EVOLUTION_DURABLE_ARTIFACT_RECORD_SCHEMA ||
|
|
517
|
+
stored.tenantId !== descriptor.artifactTenantId ||
|
|
518
|
+
stored.audience !== descriptor.audience ||
|
|
519
|
+
stored.purpose !== descriptor.purpose ||
|
|
520
|
+
stored.type !== RRSI_REGISTRY_STORE_POLICY_ARTIFACT_TYPE ||
|
|
521
|
+
stored.retention !== "ledger"
|
|
522
|
+
)
|
|
523
|
+
hold("store policy artifact retention scope differs");
|
|
524
|
+
if (!Buffer.from(rrsiCanonical(stored), "utf8").equals(resolution.bytes))
|
|
525
|
+
hold("store policy artifact bytes are not canonical");
|
|
526
|
+
return stored.value;
|
|
527
|
+
}
|
|
528
|
+
function load() {
|
|
529
|
+
for (let attempt = 0; attempt < 3; attempt++) {
|
|
530
|
+
const before = methods.verify();
|
|
531
|
+
if (
|
|
532
|
+
before.authenticated !== true ||
|
|
533
|
+
before.durable !== true ||
|
|
534
|
+
before.status !== "verified"
|
|
535
|
+
)
|
|
536
|
+
hold("store policy journal head is unauthenticated");
|
|
537
|
+
const events = methods.read({
|
|
538
|
+
afterSequence: 0,
|
|
539
|
+
limit: EVOLUTION_LEDGER_MAX_EVENTS,
|
|
540
|
+
});
|
|
541
|
+
if (events.length !== before.sequence) {
|
|
542
|
+
if (!equal(headData(before), headData(methods.verify()))) continue;
|
|
543
|
+
hold("store policy requires complete contiguous journal coverage");
|
|
544
|
+
}
|
|
545
|
+
if (events.some((event, index) => event.sequence !== index + 1))
|
|
546
|
+
hold("store policy requires complete contiguous journal coverage");
|
|
547
|
+
const identity = journalIdentity(before),
|
|
548
|
+
records = [];
|
|
549
|
+
let prepared = null,
|
|
550
|
+
proof = null;
|
|
551
|
+
for (const event of events) {
|
|
552
|
+
if (event.type !== RRSI_REGISTRY_STORE_POLICY_EVENT_TYPE) continue;
|
|
553
|
+
if (
|
|
554
|
+
records.length >= 3 ||
|
|
555
|
+
event.schema !== EVOLUTION_LEDGER_DOMAIN_EVENT_SCHEMA ||
|
|
556
|
+
event.tenantId !== descriptor.tenantId ||
|
|
557
|
+
event.artifactTenantId !== descriptor.artifactTenantId ||
|
|
558
|
+
event.correlationId !== descriptor.scopeId ||
|
|
559
|
+
event.skillName !== null ||
|
|
560
|
+
event.decision !== "committed"
|
|
561
|
+
)
|
|
562
|
+
hold("store policy event scope or phase count differs");
|
|
563
|
+
const record = resolveRecord(event.subjectRef, identity);
|
|
564
|
+
const coreNames = [
|
|
565
|
+
"descriptor",
|
|
566
|
+
"journalIdentity",
|
|
567
|
+
"operationId",
|
|
568
|
+
"phase",
|
|
569
|
+
"preparedDigest",
|
|
570
|
+
"previousRecordDigest",
|
|
571
|
+
"observedHead",
|
|
572
|
+
"payload",
|
|
573
|
+
];
|
|
574
|
+
rrsiExact(
|
|
575
|
+
record,
|
|
576
|
+
[
|
|
577
|
+
"schema",
|
|
578
|
+
...coreNames,
|
|
579
|
+
"recordDigest",
|
|
580
|
+
"structuralOnly",
|
|
581
|
+
"authenticated",
|
|
582
|
+
"readyForExecution",
|
|
583
|
+
"qualifiesForPromotion",
|
|
584
|
+
],
|
|
585
|
+
"store policy event record",
|
|
586
|
+
);
|
|
587
|
+
const rebuilt = rrsiEnvelope(
|
|
588
|
+
EVENT_SCHEMA,
|
|
589
|
+
"recordDigest",
|
|
590
|
+
Object.fromEntries(coreNames.map((name) => [name, record[name]])),
|
|
591
|
+
);
|
|
592
|
+
const previous = records.at(-1);
|
|
593
|
+
if (
|
|
594
|
+
!equal(record, rebuilt) ||
|
|
595
|
+
!equal(record.descriptor, descriptor) ||
|
|
596
|
+
!equal(record.journalIdentity, identity) ||
|
|
597
|
+
record.phase !== PHASES[records.length] ||
|
|
598
|
+
record.previousRecordDigest !==
|
|
599
|
+
(previous?.record.recordDigest ?? null) ||
|
|
600
|
+
!equal(record.observedHead, {
|
|
601
|
+
...identity,
|
|
602
|
+
sequence: event.sequence - 1,
|
|
603
|
+
headDigest: event.prevDigest,
|
|
604
|
+
}) ||
|
|
605
|
+
event.eventId !== eventId(record.operationId, record.phase) ||
|
|
606
|
+
!equal(event.sourceRefs.map(copyRef), previous ? [previous.ref] : [])
|
|
607
|
+
)
|
|
608
|
+
hold("store policy record identity, order or predecessor differs");
|
|
609
|
+
if (!prepared)
|
|
610
|
+
prepared = normalizePrepared(record.payload, descriptor, identity);
|
|
611
|
+
if (
|
|
612
|
+
record.operationId !== prepared.operationId ||
|
|
613
|
+
record.preparedDigest !== rrsiHash(PREPARED_SCHEMA, prepared)
|
|
614
|
+
)
|
|
615
|
+
hold("store policy operation or prepared bytes differ");
|
|
616
|
+
if (record.phase === "markers-installed") {
|
|
617
|
+
proof = record.payload;
|
|
618
|
+
rrsiExact(proof, ["candidate", "release"], "installed markers");
|
|
619
|
+
for (const name of ["candidate", "release"]) {
|
|
620
|
+
rrsiExact(
|
|
621
|
+
proof[name],
|
|
622
|
+
["markerDigest", "identity"],
|
|
623
|
+
"installed marker",
|
|
624
|
+
);
|
|
625
|
+
if (
|
|
626
|
+
proof[name].markerDigest !==
|
|
627
|
+
markerFor(prepared, name).markerDigest ||
|
|
628
|
+
!/^[0-9]+:[0-9]+$/u.test(proof[name].identity)
|
|
629
|
+
)
|
|
630
|
+
hold("installed marker identity differs");
|
|
631
|
+
}
|
|
632
|
+
if (proof.candidate.identity === proof.release.identity)
|
|
633
|
+
hold("marker files must be independent");
|
|
634
|
+
}
|
|
635
|
+
if (
|
|
636
|
+
record.phase === "committed" &&
|
|
637
|
+
!equal(record.payload, {
|
|
638
|
+
installedRecordDigest: previous.record.recordDigest,
|
|
639
|
+
markers: proof,
|
|
640
|
+
})
|
|
641
|
+
)
|
|
642
|
+
hold("committed provisioning differs from installed proof");
|
|
643
|
+
records.push({
|
|
644
|
+
record,
|
|
645
|
+
ref: copyRef(event.subjectRef),
|
|
646
|
+
sequence: event.sequence,
|
|
647
|
+
});
|
|
648
|
+
}
|
|
649
|
+
const after = methods.verify();
|
|
650
|
+
if (!equal(headData(before), headData(after))) continue;
|
|
651
|
+
return { head: headData(after), identity, records, prepared, proof };
|
|
652
|
+
}
|
|
653
|
+
hold("store policy journal changed repeatedly while reading");
|
|
654
|
+
}
|
|
655
|
+
function append(state, phase, payload, assertOwnership) {
|
|
656
|
+
assertOwnership();
|
|
657
|
+
const prepared = phase === "prepared" ? payload : state.prepared;
|
|
658
|
+
const previous = state.records.at(-1);
|
|
659
|
+
const record = rrsiEnvelope(EVENT_SCHEMA, "recordDigest", {
|
|
660
|
+
descriptor,
|
|
661
|
+
journalIdentity: state.identity,
|
|
662
|
+
operationId: prepared.operationId,
|
|
663
|
+
phase,
|
|
664
|
+
preparedDigest: rrsiHash(PREPARED_SCHEMA, prepared),
|
|
665
|
+
previousRecordDigest: previous?.record.recordDigest ?? null,
|
|
666
|
+
observedHead: state.head,
|
|
667
|
+
payload,
|
|
668
|
+
});
|
|
669
|
+
const published = Reflect.apply(
|
|
670
|
+
EvolutionArtifactPorts.prototype.putCanonical,
|
|
671
|
+
options.artifactPorts,
|
|
672
|
+
[
|
|
673
|
+
RRSI_REGISTRY_STORE_POLICY_ARTIFACT_TYPE,
|
|
674
|
+
record,
|
|
675
|
+
{
|
|
676
|
+
audience: descriptor.audience,
|
|
677
|
+
purpose: descriptor.purpose,
|
|
678
|
+
retention: "ledger",
|
|
679
|
+
},
|
|
680
|
+
],
|
|
681
|
+
);
|
|
682
|
+
if (
|
|
683
|
+
published?.receipt?.persisted !== true ||
|
|
684
|
+
published.receipt.readbackVerified !== true ||
|
|
685
|
+
published.receipt.integrityVerified !== true ||
|
|
686
|
+
published.receipt.retention !== "ledger" ||
|
|
687
|
+
!equal(resolveRecord(published.ref, state.identity), record)
|
|
688
|
+
)
|
|
689
|
+
hold("store policy artifact retention failed");
|
|
690
|
+
assertOwnership();
|
|
691
|
+
if (phase !== "prepared") {
|
|
692
|
+
const actual = markerProof(prepared);
|
|
693
|
+
if (!equal(actual, phase === "markers-installed" ? payload : state.proof))
|
|
694
|
+
hold("store marker identities changed before policy append");
|
|
695
|
+
} else assertLayout(prepared, [false, false]);
|
|
696
|
+
assertOwnership();
|
|
697
|
+
try {
|
|
698
|
+
const receipt = methods.appendDomainEvent(
|
|
699
|
+
{
|
|
700
|
+
type: RRSI_REGISTRY_STORE_POLICY_EVENT_TYPE,
|
|
701
|
+
eventId: eventId(prepared.operationId, phase),
|
|
702
|
+
tenantId: descriptor.tenantId,
|
|
703
|
+
artifactTenantId: descriptor.artifactTenantId,
|
|
704
|
+
correlationId: descriptor.scopeId,
|
|
705
|
+
skillName: null,
|
|
706
|
+
decision: "committed",
|
|
707
|
+
reason:
|
|
708
|
+
"Authenticated fresh Registry provisioning; independent business admission remains required",
|
|
709
|
+
sourceRefs: previous ? [previous.ref] : [],
|
|
710
|
+
subjectRef: copyRef(published.ref),
|
|
711
|
+
},
|
|
712
|
+
{
|
|
713
|
+
expectedHeadDigest: state.head.headDigest,
|
|
714
|
+
expectedSequence: state.head.sequence,
|
|
715
|
+
},
|
|
716
|
+
);
|
|
717
|
+
if (receipt?.authenticated !== true || receipt.durable !== true)
|
|
718
|
+
hold("store policy append is uncertain");
|
|
719
|
+
} catch (cause) {
|
|
720
|
+
throw Object.assign(
|
|
721
|
+
new Error(
|
|
722
|
+
"store policy commit is uncertain; recover the original operation",
|
|
723
|
+
{ cause },
|
|
724
|
+
),
|
|
725
|
+
{
|
|
726
|
+
code: "CC_RRSI_REGISTRY_STORE_POLICY_COMMIT_UNKNOWN",
|
|
727
|
+
operationId: prepared.operationId,
|
|
728
|
+
},
|
|
729
|
+
);
|
|
730
|
+
}
|
|
731
|
+
assertOwnership();
|
|
732
|
+
const readback = load();
|
|
733
|
+
if (readback.records.at(-1)?.record.recordDigest !== record.recordDigest)
|
|
734
|
+
hold("store policy committed record cannot be read back");
|
|
735
|
+
return readback;
|
|
736
|
+
}
|
|
737
|
+
function allocate(request, state, assertOwnership) {
|
|
738
|
+
const parent = directory(request.parentDir);
|
|
739
|
+
for (const protectedRoot of [
|
|
740
|
+
backend.descriptor.rootDir,
|
|
741
|
+
backend.descriptor.authorityRootDir,
|
|
742
|
+
path.dirname(backend.descriptor.witnessFilePath),
|
|
743
|
+
]) {
|
|
744
|
+
const relation = path.relative(protectedRoot, parent.path);
|
|
745
|
+
if (
|
|
746
|
+
relation === "" ||
|
|
747
|
+
(!relation.startsWith(`..${path.sep}`) &&
|
|
748
|
+
relation !== ".." &&
|
|
749
|
+
!path.isAbsolute(relation))
|
|
750
|
+
)
|
|
751
|
+
hold("fresh stores cannot be provisioned inside journal storage");
|
|
752
|
+
}
|
|
753
|
+
const present = (target) => {
|
|
754
|
+
try {
|
|
755
|
+
fs.lstatSync(target);
|
|
756
|
+
return true;
|
|
757
|
+
} catch (error) {
|
|
758
|
+
if (error.code === "ENOENT") return false;
|
|
759
|
+
throw error;
|
|
760
|
+
}
|
|
761
|
+
};
|
|
762
|
+
const assertOutsideRegistry = () => {
|
|
763
|
+
for (let ancestor = parent.path; ; ancestor = path.dirname(ancestor)) {
|
|
764
|
+
if (
|
|
765
|
+
present(path.join(ancestor, "_tenant.json")) ||
|
|
766
|
+
present(path.join(ancestor, "tenants")) ||
|
|
767
|
+
(present(path.join(ancestor, "candidate")) &&
|
|
768
|
+
present(path.join(ancestor, "release")))
|
|
769
|
+
)
|
|
770
|
+
hold("fresh stores cannot be nested inside a Registry tenant root");
|
|
771
|
+
if (ancestor === path.dirname(ancestor)) break;
|
|
772
|
+
}
|
|
773
|
+
};
|
|
774
|
+
// Invalid existing layouts need no permission helper. A valid allocation
|
|
775
|
+
// still verifies its original parent and repeats the ancestor check after
|
|
776
|
+
// that potentially slow read, before it creates any namespace.
|
|
777
|
+
assertOutsideRegistry();
|
|
778
|
+
assertPrivateParent(parent);
|
|
779
|
+
assertOutsideRegistry();
|
|
780
|
+
const namespaceId = `pair.${randomUUID()}`;
|
|
781
|
+
const layout = storePaths(parent.path, namespaceId, descriptor.tenantId);
|
|
782
|
+
let bootstrapDirectoryFsyncVerified = true;
|
|
783
|
+
const captured = [];
|
|
784
|
+
// No recursive creation, existence fallback, caller RNG or orphan adoption.
|
|
785
|
+
for (const target of layout.paths) {
|
|
786
|
+
assertOwnership();
|
|
787
|
+
assertDirectory(parent);
|
|
788
|
+
captured.forEach(assertDirectory);
|
|
789
|
+
assertOwnership();
|
|
790
|
+
fs.mkdirSync(target, { mode: 0o700 });
|
|
791
|
+
const original = directory(target);
|
|
792
|
+
assertOwnership();
|
|
793
|
+
assertDirectory(parent);
|
|
794
|
+
captured.forEach(assertDirectory);
|
|
795
|
+
assertDirectory(original);
|
|
796
|
+
assertOwnership();
|
|
797
|
+
repairPrivatePath(target, {
|
|
798
|
+
failIfUnavailable: true,
|
|
799
|
+
applyWindowsAcl: true,
|
|
800
|
+
});
|
|
801
|
+
assertDirectory(original);
|
|
802
|
+
captured.push(original);
|
|
803
|
+
bootstrapDirectoryFsyncVerified =
|
|
804
|
+
syncDirectory(path.dirname(target)) && bootstrapDirectoryFsyncVerified;
|
|
805
|
+
}
|
|
806
|
+
const prepared = {
|
|
807
|
+
operationId: request.operationId,
|
|
808
|
+
tenantId: descriptor.tenantId,
|
|
809
|
+
namespaceId,
|
|
810
|
+
parent,
|
|
811
|
+
directories: captured,
|
|
812
|
+
candidate: {
|
|
813
|
+
...layout.stores.candidate,
|
|
814
|
+
storeId: `store.${randomUUID()}`,
|
|
815
|
+
},
|
|
816
|
+
release: { ...layout.stores.release, storeId: `store.${randomUUID()}` },
|
|
817
|
+
journalIdentity: state.identity,
|
|
818
|
+
originPolicy: ORIGIN_POLICY,
|
|
819
|
+
bootstrapDirectoryFsyncVerified,
|
|
820
|
+
};
|
|
821
|
+
normalizePrepared(prepared, descriptor, state.identity);
|
|
822
|
+
assertLayout(prepared, [false, false]);
|
|
823
|
+
return prepared;
|
|
824
|
+
}
|
|
825
|
+
function install(prepared, name, presence, assertOwnership) {
|
|
826
|
+
assertOwnership();
|
|
827
|
+
assertLayout(prepared, presence);
|
|
828
|
+
assertOwnership();
|
|
829
|
+
const root = prepared[name].rootDir;
|
|
830
|
+
const target = path.join(root, "_tenant.json");
|
|
831
|
+
const temporary = path.join(root, `.rrsi-marker-${randomUUID()}.tmp`);
|
|
832
|
+
const bytes = bytesFor(markerFor(prepared, name));
|
|
833
|
+
const fd = fs.openSync(temporary, "wx", 0o600);
|
|
834
|
+
let written;
|
|
835
|
+
try {
|
|
836
|
+
assertOwnership();
|
|
837
|
+
assertLayout(prepared, presence, temporary);
|
|
838
|
+
assertOwnership();
|
|
839
|
+
fs.writeFileSync(fd, bytes);
|
|
840
|
+
fs.fsyncSync(fd);
|
|
841
|
+
written = fs.fstatSync(fd, { bigint: true });
|
|
842
|
+
} finally {
|
|
843
|
+
fs.closeSync(fd);
|
|
844
|
+
}
|
|
845
|
+
// Failures deliberately retain private debris. Never delete or overwrite a
|
|
846
|
+
// canonical marker or clean a path after ownership/identity has changed.
|
|
847
|
+
const staged = readMarker(temporary);
|
|
848
|
+
if (
|
|
849
|
+
!staged.bytes.equals(bytes) ||
|
|
850
|
+
String(written.ino) !== staged.identity.split(":")[1]
|
|
851
|
+
)
|
|
852
|
+
hold("store marker temporary identity differs");
|
|
853
|
+
assertOwnership();
|
|
854
|
+
assertLayout(prepared, presence, temporary);
|
|
855
|
+
assertOwnership();
|
|
856
|
+
ensurePrivateFile(temporary, {
|
|
857
|
+
failIfUnavailable: true,
|
|
858
|
+
applyWindowsAcl: true,
|
|
859
|
+
});
|
|
860
|
+
if (!equal(readMarker(temporary), staged))
|
|
861
|
+
hold("store marker changed during permission hardening");
|
|
862
|
+
assertOwnership();
|
|
863
|
+
assertLayout(prepared, presence, temporary);
|
|
864
|
+
assertOwnership();
|
|
865
|
+
fs.linkSync(temporary, target); // A v1 winner is never replaced.
|
|
866
|
+
const linked = readMarker(target, 2),
|
|
867
|
+
source = readMarker(temporary, 2);
|
|
868
|
+
if (
|
|
869
|
+
linked.identity !== staged.identity ||
|
|
870
|
+
source.identity !== staged.identity ||
|
|
871
|
+
!linked.bytes.equals(bytes) ||
|
|
872
|
+
!source.bytes.equals(bytes)
|
|
873
|
+
)
|
|
874
|
+
hold("store marker publication identity differs");
|
|
875
|
+
presence[name === "candidate" ? 0 : 1] = true;
|
|
876
|
+
assertOwnership();
|
|
877
|
+
assertLayout(prepared, presence, temporary);
|
|
878
|
+
if (readMarker(temporary, 2).identity !== staged.identity)
|
|
879
|
+
hold("store marker temporary was replaced");
|
|
880
|
+
assertOwnership();
|
|
881
|
+
fs.unlinkSync(temporary);
|
|
882
|
+
syncDirectory(root);
|
|
883
|
+
assertLayout(prepared, presence);
|
|
884
|
+
const final = readMarker(target);
|
|
885
|
+
if (final.identity !== staged.identity || !final.bytes.equals(bytes))
|
|
886
|
+
hold("store marker final readback differs");
|
|
887
|
+
}
|
|
888
|
+
function status(
|
|
889
|
+
state,
|
|
890
|
+
operationId,
|
|
891
|
+
{ boundaryOnly = false, verifyPermissions = true } = {},
|
|
892
|
+
) {
|
|
893
|
+
if (!state.prepared || state.prepared.operationId !== operationId)
|
|
894
|
+
hold("store provisioning operation is unregistered");
|
|
895
|
+
if (verifyPermissions) assertPrivateParent(state.prepared.parent);
|
|
896
|
+
else assertDirectory(state.prepared.parent);
|
|
897
|
+
const actual = markerProof(state.prepared, boundaryOnly);
|
|
898
|
+
if (state.proof && !equal(actual, state.proof))
|
|
899
|
+
hold("installed marker physical identity changed");
|
|
900
|
+
if (!equal(state.head, headData(methods.verify())))
|
|
901
|
+
hold("store policy journal changed during physical readback");
|
|
902
|
+
return freezeRrsiData({
|
|
903
|
+
schema: "chainlesschain.rrsi-registry-store-policy-readback/v1",
|
|
904
|
+
phase: state.records.at(-1).record.phase,
|
|
905
|
+
operationId,
|
|
906
|
+
prepared: state.prepared,
|
|
907
|
+
markers: actual,
|
|
908
|
+
records: state.records.map(({ record, ref, sequence }) => ({
|
|
909
|
+
phase: record.phase,
|
|
910
|
+
recordDigest: record.recordDigest,
|
|
911
|
+
ref,
|
|
912
|
+
sequence,
|
|
913
|
+
})),
|
|
914
|
+
currentHead: state.head,
|
|
915
|
+
historyAuthenticated: true,
|
|
916
|
+
freshPhysicalPairProvisioningCommitted: state.records.length === 3,
|
|
917
|
+
writerFloorInstalled: true,
|
|
918
|
+
bootstrapDirectoryFsyncVerified:
|
|
919
|
+
state.prepared.bootstrapDirectoryFsyncVerified,
|
|
920
|
+
markerDirectoryFsyncAttested: false,
|
|
921
|
+
originCutoverAuthenticated: false,
|
|
922
|
+
originClassificationAvailable: false,
|
|
923
|
+
legacyWriterDrainVerified: false,
|
|
924
|
+
productionAuthorityVerified: false,
|
|
925
|
+
grantsMutationOrPromotionAuthority: false,
|
|
926
|
+
readyForExecution: false,
|
|
927
|
+
qualifiesForPromotion: false,
|
|
928
|
+
});
|
|
929
|
+
}
|
|
930
|
+
function finish(state, operationId, assertOwnership) {
|
|
931
|
+
const verified = status(state, operationId); // Missing marker or debris never triggers repair.
|
|
932
|
+
// A committed operation performs no mutation. Its complete bookended
|
|
933
|
+
// readback already has the same checks as read(), with the outer ownership
|
|
934
|
+
// check still required before returning from the operation lock.
|
|
935
|
+
if (state.records.length === 3) return verified;
|
|
936
|
+
if (state.records.length === 1)
|
|
937
|
+
state = append(
|
|
938
|
+
state,
|
|
939
|
+
"markers-installed",
|
|
940
|
+
markerProof(state.prepared),
|
|
941
|
+
assertOwnership,
|
|
942
|
+
);
|
|
943
|
+
if (state.records.length === 2)
|
|
944
|
+
state = append(
|
|
945
|
+
state,
|
|
946
|
+
"committed",
|
|
947
|
+
{
|
|
948
|
+
installedRecordDigest: state.records[1].record.recordDigest,
|
|
949
|
+
markers: state.proof,
|
|
950
|
+
},
|
|
951
|
+
assertOwnership,
|
|
952
|
+
);
|
|
953
|
+
return status(state, operationId);
|
|
954
|
+
}
|
|
955
|
+
// Constructor verifies retained history but never installs missing markers.
|
|
956
|
+
load();
|
|
957
|
+
const policy = Object.freeze({
|
|
958
|
+
descriptor,
|
|
959
|
+
bindComponent(input) {
|
|
960
|
+
if (
|
|
961
|
+
!input ||
|
|
962
|
+
isProxy(input) ||
|
|
963
|
+
Object.getPrototypeOf(input) !== Object.prototype
|
|
964
|
+
)
|
|
965
|
+
hold("Registry component binding requires plain own fields");
|
|
966
|
+
const names = ["operationId", "component"];
|
|
967
|
+
if (Object.hasOwn(input ?? {}, "runtimePolicy"))
|
|
968
|
+
names.push("runtimePolicy");
|
|
969
|
+
const request = ownOptions(input, names);
|
|
970
|
+
rrsiId(request.operationId, "provision operation ID");
|
|
971
|
+
if (!["candidate", "release"].includes(request.component))
|
|
972
|
+
rrsiFail("Registry binding component is invalid");
|
|
973
|
+
// Do not take a policy operation lock or repair any Registry path. The
|
|
974
|
+
// retained head and both physical markers bookend this read-only open.
|
|
975
|
+
let original,
|
|
976
|
+
runtime = null;
|
|
977
|
+
if (request.runtimePolicy !== undefined) {
|
|
978
|
+
runtime = captureRrsiRegistryRuntimePolicy(request.runtimePolicy);
|
|
979
|
+
if (!runtime.matchesPolicy(policy))
|
|
980
|
+
hold("runtime attachment requires the original store policy");
|
|
981
|
+
}
|
|
982
|
+
try {
|
|
983
|
+
original = status(load(), request.operationId, {
|
|
984
|
+
boundaryOnly: runtime !== null,
|
|
985
|
+
});
|
|
986
|
+
} catch (cause) {
|
|
987
|
+
if (["ENOENT", "ENOTDIR"].includes(cause?.code))
|
|
988
|
+
hold("an authenticated Registry binding boundary is missing", cause);
|
|
989
|
+
throw cause;
|
|
990
|
+
}
|
|
991
|
+
if (!original.freshPhysicalPairProvisioningCommitted)
|
|
992
|
+
hold("Registry requires committed fresh store provisioning");
|
|
993
|
+
const prepared = original.prepared;
|
|
994
|
+
const component = request.component;
|
|
995
|
+
const store = prepared[component];
|
|
996
|
+
const peer =
|
|
997
|
+
prepared[component === "candidate" ? "release" : "candidate"];
|
|
998
|
+
const runtimeOriginal = runtime?.read(request.operationId) ?? null;
|
|
999
|
+
const componentDescriptor = freezeRrsiData({
|
|
1000
|
+
schema: "chainlesschain.rrsi-registry-component-binding/v1",
|
|
1001
|
+
operationId: request.operationId,
|
|
1002
|
+
component: `skill-${component}-registry`,
|
|
1003
|
+
tenantId: prepared.tenantId,
|
|
1004
|
+
...store,
|
|
1005
|
+
peerStoreId: peer.storeId,
|
|
1006
|
+
journalIdentity: prepared.journalIdentity,
|
|
1007
|
+
committedRecordDigest: original.records.at(-1).recordDigest,
|
|
1008
|
+
writerFloor: RRSI_REGISTRY_STORE_WRITER_FLOOR,
|
|
1009
|
+
registryRuntimeMode: runtime
|
|
1010
|
+
? "authenticated-empty-read-only"
|
|
1011
|
+
: "uninitialized-read-only",
|
|
1012
|
+
runtimeRecordDigest: runtimeOriginal?.committedRecordDigest ?? null,
|
|
1013
|
+
persistentStoreIdentityAuthenticated: true,
|
|
1014
|
+
originCutoverAuthenticated: false,
|
|
1015
|
+
originClassificationAvailable: false,
|
|
1016
|
+
grantsMutationOrPromotionAuthority: false,
|
|
1017
|
+
});
|
|
1018
|
+
function recheckUnchecked() {
|
|
1019
|
+
// Invalid existing physical state needs no journal recovery or locks.
|
|
1020
|
+
if (runtime) runtime.read(request.operationId);
|
|
1021
|
+
else assertLayout(prepared, [true, true]);
|
|
1022
|
+
if (!equal(markerProof(prepared, true), original.markers))
|
|
1023
|
+
hold("Registry marker identity changed after capture");
|
|
1024
|
+
const result = status(load(), request.operationId, {
|
|
1025
|
+
boundaryOnly: true,
|
|
1026
|
+
verifyPermissions: false,
|
|
1027
|
+
});
|
|
1028
|
+
if (
|
|
1029
|
+
!equal(result.prepared, prepared) ||
|
|
1030
|
+
!equal(result.markers, original.markers) ||
|
|
1031
|
+
!equal(result.records, original.records)
|
|
1032
|
+
)
|
|
1033
|
+
hold("Registry component binding changed after capture");
|
|
1034
|
+
// An explicit runtime attachment authenticates only its empty layout.
|
|
1035
|
+
// Unknown content remains HOLD, with no adoption or cleanup.
|
|
1036
|
+
if (runtime) {
|
|
1037
|
+
const current = runtime.read(request.operationId);
|
|
1038
|
+
if (
|
|
1039
|
+
!equal(current.directories, runtimeOriginal.directories) ||
|
|
1040
|
+
current.committedRecordDigest !==
|
|
1041
|
+
runtimeOriginal.committedRecordDigest
|
|
1042
|
+
)
|
|
1043
|
+
hold("runtime attachment changed after component capture");
|
|
1044
|
+
} else assertLayout(prepared, [true, true]);
|
|
1045
|
+
if (!equal(markerProof(prepared, true), original.markers))
|
|
1046
|
+
hold("Registry markers changed during uninitialized inventory read");
|
|
1047
|
+
if (!equal(result.currentHead, headData(methods.verify())))
|
|
1048
|
+
hold("Registry journal changed during uninitialized inventory read");
|
|
1049
|
+
return result;
|
|
1050
|
+
}
|
|
1051
|
+
function recheck() {
|
|
1052
|
+
try {
|
|
1053
|
+
return recheckUnchecked();
|
|
1054
|
+
} catch (cause) {
|
|
1055
|
+
if (["ENOENT", "ENOTDIR"].includes(cause?.code))
|
|
1056
|
+
hold("an authenticated Registry boundary is missing", cause);
|
|
1057
|
+
throw cause;
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
const componentBinding = Object.freeze({
|
|
1061
|
+
descriptor: componentDescriptor,
|
|
1062
|
+
});
|
|
1063
|
+
COMPONENT_BINDINGS.set(
|
|
1064
|
+
componentBinding,
|
|
1065
|
+
Object.freeze({
|
|
1066
|
+
descriptor: componentDescriptor,
|
|
1067
|
+
boundaries: freezeRrsiData({
|
|
1068
|
+
base: prepared.directories.find(
|
|
1069
|
+
(entry) => entry.path === store.baseDir,
|
|
1070
|
+
),
|
|
1071
|
+
tenants: prepared.directories.find(
|
|
1072
|
+
(entry) => entry.path === path.dirname(store.rootDir),
|
|
1073
|
+
),
|
|
1074
|
+
tenantRoot: prepared.directories.find(
|
|
1075
|
+
(entry) => entry.path === store.rootDir,
|
|
1076
|
+
),
|
|
1077
|
+
}),
|
|
1078
|
+
marker: freezeRrsiData({
|
|
1079
|
+
identity: original.markers[component].identity,
|
|
1080
|
+
marker: markerFor(prepared, component),
|
|
1081
|
+
}),
|
|
1082
|
+
runtimeDirectories:
|
|
1083
|
+
component === "release"
|
|
1084
|
+
? (runtimeOriginal?.directories ?? null)
|
|
1085
|
+
: null,
|
|
1086
|
+
recheck,
|
|
1087
|
+
recheckForOpen() {
|
|
1088
|
+
try {
|
|
1089
|
+
assertPrivateParent(prepared.parent);
|
|
1090
|
+
runtime?.verifyPrivateDirectories(request.operationId);
|
|
1091
|
+
return recheck();
|
|
1092
|
+
} catch (cause) {
|
|
1093
|
+
if (["ENOENT", "ENOTDIR"].includes(cause?.code))
|
|
1094
|
+
hold(
|
|
1095
|
+
"an authenticated Registry open boundary is missing",
|
|
1096
|
+
cause,
|
|
1097
|
+
);
|
|
1098
|
+
throw cause;
|
|
1099
|
+
}
|
|
1100
|
+
},
|
|
1101
|
+
matchesPolicy: (value) => value === policy,
|
|
1102
|
+
assertTransactionLedger(value) {
|
|
1103
|
+
let reader;
|
|
1104
|
+
try {
|
|
1105
|
+
reader = captureSkillReleaseOperationReader(value);
|
|
1106
|
+
} catch (cause) {
|
|
1107
|
+
hold("a genuine Registry transaction port is required", cause);
|
|
1108
|
+
}
|
|
1109
|
+
if (
|
|
1110
|
+
!reader.matchesLedger(ledger) ||
|
|
1111
|
+
!reader.matchesArtifactPorts(options.artifactPorts) ||
|
|
1112
|
+
!equal(reader.scope, {
|
|
1113
|
+
artifactTenantId: descriptor.artifactTenantId,
|
|
1114
|
+
audience: descriptor.audience,
|
|
1115
|
+
purpose: descriptor.purpose,
|
|
1116
|
+
})
|
|
1117
|
+
)
|
|
1118
|
+
hold(
|
|
1119
|
+
"Registry transaction ports require the original journal, artifact ports and exact scope",
|
|
1120
|
+
);
|
|
1121
|
+
},
|
|
1122
|
+
assertMutationAllowed() {
|
|
1123
|
+
hold(
|
|
1124
|
+
"Registry origin admission and business mutation are not implemented",
|
|
1125
|
+
);
|
|
1126
|
+
},
|
|
1127
|
+
}),
|
|
1128
|
+
);
|
|
1129
|
+
return componentBinding;
|
|
1130
|
+
},
|
|
1131
|
+
provisionFresh(input) {
|
|
1132
|
+
const request = snapshotRrsiData(input);
|
|
1133
|
+
rrsiExact(
|
|
1134
|
+
request,
|
|
1135
|
+
["parentDir", "operationId"],
|
|
1136
|
+
"fresh provisioning request",
|
|
1137
|
+
);
|
|
1138
|
+
rrsiId(request.operationId, "provision operation ID");
|
|
1139
|
+
if (
|
|
1140
|
+
typeof request.parentDir !== "string" ||
|
|
1141
|
+
path.resolve(request.parentDir) !== request.parentDir
|
|
1142
|
+
)
|
|
1143
|
+
rrsiFail(
|
|
1144
|
+
"provision parent must be an existing absolute canonical directory",
|
|
1145
|
+
);
|
|
1146
|
+
return locked((assertOwnership) => {
|
|
1147
|
+
let state = load();
|
|
1148
|
+
if (state.prepared) {
|
|
1149
|
+
if (
|
|
1150
|
+
state.prepared.operationId !== request.operationId ||
|
|
1151
|
+
state.prepared.parent.path !== request.parentDir
|
|
1152
|
+
)
|
|
1153
|
+
hold(
|
|
1154
|
+
"this tenant already has a bound store provisioning operation",
|
|
1155
|
+
);
|
|
1156
|
+
return finish(state, request.operationId, assertOwnership);
|
|
1157
|
+
}
|
|
1158
|
+
const prepared = allocate(request, state, assertOwnership);
|
|
1159
|
+
state = append(state, "prepared", prepared, assertOwnership);
|
|
1160
|
+
const presence = [false, false];
|
|
1161
|
+
install(prepared, "candidate", presence, assertOwnership);
|
|
1162
|
+
install(prepared, "release", presence, assertOwnership);
|
|
1163
|
+
return finish(state, request.operationId, assertOwnership);
|
|
1164
|
+
});
|
|
1165
|
+
},
|
|
1166
|
+
recover(operationId) {
|
|
1167
|
+
rrsiId(operationId, "provision operation ID");
|
|
1168
|
+
return locked((assertOwnership) =>
|
|
1169
|
+
finish(load(), operationId, assertOwnership),
|
|
1170
|
+
);
|
|
1171
|
+
},
|
|
1172
|
+
read(operationId) {
|
|
1173
|
+
rrsiId(operationId, "provision operation ID");
|
|
1174
|
+
return locked(() => status(load(), operationId));
|
|
1175
|
+
},
|
|
1176
|
+
});
|
|
1177
|
+
POLICIES.set(
|
|
1178
|
+
policy,
|
|
1179
|
+
Object.freeze({
|
|
1180
|
+
descriptor,
|
|
1181
|
+
matchesBackend: (value) => value === backend,
|
|
1182
|
+
matchesArtifactPorts: (value) => value === options.artifactPorts,
|
|
1183
|
+
matchesArtifactResolver: (value) => value === resolver,
|
|
1184
|
+
read: policy.read,
|
|
1185
|
+
captureCommittedBoundary(operationId) {
|
|
1186
|
+
rrsiId(operationId, "provision operation ID");
|
|
1187
|
+
try {
|
|
1188
|
+
const state = load();
|
|
1189
|
+
const result = status(state, operationId, {
|
|
1190
|
+
boundaryOnly: true,
|
|
1191
|
+
verifyPermissions: false,
|
|
1192
|
+
});
|
|
1193
|
+
if (!result.freshPhysicalPairProvisioningCommitted)
|
|
1194
|
+
hold("runtime requires committed store provisioning");
|
|
1195
|
+
return Object.freeze({
|
|
1196
|
+
snapshot: result,
|
|
1197
|
+
recheck() {
|
|
1198
|
+
// Reuse only this authenticated immutable history at its exact
|
|
1199
|
+
// captured head. No caller-supplied state or newer head is used.
|
|
1200
|
+
try {
|
|
1201
|
+
return status(state, operationId, {
|
|
1202
|
+
boundaryOnly: true,
|
|
1203
|
+
verifyPermissions: false,
|
|
1204
|
+
});
|
|
1205
|
+
} catch (cause) {
|
|
1206
|
+
if (["ENOENT", "ENOTDIR"].includes(cause?.code))
|
|
1207
|
+
hold("an authenticated store boundary is missing", cause);
|
|
1208
|
+
throw cause;
|
|
1209
|
+
}
|
|
1210
|
+
},
|
|
1211
|
+
});
|
|
1212
|
+
} catch (cause) {
|
|
1213
|
+
if (["ENOENT", "ENOTDIR"].includes(cause?.code))
|
|
1214
|
+
hold("an authenticated store boundary is missing", cause);
|
|
1215
|
+
throw cause;
|
|
1216
|
+
}
|
|
1217
|
+
},
|
|
1218
|
+
maintain(operation) {
|
|
1219
|
+
if (typeof operation !== "function" || isProxy(operation))
|
|
1220
|
+
hold("runtime maintenance requires an original synchronous callback");
|
|
1221
|
+
return locked((assertOwnership) => {
|
|
1222
|
+
const result = operation(assertOwnership);
|
|
1223
|
+
if (result && typeof result.then === "function")
|
|
1224
|
+
hold("runtime maintenance cannot return a Promise");
|
|
1225
|
+
return result;
|
|
1226
|
+
});
|
|
1227
|
+
},
|
|
1228
|
+
}),
|
|
1229
|
+
);
|
|
1230
|
+
return policy;
|
|
1231
|
+
}
|
|
1232
|
+
|
|
1233
|
+
export function captureRrsiRegistryStorePolicy(value) {
|
|
1234
|
+
const captured = POLICIES.get(value);
|
|
1235
|
+
if (!captured) rrsiFail("a genuine Registry store policy is required");
|
|
1236
|
+
return captured;
|
|
1237
|
+
}
|
|
1238
|
+
|
|
1239
|
+
export function captureRrsiRegistryComponentBinding(value) {
|
|
1240
|
+
const captured = COMPONENT_BINDINGS.get(value);
|
|
1241
|
+
if (!captured) hold("a genuine Registry component binding is required");
|
|
1242
|
+
return captured;
|
|
1243
|
+
}
|
|
1244
|
+
|
|
1245
|
+
/** Current v1 constructors must never bootstrap in the reserved fresh namespace,
|
|
1246
|
+
* including after marker loss and through a canonical filesystem alias. */
|
|
1247
|
+
export function assertOutsideRrsiRegistryNamespace(baseDir) {
|
|
1248
|
+
const reserved = (value) =>
|
|
1249
|
+
value.split(/[\\/]/u).some((part) => /^pair\.[a-f0-9-]{36}$/iu.test(part));
|
|
1250
|
+
const requested = path.resolve(baseDir);
|
|
1251
|
+
if (reserved(requested))
|
|
1252
|
+
hold(
|
|
1253
|
+
"reserved v2 Registry namespace requires its genuine component binding",
|
|
1254
|
+
);
|
|
1255
|
+
for (let current = requested; ; current = path.dirname(current)) {
|
|
1256
|
+
try {
|
|
1257
|
+
const target = path.resolve(
|
|
1258
|
+
fs.realpathSync.native(current),
|
|
1259
|
+
path.relative(current, requested),
|
|
1260
|
+
);
|
|
1261
|
+
if (reserved(target))
|
|
1262
|
+
hold(
|
|
1263
|
+
"reserved v2 Registry namespace cannot be opened through an unbound alias",
|
|
1264
|
+
);
|
|
1265
|
+
return target;
|
|
1266
|
+
} catch (error) {
|
|
1267
|
+
if (error.code !== "ENOENT") throw error;
|
|
1268
|
+
if (current === path.dirname(current)) throw error;
|
|
1269
|
+
}
|
|
1270
|
+
}
|
|
1271
|
+
}
|