chainlesschain 0.166.106 → 0.166.108
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 +187 -8
- package/package.json +2 -2
- package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
- package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-CKwEku2e.js → AIOps-B5HWUFaS.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-BSUOuTpW.js → ActionButton-DCIKpxoQ.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-CGxvL_FI.js → Analytics-dsgvjCEQ.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-bCPwz-5w.js → AppLayout-DWDH5Qbm.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-2_BOWo6e.js → Artifacts-BD8S8kzU.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-CBOruzgW.js → Audit-CDXv4Aoj.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-lg0IU5HN.js → BackgroundAgents-BLAQ5Uy0.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-OspCi452.js → Backup-moPac6tF.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-BMkliqlz.js → BaseInput-C_6nzsLU.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BfoiUZwp.js → Chat-cYuslkFG.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BjnfMwep.js → ChatBubbleRenderer-Ceh3MG6M.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-jTMlkNQZ.js → Checkbox-Qjb-XOX_.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-CycyJcnU.js → Codegen-IYRWXlMA.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BVWlReQj.js → Col-CsQ9jv8M.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CKRHl4xK.js → Community-BbNmfTwe.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BtpfMdhT.js → Compact-SShDx-nr.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-DtHB1Ct1.js → Compliance-CLWhKIJp.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-D_8X7-RK.js → Cowork-D4tK9_s3.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-DMCOyvEa.js → Cron-D7GnfQ1z.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CeHdsu7m.js → Crosschain-TG86SH3X.js} +1 -1
- package/src/assets/web-panel/assets/{DID-CL6mR6zX.js → DID-6pue0NCO.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-oz9n6DC7.js → Dashboard-BwkJ29du.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-CbWm8mWu.js → Dropdown-DEvpYDof.js} +1 -1
- package/src/assets/web-panel/assets/EmailListRenderer-CQj7UlKJ.js +1 -0
- package/src/assets/web-panel/assets/{EvolutionSettings-DPwqgRYm.js → EvolutionSettings-tpvPMM9W.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-DkV4YMFj.js → FamilyGuardDashboard-CWYFW75O.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-CpUKHIOH.js → Federation-gRIi96rM.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-C6KXRgjR.js → FormItemContext-B7Nr3YkV.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-C5Y4L5E3.js → GenericCardRenderer-b1BgGgLT.js} +1 -1
- package/src/assets/web-panel/assets/{Git-CBwKdF8s.js → Git-Dis1jjO7.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-Dt-OR7Aa.js → Governance-C4Yg99Am.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Czkl-Lhl.js → Inference-D4UQRbGu.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-C7NE4949.js → KnowledgeGraph-Co_kLMAq.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CoAuZiMG.js → Logs-D4BRp7bg.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-DTkjTwGI.js → MarkdownRenderer-Dgy8arQI.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-IzrV-7zH.js → Marketplace-ys0j1vr9.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-DYEea7aS.js → McpTools-B3ZvRZe_.js} +2 -2
- package/src/assets/web-panel/assets/{Memory-Uqe7ZrV6.js → Memory-BOa0jbr-.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-D092EGyM.js → MobileBridge-CWJRmUbj.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-7ty8vTpj.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-gg4jzfB-.js → Mtc-CCcOk5m5.js} +5 -5
- package/src/assets/web-panel/assets/{MtcAudit-BibGfs71.js → MtcAudit-vlrrScbQ.js} +4 -4
- package/src/assets/web-panel/assets/{Multisig-BmB5yRXP.js → Multisig-C8K59S_8.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-BBSlPDyY.js → NLProgramming-oTpFYVme.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-Bui3-KiC.js → Notes-BhqAJWiz.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-ovXtbHe5.js → NotificationSettings-DCnV1CJM.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-Bq-qD7eG.js → OrderTableRenderer-CZGnsrVW.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-DacIY9oJ.js → Organization-DX1LuQlJ.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CX8osheZ.js → Overflow-CbfKrFdT.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-1lnikqVp.js → P2P-BEwYZuHL.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DCrU-V6A.js → PdhVaultBrowser-YNmWbwd7.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-Dfcb6kM2.js → Permissions-DyZ18LiA.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-dvgai4hf.js → PersonalDataHub-B2J3aw2g.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-nymAEtnK.js → Pipeline-CXl2ZlhQ.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-D0Vd91oM.js → Privacy-I5SDmgAp.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-BpUBcu7o.js → ProjectInit-Crl9tXw3.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-DIzwTT-M.js → ProjectSettings-DCFtFlSK.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-Den4RUw6.js → Projects-Do5BQq1i.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-Cq8p-M0d.js → Providers-Cb7OWa3y.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DSmqOfYk.js → QrScannerModal-CJmhAU-s.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BDHCeSnA.js → QuickAsk-Cu6lEWou.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-B_9M8oF4.js → Recommend-Crip3jLB.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-5ot4leHy.js → RemoteSession-KxYY-2CB.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-B3F013PY.js → Reputation-D0vN-htI.js} +1 -1
- package/src/assets/web-panel/assets/{Row-BMyBJnZw.js → Row-wqzd_xCD.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-WmV0l52g.js → RssFeed-8lsPfb0a.js} +3 -3
- package/src/assets/web-panel/assets/{Search-Wl80yXnR.js → Search-C-e8qAeU.js} +1 -1
- package/src/assets/web-panel/assets/{Security-gbDMKb5m.js → Security-CMM9WgFy.js} +4 -4
- package/src/assets/web-panel/assets/{Services-Bx2Bjhah.js → Services-CZdxjN2B.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-BvJNybjV.js → Skeleton-BI3XNk35.js} +1 -1
- package/src/assets/web-panel/assets/Skills-DMlsh9It.js +1 -0
- package/src/assets/web-panel/assets/{Sla-BM9gyZwc.js → Sla-HQruznKY.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Bu9FL9Tz.js → SpeechSettings-Dl-MFq3w.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-D8ICJsMt.js → SyncSettings-BU224vDm.js} +2 -2
- package/src/assets/web-panel/assets/Tasks-DESg3yCs.js +1 -0
- package/src/assets/web-panel/assets/{Templates-BJzpTD0W.js → Templates-Jb3TPz4Q.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-BN8eX4R2.js → Tenant-CQ-GTrfa.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-CjtP9bpq.js → Terminal-BbMl3Rue.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-CpNRa4XW.js → TimelineRenderer-CS_vstKF.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-2lNNvNAH.js → Tokens-BaopVl1f.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-DxtxXyPg.js → Trigger-DtnD4WUN.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CakzGeF9.js → Trust-CsvG4Tmf.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-CvJeRXwV.js → UkeySign-C1-A-cOV.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-D80pMzcO.js → VideoEditing-CNgJPdSL.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BF-VOBmZ.js → Wallet-CdF41501.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DXyoAzE_.js → WebAuthn-D-JmxD5g.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-X6CDxzrF.js → WorkflowEditor-C0XunR13.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BcUhqjeA.js → chat-BBAZpD_G.js} +1 -1
- package/src/assets/web-panel/assets/{colors-Dw4bKaCm.js → colors-BxSxO5BX.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BWhu5Eri.js → compact-item-BoxL8pg5.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BLJ9JKoN.js → createContext-W5JAyZmY.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-BI5TZ7EX.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DIkCiJbt.js → hasIn-HpiKDPVC.js} +1 -1
- package/src/assets/web-panel/assets/index-9-JLDK5n.js +1 -0
- package/src/assets/web-panel/assets/{index-BAwcMKaD.js → index-9caMEwQ-.js} +1 -1
- package/src/assets/web-panel/assets/{index-0mHbawfQ.js → index-B05_YRHZ.js} +1 -1
- package/src/assets/web-panel/assets/index-B7m8ioaQ.js +1 -0
- package/src/assets/web-panel/assets/{index-D3wp_jgy.js → index-B9VxE1C-.js} +1 -1
- package/src/assets/web-panel/assets/{index-BotMuGS2.js → index-BA3WWQAT.js} +1 -1
- package/src/assets/web-panel/assets/{index-LOsZYbic.js → index-BSJ0cAle.js} +1 -1
- package/src/assets/web-panel/assets/{index-C5qms36i.js → index-BW4PP5GN.js} +1 -1
- package/src/assets/web-panel/assets/{index-CDfB6hWf.js → index-BZAbaY64.js} +1 -1
- package/src/assets/web-panel/assets/{index-BipG-tDs.js → index-Ba4usRFM.js} +1 -1
- package/src/assets/web-panel/assets/{index-CgsVg5Up.js → index-BgMViZIY.js} +1 -1
- package/src/assets/web-panel/assets/{index-CTPoBbIR.js → index-BtFAEG08.js} +1 -1
- package/src/assets/web-panel/assets/{index-DKzG3kBM.js → index-Bwn6k_qw.js} +1 -1
- package/src/assets/web-panel/assets/{index-PRYboDFm.js → index-C4uI7WCB.js} +1 -1
- package/src/assets/web-panel/assets/{index-DQbWGvE4.js → index-C5kqii-G.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1fufz1A.js → index-C7GIUWWF.js} +1 -1
- package/src/assets/web-panel/assets/{index-DjEP26Te.js → index-CM_gdohF.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcTch6Fp.js → index-CRg3aOoj.js} +1 -1
- package/src/assets/web-panel/assets/{index-04SKBH8l.js → index-CcyzZloJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-DHLW27Je.js → index-CehxfRUR.js} +1 -1
- package/src/assets/web-panel/assets/{index-5vzPcr1g.js → index-D6m6B4Wy.js} +1 -1
- package/src/assets/web-panel/assets/{index-DV4FSjmI.js → index-DSyfxxQS.js} +3 -3
- package/src/assets/web-panel/assets/{index-D5vFJmmX.js → index-DaJk9U9V.js} +1 -1
- package/src/assets/web-panel/assets/{index-BxLDNxEQ.js → index-DcmAiq_Y.js} +1 -1
- package/src/assets/web-panel/assets/{index-D-Erv8A9.js → index-DdEcQ1rp.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqGqoID5.js → index-DinY7IQa.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGMzh2oj.js → index-DkSnlgK_.js} +1 -1
- package/src/assets/web-panel/assets/{index-BxfimJOH.js → index-DlPvZgv8.js} +1 -1
- package/src/assets/web-panel/assets/{index-DOjQi7oU.js → index-DuM9I1If.js} +1 -1
- package/src/assets/web-panel/assets/{index-uMtx6e3D.js → index-DwidpDEE.js} +1 -1
- package/src/assets/web-panel/assets/{index-CXJ8t1hK.js → index-IO2I7nzu.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cmc7qH2C.js → index-JrMBRi62.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaStonJm.js → index-QzlEbtRL.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cr05twNa.js → index-V6VIJe2N.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGFqk1-E.js → index-_38IJqoP.js} +1 -1
- package/src/assets/web-panel/assets/{index-u0tqS0iq.js → index-_p4e3KfB.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1r2R4a-.js → index-gvfIRZxJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CFGOV-YT.js → index-ooP1pQCt.js} +1 -1
- package/src/assets/web-panel/assets/{index-BUC4TZWg.js → index-p6rNtYud.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-Bx6Hj_FG.js → initDefaultProps-Dfn6M05J.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BSWivB70.js → motion-BRRwFqGO.js} +1 -1
- package/src/assets/web-panel/assets/{move-Dv1Pg9R7.js → move-DpDPN6zR.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-Bqtl8fsk.js → mtc-parser-BHlSE87F.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DpOBjOSQ.js → omit-CEOri_L5.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-D-M-dz48.js → pickAttrs-DqICh-ad.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-DsTLAifk.js → placementArrow-DQizFFiV.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BVTetMpc.js → responsiveObserve-DmFHPdvC.js} +1 -1
- package/src/assets/web-panel/assets/{slide-abEcz-Cv.js → slide-s2s1XS3T.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-CBhrdU2G.js → statusUtils-IwEIbBan.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-CFq8ZCKa.js → styleChecker-CIFLlbXH.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-o4eE7fDV.js → useFlexGapSupport-CFkN2Cgh.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-Hs9W8ihi.js → useFs-BSY6O48s.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-Bnet5Jho.js → usePersonalDataHub-iJl3VnU0.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-DJfN4kWs.js → vnode-vu8ReMs7.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-BzV-2e7X.js → zoom-CeSrYAHp.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +2 -2
- package/src/commands/agent.js +20 -20
- package/src/commands/goal.js +299 -1
- package/src/commands/plugin.js +129 -68
- package/src/constants.js +2 -2
- package/src/data/changelog.json +29 -3
- package/src/harness/jsonl-session-store.js +128 -5
- package/src/harness/prompt-compressor.js +18 -10
- package/src/harness/structured-handoff.js +151 -13
- package/src/lib/agent-session-export.js +20 -0
- package/src/lib/background-agent-supervisor.js +54 -0
- package/src/lib/cli-goal-catalog.js +129 -0
- package/src/lib/cli-goal-source.js +119 -0
- package/src/lib/coding-task-guidance.js +2 -1
- package/src/lib/config-schema.js +1 -1
- package/src/lib/context-memory-kernel/live-compaction.js +75 -12
- package/src/lib/eval/stream-terminal.js +27 -2
- package/src/lib/evolution/evolution-artifact-ports.js +22 -11
- package/src/lib/evolution/evolution-file-witness.js +86 -63
- package/src/lib/evolution/evolution-ledger-file-backend.js +22 -0
- package/src/lib/evolution/evolution-ledger-file-manifest-catalog.js +34 -35
- package/src/lib/evolution/evolution-ledger-file-manifest-head-store.js +32 -39
- package/src/lib/evolution/evolution-ledger.js +367 -169
- package/src/lib/evolution/evolution-manifest-state-reader.js +58 -0
- package/src/lib/evolution/evolution-physical-state-reader.js +308 -0
- package/src/lib/evolution/evolution-witness-segments.js +12 -13
- package/src/lib/evolution/governed-wiki-pruning-ledger-adapter.js +82 -56
- package/src/lib/evolution/rrsi-observed-text-index.js +17 -1
- package/src/lib/evolution/rrsi-tenant-index-anchor-history.js +5 -0
- package/src/lib/execution-location-failure-site.js +20 -3
- package/src/lib/goal-store.js +8 -1
- package/src/lib/image-input.js +43 -14
- package/src/lib/llm-providers.js +1 -0
- package/src/lib/plugin-runtime/capabilities.js +10 -0
- package/src/lib/plugin-runtime/cli-goal-draft-request.js +138 -0
- package/src/lib/plugin-runtime/cli-goal-drafts.js +413 -0
- package/src/lib/plugin-runtime/cli-goal-extension-host.js +234 -0
- package/src/lib/plugin-runtime/examples/goal-description-drafter/plugin.json +17 -0
- package/src/lib/plugin-runtime/examples/goal-evidence-viewer/plugin.json +25 -0
- package/src/lib/plugin-runtime/examples/goal-progress-viewer/plugin.json +21 -0
- package/src/lib/plugin-runtime/examples/project-delivery-assistant/plugin.json +38 -0
- package/src/lib/plugin-runtime/goal-extension-contract.js +190 -0
- package/src/lib/plugin-runtime/goal-extensions.js +277 -0
- package/src/lib/plugin-runtime/install.js +40 -40
- package/src/lib/plugin-runtime/manifest.js +12 -0
- package/src/lib/plugin-runtime/policy.js +1 -0
- package/src/lib/project-goal-connection.js +225 -0
- package/src/lib/project-goal-draft-connection.js +217 -0
- package/src/lib/read-file-loop-guard.js +124 -8
- package/src/lib/session-host-snapshot.js +3 -0
- package/src/lib/session-message-provenance.js +24 -5
- package/src/lib/session-tool-round.js +39 -0
- package/src/lib/session-transcript-history.js +2 -0
- package/src/lib/session-transcript-page.js +4 -1
- package/src/lib/session-transcript-structure.js +4 -0
- package/src/lib/task-model-selector.js +1 -1
- package/src/repl/agent-repl.js +14 -7
- package/src/repl/config-summary.js +1 -1
- package/src/runtime/__tests__/message-roles.test.js +29 -0
- package/src/runtime/agent-core.js +116 -44
- package/src/runtime/headless-runner.js +191 -34
- package/src/runtime/headless-stream.js +144 -48
- package/src/runtime/message-roles.js +43 -0
- package/src/runtime/policies/agent-policy.js +1 -0
- package/src/workers/background-agent-worker.js +146 -47
- package/src/assets/web-panel/assets/EmailListRenderer-DXlmbC45.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-CwA1VoVJ.js +0 -1
- package/src/assets/web-panel/assets/Skills-Tz4rQcDp.js +0 -1
- package/src/assets/web-panel/assets/Tasks-B6MowBDo.js +0 -1
- package/src/assets/web-panel/assets/devWarning-D_MJgDGJ.js +0 -1
- package/src/assets/web-panel/assets/index-BP8log78.js +0 -1
- package/src/assets/web-panel/assets/index-PexF9zYf.js +0 -1
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
/** Stable manifest windows; no retention, installation or graph grant. */
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import {
|
|
4
|
+
createEvolutionPhysicalStateReader,
|
|
5
|
+
captureEvolutionPhysicalStateReaderDirectoryBoundary,
|
|
6
|
+
} from "./evolution-physical-state-reader.js";
|
|
7
|
+
|
|
8
|
+
const DIRECTORY_BOUNDARIES = new WeakMap();
|
|
9
|
+
|
|
10
|
+
export const EVOLUTION_MANIFEST_STATE_READ_FAILED_CODE =
|
|
11
|
+
"CC_EVOLUTION_MANIFEST_STATE_READ_FAILED";
|
|
12
|
+
|
|
13
|
+
export function createEvolutionManifestStateReader({
|
|
14
|
+
file,
|
|
15
|
+
fsImpl,
|
|
16
|
+
maximumBytes,
|
|
17
|
+
label,
|
|
18
|
+
}) {
|
|
19
|
+
if (
|
|
20
|
+
typeof file !== "string" ||
|
|
21
|
+
!path.isAbsolute(file) ||
|
|
22
|
+
path.resolve(file) !== file ||
|
|
23
|
+
!Number.isSafeInteger(maximumBytes) ||
|
|
24
|
+
maximumBytes < 1 ||
|
|
25
|
+
typeof label !== "string" ||
|
|
26
|
+
!label ||
|
|
27
|
+
!fsImpl
|
|
28
|
+
)
|
|
29
|
+
throw new TypeError(
|
|
30
|
+
"manifest state reader requires a canonical file, filesystem and byte limit",
|
|
31
|
+
);
|
|
32
|
+
const reader = createEvolutionPhysicalStateReader({
|
|
33
|
+
directoryPath: path.dirname(file),
|
|
34
|
+
fsImpl,
|
|
35
|
+
maximumBytes,
|
|
36
|
+
label,
|
|
37
|
+
errorCode: EVOLUTION_MANIFEST_STATE_READ_FAILED_CODE,
|
|
38
|
+
});
|
|
39
|
+
const stateReader = Object.freeze({
|
|
40
|
+
read: () => reader.read(file, { allowAbsent: true }),
|
|
41
|
+
recheck: reader.recheck,
|
|
42
|
+
});
|
|
43
|
+
DIRECTORY_BOUNDARIES.set(
|
|
44
|
+
stateReader,
|
|
45
|
+
captureEvolutionPhysicalStateReaderDirectoryBoundary(reader),
|
|
46
|
+
);
|
|
47
|
+
return stateReader;
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
export function captureEvolutionManifestStateReaderDirectoryBoundary(reader) {
|
|
51
|
+
if (arguments.length !== 1)
|
|
52
|
+
throw new TypeError("manifest directory capture takes one original reader");
|
|
53
|
+
const boundary = DIRECTORY_BOUNDARIES.get(reader);
|
|
54
|
+
if (!boundary)
|
|
55
|
+
throw new TypeError("an original manifest state reader is required");
|
|
56
|
+
boundary.recheck();
|
|
57
|
+
return boundary;
|
|
58
|
+
}
|
|
@@ -0,0 +1,308 @@
|
|
|
1
|
+
/** Stable local state windows, never a retention, installation or graph grant. */
|
|
2
|
+
import path from "node:path";
|
|
3
|
+
import { artifactPhysicalIdentity } from "./evolution-artifact-identity.js";
|
|
4
|
+
import { withEvolutionFileIdentity } from "./evolution-file-identity.js";
|
|
5
|
+
import { readBoundedDescriptor } from "./bounded-descriptor-read.js";
|
|
6
|
+
|
|
7
|
+
const DIRECTORY_BOUNDARIES = new WeakMap();
|
|
8
|
+
|
|
9
|
+
const FILE_FIELDS = Object.freeze([
|
|
10
|
+
"dev",
|
|
11
|
+
"ino",
|
|
12
|
+
"mode",
|
|
13
|
+
"uid",
|
|
14
|
+
"gid",
|
|
15
|
+
"nlink",
|
|
16
|
+
"size",
|
|
17
|
+
"mtimeNs",
|
|
18
|
+
"ctimeNs",
|
|
19
|
+
]);
|
|
20
|
+
const DIRECTORY_FIELDS = Object.freeze(["dev", "ino", "mode", "uid", "gid"]);
|
|
21
|
+
const MUTATION_FIELDS = FILE_FIELDS.filter(
|
|
22
|
+
(field) => field !== "dev" && field !== "ino",
|
|
23
|
+
);
|
|
24
|
+
const samePath = (left, right) =>
|
|
25
|
+
process.platform === "win32"
|
|
26
|
+
? left.toLowerCase() === right.toLowerCase()
|
|
27
|
+
: left === right;
|
|
28
|
+
const birthtime = (stat) => artifactPhysicalIdentity(stat).birthtimeMs;
|
|
29
|
+
const sameMetadata = (left, right) =>
|
|
30
|
+
MUTATION_FIELDS.every((field) => left[field] === right[field]) &&
|
|
31
|
+
birthtime(left) === birthtime(right);
|
|
32
|
+
const sameStat = (left, right) =>
|
|
33
|
+
left.dev === right.dev && left.ino === right.ino && sameMetadata(left, right);
|
|
34
|
+
|
|
35
|
+
export function createEvolutionPhysicalStateReader({
|
|
36
|
+
directoryPath: directory,
|
|
37
|
+
fsImpl,
|
|
38
|
+
maximumBytes,
|
|
39
|
+
minimumBytes = 0,
|
|
40
|
+
directoryMode = null,
|
|
41
|
+
sizeError = "exceeds maximumBytes",
|
|
42
|
+
label,
|
|
43
|
+
errorCode,
|
|
44
|
+
readFailureMessage = `${label} bounded state read failed`,
|
|
45
|
+
}) {
|
|
46
|
+
if (
|
|
47
|
+
typeof directory !== "string" ||
|
|
48
|
+
!path.isAbsolute(directory) ||
|
|
49
|
+
path.resolve(directory) !== directory ||
|
|
50
|
+
!Number.isSafeInteger(maximumBytes) ||
|
|
51
|
+
maximumBytes < 1 ||
|
|
52
|
+
!Number.isSafeInteger(minimumBytes) ||
|
|
53
|
+
minimumBytes < 0 ||
|
|
54
|
+
minimumBytes > maximumBytes ||
|
|
55
|
+
(directoryMode !== null && typeof directoryMode !== "bigint") ||
|
|
56
|
+
typeof sizeError !== "string" ||
|
|
57
|
+
!sizeError ||
|
|
58
|
+
typeof errorCode !== "string" ||
|
|
59
|
+
!errorCode ||
|
|
60
|
+
typeof readFailureMessage !== "string" ||
|
|
61
|
+
!readFailureMessage ||
|
|
62
|
+
typeof label !== "string" ||
|
|
63
|
+
!label ||
|
|
64
|
+
!fsImpl
|
|
65
|
+
)
|
|
66
|
+
throw new TypeError(
|
|
67
|
+
"physical state reader requires a canonical directory, filesystem, code and byte bounds",
|
|
68
|
+
);
|
|
69
|
+
const physicalPath = (target) => {
|
|
70
|
+
const realpath = fsImpl.realpathSync;
|
|
71
|
+
return typeof realpath.native === "function"
|
|
72
|
+
? realpath.native(target)
|
|
73
|
+
: realpath.call(fsImpl, target);
|
|
74
|
+
};
|
|
75
|
+
const fail = (message, cause) => {
|
|
76
|
+
throw Object.assign(new Error(message, cause ? { cause } : undefined), {
|
|
77
|
+
code: errorCode,
|
|
78
|
+
});
|
|
79
|
+
};
|
|
80
|
+
const guard = (operation) => {
|
|
81
|
+
try {
|
|
82
|
+
return operation();
|
|
83
|
+
} catch (cause) {
|
|
84
|
+
if (cause?.code === errorCode) throw cause;
|
|
85
|
+
fail(readFailureMessage, cause);
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
const inspectDirectory = () => {
|
|
89
|
+
const stat = fsImpl.lstatSync(directory, { bigint: true });
|
|
90
|
+
if (DIRECTORY_FIELDS.some((field) => typeof stat?.[field] !== "bigint"))
|
|
91
|
+
fail(`${label} directory requires full-precision stat fields`);
|
|
92
|
+
if (
|
|
93
|
+
!stat.isDirectory() ||
|
|
94
|
+
stat.isSymbolicLink() ||
|
|
95
|
+
!samePath(physicalPath(directory), directory)
|
|
96
|
+
)
|
|
97
|
+
fail(`${label} directory must be physically canonical`);
|
|
98
|
+
if (directoryMode !== null && (stat.mode & 0o7777n) !== directoryMode)
|
|
99
|
+
fail(`${label} directory permissions are invalid`);
|
|
100
|
+
return stat;
|
|
101
|
+
};
|
|
102
|
+
// Contents, link count and mutation times may change during legitimate locks
|
|
103
|
+
// and temporary+rename writes. Only the original directory identity/owner is
|
|
104
|
+
// retained; mutable leaf files acquire a new identity on every read.
|
|
105
|
+
const originalDirectory = guard(() => {
|
|
106
|
+
const stat = inspectDirectory();
|
|
107
|
+
return Object.freeze({
|
|
108
|
+
...Object.fromEntries(
|
|
109
|
+
DIRECTORY_FIELDS.map((field) => [field, stat[field]]),
|
|
110
|
+
),
|
|
111
|
+
birthtimeMs: birthtime(stat),
|
|
112
|
+
});
|
|
113
|
+
});
|
|
114
|
+
const recheck = () => {
|
|
115
|
+
const current = inspectDirectory();
|
|
116
|
+
if (
|
|
117
|
+
!DIRECTORY_FIELDS.every(
|
|
118
|
+
(field) => originalDirectory[field] === current[field],
|
|
119
|
+
) ||
|
|
120
|
+
originalDirectory.birthtimeMs !== birthtime(current)
|
|
121
|
+
)
|
|
122
|
+
fail(`${label} directory physical identity changed`);
|
|
123
|
+
};
|
|
124
|
+
const safeStat = (stat, source, byteLimit) => {
|
|
125
|
+
if (FILE_FIELDS.some((field) => typeof stat?.[field] !== "bigint"))
|
|
126
|
+
fail(`${label} ${source} requires full-precision stat fields`);
|
|
127
|
+
if (!stat.isFile() || stat.isSymbolicLink() || stat.nlink !== 1n)
|
|
128
|
+
fail(`${label} file identity is invalid`);
|
|
129
|
+
if (stat.size < BigInt(minimumBytes) || stat.size > BigInt(byteLimit))
|
|
130
|
+
fail(`${label} ${sizeError}`);
|
|
131
|
+
};
|
|
132
|
+
// Only identity-helper directory opens use this adapter. All operations,
|
|
133
|
+
// including Windows path/handle bridging, retain the caller's original fsImpl.
|
|
134
|
+
const directoryFs = Object.freeze({
|
|
135
|
+
constants: fsImpl.constants,
|
|
136
|
+
realpathSync: Object.assign(
|
|
137
|
+
(...args) => fsImpl.realpathSync(...args),
|
|
138
|
+
typeof fsImpl.realpathSync.native === "function"
|
|
139
|
+
? { native: (...args) => fsImpl.realpathSync.native(...args) }
|
|
140
|
+
: {},
|
|
141
|
+
),
|
|
142
|
+
lstatSync: (...args) => fsImpl.lstatSync(...args),
|
|
143
|
+
fstatSync: (...args) => fsImpl.fstatSync(...args),
|
|
144
|
+
closeSync: (...args) => fsImpl.closeSync(...args),
|
|
145
|
+
openSync: (target, flags) =>
|
|
146
|
+
fsImpl.openSync(
|
|
147
|
+
target,
|
|
148
|
+
flags |
|
|
149
|
+
(fsImpl.constants.O_NONBLOCK || 0) |
|
|
150
|
+
(fsImpl.constants.O_DIRECTORY || 0),
|
|
151
|
+
),
|
|
152
|
+
});
|
|
153
|
+
const read = (file, byteLimit, allowAbsent, prepareFile) => {
|
|
154
|
+
if (
|
|
155
|
+
typeof file !== "string" ||
|
|
156
|
+
!path.isAbsolute(file) ||
|
|
157
|
+
path.resolve(file) !== file ||
|
|
158
|
+
path.dirname(file) !== directory ||
|
|
159
|
+
!Number.isSafeInteger(byteLimit) ||
|
|
160
|
+
byteLimit < 0 ||
|
|
161
|
+
typeof allowAbsent !== "boolean" ||
|
|
162
|
+
(prepareFile !== null && typeof prepareFile !== "function")
|
|
163
|
+
)
|
|
164
|
+
throw new TypeError(
|
|
165
|
+
"physical state read requires a direct canonical child and finite byte bound",
|
|
166
|
+
);
|
|
167
|
+
byteLimit = Math.min(maximumBytes, byteLimit);
|
|
168
|
+
recheck();
|
|
169
|
+
let before;
|
|
170
|
+
try {
|
|
171
|
+
before = fsImpl.lstatSync(file, { bigint: true });
|
|
172
|
+
} catch (cause) {
|
|
173
|
+
// Preserve the backend's absent-state contract, without treating access
|
|
174
|
+
// errors, dangling links or a disappearance during open/read as absence.
|
|
175
|
+
if (cause?.code !== "ENOENT" || !allowAbsent) throw cause;
|
|
176
|
+
recheck();
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
safeStat(before, "path", byteLimit);
|
|
180
|
+
if (prepareFile !== null) {
|
|
181
|
+
// Preserve existing private-file preparation without treating a repaired
|
|
182
|
+
// ctime/mode as mutation inside the later stable content window.
|
|
183
|
+
const identity = {
|
|
184
|
+
dev: before.dev,
|
|
185
|
+
ino: before.ino,
|
|
186
|
+
birthtimeMs: birthtime(before),
|
|
187
|
+
};
|
|
188
|
+
prepareFile(file);
|
|
189
|
+
recheck();
|
|
190
|
+
before = fsImpl.lstatSync(file, { bigint: true });
|
|
191
|
+
safeStat(before, "path", byteLimit);
|
|
192
|
+
if (
|
|
193
|
+
before.dev !== identity.dev ||
|
|
194
|
+
before.ino !== identity.ino ||
|
|
195
|
+
birthtime(before) !== identity.birthtimeMs
|
|
196
|
+
)
|
|
197
|
+
fail(`${label} file changed during private-file preparation`);
|
|
198
|
+
}
|
|
199
|
+
if (!samePath(physicalPath(file), file))
|
|
200
|
+
fail(`${label} file must be physically canonical`);
|
|
201
|
+
return withEvolutionFileIdentity(directoryFs, file, (samePathHandle) => {
|
|
202
|
+
recheck();
|
|
203
|
+
let descriptor = null;
|
|
204
|
+
try {
|
|
205
|
+
descriptor = fsImpl.openSync(
|
|
206
|
+
file,
|
|
207
|
+
fsImpl.constants.O_RDONLY |
|
|
208
|
+
(fsImpl.constants.O_NOFOLLOW || 0) |
|
|
209
|
+
(fsImpl.constants.O_NONBLOCK || 0),
|
|
210
|
+
);
|
|
211
|
+
const opened = fsImpl.fstatSync(descriptor, { bigint: true });
|
|
212
|
+
safeStat(opened, "descriptor", byteLimit);
|
|
213
|
+
if (!samePathHandle(before, opened) || !sameMetadata(before, opened))
|
|
214
|
+
fail(`${label} path and descriptor identities differ`);
|
|
215
|
+
const bytes = readBoundedDescriptor(
|
|
216
|
+
fsImpl,
|
|
217
|
+
descriptor,
|
|
218
|
+
Number(opened.size),
|
|
219
|
+
byteLimit,
|
|
220
|
+
);
|
|
221
|
+
const afterDescriptor = fsImpl.fstatSync(descriptor, { bigint: true });
|
|
222
|
+
const afterPath = fsImpl.lstatSync(file, { bigint: true });
|
|
223
|
+
safeStat(afterDescriptor, "descriptor", byteLimit);
|
|
224
|
+
safeStat(afterPath, "path", byteLimit);
|
|
225
|
+
if (!sameStat(opened, afterDescriptor))
|
|
226
|
+
fail(`${label} descriptor changed during bounded read`);
|
|
227
|
+
if (
|
|
228
|
+
!sameStat(before, afterPath) ||
|
|
229
|
+
!samePathHandle(afterPath, afterDescriptor) ||
|
|
230
|
+
!sameMetadata(afterPath, afterDescriptor) ||
|
|
231
|
+
!samePath(physicalPath(file), file)
|
|
232
|
+
)
|
|
233
|
+
fail(`${label} pathname changed during bounded read`);
|
|
234
|
+
recheck();
|
|
235
|
+
return bytes;
|
|
236
|
+
} finally {
|
|
237
|
+
if (descriptor !== null) fsImpl.closeSync(descriptor);
|
|
238
|
+
}
|
|
239
|
+
});
|
|
240
|
+
};
|
|
241
|
+
const reader = Object.freeze({
|
|
242
|
+
read: (
|
|
243
|
+
file,
|
|
244
|
+
{
|
|
245
|
+
maximumBytes: byteLimit = maximumBytes,
|
|
246
|
+
allowAbsent = false,
|
|
247
|
+
prepareFile = null,
|
|
248
|
+
} = {},
|
|
249
|
+
) => guard(() => read(file, byteLimit, allowAbsent, prepareFile)),
|
|
250
|
+
recheck: () => guard(recheck),
|
|
251
|
+
});
|
|
252
|
+
// Project the original construction pin; later capture never claims a new
|
|
253
|
+
// pathname. The brand identifies this factory/its original ports, not native
|
|
254
|
+
// filesystem trust, installation authority, or any leaf inventory.
|
|
255
|
+
const descriptor = Object.freeze({
|
|
256
|
+
schema: "chainlesschain.evolution-physical-state-directory-boundary/v1",
|
|
257
|
+
scope: "single-original-state-directory",
|
|
258
|
+
directory: Object.freeze({
|
|
259
|
+
path: directory,
|
|
260
|
+
identity: Object.freeze({
|
|
261
|
+
...Object.fromEntries(
|
|
262
|
+
DIRECTORY_FIELDS.map((field) => [
|
|
263
|
+
field,
|
|
264
|
+
String(originalDirectory[field]),
|
|
265
|
+
]),
|
|
266
|
+
),
|
|
267
|
+
birthtimeMs: originalDirectory.birthtimeMs,
|
|
268
|
+
}),
|
|
269
|
+
}),
|
|
270
|
+
fileIdentityVerified: false,
|
|
271
|
+
fileContentsVerified: false,
|
|
272
|
+
storageInventoryVerified: false,
|
|
273
|
+
retentionStorageVerified: false,
|
|
274
|
+
fullPhysicalStorageGraphVerified: false,
|
|
275
|
+
rootAuthorityVerified: false,
|
|
276
|
+
routingPinVerified: false,
|
|
277
|
+
crossProcessRollbackProtectionVerified: false,
|
|
278
|
+
grantsMutationOrPromotionAuthority: false,
|
|
279
|
+
});
|
|
280
|
+
const recheckDirectory = (...args) => {
|
|
281
|
+
if (args.length)
|
|
282
|
+
throw new TypeError("directory boundary takes no replacement context");
|
|
283
|
+
return guard(() => {
|
|
284
|
+
recheck();
|
|
285
|
+
return descriptor;
|
|
286
|
+
});
|
|
287
|
+
};
|
|
288
|
+
recheckDirectory();
|
|
289
|
+
DIRECTORY_BOUNDARIES.set(
|
|
290
|
+
reader,
|
|
291
|
+
Object.freeze({
|
|
292
|
+
descriptor,
|
|
293
|
+
recheck: Object.freeze(recheckDirectory),
|
|
294
|
+
}),
|
|
295
|
+
);
|
|
296
|
+
return reader;
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
/** Metadata-only original directory of a genuine physical state reader. */
|
|
300
|
+
export function captureEvolutionPhysicalStateReaderDirectoryBoundary(reader) {
|
|
301
|
+
if (arguments.length !== 1)
|
|
302
|
+
throw new TypeError("directory capture takes one original reader");
|
|
303
|
+
const boundary = DIRECTORY_BOUNDARIES.get(reader);
|
|
304
|
+
if (!boundary)
|
|
305
|
+
throw new TypeError("an original physical state reader is required");
|
|
306
|
+
boundary.recheck();
|
|
307
|
+
return boundary;
|
|
308
|
+
}
|
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import path from "node:path";
|
|
2
|
-
import { ensurePrivateDirectory } from "../secure-fs.js";
|
|
3
2
|
|
|
4
3
|
export const EVOLUTION_SEGMENTED_WITNESS_SCHEMA =
|
|
5
4
|
"chainlesschain.evolution-file-witness-store/v2";
|
|
@@ -43,6 +42,8 @@ function matchesDiscard(entry, snapshot) {
|
|
|
43
42
|
export function createEvolutionWitnessSegments({
|
|
44
43
|
filePath,
|
|
45
44
|
fsImpl,
|
|
45
|
+
checkDirectory,
|
|
46
|
+
prepareDirectory,
|
|
46
47
|
readBytes,
|
|
47
48
|
writeBytes,
|
|
48
49
|
confirmDurable,
|
|
@@ -54,7 +55,6 @@ export function createEvolutionWitnessSegments({
|
|
|
54
55
|
maximumHistoryBytes,
|
|
55
56
|
}) {
|
|
56
57
|
const segmentDirectory = `${filePath}.segments-v2`;
|
|
57
|
-
const secureOptions = { deps: { fs: fsImpl }, failIfUnavailable: true };
|
|
58
58
|
const metadata = new WeakMap();
|
|
59
59
|
const cache = new Map();
|
|
60
60
|
let cacheBytes = 0;
|
|
@@ -65,14 +65,6 @@ export function createEvolutionWitnessSegments({
|
|
|
65
65
|
return path.join(segmentDirectory, `${digest.slice(7)}.json`);
|
|
66
66
|
};
|
|
67
67
|
|
|
68
|
-
const assertDirectory = () => {
|
|
69
|
-
ensurePrivateDirectory(segmentDirectory, secureOptions);
|
|
70
|
-
const observed = fsImpl.lstatSync(segmentDirectory);
|
|
71
|
-
if (!observed.isDirectory() || observed.isSymbolicLink()) {
|
|
72
|
-
throw new Error("witness segment directory is invalid");
|
|
73
|
-
}
|
|
74
|
-
};
|
|
75
|
-
|
|
76
68
|
const validateSegment = (segment, previous, trustEpoch) => {
|
|
77
69
|
exact(segment, SEGMENT_KEYS, "witness segment");
|
|
78
70
|
if (
|
|
@@ -150,7 +142,7 @@ export function createEvolutionWitnessSegments({
|
|
|
150
142
|
) {
|
|
151
143
|
throw new Error("segmented witness store capacity or schema is invalid");
|
|
152
144
|
}
|
|
153
|
-
|
|
145
|
+
checkDirectory();
|
|
154
146
|
const seen = new Set();
|
|
155
147
|
const observations = {
|
|
156
148
|
discards: (options.discards || [])
|
|
@@ -254,7 +246,7 @@ export function createEvolutionWitnessSegments({
|
|
|
254
246
|
) {
|
|
255
247
|
throw new Error("witness history exceeds its configured record capacity");
|
|
256
248
|
}
|
|
257
|
-
|
|
249
|
+
prepareDirectory();
|
|
258
250
|
let history = store.history;
|
|
259
251
|
let discardedAnchors = store.discardedAnchors;
|
|
260
252
|
let segmentBytes = existing?.segmentBytes ?? 0;
|
|
@@ -282,7 +274,14 @@ export function createEvolutionWitnessSegments({
|
|
|
282
274
|
}
|
|
283
275
|
const digest = hash(bytes);
|
|
284
276
|
const destination = segmentPath(digest);
|
|
285
|
-
|
|
277
|
+
let exists = false;
|
|
278
|
+
try {
|
|
279
|
+
fsImpl.lstatSync(destination, { bigint: true });
|
|
280
|
+
exists = true;
|
|
281
|
+
} catch (cause) {
|
|
282
|
+
if (cause?.code !== "ENOENT") throw cause;
|
|
283
|
+
}
|
|
284
|
+
if (exists) {
|
|
286
285
|
if (!readBytes(destination).equals(bytes)) {
|
|
287
286
|
throw new Error(
|
|
288
287
|
"existing witness segment differs from its content address",
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import {
|
|
2
2
|
EVOLUTION_DURABLE_ARTIFACT_RECORD_SCHEMA,
|
|
3
3
|
isEvolutionLedgerArtifactResolver,
|
|
4
|
+
captureEvolutionLedgerBatchResolver,
|
|
4
5
|
} from "./evolution-artifact-ports.js";
|
|
5
6
|
import {
|
|
6
7
|
EVOLUTION_ARTIFACT_RESOLUTION_SCHEMA,
|
|
@@ -17,6 +18,7 @@ import {
|
|
|
17
18
|
|
|
18
19
|
export const WIKI_PRUNING_JOURNAL_EVENT_TYPE = "wiki.pruning.journal-committed";
|
|
19
20
|
export const WIKI_PRUNING_JOURNAL_ARTIFACT_TYPE = "wiki-pruning-journal";
|
|
21
|
+
const JOURNAL_ARTIFACT_RESOLVE_BATCH_SIZE = 4;
|
|
20
22
|
const STORES = new WeakMap();
|
|
21
23
|
const DIGEST = /^sha256:[a-f0-9]{64}$/u;
|
|
22
24
|
function currentContext(head) {
|
|
@@ -98,7 +100,7 @@ export class GovernedWikiPruningLedgerAdapter {
|
|
|
98
100
|
#read;
|
|
99
101
|
#verifyLedger;
|
|
100
102
|
#append;
|
|
101
|
-
#
|
|
103
|
+
#resolveBatch;
|
|
102
104
|
#verifyPlan;
|
|
103
105
|
#verifyOperation;
|
|
104
106
|
#verifyOperations;
|
|
@@ -122,7 +124,9 @@ export class GovernedWikiPruningLedgerAdapter {
|
|
|
122
124
|
this.#read = capture(ledger, "read");
|
|
123
125
|
this.#verifyLedger = capture(ledger, "verify");
|
|
124
126
|
this.#append = capture(ledger, "appendDomainEvent");
|
|
125
|
-
this.#
|
|
127
|
+
this.#resolveBatch = captureEvolutionLedgerBatchResolver(
|
|
128
|
+
ledgerArtifactResolver,
|
|
129
|
+
);
|
|
126
130
|
this.#verifyPlan = capture(planVerifier, "verify");
|
|
127
131
|
this.#verifyOperation = capture(operationReceiptVerifier, "verify");
|
|
128
132
|
this.#verifyOperations =
|
|
@@ -215,74 +219,96 @@ export class GovernedWikiPruningLedgerAdapter {
|
|
|
215
219
|
event.tenantId === this.descriptor.tenantId &&
|
|
216
220
|
event.correlationId === this.descriptor.streamId,
|
|
217
221
|
);
|
|
218
|
-
|
|
219
|
-
const plans = new Set();
|
|
222
|
+
let previousEvent = null;
|
|
220
223
|
for (const event of events) {
|
|
221
|
-
const previous = history.at(-1) ?? null;
|
|
222
224
|
if (
|
|
223
225
|
event.artifactTenantId !== this.descriptor.artifactTenantId ||
|
|
224
226
|
event.decision !== "committed" ||
|
|
225
227
|
event.skillName !== null ||
|
|
226
228
|
!Array.isArray(event.sourceRefs) ||
|
|
227
|
-
event.sourceRefs.length !== (
|
|
228
|
-
(
|
|
229
|
+
event.sourceRefs.length !== (previousEvent ? 1 : 0) ||
|
|
230
|
+
(previousEvent &&
|
|
229
231
|
pruningCanonical(event.sourceRefs[0]) !==
|
|
230
|
-
pruningCanonical(
|
|
232
|
+
pruningCanonical(previousEvent.subjectRef))
|
|
231
233
|
)
|
|
232
234
|
fail("pruning journal event lineage is invalid");
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
244
|
-
|
|
245
|
-
!DIGEST.test(resolution.receiptDigest) ||
|
|
246
|
-
!Buffer.isBuffer(resolution.bytes) ||
|
|
247
|
-
resolution.bytes.length > WIKI_PRUNING_JOURNAL_MAX_BYTES
|
|
248
|
-
)
|
|
249
|
-
fail("pruning journal artifact resolution is invalid");
|
|
250
|
-
const record = JSON.parse(
|
|
251
|
-
new TextDecoder("utf-8", { fatal: true }).decode(resolution.bytes),
|
|
235
|
+
previousEvent = event;
|
|
236
|
+
}
|
|
237
|
+
const history = [];
|
|
238
|
+
const plans = new Set();
|
|
239
|
+
for (
|
|
240
|
+
let offset = 0;
|
|
241
|
+
offset < events.length;
|
|
242
|
+
offset += JOURNAL_ARTIFACT_RESOLVE_BATCH_SIZE
|
|
243
|
+
) {
|
|
244
|
+
const batchEvents = events.slice(
|
|
245
|
+
offset,
|
|
246
|
+
offset + JOURNAL_ARTIFACT_RESOLVE_BATCH_SIZE,
|
|
252
247
|
);
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
)
|
|
261
|
-
fail("pruning journal durable artifact binding is invalid");
|
|
262
|
-
const state = verifyWikiPruningJournal(
|
|
263
|
-
record.value,
|
|
264
|
-
this.descriptor.tenantId,
|
|
248
|
+
const resolutions = this.#resolveBatch(
|
|
249
|
+
batchEvents.map((event) => ({
|
|
250
|
+
epoch: head.epoch,
|
|
251
|
+
ledgerId: head.ledgerId,
|
|
252
|
+
ref: event.subjectRef,
|
|
253
|
+
tenantId: this.descriptor.artifactTenantId,
|
|
254
|
+
})),
|
|
265
255
|
);
|
|
266
256
|
if (
|
|
267
|
-
|
|
268
|
-
|
|
257
|
+
!Array.isArray(resolutions) ||
|
|
258
|
+
resolutions.length !== batchEvents.length
|
|
269
259
|
)
|
|
270
|
-
fail("pruning journal
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
260
|
+
fail("pruning journal artifact batch resolution is incomplete");
|
|
261
|
+
for (const [batchIndex, event] of batchEvents.entries()) {
|
|
262
|
+
const previous = history.at(-1) ?? null;
|
|
263
|
+
const resolution = resolutions[batchIndex];
|
|
264
|
+
if (
|
|
265
|
+
resolution?.schema !== EVOLUTION_ARTIFACT_RESOLUTION_SCHEMA ||
|
|
266
|
+
resolution.authenticated !== true ||
|
|
267
|
+
resolution.found !== true ||
|
|
268
|
+
resolution.ref !== event.subjectRef.ref ||
|
|
269
|
+
resolution.digest !== event.subjectRef.digest ||
|
|
270
|
+
!DIGEST.test(resolution.receiptDigest) ||
|
|
271
|
+
!Buffer.isBuffer(resolution.bytes) ||
|
|
272
|
+
resolution.bytes.length > WIKI_PRUNING_JOURNAL_MAX_BYTES
|
|
273
|
+
)
|
|
274
|
+
fail("pruning journal artifact resolution is invalid");
|
|
275
|
+
const record = JSON.parse(
|
|
276
|
+
new TextDecoder("utf-8", { fatal: true }).decode(resolution.bytes),
|
|
277
|
+
);
|
|
278
|
+
if (
|
|
279
|
+
record?.schema !== EVOLUTION_DURABLE_ARTIFACT_RECORD_SCHEMA ||
|
|
280
|
+
record.tenantId !== this.descriptor.artifactTenantId ||
|
|
281
|
+
record.audience !== this.descriptor.audience ||
|
|
282
|
+
record.purpose !== this.descriptor.purpose ||
|
|
283
|
+
record.retention !== "ledger" ||
|
|
284
|
+
record.type !== WIKI_PRUNING_JOURNAL_ARTIFACT_TYPE
|
|
285
|
+
)
|
|
286
|
+
fail("pruning journal durable artifact binding is invalid");
|
|
287
|
+
const state = verifyWikiPruningJournal(
|
|
288
|
+
record.value,
|
|
289
|
+
this.descriptor.tenantId,
|
|
290
|
+
);
|
|
291
|
+
if (
|
|
292
|
+
event.eventId !==
|
|
293
|
+
`${WIKI_PRUNING_JOURNAL_EVENT_TYPE}.${state.journalDigest.slice(7)}`
|
|
294
|
+
)
|
|
295
|
+
fail("pruning journal event identity is invalid");
|
|
296
|
+
assertPruningJournalTransition(previous?.state ?? null, state);
|
|
297
|
+
if (previous?.state.plan.planDigest !== state.plan.planDigest) {
|
|
298
|
+
if (plans.has(state.plan.planDigest))
|
|
299
|
+
fail("pruning journal restarted a historical plan");
|
|
300
|
+
plans.add(state.plan.planDigest);
|
|
301
|
+
}
|
|
302
|
+
const samePlan =
|
|
303
|
+
previous?.state.plan.planDigest === state.plan.planDigest;
|
|
304
|
+
const entry = {
|
|
305
|
+
event,
|
|
306
|
+
state,
|
|
307
|
+
prepared: samePlan ? previous.prepared : { event, state },
|
|
308
|
+
};
|
|
309
|
+
if (samePlan) history[history.length - 1] = entry;
|
|
310
|
+
else history.push(entry);
|
|
276
311
|
}
|
|
277
|
-
const samePlan =
|
|
278
|
-
previous?.state.plan.planDigest === state.plan.planDigest;
|
|
279
|
-
const entry = {
|
|
280
|
-
event,
|
|
281
|
-
state,
|
|
282
|
-
prepared: samePlan ? previous.prepared : { event, state },
|
|
283
|
-
};
|
|
284
|
-
if (samePlan) history[history.length - 1] = entry;
|
|
285
|
-
else history.push(entry);
|
|
286
312
|
}
|
|
287
313
|
// Every immutable event/transition above is checked. Per plan, authenticate
|
|
288
314
|
// its source at preparation and its full retained receipt prefix at the
|
|
@@ -2,7 +2,10 @@
|
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { isProxy } from "node:util/types";
|
|
4
4
|
import { captureRrsiRegistryStorePolicy } from "./rrsi-registry-store-policy.js";
|
|
5
|
-
import {
|
|
5
|
+
import {
|
|
6
|
+
captureEvolutionLedgerFileBackend,
|
|
7
|
+
captureEvolutionLedgerFileBackendSourceDirectoryBoundary,
|
|
8
|
+
} from "./evolution-ledger-file-backend.js";
|
|
6
9
|
import {
|
|
7
10
|
EvolutionArtifactPorts,
|
|
8
11
|
EVOLUTION_DURABLE_ARTIFACT_RECORD_SCHEMA,
|
|
@@ -172,6 +175,12 @@ export function createRrsiObservedTextIndex(input) {
|
|
|
172
175
|
)
|
|
173
176
|
hold("text index requires the original policy storage graph");
|
|
174
177
|
const ledger = backend.ledger;
|
|
178
|
+
const sourceJournalBoundary =
|
|
179
|
+
captureEvolutionLedgerFileBackendSourceDirectoryBoundary(backend);
|
|
180
|
+
const sourceJournalDirectoryBoundaryDigest = rrsiHash(
|
|
181
|
+
"chainlesschain.rrsi-source-journal-directory-boundary/v1",
|
|
182
|
+
sourceJournalBoundary.descriptor,
|
|
183
|
+
);
|
|
175
184
|
const artifactBoundary = captureEvolutionArtifactStoreDirectoryBoundary(
|
|
176
185
|
options.artifactPorts,
|
|
177
186
|
);
|
|
@@ -597,6 +606,7 @@ export function createRrsiObservedTextIndex(input) {
|
|
|
597
606
|
hold("preparation declaration reference differs");
|
|
598
607
|
}
|
|
599
608
|
function load() {
|
|
609
|
+
sourceJournalBoundary.recheck();
|
|
600
610
|
artifactBoundary.recheck();
|
|
601
611
|
const before = head(methods.verify());
|
|
602
612
|
if (!same(identity(before), originalIdentity))
|
|
@@ -683,6 +693,7 @@ export function createRrsiObservedTextIndex(input) {
|
|
|
683
693
|
if (!same(before, head(methods.verify())))
|
|
684
694
|
hold("text index journal changed during authenticated readback");
|
|
685
695
|
artifactBoundary.recheck();
|
|
696
|
+
sourceJournalBoundary.recheck();
|
|
686
697
|
return { head: before, events, records, entries, operations };
|
|
687
698
|
}
|
|
688
699
|
function historyPrefix(state, checkpoint) {
|
|
@@ -778,6 +789,9 @@ export function createRrsiObservedTextIndex(input) {
|
|
|
778
789
|
artifactStoreDirectoryBoundaryDigest,
|
|
779
790
|
artifactStoreDirectoryBoundaryRechecked: true,
|
|
780
791
|
artifactStoreBoundaryScope: "root-and-files-directories",
|
|
792
|
+
sourceJournalDirectoryBoundaryDigest,
|
|
793
|
+
sourceJournalDirectoryBoundaryRechecked: true,
|
|
794
|
+
sourceJournalBoundaryScope: "source-journal-four-directories",
|
|
781
795
|
fullPhysicalStorageGraphVerified: false,
|
|
782
796
|
tenantWideIndexAuthorityVerified: false,
|
|
783
797
|
generationProvenanceVerified: false,
|
|
@@ -787,10 +801,12 @@ export function createRrsiObservedTextIndex(input) {
|
|
|
787
801
|
);
|
|
788
802
|
const recheck = () =>
|
|
789
803
|
guarded(() => {
|
|
804
|
+
sourceJournalBoundary.recheck();
|
|
790
805
|
artifactBoundary.recheck();
|
|
791
806
|
if (!same(state.head, head(methods.verify())))
|
|
792
807
|
hold("observed history head changed after prefix capture");
|
|
793
808
|
artifactBoundary.recheck();
|
|
809
|
+
sourceJournalBoundary.recheck();
|
|
794
810
|
return true;
|
|
795
811
|
});
|
|
796
812
|
recheck();
|