chainlesschain 0.166.105 → 0.166.107
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 +182 -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-CcD-AVDw.js → AIOps-DCx6L_HW.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-CYy-g36A.js → ActionButton-DYm1CN3Z.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-DPFDNaBL.js → Analytics-D5ItUS_y.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-PVIuRdgN.js → AppLayout-Cb7XeRdH.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-BXGD3bM-.js → Artifacts-DDDeqCDQ.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-Bu5s_4tu.js → Audit-BRt3x13w.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-BHmgpnzo.js → BackgroundAgents-C_oNALMd.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-C872jURx.js → Backup-PSU2p6CR.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-Bh7xO30H.js → BaseInput-G3oL4hcX.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-DaacK53T.js → Chat-C2cLI10b.js} +6 -6
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-PYrSKMM_.js → ChatBubbleRenderer-KX1OITf8.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-Cnt7_8I2.js → Checkbox-CHLnEgFD.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-AZRz6kmW.js → Codegen-DZm9g_Bc.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BtOXzMFt.js → Col-D5Z4zSay.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BFmFygS7.js → Community-DHw69NSk.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-2z5CfKpR.js → Compact-B-vjYNWz.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-B5hKrPVF.js → Compliance-DaIYVZQY.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-CxEyp2ww.js → Cowork-CoA9U-gF.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-B-8f9_qF.js → Cron-DXqmxlRC.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-XARMOcc4.js → Crosschain-Cdkc3ue1.js} +1 -1
- package/src/assets/web-panel/assets/{DID-DwLtEKAe.js → DID-oKfYyNSR.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-Cjw0L_iA.js → Dashboard-n_ycvgWu.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BBGWIP7l.js → Dropdown-D38rZ8WY.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DG4kZFkm.js → EmailListRenderer-BjreeBQ1.js} +1 -1
- package/src/assets/web-panel/assets/{EvolutionSettings-D8vuNDde.js → EvolutionSettings-CTxA_SIq.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-COXCzDXI.js → FamilyGuardDashboard-DmECNwV5.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-Btb-aMhW.js → Federation-B2IaaDmQ.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-TO-E6l6e.js → FormItemContext-DgcgqUIk.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-BWXdJCT0.js → GenericCardRenderer-B1-du6Qm.js} +1 -1
- package/src/assets/web-panel/assets/{Git-B6icGhEv.js → Git-XceM21Il.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-C5Y__K6S.js → Governance-CV97iz3z.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-DZGEKnbN.js → Inference-CI7fj305.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph--Kga5yvm.js → KnowledgeGraph-B5mekWRy.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-BXs3EPKj.js → Logs-BlxFA0xY.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-rDE-XyMY.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-JSEAu0su.js → Marketplace-DL1BVCiX.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-BUBifkKP.js → McpTools-BPtwrO3c.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-Cb7sE4a-.js → Memory-DuSNw4E-.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-BOzmmNV2.js → MobileBridge-DTgY9ywI.js} +1 -1
- package/src/assets/web-panel/assets/MobileProjects-CTpVHJMa.js +1 -0
- package/src/assets/web-panel/assets/{Mtc-DmMWErG1.js → Mtc-h4fCLjXw.js} +2 -2
- package/src/assets/web-panel/assets/{MtcAudit-DnGkRVXF.js → MtcAudit-fqMDY3-w.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-C3mzascL.js → Multisig-Do3FehBL.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-CrmMZZ1t.js → NLProgramming-CQVY0kQF.js} +1 -1
- package/src/assets/web-panel/assets/Notes-4c3oNvWh.js +7 -0
- package/src/assets/web-panel/assets/{NotificationSettings-gn3UrWwW.js → NotificationSettings-BKr2JD4k.js} +1 -1
- package/src/assets/web-panel/assets/OrderTableRenderer-Bq4jgzRt.js +1 -0
- package/src/assets/web-panel/assets/{Organization-fMlkdgpW.js → Organization-Dy3XidyA.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-DGwCUPU1.js → Overflow-DFF0qN5K.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-BDGcpPmf.js → P2P-DAgYYykd.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-CccRPAB9.js → PdhVaultBrowser-DgNHa8b1.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-C33dNECx.js → Permissions-Cev6uSBc.js} +3 -3
- package/src/assets/web-panel/assets/{PersonalDataHub-BeqbCStc.js → PersonalDataHub-DJzbN06K.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-CTmasgbQ.js → Pipeline-CoJWvbQ5.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-ktd37BSp.js → Privacy-DZxV6etm.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-KOGvQtBX.js → ProjectInit-BFW87JnM.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CUvH_hC6.js → ProjectSettings-LBrRT27p.js} +2 -2
- package/src/assets/web-panel/assets/Projects-O3uz76R1.js +1 -0
- package/src/assets/web-panel/assets/{Providers-C-hmLXlO.js → Providers-CnRbjOYk.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-DzG_rbSL.js → QrScannerModal-BVFnIS3r.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-ShwiATg4.js → QuickAsk-DrcHx5f1.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-DkV8Ruep.js → Recommend-CbRgrSJm.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-Ca2dOx_G.js → RemoteSession-B2Uz_ElB.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-DYA5BJ6A.js → Reputation-DS0ljTxL.js} +1 -1
- package/src/assets/web-panel/assets/{Row-DvGDgxLS.js → Row-ClLobFht.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DbwTBSPw.js → RssFeed-DxxcTD0x.js} +3 -3
- package/src/assets/web-panel/assets/{Search-C5XAMQ9T.js → Search-BbHN0Fik.js} +1 -1
- package/src/assets/web-panel/assets/{Security-CKWT60Qt.js → Security-BAVU_EcY.js} +3 -3
- package/src/assets/web-panel/assets/{Services-CZvASlWL.js → Services-CtqRSVq_.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-DFkMVqws.js → Skeleton-IJ8ZKdSZ.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-hslZ_GyP.js → Skills-DXxcznnO.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-BLW5rmZ5.js → Sla-fRkhnA3i.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-Cn4ffCi5.js → SpeechSettings-Bi8sYJ-I.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-D1K0nJuo.js → SyncSettings-CjeplUtS.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-CCRu0xoN.js → Tasks-D4KB2Hbx.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-L19GYuS4.js → Templates-BwVugi8n.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CZ6HE3PF.js → Tenant-CsPuu5xK.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BiAwzaBT.js → Terminal-DuaeOPjY.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-DRHSWQT5.js → TimelineRenderer-D-PMrFxO.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-Cm-XATgh.js → Tokens-BXZuiQPZ.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BVXtx3kn.js → Trigger-DgpTaHL3.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-Bb0TmkLJ.js → Trust-Bn7lEGRY.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-Dbt9R7l0.js → UkeySign-zz-8JAAQ.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-z0-Ti8eJ.js → VideoEditing-DCzfQGTc.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-BZh31tI-.js → Wallet-BiRHM_zB.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-DQ0yIr5K.js → WebAuthn-QmPo2JwU.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-DCt_yuWG.js → WorkflowEditor-gL7SsPnY.js} +1 -1
- package/src/assets/web-panel/assets/{chat-BLgtmFeS.js → chat-BysvK4hk.js} +1 -1
- package/src/assets/web-panel/assets/{colors-xU4UH8M1.js → colors-CQq20vqN.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-C4w4v16T.js → compact-item-CAHlOf1X.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-CZM9hAaj.js → createContext-BeF3ESj_.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-D-Iugqxv.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-DvHqEpE7.js → hasIn-CiqnRf-U.js} +1 -1
- package/src/assets/web-panel/assets/{index-BkTKWAgq.js → index-4a0Lmz_y.js} +1 -1
- package/src/assets/web-panel/assets/index-B0Oz2_rQ.js +1 -0
- package/src/assets/web-panel/assets/{index-B0IvKEVO.js → index-B2AOvjgR.js} +1 -1
- package/src/assets/web-panel/assets/{index-BaQZvM4m.js → index-B2xbtl_c.js} +1 -1
- package/src/assets/web-panel/assets/{index-BRhD9RMV.js → index-B5941vyo.js} +1 -1
- package/src/assets/web-panel/assets/{index-CqDiY-jl.js → index-BHpHGGmB.js} +1 -1
- package/src/assets/web-panel/assets/{index-CQEzxnYS.js → index-BOiyW31p.js} +1 -1
- package/src/assets/web-panel/assets/{index-BTxhDCtg.js → index-BcVmt6u_.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPDw21XY.js → index-BfJabF_8.js} +1 -1
- package/src/assets/web-panel/assets/{index-CExiTobS.js → index-Br5tGVbC.js} +1 -1
- package/src/assets/web-panel/assets/{index-QHmhwgfc.js → index-BsnLkZjz.js} +1 -1
- package/src/assets/web-panel/assets/{index-BlN5KPq6.js → index-BtWchYfW.js} +1 -1
- package/src/assets/web-panel/assets/{index-SNV95Zen.js → index-Bvkbj3x-.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHK3CQHq.js → index-CCX0daFQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-CPC7OGCe.js → index-CImCEo8S.js} +1 -1
- package/src/assets/web-panel/assets/{index-CKugxsw3.js → index-CTp89vmg.js} +1 -1
- package/src/assets/web-panel/assets/{index-55Rkd1KA.js → index-CcccefJc.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ds6wKTMh.js → index-CeIL3-nY.js} +1 -1
- package/src/assets/web-panel/assets/{index-DifMvnMQ.js → index-CgkUkmEJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bf8Wm4ks.js → index-CowbNBJs.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cm00787d.js → index-Cp3IRTDK.js} +1 -1
- package/src/assets/web-panel/assets/{index-C9YnZFv2.js → index-D7q5VH2n.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHg9NoJ5.js → index-DBmGQHEz.js} +3 -3
- package/src/assets/web-panel/assets/{index-B6AQ2b5U.js → index-DHGtFwwR.js} +1 -1
- package/src/assets/web-panel/assets/{index-CYrQnkqA.js → index-DKYbru3i.js} +1 -1
- package/src/assets/web-panel/assets/{index-CyV8fNBg.js → index-DTPUVm71.js} +1 -1
- package/src/assets/web-panel/assets/{index-B4jIcYSv.js → index-DXT7MKkD.js} +1 -1
- package/src/assets/web-panel/assets/{index-B64Vf-8H.js → index-DXsXhR7p.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpiuuIww.js → index-Dqkls2_6.js} +1 -1
- package/src/assets/web-panel/assets/index-DwoycEQa.js +1 -0
- package/src/assets/web-panel/assets/{index-DGdLkss8.js → index-QALafVsT.js} +1 -1
- package/src/assets/web-panel/assets/{index-Ds1tM734.js → index-TdVEaJfk.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bh14b1k0.js → index-ZBuJ6O6g.js} +1 -1
- package/src/assets/web-panel/assets/{index-C6ZrTSw5.js → index-i9aJV9qk.js} +1 -1
- package/src/assets/web-panel/assets/{index-CF9ebAIB.js → index-jJhemX1R.js} +1 -1
- package/src/assets/web-panel/assets/{index-BIWumeQl.js → index-qjaCk1Mj.js} +1 -1
- package/src/assets/web-panel/assets/{index-n6SAUmmI.js → index-xwtRrgVY.js} +1 -1
- package/src/assets/web-panel/assets/{index-CRLP56-W.js → index-yFQWSRoW.js} +1 -1
- package/src/assets/web-panel/assets/{index-wYRjvvbl.js → index-zNkw0IY7.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-C44ZER6c.js → initDefaultProps-BQR2HnKb.js} +1 -1
- package/src/assets/web-panel/assets/{motion-DbNzzScd.js → motion-gAz6l-nZ.js} +1 -1
- package/src/assets/web-panel/assets/{move-BrRrDH2u.js → move-Cy5TN856.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-DZ_T_Pw8.js → mtc-parser-BeT7VrGi.js} +1 -1
- package/src/assets/web-panel/assets/{omit-5QHHk_V3.js → omit-BLN3mlgK.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BdxEl0G2.js → pickAttrs-DtwZ8u2d.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-CGyD615D.js → placementArrow-ViMwQSa-.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-QqcWAQmN.js → responsiveObserve-dEL3u4E7.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BumQrJ5R.js → slide-l05us-Gz.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Pf64olM7.js → statusUtils-Bk-Qn2Og.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DNwm5SQZ.js → styleChecker-Bn186qeY.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-c3gLKs6e.js → useFlexGapSupport-WP-ugR3H.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-TzTd6NDM.js → useFs-1SvoeSCL.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-BP5E_0Fe.js → usePersonalDataHub-CfNU8N3z.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-CVEkRfZU.js → vnode-Bd2Ya101.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-C18X_8TJ.js → zoom-Def_MeZv.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 +26 -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 +3 -1
- package/src/lib/config-schema.js +1 -1
- 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/provider-http-error.js +4 -5
- 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/agent-core.js +1 -1
- package/src/runtime/coding-agent-contract-shared.cjs +3 -2
- package/src/runtime/headless-stream.js +4 -6
- package/src/runtime/policies/agent-policy.js +1 -0
- package/src/assets/web-panel/assets/MarkdownRenderer-BsSiyE-A.js +0 -1
- package/src/assets/web-panel/assets/MobileProjects-ClRlavdF.js +0 -1
- package/src/assets/web-panel/assets/Notes-4S6uts9Q.js +0 -7
- package/src/assets/web-panel/assets/OrderTableRenderer-CDVAlBUf.js +0 -1
- package/src/assets/web-panel/assets/Projects-BvP_Os-1.js +0 -1
- package/src/assets/web-panel/assets/devWarning-BZUZgMkO.js +0 -1
- package/src/assets/web-panel/assets/index--_-pNhk0.js +0 -1
- package/src/assets/web-panel/assets/index-ftwO31cp.js +0 -1
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
/** Bounded standalone file reads shared by explicit CLI snapshots. */
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import { createHash } from "node:crypto";
|
|
4
|
+
import { createGoalFileAdapter } from "./goal-store.js";
|
|
5
|
+
|
|
6
|
+
function fail(code) {
|
|
7
|
+
throw new Error(code);
|
|
8
|
+
}
|
|
9
|
+
function sameFile(a, b) {
|
|
10
|
+
return (
|
|
11
|
+
a.dev === b.dev &&
|
|
12
|
+
a.ino === b.ino &&
|
|
13
|
+
a.size === b.size &&
|
|
14
|
+
a.mtimeMs === b.mtimeMs &&
|
|
15
|
+
a.ctimeMs === b.ctimeMs
|
|
16
|
+
);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/** Pin the configured store, including directory identity, for this snapshot. */
|
|
20
|
+
function sourceReader(root, onRead) {
|
|
21
|
+
const realRoot = fs.realpathSync(root);
|
|
22
|
+
const initialRoot = fs.statSync(root);
|
|
23
|
+
function checkRoot() {
|
|
24
|
+
const stat = fs.statSync(root);
|
|
25
|
+
if (
|
|
26
|
+
!stat.isDirectory() ||
|
|
27
|
+
fs.realpathSync(root) !== realRoot ||
|
|
28
|
+
stat.dev !== initialRoot.dev ||
|
|
29
|
+
stat.ino !== initialRoot.ino
|
|
30
|
+
)
|
|
31
|
+
fail("CLI_GOAL_STORE_CHANGED");
|
|
32
|
+
}
|
|
33
|
+
const readJson = (file) => {
|
|
34
|
+
checkRoot();
|
|
35
|
+
const entry = fs.lstatSync(file);
|
|
36
|
+
if (!entry.isFile() || entry.isSymbolicLink())
|
|
37
|
+
fail("CLI_GOAL_REGULAR_FILE_REQUIRED");
|
|
38
|
+
let fd;
|
|
39
|
+
try {
|
|
40
|
+
fd = fs.openSync(
|
|
41
|
+
file,
|
|
42
|
+
fs.constants.O_RDONLY |
|
|
43
|
+
(fs.constants.O_NOFOLLOW || 0) |
|
|
44
|
+
(fs.constants.O_NONBLOCK || 0),
|
|
45
|
+
);
|
|
46
|
+
const before = fs.fstatSync(fd);
|
|
47
|
+
if (!before.isFile() || !sameFile(entry, before))
|
|
48
|
+
fail("CLI_GOAL_SOURCE_CHANGED");
|
|
49
|
+
if (before.size > 64 * 1024) fail("CLI_GOAL_SOURCE_TOO_LARGE");
|
|
50
|
+
const buffer = Buffer.alloc(before.size + 1);
|
|
51
|
+
let count = 0;
|
|
52
|
+
while (count < buffer.length) {
|
|
53
|
+
const size = fs.readSync(
|
|
54
|
+
fd,
|
|
55
|
+
buffer,
|
|
56
|
+
count,
|
|
57
|
+
buffer.length - count,
|
|
58
|
+
count,
|
|
59
|
+
);
|
|
60
|
+
if (!size) break;
|
|
61
|
+
count += size;
|
|
62
|
+
}
|
|
63
|
+
checkRoot();
|
|
64
|
+
const after = fs.lstatSync(file);
|
|
65
|
+
if (
|
|
66
|
+
count !== before.size ||
|
|
67
|
+
!sameFile(before, fs.fstatSync(fd)) ||
|
|
68
|
+
!after.isFile() ||
|
|
69
|
+
after.isSymbolicLink() ||
|
|
70
|
+
!sameFile(before, after)
|
|
71
|
+
)
|
|
72
|
+
fail("CLI_GOAL_SOURCE_CHANGED");
|
|
73
|
+
const bytes = buffer.subarray(0, count);
|
|
74
|
+
const value = JSON.parse(
|
|
75
|
+
new TextDecoder("utf-8", { fatal: true }).decode(bytes),
|
|
76
|
+
);
|
|
77
|
+
onRead(createHash("sha256").update(bytes).digest("hex"));
|
|
78
|
+
return value;
|
|
79
|
+
} finally {
|
|
80
|
+
if (fd !== undefined) fs.closeSync(fd);
|
|
81
|
+
}
|
|
82
|
+
};
|
|
83
|
+
return { readJson, checkRoot };
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
export function createCliGoalSnapshotReader(root) {
|
|
87
|
+
let rawDigest;
|
|
88
|
+
const source = sourceReader(root, (value) => {
|
|
89
|
+
rawDigest = value;
|
|
90
|
+
});
|
|
91
|
+
const adapter = createGoalFileAdapter({ root, readJson: source.readJson });
|
|
92
|
+
function read(id) {
|
|
93
|
+
const goal = adapter.get(id);
|
|
94
|
+
if (!goal) fail("CLI_GOAL_NOT_FOUND");
|
|
95
|
+
return { goal, digest: rawDigest };
|
|
96
|
+
}
|
|
97
|
+
function revalidate(snapshot) {
|
|
98
|
+
let current;
|
|
99
|
+
try {
|
|
100
|
+
current = read(snapshot.goal.id);
|
|
101
|
+
} catch {
|
|
102
|
+
fail("CLI_GOAL_SOURCE_CHANGED");
|
|
103
|
+
}
|
|
104
|
+
if (
|
|
105
|
+
current.digest !== snapshot.digest ||
|
|
106
|
+
current.goal.storeId !== snapshot.goal.storeId ||
|
|
107
|
+
current.goal.revision !== snapshot.goal.revision ||
|
|
108
|
+
current.goal.controlGeneration !== snapshot.goal.controlGeneration
|
|
109
|
+
)
|
|
110
|
+
fail("CLI_GOAL_SOURCE_CHANGED");
|
|
111
|
+
return current.goal;
|
|
112
|
+
}
|
|
113
|
+
return {
|
|
114
|
+
storeId: adapter.storeId,
|
|
115
|
+
read,
|
|
116
|
+
revalidate,
|
|
117
|
+
checkRoot: source.checkRoot,
|
|
118
|
+
};
|
|
119
|
+
}
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
/** Shared correctness guidance for normal and file-only coding sessions. */
|
|
2
2
|
export const CODING_TASK_QUALITY_GUIDANCE = `Coding task quality:
|
|
3
3
|
- Read the actual implementation and its exported API before writing a caller or test. Check parameter order, return shapes, asynchronous lifecycle and fixture requirements against the source; an example in another directory can have different relative imports.
|
|
4
|
+
- Keep progress explanations to one or two sentences. Once the relevant API and behavior are clear, make the next tool call; resolve a remaining uncertainty with a targeted read. Put implementation code directly in write/edit tool arguments instead of rehearsing the code or repeatedly analyzing the same source in ordinary assistant text.
|
|
4
5
|
- Resolve each relative import from the destination file's directory. Read back the completed file and fix newDiagnostics from write/edit results before finishing. A successful write confirms persistence, not syntax, test execution or acceptance.
|
|
5
6
|
- Regression tests need a working positive control and a negative control that exercises the requested behavior through the real production entry point. Assert observable effects; avoid mocks or alternate calls that bypass the behavior under test. Complete the current worker/request when testing cancellation and replacement.
|
|
7
|
+
- For boundary regressions, derive expected defaults, computed values and rejection rules from the implementation or the explicit requested contract. Inject mismatched, tampered or stale input through the real entry point while keeping other prerequisites valid; verify that the fixture reaches the intended check rather than failing during setup. Compare observable results with independent expected values or authoritative state, not a helper that merely recomputes the same result.
|
|
6
8
|
- Use available execution tools for a focused check when permitted. When execution tools are unavailable, inspect imports, API contracts and fixtures using the available file tools, and clearly say that tests were not run. Never describe a read-back or static check as a passing test or end-to-end verification.
|
|
7
|
-
- Finish requested file changes before the final response. For large files, write a
|
|
9
|
+
- Finish requested file changes before the final response. Reserve output and remaining requests for writing, read-back and focused validation. For large files, aim for a few thousand characters of new content per call: write a small complete section, then extend it with edit_file using a unique existing anchor and a replacement containing that anchor plus the next complete section. Keep each tool call complete and wait for its result before extending the file again; do not repeatedly rewrite the growing file. State what was delivered, what was checked and what remains unverified.`;
|
|
8
10
|
|
|
9
11
|
export const HERMETIC_CODING_SYSTEM_PROMPT = `You are a hermetic coding assistant. The exposed file tools are the complete tool surface. Inspect and mutate only the exact paths authorized by the user request. Do not invoke hidden tools, commands, language servers or other execution paths.
|
|
10
12
|
|
package/src/lib/config-schema.js
CHANGED
|
@@ -83,7 +83,7 @@ const DESCRIPTORS = [
|
|
|
83
83
|
"Requested live service tier; model and endpoint support are checked before dispatch",
|
|
84
84
|
}),
|
|
85
85
|
descriptor("llm.visionModel", ["string", "null"], {
|
|
86
|
-
description: "
|
|
86
|
+
description: "Fallback for image turns when the main model lacks vision",
|
|
87
87
|
}),
|
|
88
88
|
descriptor("llm.fallbackModel", ["string", "null"], {
|
|
89
89
|
deprecated: true,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/** Parse raw agent NDJSON. A declaration of success is never terminal evidence. */
|
|
2
|
-
export function verifyAgentTerminal(output) {
|
|
2
|
+
export function verifyAgentTerminal(output, { allowSessionEnd = false } = {}) {
|
|
3
3
|
try {
|
|
4
4
|
const events = output
|
|
5
5
|
.split(/\r?\n/u)
|
|
@@ -7,6 +7,31 @@ export function verifyAgentTerminal(output) {
|
|
|
7
7
|
.map((line) => JSON.parse(line));
|
|
8
8
|
const results = events.filter((event) => event?.type === "result");
|
|
9
9
|
const terminal = results[0];
|
|
10
|
+
const last = events.at(-1);
|
|
11
|
+
const init = events.find(
|
|
12
|
+
(event) => event?.type === "system" && event.subtype === "init",
|
|
13
|
+
);
|
|
14
|
+
const sessionEnded =
|
|
15
|
+
allowSessionEnd &&
|
|
16
|
+
events.at(-2) === terminal &&
|
|
17
|
+
last?.type === "system" &&
|
|
18
|
+
last.subtype === "end" &&
|
|
19
|
+
typeof terminal?.session_id === "string" &&
|
|
20
|
+
terminal.session_id.length > 0 &&
|
|
21
|
+
last.session_id === terminal.session_id &&
|
|
22
|
+
init?.session_id === terminal.session_id &&
|
|
23
|
+
typeof terminal.trace_id === "string" &&
|
|
24
|
+
terminal.trace_id.length > 0 &&
|
|
25
|
+
last.trace_id === terminal.trace_id &&
|
|
26
|
+
Number.isSafeInteger(terminal.seq) &&
|
|
27
|
+
terminal.seq > 0 &&
|
|
28
|
+
last.seq === terminal.seq + 1 &&
|
|
29
|
+
Number.isSafeInteger(terminal.turn) &&
|
|
30
|
+
terminal.turn > 0 &&
|
|
31
|
+
last.turns === terminal.turn &&
|
|
32
|
+
events.filter(
|
|
33
|
+
(event) => event?.type === "system" && event.subtype === "end",
|
|
34
|
+
).length === 1;
|
|
10
35
|
const usage = terminal?.usage;
|
|
11
36
|
const normalizedUsage =
|
|
12
37
|
usage &&
|
|
@@ -26,7 +51,7 @@ export function verifyAgentTerminal(output) {
|
|
|
26
51
|
return {
|
|
27
52
|
terminalVerified:
|
|
28
53
|
results.length === 1 &&
|
|
29
|
-
|
|
54
|
+
(last === terminal || sessionEnded) &&
|
|
30
55
|
terminal.subtype === "success" &&
|
|
31
56
|
terminal.is_error === false &&
|
|
32
57
|
!events.some((event) => event?.type === "error"),
|
|
@@ -23,6 +23,11 @@ import {
|
|
|
23
23
|
} from "./evolution-ledger.js";
|
|
24
24
|
|
|
25
25
|
const ARTIFACT_STORE_DIRECTORY_BOUNDARIES = new WeakMap();
|
|
26
|
+
// Capture repository function identity before binding instance methods. Only
|
|
27
|
+
// these exact defaults may skip legacy list/get I/O; overrides still execute
|
|
28
|
+
// and must match the independently authenticated descriptor snapshots.
|
|
29
|
+
const DEFAULT_STORE_LIST = ArtifactStore.prototype.list;
|
|
30
|
+
const DEFAULT_STORE_GET = ArtifactStore.prototype.get;
|
|
26
31
|
// Only the shared identity helper's directory opens use this adapter. A POSIX
|
|
27
32
|
// directory/FIFO swap must fail before reaching a potentially blocking open.
|
|
28
33
|
const INDEX_DIRECTORY_FS = Object.freeze({
|
|
@@ -1014,7 +1019,7 @@ function normalizeKeyId(value, label) {
|
|
|
1014
1019
|
return value;
|
|
1015
1020
|
}
|
|
1016
1021
|
|
|
1017
|
-
function
|
|
1022
|
+
function captureDataMethod(port, method, label) {
|
|
1018
1023
|
if (!port || (typeof port !== "object" && typeof port !== "function")) {
|
|
1019
1024
|
throw new TypeError(`EvolutionArtifactPorts requires ${label}.${method}`);
|
|
1020
1025
|
}
|
|
@@ -1029,13 +1034,17 @@ function captureMethod(port, method, label) {
|
|
|
1029
1034
|
`EvolutionArtifactPorts requires ${label}.${method} as a data method`,
|
|
1030
1035
|
);
|
|
1031
1036
|
}
|
|
1032
|
-
return
|
|
1037
|
+
return descriptor.value;
|
|
1033
1038
|
}
|
|
1034
1039
|
cursor = Object.getPrototypeOf(cursor);
|
|
1035
1040
|
}
|
|
1036
1041
|
throw new TypeError(`EvolutionArtifactPorts requires ${label}.${method}`);
|
|
1037
1042
|
}
|
|
1038
1043
|
|
|
1044
|
+
function captureMethod(port, method, label) {
|
|
1045
|
+
return Object.freeze(captureDataMethod(port, method, label).bind(port));
|
|
1046
|
+
}
|
|
1047
|
+
|
|
1039
1048
|
function captureArtifactStore(store) {
|
|
1040
1049
|
rejectProxy(store, "artifactStore");
|
|
1041
1050
|
if (!hasStablePrototype(store, ArtifactStore.prototype, "artifactStore")) {
|
|
@@ -1049,19 +1058,21 @@ function captureArtifactStore(store) {
|
|
|
1049
1058
|
}
|
|
1050
1059
|
const resolvedDir = path.resolve(dir);
|
|
1051
1060
|
const layout = establishStoreLayout(resolvedDir);
|
|
1061
|
+
const list = captureDataMethod(store, "list", "artifactStore");
|
|
1062
|
+
const get = captureDataMethod(store, "get", "artifactStore");
|
|
1052
1063
|
const captured = Object.freeze({
|
|
1053
1064
|
dir: resolvedDir,
|
|
1054
|
-
get:
|
|
1065
|
+
get: get === DEFAULT_STORE_GET ? null : Object.freeze(get.bind(store)),
|
|
1055
1066
|
layout,
|
|
1056
|
-
list:
|
|
1067
|
+
list: list === DEFAULT_STORE_LIST ? null : Object.freeze(list.bind(store)),
|
|
1057
1068
|
publishDataOnce: captureMethod(store, "publishDataOnce", "artifactStore"),
|
|
1058
1069
|
storedPath: captureMethod(store, "storedPath", "artifactStore"),
|
|
1059
1070
|
verifyIntegrity: captureMethod(store, "verifyIntegrity", "artifactStore"),
|
|
1060
1071
|
});
|
|
1061
1072
|
// ArtifactStore methods consult public instance fields such as `dir` on
|
|
1062
1073
|
// every call. Freeze the trusted instance after capturing its methods so a
|
|
1063
|
-
// later collaborator cannot redirect a bound get/list/integrity call
|
|
1064
|
-
// different store root.
|
|
1074
|
+
// later collaborator cannot redirect a bound custom get/list/integrity call
|
|
1075
|
+
// to a different store root. Null index methods select trusted snapshots.
|
|
1065
1076
|
Object.freeze(store);
|
|
1066
1077
|
return captured;
|
|
1067
1078
|
}
|
|
@@ -2164,7 +2175,7 @@ export class EvolutionArtifactPorts {
|
|
|
2164
2175
|
let entries;
|
|
2165
2176
|
try {
|
|
2166
2177
|
entries = rejectPromise(
|
|
2167
|
-
this.#store.list(),
|
|
2178
|
+
this.#store.list ? this.#store.list() : trustedBefore.entries,
|
|
2168
2179
|
"ArtifactStore.list",
|
|
2169
2180
|
EVOLUTION_ARTIFACT_STORE_FAILED_CODE,
|
|
2170
2181
|
);
|
|
@@ -2187,9 +2198,9 @@ export class EvolutionArtifactPorts {
|
|
|
2187
2198
|
"ArtifactStore.list did not return an array",
|
|
2188
2199
|
);
|
|
2189
2200
|
}
|
|
2190
|
-
//
|
|
2191
|
-
//
|
|
2192
|
-
//
|
|
2201
|
+
// Default entries already came from a bounded descriptor read. Custom
|
|
2202
|
+
// list hooks retain their original execution and exact cross-check; their
|
|
2203
|
+
// own I/O cannot be constrained by this adapter's snapshot byte limit.
|
|
2193
2204
|
if (entries.length > EVOLUTION_ARTIFACT_MAX_INDEX_ENTRIES) {
|
|
2194
2205
|
throw artifactError(
|
|
2195
2206
|
EVOLUTION_ARTIFACT_STORE_FAILED_CODE,
|
|
@@ -2280,7 +2291,7 @@ export class EvolutionArtifactPorts {
|
|
|
2280
2291
|
let current;
|
|
2281
2292
|
try {
|
|
2282
2293
|
current = rejectPromise(
|
|
2283
|
-
this.#store.get(artifactId),
|
|
2294
|
+
this.#store.get ? this.#store.get(artifactId) : listed[0],
|
|
2284
2295
|
"ArtifactStore.get",
|
|
2285
2296
|
EVOLUTION_ARTIFACT_STORE_FAILED_CODE,
|
|
2286
2297
|
);
|
|
@@ -4,8 +4,7 @@ import path from "node:path";
|
|
|
4
4
|
import { types } from "node:util";
|
|
5
5
|
import { ensurePrivateDirectory, ensurePrivateFile } from "../secure-fs.js";
|
|
6
6
|
import { withFileLock } from "../with-file-lock.js";
|
|
7
|
-
import {
|
|
8
|
-
import { withEvolutionFileIdentity } from "./evolution-file-identity.js";
|
|
7
|
+
import { createEvolutionPhysicalStateReader } from "./evolution-physical-state-reader.js";
|
|
9
8
|
import {
|
|
10
9
|
createEvolutionWitnessSegments,
|
|
11
10
|
EVOLUTION_SEGMENTED_WITNESS_SCHEMA,
|
|
@@ -17,6 +16,8 @@ import {
|
|
|
17
16
|
|
|
18
17
|
export const EVOLUTION_FILE_WITNESS_STORE_SCHEMA =
|
|
19
18
|
"chainlesschain.evolution-file-witness-store/v1";
|
|
19
|
+
export const EVOLUTION_WITNESS_STATE_READ_FAILED_CODE =
|
|
20
|
+
"CC_EVOLUTION_WITNESS_STATE_READ_FAILED";
|
|
20
21
|
|
|
21
22
|
const WITNESS_DOMAIN = `${EVOLUTION_LEDGER_WITNESS_SCHEMA}\0`;
|
|
22
23
|
const ANCESTRY_DOMAIN = `${EVOLUTION_LEDGER_WITNESS_ANCESTRY_SCHEMA}\0`;
|
|
@@ -247,6 +248,63 @@ export function createEvolutionFileWitness({
|
|
|
247
248
|
}
|
|
248
249
|
const secureOptions = { deps: { fs: fsImpl }, failIfUnavailable: true };
|
|
249
250
|
ensurePrivateDirectory(directory, secureOptions);
|
|
251
|
+
const readerOptions = {
|
|
252
|
+
fsImpl,
|
|
253
|
+
maximumBytes,
|
|
254
|
+
minimumBytes: 2,
|
|
255
|
+
directoryMode: process.platform === "win32" ? null : 0o700n,
|
|
256
|
+
sizeError: "store size is invalid",
|
|
257
|
+
label: "witness",
|
|
258
|
+
errorCode: EVOLUTION_WITNESS_STATE_READ_FAILED_CODE,
|
|
259
|
+
readFailureMessage: "witness bounded read failed",
|
|
260
|
+
};
|
|
261
|
+
const rootReader = createEvolutionPhysicalStateReader({
|
|
262
|
+
...readerOptions,
|
|
263
|
+
directoryPath: directory,
|
|
264
|
+
});
|
|
265
|
+
const segmentDirectory = `${target}.segments-v2`;
|
|
266
|
+
let segmentReader = null;
|
|
267
|
+
const captureSegments = () => {
|
|
268
|
+
rootReader.recheck();
|
|
269
|
+
segmentReader = createEvolutionPhysicalStateReader({
|
|
270
|
+
...readerOptions,
|
|
271
|
+
label: "witness segment",
|
|
272
|
+
readFailureMessage: "witness segment bounded read failed",
|
|
273
|
+
directoryPath: segmentDirectory,
|
|
274
|
+
});
|
|
275
|
+
rootReader.recheck();
|
|
276
|
+
return segmentReader;
|
|
277
|
+
};
|
|
278
|
+
// A constructor may secure existing local storage. Read paths never create
|
|
279
|
+
// or repair a missing segment directory; first publication retains that job.
|
|
280
|
+
let existingSegments = false;
|
|
281
|
+
try {
|
|
282
|
+
fsImpl.lstatSync(segmentDirectory, { bigint: true });
|
|
283
|
+
existingSegments = true;
|
|
284
|
+
} catch (cause) {
|
|
285
|
+
if (cause?.code !== "ENOENT") throw cause;
|
|
286
|
+
}
|
|
287
|
+
if (existingSegments) {
|
|
288
|
+
ensurePrivateDirectory(segmentDirectory, secureOptions);
|
|
289
|
+
captureSegments();
|
|
290
|
+
}
|
|
291
|
+
const checkSegments = () => {
|
|
292
|
+
const reader = segmentReader ?? captureSegments();
|
|
293
|
+
rootReader.recheck();
|
|
294
|
+
reader.recheck();
|
|
295
|
+
return reader;
|
|
296
|
+
};
|
|
297
|
+
const prepareSegments = () => {
|
|
298
|
+
rootReader.recheck();
|
|
299
|
+
if (segmentReader !== null) return segmentReader.recheck();
|
|
300
|
+
ensurePrivateDirectory(segmentDirectory, secureOptions);
|
|
301
|
+
captureSegments();
|
|
302
|
+
};
|
|
303
|
+
const recheckStorage = () => {
|
|
304
|
+
rootReader.recheck();
|
|
305
|
+
segmentReader?.recheck();
|
|
306
|
+
rootReader.recheck();
|
|
307
|
+
};
|
|
250
308
|
// Cache immutable encodings, never an authority decision. Keep the earliest
|
|
251
309
|
// entries when full so full-history scans cannot churn the bounded cache.
|
|
252
310
|
const recordEncodings = new Map();
|
|
@@ -587,67 +645,25 @@ export function createEvolutionFileWitness({
|
|
|
587
645
|
return store;
|
|
588
646
|
};
|
|
589
647
|
|
|
590
|
-
const readBytes = (file, byteLimit = maximumBytes) =>
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
),
|
|
597
|
-
);
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
ensurePrivateFile(file, secureOptions);
|
|
601
|
-
const stat = fsImpl.lstatSync(file);
|
|
602
|
-
if (
|
|
603
|
-
!stat.isFile() ||
|
|
604
|
-
stat.isSymbolicLink() ||
|
|
605
|
-
stat.nlink !== 1 ||
|
|
606
|
-
stat.size < 2 ||
|
|
607
|
-
stat.size > byteLimit
|
|
608
|
-
) {
|
|
609
|
-
throw new Error("witness store size is invalid");
|
|
610
|
-
}
|
|
611
|
-
let descriptor;
|
|
612
|
-
const sameFile = (observed) =>
|
|
613
|
-
observed.isFile() &&
|
|
614
|
-
observed.nlink === 1 &&
|
|
615
|
-
observed.dev === stat.dev &&
|
|
616
|
-
observed.ino === stat.ino &&
|
|
617
|
-
observed.size === stat.size &&
|
|
618
|
-
observed.mtimeMs === stat.mtimeMs &&
|
|
619
|
-
observed.ctimeMs === stat.ctimeMs;
|
|
620
|
-
try {
|
|
621
|
-
descriptor = fsImpl.openSync(
|
|
622
|
-
file,
|
|
623
|
-
fsImpl.constants.O_RDONLY | (fsImpl.constants.O_NOFOLLOW || 0),
|
|
624
|
-
);
|
|
625
|
-
if (!samePathHandle(stat, fsImpl.fstatSync(descriptor))) {
|
|
626
|
-
throw new Error("witness store changed while opening");
|
|
627
|
-
}
|
|
628
|
-
const bytes = readBoundedDescriptor(
|
|
629
|
-
fsImpl,
|
|
630
|
-
descriptor,
|
|
631
|
-
stat.size,
|
|
632
|
-
byteLimit,
|
|
633
|
-
);
|
|
634
|
-
const afterPath = fsImpl.lstatSync(file);
|
|
635
|
-
if (
|
|
636
|
-
!samePathHandle(stat, fsImpl.fstatSync(descriptor)) ||
|
|
637
|
-
afterPath.isSymbolicLink() ||
|
|
638
|
-
!sameFile(afterPath)
|
|
639
|
-
) {
|
|
640
|
-
throw new Error("witness store changed while reading");
|
|
641
|
-
}
|
|
642
|
-
return bytes;
|
|
643
|
-
} finally {
|
|
644
|
-
if (descriptor !== undefined) fsImpl.closeSync(descriptor);
|
|
645
|
-
}
|
|
648
|
+
const readBytes = (file, byteLimit = maximumBytes, allowAbsent = false) => {
|
|
649
|
+
recheckStorage();
|
|
650
|
+
const reader = file === target ? rootReader : checkSegments();
|
|
651
|
+
const bytes = reader.read(file, {
|
|
652
|
+
maximumBytes: byteLimit,
|
|
653
|
+
allowAbsent,
|
|
654
|
+
prepareFile: (name) => ensurePrivateFile(name, secureOptions),
|
|
655
|
+
});
|
|
656
|
+
recheckStorage();
|
|
657
|
+
return bytes;
|
|
646
658
|
};
|
|
647
659
|
|
|
648
660
|
const readStore = (options = {}) => {
|
|
649
|
-
|
|
650
|
-
|
|
661
|
+
const bytes = readBytes(target, maximumHistoryBytes, true);
|
|
662
|
+
if (bytes === null) {
|
|
663
|
+
const store = emptyStore();
|
|
664
|
+
recheckStorage();
|
|
665
|
+
return store;
|
|
666
|
+
}
|
|
651
667
|
const parsed = JSON.parse(
|
|
652
668
|
new TextDecoder("utf-8", { fatal: true }).decode(bytes),
|
|
653
669
|
);
|
|
@@ -660,7 +676,10 @@ export function createEvolutionFileWitness({
|
|
|
660
676
|
headBytes: bytes.length,
|
|
661
677
|
})
|
|
662
678
|
: validateStore(parsed, trustEpoch);
|
|
663
|
-
if (trustEpoch === currentTrustEpoch())
|
|
679
|
+
if (trustEpoch === currentTrustEpoch()) {
|
|
680
|
+
recheckStorage();
|
|
681
|
+
return store;
|
|
682
|
+
}
|
|
664
683
|
}
|
|
665
684
|
throw new Error("witness verifier trust epoch changed while reading");
|
|
666
685
|
};
|
|
@@ -716,6 +735,8 @@ export function createEvolutionFileWitness({
|
|
|
716
735
|
const segments = createEvolutionWitnessSegments({
|
|
717
736
|
filePath: target,
|
|
718
737
|
fsImpl,
|
|
738
|
+
checkDirectory: checkSegments,
|
|
739
|
+
prepareDirectory: prepareSegments,
|
|
719
740
|
readBytes,
|
|
720
741
|
writeBytes,
|
|
721
742
|
validateStore,
|
|
@@ -760,8 +781,10 @@ export function createEvolutionFileWitness({
|
|
|
760
781
|
return store.current;
|
|
761
782
|
};
|
|
762
783
|
|
|
763
|
-
const underLock = (operation) =>
|
|
764
|
-
|
|
784
|
+
const underLock = (operation) => {
|
|
785
|
+
recheckStorage();
|
|
786
|
+
return lock(target, operation, { failIfUnavailable: true, _fs: fsImpl });
|
|
787
|
+
};
|
|
765
788
|
const discarded = (store, snapshot) =>
|
|
766
789
|
segments.isDiscarded(store, snapshot) ||
|
|
767
790
|
store.discardedAnchors.some(
|
|
@@ -4,6 +4,7 @@ import { createEvolutionFileWitness } from "./evolution-file-witness.js";
|
|
|
4
4
|
import {
|
|
5
5
|
EvolutionLedger,
|
|
6
6
|
captureEvolutionLedgerMigrationSource,
|
|
7
|
+
captureEvolutionLedgerDirectoryBoundary,
|
|
7
8
|
} from "./evolution-ledger.js";
|
|
8
9
|
import { createEvolutionLedgerV2JournalFromFactory } from "./evolution-ledger-v2-journal.js";
|
|
9
10
|
|
|
@@ -12,6 +13,7 @@ export const EVOLUTION_LEDGER_FILE_BACKEND_SCHEMA =
|
|
|
12
13
|
|
|
13
14
|
const BACKENDS = new WeakSet();
|
|
14
15
|
const BACKEND_BINDINGS = new WeakMap();
|
|
16
|
+
const SOURCE_DIRECTORY_BOUNDARIES = new WeakMap();
|
|
15
17
|
|
|
16
18
|
function requiredString(value, label) {
|
|
17
19
|
if (typeof value !== "string" || value.trim() === "") {
|
|
@@ -205,7 +207,10 @@ export function createEvolutionLedgerFileBackend({
|
|
|
205
207
|
ledger,
|
|
206
208
|
witness,
|
|
207
209
|
});
|
|
210
|
+
const sourceDirectoryBoundary =
|
|
211
|
+
captureEvolutionLedgerDirectoryBoundary(sourceLedger);
|
|
208
212
|
BACKENDS.add(backend);
|
|
213
|
+
SOURCE_DIRECTORY_BOUNDARIES.set(backend, sourceDirectoryBoundary);
|
|
209
214
|
BACKEND_BINDINGS.set(
|
|
210
215
|
backend,
|
|
211
216
|
Object.freeze({
|
|
@@ -234,3 +239,20 @@ export function captureEvolutionLedgerFileBackendBinding(value) {
|
|
|
234
239
|
);
|
|
235
240
|
return binding;
|
|
236
241
|
}
|
|
242
|
+
|
|
243
|
+
/** Captured private v1 source even when the public journal is a v2 wrapper. */
|
|
244
|
+
export function captureEvolutionLedgerFileBackendSourceDirectoryBoundary(
|
|
245
|
+
value,
|
|
246
|
+
) {
|
|
247
|
+
if (arguments.length !== 1)
|
|
248
|
+
throw new TypeError(
|
|
249
|
+
"source directory boundary capture takes exactly one original backend",
|
|
250
|
+
);
|
|
251
|
+
const boundary = SOURCE_DIRECTORY_BOUNDARIES.get(value);
|
|
252
|
+
if (!boundary)
|
|
253
|
+
throw new TypeError(
|
|
254
|
+
"a branded file backend source directory boundary is required",
|
|
255
|
+
);
|
|
256
|
+
boundary.recheck();
|
|
257
|
+
return boundary;
|
|
258
|
+
}
|
|
@@ -5,7 +5,10 @@ import { types as utilTypes } from "node:util";
|
|
|
5
5
|
|
|
6
6
|
import { ensurePrivateDirectory, ensurePrivateFile } from "../secure-fs.js";
|
|
7
7
|
import { withFileLock } from "../with-file-lock.js";
|
|
8
|
-
import {
|
|
8
|
+
import {
|
|
9
|
+
createEvolutionManifestStateReader,
|
|
10
|
+
captureEvolutionManifestStateReaderDirectoryBoundary,
|
|
11
|
+
} from "./evolution-manifest-state-reader.js";
|
|
9
12
|
import { EVOLUTION_LEDGER_MANIFEST_CATALOG_APPEND_RESULT_SCHEMA } from "./evolution-ledger-manifest-catalog.js";
|
|
10
13
|
|
|
11
14
|
export const EVOLUTION_LEDGER_FILE_MANIFEST_CATALOG_SCHEMA =
|
|
@@ -15,6 +18,7 @@ const STATE_SCHEMA =
|
|
|
15
18
|
"chainlesschain.evolution-ledger-file-manifest-catalog-state/v2";
|
|
16
19
|
const STATE_KEYS = new Set(["manifests", "schema"]);
|
|
17
20
|
const DEFAULT_MAXIMUM_BYTES = 64 * 1024 * 1024;
|
|
21
|
+
const DIRECTORY_BOUNDARIES = new WeakMap();
|
|
18
22
|
|
|
19
23
|
function exactRecord(value, keys, label) {
|
|
20
24
|
if (
|
|
@@ -75,39 +79,13 @@ export function createEvolutionLedgerFileManifestCatalogBackend({
|
|
|
75
79
|
const secureOptions = { deps: { fs: fsImpl }, failIfUnavailable: true };
|
|
76
80
|
ensurePrivateDirectory(directory, secureOptions);
|
|
77
81
|
|
|
78
|
-
const
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
throw new Error("manifest catalog exceeds maximumBytes");
|
|
86
|
-
let descriptor;
|
|
87
|
-
try {
|
|
88
|
-
descriptor = fsImpl.openSync(
|
|
89
|
-
file,
|
|
90
|
-
fsImpl.constants.O_RDONLY | (fsImpl.constants.O_NOFOLLOW || 0),
|
|
91
|
-
);
|
|
92
|
-
const opened = fsImpl.fstatSync(descriptor);
|
|
93
|
-
if (!opened.isFile() || opened.nlink !== 1 || opened.size !== stat.size) {
|
|
94
|
-
throw new Error("manifest catalog changed while opening");
|
|
95
|
-
}
|
|
96
|
-
const bytes = readBoundedDescriptor(
|
|
97
|
-
fsImpl,
|
|
98
|
-
descriptor,
|
|
99
|
-
stat.size,
|
|
100
|
-
maximumBytes,
|
|
101
|
-
);
|
|
102
|
-
const after = fsImpl.lstatSync(file);
|
|
103
|
-
if (after.isSymbolicLink() || after.size !== stat.size) {
|
|
104
|
-
throw new Error("manifest catalog changed while reading");
|
|
105
|
-
}
|
|
106
|
-
return bytes;
|
|
107
|
-
} finally {
|
|
108
|
-
if (descriptor !== undefined) fsImpl.closeSync(descriptor);
|
|
109
|
-
}
|
|
110
|
-
};
|
|
82
|
+
const stateReader = createEvolutionManifestStateReader({
|
|
83
|
+
file,
|
|
84
|
+
fsImpl,
|
|
85
|
+
maximumBytes,
|
|
86
|
+
label: "manifest catalog",
|
|
87
|
+
});
|
|
88
|
+
const readBytes = stateReader.read;
|
|
111
89
|
|
|
112
90
|
const readState = () => {
|
|
113
91
|
const bytes = readBytes();
|
|
@@ -173,13 +151,14 @@ export function createEvolutionLedgerFileManifestCatalogBackend({
|
|
|
173
151
|
}
|
|
174
152
|
};
|
|
175
153
|
|
|
176
|
-
|
|
154
|
+
const backend = Object.freeze({
|
|
177
155
|
descriptor: Object.freeze({
|
|
178
156
|
file,
|
|
179
157
|
localOnly: true,
|
|
180
158
|
schema: EVOLUTION_LEDGER_FILE_MANIFEST_CATALOG_SCHEMA,
|
|
181
159
|
}),
|
|
182
160
|
compareAndAppend(request) {
|
|
161
|
+
stateReader.recheck();
|
|
183
162
|
return lock(
|
|
184
163
|
file,
|
|
185
164
|
() => {
|
|
@@ -215,4 +194,24 @@ export function createEvolutionLedgerFileManifestCatalogBackend({
|
|
|
215
194
|
return structuredClone(readState().manifests.at(-1) ?? null);
|
|
216
195
|
},
|
|
217
196
|
});
|
|
197
|
+
DIRECTORY_BOUNDARIES.set(
|
|
198
|
+
backend,
|
|
199
|
+
captureEvolutionManifestStateReaderDirectoryBoundary(stateReader),
|
|
200
|
+
);
|
|
201
|
+
return backend;
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
/** Original local catalog directory only; structural wrappers are not owners. */
|
|
205
|
+
export function captureEvolutionLedgerFileManifestCatalogDirectoryBoundary(
|
|
206
|
+
backend,
|
|
207
|
+
) {
|
|
208
|
+
if (arguments.length !== 1)
|
|
209
|
+
throw new TypeError("catalog directory capture takes one original backend");
|
|
210
|
+
const boundary = DIRECTORY_BOUNDARIES.get(backend);
|
|
211
|
+
if (!boundary)
|
|
212
|
+
throw new TypeError(
|
|
213
|
+
"an original file manifest catalog backend is required",
|
|
214
|
+
);
|
|
215
|
+
boundary.recheck();
|
|
216
|
+
return boundary;
|
|
218
217
|
}
|