chainlesschain 0.166.115 → 0.166.118
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 +43 -5
- package/package.json +4 -4
- 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/.build-hash +1 -1
- package/src/assets/web-panel/assets/{AIOps-BsoSoDNb.js → AIOps-BGySwMIP.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-Dcx7Gm5t.js → ActionButton-Chn7fBLr.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-S1hsBIz3.js → Analytics-ORH7UkGa.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-B2j067qM.js → AppLayout-8xV4jWjP.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-D5iGV9ic.js → Artifacts-26SBK-x-.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-C8ChVvXu.js → Audit-CtXpqbXg.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-Cg97AAJS.js → BackgroundAgents-C5zLvQis.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-CyxU9IUg.js → Backup-C93xY4vO.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-CKlWjA8I.js → BaseInput-DpVCHfAn.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-C-EqZza6.js → Chat-DM0LblR2.js} +5 -5
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-BRFiXKPX.js → ChatBubbleRenderer-unlQUF1T.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-kkI13tSJ.js → Checkbox-BsAt3cKR.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-NlqEDwHK.js → Codegen-E_kp4KFd.js} +1 -1
- package/src/assets/web-panel/assets/{Col-CfepEu79.js → Col-6msR3T6I.js} +1 -1
- package/src/assets/web-panel/assets/{Community-BRo0qLXM.js → Community-PyX9c5D-.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-BMu8TP0E.js → Compact-7G1bVDSH.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-B7Q3Moef.js → Compliance-DcHN7GlD.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-BVwt7Xmc.js → Cowork-WUIHJYDx.js} +3 -3
- package/src/assets/web-panel/assets/{Cron-CgBv20Qy.js → Cron-CWgCbptg.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-CwcSBJk2.js → Crosschain-BzKZ6roD.js} +1 -1
- package/src/assets/web-panel/assets/{DID-d20rCiIm.js → DID-r7zxeItn.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-PpZaUYpS.js → Dashboard-D3pBiN2H.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-DjsLdX_Z.js → Dropdown-C4otRJlZ.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-CaPkrtxn.js → EmailListRenderer-CzDTC8lm.js} +1 -1
- package/src/assets/web-panel/assets/{EvolutionSettings-DDY8cQnE.js → EvolutionSettings-DGkXiIs-.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-VQavMjyj.js → FamilyGuardDashboard-BKdEyuiR.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-D0GvKQgI.js → Federation-dsI-xcpf.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-Dl_TWHmb.js → FormItemContext-CEEb1fMR.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-Bny2uagG.js → GenericCardRenderer-D_mfO3Ts.js} +1 -1
- package/src/assets/web-panel/assets/{Git-B01KXq47.js → Git-CBwr2ZI3.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-F4c4Vwo1.js → Governance-D-Wy2Jjr.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-_025Q-8K.js → Inference-B4Yk_IRx.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DwbTmQb1.js → KnowledgeGraph-DoI7bi6m.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-CnNo0hCm.js → Logs-Ce1IiKAj.js} +2 -2
- package/src/assets/web-panel/assets/MarkdownRenderer-GND_8y0V.js +1 -0
- package/src/assets/web-panel/assets/{Marketplace-Dkz5bv7F.js → Marketplace-CsIEByzK.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-CBtRwDW_.js → McpTools-_DRzjcIn.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-Y9jnyTb7.js → Memory-zvLpyi__.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-Br1urkOz.js → MobileBridge-DXd1ik2Z.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-DnsyuerX.js → MobileProjects-DKh2q-Y9.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-he3ZzieF.js → Mtc-Ct-fhtIx.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-BadphAuN.js → MtcAudit-J4zY3Oqk.js} +2 -2
- package/src/assets/web-panel/assets/{Multisig-3_pVLymK.js → Multisig-B2HJMCKe.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-DmtmHfsI.js → NLProgramming-CwcSmU4q.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-CE4L2O55.js → Notes-Da53L6wb.js} +4 -4
- package/src/assets/web-panel/assets/{NotificationSettings-B7QmfJn9.js → NotificationSettings-BxhufdrA.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-O0wRSyJ0.js → OrderTableRenderer-BoFwohAi.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-BrlMSsyT.js → Organization-MIsMrUes.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-CY4ULPrZ.js → Overflow-Dy2P4v3q.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-kIANZYKd.js → P2P-CD9Sxn2y.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-DEgB4rvF.js → PdhVaultBrowser-BvBWgfG0.js} +5 -5
- package/src/assets/web-panel/assets/{Permissions-CWxywRp5.js → Permissions-CwOlDRis.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-DAg7iCbr.js → PersonalDataHub-CZYaxEpv.js} +4 -4
- package/src/assets/web-panel/assets/{Pipeline-Brc6ONnR.js → Pipeline-kLU7PUbB.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-CcinYTL8.js → Privacy-BVRycSiI.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-Bz8XEsom.js → ProjectInit-DP3pr7uJ.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CsOMlSTZ.js → ProjectSettings-CACaDYjs.js} +2 -2
- package/src/assets/web-panel/assets/Projects-CB1k4zTC.js +1 -0
- package/src/assets/web-panel/assets/{Providers-CeunOQiH.js → Providers-D7Y9WHRE.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-CN2a2_Fb.js → QrScannerModal-CeP8xzs1.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-CVeLif3e.js → QuickAsk-iJVFrhZ2.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-1k9WOEGM.js → Recommend-DmTpnagw.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-Q8UCqIMw.js → RemoteSession-C6ek8Uni.js} +2 -2
- package/src/assets/web-panel/assets/{Reputation-CkrCgXKM.js → Reputation-CPfwc9de.js} +1 -1
- package/src/assets/web-panel/assets/{Row-JFdvuTzi.js → Row-C1wwtSMJ.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-C74JHCmk.js → RssFeed-BiDJsqaH.js} +3 -3
- package/src/assets/web-panel/assets/{Search-DrJiVO1T.js → Search-BF6Aon95.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BJzNlSJq.js → Security-Bps0XnV4.js} +3 -3
- package/src/assets/web-panel/assets/{Services-D4TLMnJm.js → Services-uf92smGR.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-cswTDqhh.js → Skeleton-DQd4eVWW.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-Bv0rM1BS.js → Skills-DgamiYa5.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-D9rE7hNh.js → Sla-CF1IVIrm.js} +1 -1
- package/src/assets/web-panel/assets/SpeechSettings-Bzxmjbpc.js +1 -0
- package/src/assets/web-panel/assets/{SyncSettings-BwnMG8kq.js → SyncSettings-B8zKauiq.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-wJvU24cB.js → Tasks-DPKV_xSt.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-CZbRU3tN.js → Templates-BikeSZIF.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-_jbpFJPZ.js → Tenant-CEaUaDkJ.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-BzNm5WKZ.js → Terminal-ZPgOSQ_q.js} +3 -3
- package/src/assets/web-panel/assets/{TimelineRenderer-bmybWjuS.js → TimelineRenderer-DmyB4Udk.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-o1WDcqVQ.js → Tokens-g85Pm5TO.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-BB1D6sIB.js → Trigger-D202hx9a.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-CLxdCfy2.js → Trust-DzvQYVFy.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-B9LS6vS0.js → UkeySign-KRzwddVg.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-Bg0-wxjz.js → VideoEditing-IRBXLO2z.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-Bw0G2F6P.js → Wallet-DSf5KPgQ.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-irEiNY9b.js → WebAuthn-DNPxLTCc.js} +5 -5
- package/src/assets/web-panel/assets/{WorkflowEditor-Bh08FbJs.js → WorkflowEditor-C3wAtVcK.js} +1 -1
- package/src/assets/web-panel/assets/{addon-fit-CK6X9sAG.js → addon-fit-CbkjSUJl.js} +1 -1
- package/src/assets/web-panel/assets/{chat-De-FPjVT.js → chat-q3XXUtv2.js} +1 -1
- package/src/assets/web-panel/assets/{colors-ygdyvinU.js → colors-CF54SNei.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-BQ9jH9KV.js → compact-item-BaRkvZuT.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BFuVSyox.js → createContext-CDR9hsfs.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-1LO-NplX.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-i19GtR1P.js → hasIn-DlC--S8R.js} +1 -1
- package/src/assets/web-panel/assets/{index-DS1IwwGL.js → index-B0Ptc7yu.js} +1 -1
- package/src/assets/web-panel/assets/{index-B1IsP_JO.js → index-B0V9rgdu.js} +1 -1
- package/src/assets/web-panel/assets/{index-CWfgeaG4.js → index-BEiUlFKb.js} +1 -1
- package/src/assets/web-panel/assets/{index-DTY20pWu.js → index-BJO0n1LQ.js} +1 -1
- package/src/assets/web-panel/assets/index-BLaJsev4.js +1 -0
- package/src/assets/web-panel/assets/index-BLmviChG.js +1 -0
- package/src/assets/web-panel/assets/{index-IXkif6J_.js → index-BU5DJqk7.js} +1 -1
- package/src/assets/web-panel/assets/{index-BVhtukYX.js → index-BY-Tf3OM.js} +1 -1
- package/src/assets/web-panel/assets/{index-BKlU5VZy.js → index-BZrE0Go8.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bw5pTpnI.js → index-BizdX2X5.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGMxK155.js → index-C87Yr3h1.js} +1 -1
- package/src/assets/web-panel/assets/{index-DVnFgasM.js → index-C9Wypz9e.js} +1 -1
- package/src/assets/web-panel/assets/{index-C61w3omz.js → index-CAoG_E-q.js} +1 -1
- package/src/assets/web-panel/assets/{index-l5-J0jHx.js → index-CAxSyMrF.js} +1 -1
- package/src/assets/web-panel/assets/{index-28DjsABm.js → index-CC5HjNM6.js} +1 -1
- package/src/assets/web-panel/assets/{index-BPUCvc8i.js → index-CNZQEZ9g.js} +1 -1
- package/src/assets/web-panel/assets/{index-CpdXhhMY.js → index-CPvIhha6.js} +1 -1
- package/src/assets/web-panel/assets/{index-DAboGZTw.js → index-CR_wqMGy.js} +1 -1
- package/src/assets/web-panel/assets/{index-DL7ewjD2.js → index-CSClXoB3.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bhf-MuUh.js → index-CZ8azVgB.js} +3 -3
- package/src/assets/web-panel/assets/{index-BaaydJcC.js → index-Ccc5z8eJ.js} +1 -1
- package/src/assets/web-panel/assets/{index-B2LieObB.js → index-CeiKbM3a.js} +1 -1
- package/src/assets/web-panel/assets/{index-We2nK3ju.js → index-Cl6av3X7.js} +1 -1
- package/src/assets/web-panel/assets/{index-m8waSAyv.js → index-CtGfeD7E.js} +1 -1
- package/src/assets/web-panel/assets/{index-cDJsyAkI.js → index-Ctid12Gq.js} +1 -1
- package/src/assets/web-panel/assets/{index-DlwxUZQL.js → index-D0H-N0uG.js} +1 -1
- package/src/assets/web-panel/assets/{index-BBYiCI1d.js → index-D0vIgprF.js} +1 -1
- package/src/assets/web-panel/assets/{index-_9tUBcLm.js → index-D4wUDFRg.js} +1 -1
- package/src/assets/web-panel/assets/{index-4HoFz1MA.js → index-DJFipyK7.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cq9k4-Ws.js → index-DX2REKNp.js} +1 -1
- package/src/assets/web-panel/assets/{index-DuuOX5u3.js → index-Dc3gD0cw.js} +1 -1
- package/src/assets/web-panel/assets/{index-DgjLaB7k.js → index-DiQzer2R.js} +1 -1
- package/src/assets/web-panel/assets/{index-CMNjaSir.js → index-DntKLOs9.js} +1 -1
- package/src/assets/web-panel/assets/{index-vXtH3o6O.js → index-Dplhl6gh.js} +1 -1
- package/src/assets/web-panel/assets/{index-BLMtCKAY.js → index-Dq8q_PWB.js} +1 -1
- package/src/assets/web-panel/assets/{index-CXrk8OYO.js → index-IVQdOBNN.js} +1 -1
- package/src/assets/web-panel/assets/{index-CBVcGaxk.js → index-Kerduyao.js} +1 -1
- package/src/assets/web-panel/assets/{index-BKblSCL9.js → index-W6cLGjpW.js} +1 -1
- package/src/assets/web-panel/assets/{index-CgoSgmjK.js → index-qZDNFll9.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-B_EZHeqN.js → initDefaultProps-Ccz7v71u.js} +1 -1
- package/src/assets/web-panel/assets/{markdown-Yo_c6Krv.js → markdown-Bq2x0XsR.js} +1 -1
- package/src/assets/web-panel/assets/markdown-DUKxbhE4.js +51 -0
- package/src/assets/web-panel/assets/{motion-BrVb_Gy1.js → motion-BlCLpVDp.js} +1 -1
- package/src/assets/web-panel/assets/{move-CKxVRL_J.js → move-B80G7fLU.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-C80rvQ1J.js → mtc-parser-Djw08NJh.js} +1 -1
- package/src/assets/web-panel/assets/{omit-De5vD7BR.js → omit-Bg_G1eUr.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BTyEDjth.js → pickAttrs-Df9zYUg2.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-BWR94AjR.js → placementArrow-DjCDLhJ4.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-1g-Vnibk.js → responsiveObserve--Naz7NgY.js} +1 -1
- package/src/assets/web-panel/assets/{slide-Dmg6dYuF.js → slide-CHn3ald-.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Ddj6rSab.js → statusUtils-BiGwoIK_.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DyP1mPDf.js → styleChecker-Zf2dQx1j.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-D3d_0YuY.js → useFlexGapSupport-sLES8nyD.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-M3vq-gE2.js → useFs-DV1Hl4qu.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-GtP2TgtK.js → usePersonalDataHub-CZXr2Qxg.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-BurE2ATQ.js → vnode-e8Eo3Oyl.js} +1 -1
- package/src/assets/web-panel/assets/{xterm-BZcWGsqw.js → xterm-zbskxxGA.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-Ch-ViiDe.js → zoom-B8A6xKMF.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/commands/compact.js +7 -2
- package/src/data/changelog.json +27 -1
- package/src/harness/jsonl-session-store.js +80 -74
- package/src/harness/prompt-compressor.js +57 -3
- package/src/lib/artifact-store.js +129 -20
- package/src/lib/bounded-artifact-file-read.js +128 -46
- package/src/lib/context-memory-kernel/context-image-transport.js +248 -0
- package/src/lib/context-memory-kernel/jsonl-session-context-port.js +4 -1
- package/src/lib/context-memory-kernel/live-compaction.js +40 -10
- package/src/lib/context-memory-kernel/message-adapter.js +80 -10
- package/src/lib/context-memory-kernel/message-bundle-store.js +1140 -0
- package/src/lib/context-memory-kernel/provider-context.js +23 -8
- package/src/lib/evolution/agent-evolution-ingress.js +103 -19
- package/src/lib/evolution/agent-evolution-runtime-composition.js +65 -27
- package/src/lib/evolution/agent-model-projection.js +90 -18
- package/src/lib/evolution/evidence-json-text.js +341 -42
- package/src/lib/evolution/evolution-artifact-ports.js +1005 -83
- package/src/lib/evolution/evolution-evidence-artifact-adapter.js +59 -15
- package/src/lib/evolution/evolution-evidence-projector.js +153 -16
- package/src/lib/evolution/evolution-evidence-storage-layout.js +115 -0
- package/src/lib/evolution/evolution-file-witness.js +7 -3
- package/src/lib/evolution/evolution-ledger-file-manifest-catalog.js +6 -2
- package/src/lib/evolution/evolution-ledger-file-manifest-head-store.js +6 -2
- package/src/lib/evolution/evolution-ledger.js +226 -55
- package/src/lib/evolution/evolution-private-storage.js +20 -0
- package/src/lib/evolution/evolution-run-ledger-adapter.js +298 -11
- package/src/lib/evolution/evolution-windows-directory-durability.js +215 -32
- package/src/lib/evolution/local-ui-evolution-runtime.js +3 -38
- package/src/lib/evolution/local-ui-signing-authority.js +69 -0
- package/src/lib/image-token-estimate.js +59 -0
- package/src/lib/ollama-provider-fetch.js +33 -0
- package/src/lib/provider-stream.js +14 -13
- package/src/lib/read-file-loop-guard.js +7 -4
- package/src/lib/session-history-origins.js +94 -35
- package/src/lib/session-message-provenance.js +26 -2
- package/src/lib/session-runtime-retention.js +10 -1
- package/src/lib/session-transcript-history.js +5 -1
- package/src/lib/session-transcript-migration.js +67 -12
- package/src/lib/storage-failure-cause-codes.js +449 -0
- package/src/lib/tool-result-preview.js +276 -0
- package/src/runtime/agent-core.js +111 -92
- package/src/runtime/headless-stream.js +121 -27
- package/src/assets/web-panel/assets/MarkdownRenderer-D8_iZief.js +0 -1
- package/src/assets/web-panel/assets/Projects-BeSOM6wi.js +0 -1
- package/src/assets/web-panel/assets/SpeechSettings-Bfhvin8B.js +0 -1
- package/src/assets/web-panel/assets/devWarning-c2I2Rn4_.js +0 -1
- package/src/assets/web-panel/assets/index-CTs4dYcV.js +0 -1
- package/src/assets/web-panel/assets/index-DeuvaW15.js +0 -1
- package/src/assets/web-panel/assets/markdown-CsiA8-E5.js +0 -50
|
@@ -12,22 +12,55 @@ import crypto from "node:crypto";
|
|
|
12
12
|
import fs from "node:fs";
|
|
13
13
|
import path from "node:path";
|
|
14
14
|
import { types as utilTypes } from "node:util";
|
|
15
|
-
import {
|
|
15
|
+
import {
|
|
16
|
+
ArtifactStore,
|
|
17
|
+
ARTIFACT_STORE_DEFAULT_READ_METHODS,
|
|
18
|
+
ARTIFACT_STORE_DEFAULT_PUBLISH_DATA_ONCE,
|
|
19
|
+
captureArtifactStoreTypedIndexPublisher,
|
|
20
|
+
} from "../artifact-store.js";
|
|
21
|
+
import { withFileLock } from "../with-file-lock.js";
|
|
22
|
+
import {
|
|
23
|
+
sameDirectoryStatIdentity,
|
|
24
|
+
samePathHandleDirectoryIdentity,
|
|
25
|
+
withTrustedFileParentSync,
|
|
26
|
+
} from "../secure-file-identity.js";
|
|
27
|
+
import {
|
|
28
|
+
recordStorageFailureReason,
|
|
29
|
+
recordStorageFailureStage,
|
|
30
|
+
} from "../storage-failure-cause-codes.js";
|
|
16
31
|
import { artifactPhysicalIdentity as physicalIdentity } from "./evolution-artifact-identity.js";
|
|
17
32
|
import { withEvolutionFileIdentity } from "./evolution-file-identity.js";
|
|
18
33
|
import { readBoundedDescriptor } from "./bounded-descriptor-read.js";
|
|
19
|
-
import {
|
|
34
|
+
import {
|
|
35
|
+
readBoundedArtifactFile,
|
|
36
|
+
withBoundedArtifactDirectoryRead,
|
|
37
|
+
} from "../bounded-artifact-file-read.js";
|
|
20
38
|
import {
|
|
21
39
|
EVOLUTION_ARTIFACT_REF_SCHEMA,
|
|
22
40
|
EVOLUTION_ARTIFACT_RESOLUTION_SCHEMA,
|
|
23
41
|
} from "./evolution-ledger.js";
|
|
24
42
|
|
|
25
43
|
const ARTIFACT_STORE_DIRECTORY_BOUNDARIES = new WeakMap();
|
|
44
|
+
// Derived lookup tables belong only to deeply frozen parsed index rows.
|
|
45
|
+
// Every reuse reattests current descriptor bytes; authority and payloads are
|
|
46
|
+
// never cached. All duplicate matches remain in these derived lookup tables.
|
|
47
|
+
const LEDGER_BATCH_INDEX_LOOKUPS = new WeakMap();
|
|
48
|
+
// Captured layout object identity, never a caller pathname or digest, owns a
|
|
49
|
+
// latest compact bytes and optional hot parse. Bound aggregate retention plus
|
|
50
|
+
// in-flight compact reservations, as well as each snapshot;
|
|
51
|
+
// an overflow only disables reuse, never changes index admission limits.
|
|
52
|
+
const TRUSTED_INDEX_PARSES = new Map();
|
|
53
|
+
const MAX_RETAINED_INDEX_LAYOUTS = 8;
|
|
54
|
+
const MAX_RETAINED_INDEX_ROWS = 10_000;
|
|
55
|
+
const MAX_RETAINED_INDEX_BYTES = 24 * 1024 * 1024;
|
|
56
|
+
const MAX_RETAINED_INDEX_MEMORY = 112 * 1024 * 1024;
|
|
57
|
+
let retainedIndexMemory = 0;
|
|
58
|
+
let reservedIndexMemory = 0;
|
|
26
59
|
// Capture repository function identity before binding instance methods. Only
|
|
27
60
|
// these exact defaults may skip legacy list/get I/O; overrides still execute
|
|
28
61
|
// and must match the independently authenticated descriptor snapshots.
|
|
29
|
-
const DEFAULT_STORE_LIST =
|
|
30
|
-
const DEFAULT_STORE_GET =
|
|
62
|
+
const DEFAULT_STORE_LIST = ARTIFACT_STORE_DEFAULT_READ_METHODS.list;
|
|
63
|
+
const DEFAULT_STORE_GET = ARTIFACT_STORE_DEFAULT_READ_METHODS.get;
|
|
31
64
|
// Only the shared identity helper's directory opens use this adapter. A POSIX
|
|
32
65
|
// directory/FIFO swap must fail before reaching a potentially blocking open.
|
|
33
66
|
const INDEX_DIRECTORY_FS = Object.freeze({
|
|
@@ -406,6 +439,9 @@ const CONSTRUCTOR_REQUIRED_KEYS = new Set([
|
|
|
406
439
|
const LEDGER_RESOLVER_OPTION_KEYS = new Set(["purpose"]);
|
|
407
440
|
const EVOLUTION_LEDGER_ARTIFACT_RESOLVERS = new WeakSet();
|
|
408
441
|
const EVOLUTION_LEDGER_BATCH_RESOLVERS = new WeakMap();
|
|
442
|
+
const EVOLUTION_LEDGER_BATCH_READ_WINDOWS = new WeakMap();
|
|
443
|
+
const EVOLUTION_LEDGER_WHOLE_HISTORY_READERS = new WeakMap();
|
|
444
|
+
const EVOLUTION_LEDGER_DEFAULT_PUBLICATIONS = new WeakMap();
|
|
409
445
|
const EVOLUTION_LEDGER_RESOLVER_BINDINGS = new WeakMap();
|
|
410
446
|
const isProxy = Object.freeze(utilTypes.isProxy.bind(utilTypes));
|
|
411
447
|
const isDate = Object.freeze(utilTypes.isDate.bind(utilTypes));
|
|
@@ -822,6 +858,68 @@ function canonicalJson(
|
|
|
822
858
|
return state.fragments.join("");
|
|
823
859
|
}
|
|
824
860
|
|
|
861
|
+
// Only explicitly opted-in repository publications reach this formatter. Keep
|
|
862
|
+
// the envelope opaque and reuse the exact encoder used by index fingerprints.
|
|
863
|
+
function encodeTypedEvolutionIndexRow(entry) {
|
|
864
|
+
const exactData = (value, keys) => {
|
|
865
|
+
if (
|
|
866
|
+
!value ||
|
|
867
|
+
typeof value !== "object" ||
|
|
868
|
+
isProxy(value) ||
|
|
869
|
+
Object.getPrototypeOf(value) !== Object.prototype
|
|
870
|
+
)
|
|
871
|
+
return false;
|
|
872
|
+
const ownKeys = Reflect.ownKeys(value);
|
|
873
|
+
return (
|
|
874
|
+
ownKeys.length === keys.size &&
|
|
875
|
+
ownKeys.every((key) => {
|
|
876
|
+
const descriptor = Object.getOwnPropertyDescriptor(value, key);
|
|
877
|
+
return (
|
|
878
|
+
keys.has(key) &&
|
|
879
|
+
descriptor?.enumerable === true &&
|
|
880
|
+
"value" in descriptor
|
|
881
|
+
);
|
|
882
|
+
})
|
|
883
|
+
);
|
|
884
|
+
};
|
|
885
|
+
if (
|
|
886
|
+
!exactData(entry, INDEX_ENTRY_KEYS) ||
|
|
887
|
+
!exactData(entry.lineage, LINEAGE_KEYS)
|
|
888
|
+
)
|
|
889
|
+
return null;
|
|
890
|
+
if (
|
|
891
|
+
[...LINEAGE_KEYS].some((key) => typeof entry.lineage[key] !== "string") ||
|
|
892
|
+
entry.lineage.schema !== EVOLUTION_ARTIFACT_LINEAGE_SCHEMA ||
|
|
893
|
+
entry.immutable !== true ||
|
|
894
|
+
entry.kind !== "data" ||
|
|
895
|
+
entry.mime !== "application/json" ||
|
|
896
|
+
entry.sessionId !== null ||
|
|
897
|
+
entry.sourcePath !== null ||
|
|
898
|
+
!Number.isSafeInteger(entry.size) ||
|
|
899
|
+
entry.size < 1 ||
|
|
900
|
+
entry.size > EVOLUTION_ARTIFACT_MAX_CANONICAL_BYTES ||
|
|
901
|
+
[...INDEX_ENTRY_KEYS].some(
|
|
902
|
+
(key) =>
|
|
903
|
+
!["lineage", "immutable", "sessionId", "sourcePath", "size"].includes(
|
|
904
|
+
key,
|
|
905
|
+
) && typeof entry[key] !== "string",
|
|
906
|
+
)
|
|
907
|
+
)
|
|
908
|
+
return null;
|
|
909
|
+
try {
|
|
910
|
+
const canonical = canonicalJson(entry, MAX_INDEX_ENTRY_BYTES);
|
|
911
|
+
// Fixed keys contain no integer indices; all scalars use JSON.stringify in
|
|
912
|
+
// canonicalWalk. Preserve the original path if these bounds ever diverge.
|
|
913
|
+
return JSON.stringify(JSON.parse(canonical)) === canonical
|
|
914
|
+
? canonical
|
|
915
|
+
: null;
|
|
916
|
+
} catch {
|
|
917
|
+
// Formatting is optional, not a new publication admission gate. Original
|
|
918
|
+
// serialization/readback still determines the outcome for an ineligible row.
|
|
919
|
+
return null;
|
|
920
|
+
}
|
|
921
|
+
}
|
|
922
|
+
|
|
825
923
|
function deepFreeze(value, seen = new WeakSet()) {
|
|
826
924
|
if (!value || typeof value !== "object" || seen.has(value)) return value;
|
|
827
925
|
rejectProxy(value, "value being frozen");
|
|
@@ -1045,6 +1143,18 @@ function captureMethod(port, method, label) {
|
|
|
1045
1143
|
return Object.freeze(captureDataMethod(port, method, label).bind(port));
|
|
1046
1144
|
}
|
|
1047
1145
|
|
|
1146
|
+
function hasRepositoryDataMethod(store, method, expected) {
|
|
1147
|
+
let cursor = store;
|
|
1148
|
+
while (cursor) {
|
|
1149
|
+
rejectProxy(cursor, "artifactStore read method prototype");
|
|
1150
|
+
const descriptor = Object.getOwnPropertyDescriptor(cursor, method);
|
|
1151
|
+
if (descriptor)
|
|
1152
|
+
return "value" in descriptor && descriptor.value === expected;
|
|
1153
|
+
cursor = Object.getPrototypeOf(cursor);
|
|
1154
|
+
}
|
|
1155
|
+
return false;
|
|
1156
|
+
}
|
|
1157
|
+
|
|
1048
1158
|
function captureArtifactStore(store) {
|
|
1049
1159
|
rejectProxy(store, "artifactStore");
|
|
1050
1160
|
if (!hasStablePrototype(store, ArtifactStore.prototype, "artifactStore")) {
|
|
@@ -1060,14 +1170,74 @@ function captureArtifactStore(store) {
|
|
|
1060
1170
|
const layout = establishStoreLayout(resolvedDir);
|
|
1061
1171
|
const list = captureDataMethod(store, "list", "artifactStore");
|
|
1062
1172
|
const get = captureDataMethod(store, "get", "artifactStore");
|
|
1173
|
+
const storedPath = captureDataMethod(store, "storedPath", "artifactStore");
|
|
1174
|
+
const verifyIntegrity = captureDataMethod(
|
|
1175
|
+
store,
|
|
1176
|
+
"verifyIntegrity",
|
|
1177
|
+
"artifactStore",
|
|
1178
|
+
);
|
|
1179
|
+
const publishDataOnce = captureDataMethod(
|
|
1180
|
+
store,
|
|
1181
|
+
"publishDataOnce",
|
|
1182
|
+
"artifactStore",
|
|
1183
|
+
);
|
|
1184
|
+
const defaultPublication =
|
|
1185
|
+
Object.getPrototypeOf(store) === ArtifactStore.prototype &&
|
|
1186
|
+
publishDataOnce === ARTIFACT_STORE_DEFAULT_PUBLISH_DATA_ONCE &&
|
|
1187
|
+
Object.getOwnPropertyDescriptor(store, "_indexLock")?.value ===
|
|
1188
|
+
withFileLock;
|
|
1189
|
+
const defaults = ARTIFACT_STORE_DEFAULT_READ_METHODS;
|
|
1190
|
+
const initiallyDefaultRead =
|
|
1191
|
+
storedPath === defaults.storedPath &&
|
|
1192
|
+
verifyIntegrity === defaults.verifyIntegrity &&
|
|
1193
|
+
hasRepositoryDataMethod(store, "_filesDir", defaults.filesDir);
|
|
1063
1194
|
const captured = Object.freeze({
|
|
1064
1195
|
dir: resolvedDir,
|
|
1196
|
+
repositoryInstance:
|
|
1197
|
+
Object.getPrototypeOf(store) === ArtifactStore.prototype,
|
|
1065
1198
|
get: get === DEFAULT_STORE_GET ? null : Object.freeze(get.bind(store)),
|
|
1066
1199
|
layout,
|
|
1067
1200
|
list: list === DEFAULT_STORE_LIST ? null : Object.freeze(list.bind(store)),
|
|
1068
|
-
publishDataOnce:
|
|
1069
|
-
|
|
1070
|
-
|
|
1201
|
+
publishDataOnce: captureArtifactStoreTypedIndexPublisher(
|
|
1202
|
+
store,
|
|
1203
|
+
publishDataOnce,
|
|
1204
|
+
encodeTypedEvolutionIndexRow,
|
|
1205
|
+
),
|
|
1206
|
+
canUseDefaultPublication: Object.freeze(
|
|
1207
|
+
() =>
|
|
1208
|
+
defaultPublication &&
|
|
1209
|
+
hasRepositoryDataMethod(
|
|
1210
|
+
store,
|
|
1211
|
+
"publishDataOnce",
|
|
1212
|
+
ARTIFACT_STORE_DEFAULT_PUBLISH_DATA_ONCE,
|
|
1213
|
+
),
|
|
1214
|
+
),
|
|
1215
|
+
storedPath: Object.freeze(storedPath.bind(store)),
|
|
1216
|
+
verifyIntegrity: Object.freeze(verifyIntegrity.bind(store)),
|
|
1217
|
+
// Default verifyIntegrity repeats the same bounded physical read and hash
|
|
1218
|
+
// performed below. Its dynamic path dependencies must still be repository
|
|
1219
|
+
// defaults at EACH skipped hook point; freezing the instance does not freeze
|
|
1220
|
+
// its prototype. Custom implementations retain both original hook calls.
|
|
1221
|
+
canUseSingleBoundedRead: Object.freeze(
|
|
1222
|
+
() =>
|
|
1223
|
+
initiallyDefaultRead &&
|
|
1224
|
+
hasRepositoryDataMethod(store, "storedPath", defaults.storedPath) &&
|
|
1225
|
+
hasRepositoryDataMethod(store, "_filesDir", defaults.filesDir),
|
|
1226
|
+
),
|
|
1227
|
+
canUseSealedDirectoryRead: Object.freeze(
|
|
1228
|
+
() =>
|
|
1229
|
+
initiallyDefaultRead &&
|
|
1230
|
+
Object.getPrototypeOf(store) === ArtifactStore.prototype &&
|
|
1231
|
+
hasRepositoryDataMethod(store, "list", defaults.list) &&
|
|
1232
|
+
hasRepositoryDataMethod(store, "get", defaults.get) &&
|
|
1233
|
+
hasRepositoryDataMethod(store, "storedPath", defaults.storedPath) &&
|
|
1234
|
+
hasRepositoryDataMethod(store, "_filesDir", defaults.filesDir) &&
|
|
1235
|
+
hasRepositoryDataMethod(
|
|
1236
|
+
store,
|
|
1237
|
+
"verifyIntegrity",
|
|
1238
|
+
defaults.verifyIntegrity,
|
|
1239
|
+
),
|
|
1240
|
+
),
|
|
1071
1241
|
});
|
|
1072
1242
|
// ArtifactStore methods consult public instance fields such as `dir` on
|
|
1073
1243
|
// every call. Freeze the trusted instance after capturing its methods so a
|
|
@@ -1421,6 +1591,111 @@ function attestStoreDirectories(layout) {
|
|
|
1421
1591
|
}
|
|
1422
1592
|
}
|
|
1423
1593
|
|
|
1594
|
+
// Only the sealed whole-history reader owns this synchronous capability. A
|
|
1595
|
+
// held descriptor does not anchor absolute-path opens like openat: each item
|
|
1596
|
+
// must still bind BOTH current directory paths to their captured, held FDs.
|
|
1597
|
+
// Only repeated directory realpath calls move to the complete read boundary.
|
|
1598
|
+
function withSealedStoreDirectoryRead(store, operation) {
|
|
1599
|
+
const layout = store.layout;
|
|
1600
|
+
const fail = () => {
|
|
1601
|
+
throw artifactError(
|
|
1602
|
+
EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
|
|
1603
|
+
"ArtifactStore sealed directory identity or reader changed",
|
|
1604
|
+
);
|
|
1605
|
+
};
|
|
1606
|
+
attestStoreDirectories(layout);
|
|
1607
|
+
return withTrustedFileParentSync(
|
|
1608
|
+
INDEX_DIRECTORY_FS,
|
|
1609
|
+
path.join(layout.rootDir, ".sealed-directory-scope"),
|
|
1610
|
+
(root) =>
|
|
1611
|
+
withTrustedFileParentSync(
|
|
1612
|
+
INDEX_DIRECTORY_FS,
|
|
1613
|
+
path.join(layout.filesDir, ".sealed-directory-scope"),
|
|
1614
|
+
(files) => {
|
|
1615
|
+
const held = [
|
|
1616
|
+
{
|
|
1617
|
+
...root,
|
|
1618
|
+
path: layout.rootDir,
|
|
1619
|
+
identity: layout.rootIdentity,
|
|
1620
|
+
},
|
|
1621
|
+
{
|
|
1622
|
+
...files,
|
|
1623
|
+
path: layout.filesDir,
|
|
1624
|
+
identity: layout.filesIdentity,
|
|
1625
|
+
},
|
|
1626
|
+
].map((entry) => ({
|
|
1627
|
+
...entry,
|
|
1628
|
+
initial: fs.fstatSync(entry.parentDescriptor, { bigint: true }),
|
|
1629
|
+
}));
|
|
1630
|
+
let active = true;
|
|
1631
|
+
const assertReader = () => {
|
|
1632
|
+
if (!active || !store.canUseSealedDirectoryRead()) fail();
|
|
1633
|
+
};
|
|
1634
|
+
const check = (leaf = null) => {
|
|
1635
|
+
assertReader();
|
|
1636
|
+
for (const entry of held) {
|
|
1637
|
+
if (!samePath(entry.parentPath, entry.path)) fail();
|
|
1638
|
+
const current = fs.lstatSync(entry.path, { bigint: true });
|
|
1639
|
+
const descriptor = fs.fstatSync(entry.parentDescriptor, {
|
|
1640
|
+
bigint: true,
|
|
1641
|
+
});
|
|
1642
|
+
assertDirectory(current, "ArtifactStore held directory path");
|
|
1643
|
+
assertDirectory(descriptor, "ArtifactStore held directory FD");
|
|
1644
|
+
const identity = physicalIdentity(current);
|
|
1645
|
+
const stableFields = ["ino", "mode", "uid", "gid", "birthtimeNs"];
|
|
1646
|
+
if (
|
|
1647
|
+
typeof current.dev !== "bigint" ||
|
|
1648
|
+
typeof descriptor.dev !== "bigint" ||
|
|
1649
|
+
stableFields.some(
|
|
1650
|
+
(field) =>
|
|
1651
|
+
typeof current[field] !== "bigint" ||
|
|
1652
|
+
typeof descriptor[field] !== "bigint" ||
|
|
1653
|
+
current[field] !== descriptor[field] ||
|
|
1654
|
+
descriptor[field] !== entry.initial[field],
|
|
1655
|
+
) ||
|
|
1656
|
+
identity.dev !== entry.identity.dev ||
|
|
1657
|
+
identity.ino !== entry.identity.ino ||
|
|
1658
|
+
identity.birthtimeMs !== entry.identity.birthtimeMs ||
|
|
1659
|
+
!sameDirectoryStatIdentity(descriptor, entry.initial) ||
|
|
1660
|
+
!samePathHandleDirectoryIdentity(
|
|
1661
|
+
current,
|
|
1662
|
+
descriptor,
|
|
1663
|
+
entry.parentDevice,
|
|
1664
|
+
)
|
|
1665
|
+
)
|
|
1666
|
+
fail();
|
|
1667
|
+
}
|
|
1668
|
+
// This runs at the original post-attest point, AFTER hashing and
|
|
1669
|
+
// any required hook, not merely at the earlier file read/close.
|
|
1670
|
+
if (
|
|
1671
|
+
leaf !== null &&
|
|
1672
|
+
(!samePath(path.dirname(leaf), layout.filesDir) ||
|
|
1673
|
+
!samePath(physicalRealpath(leaf), leaf))
|
|
1674
|
+
)
|
|
1675
|
+
fail();
|
|
1676
|
+
};
|
|
1677
|
+
const scope = Object.freeze({
|
|
1678
|
+
check: Object.freeze(check),
|
|
1679
|
+
assertReader: Object.freeze(assertReader),
|
|
1680
|
+
});
|
|
1681
|
+
try {
|
|
1682
|
+
check();
|
|
1683
|
+
return operation(scope);
|
|
1684
|
+
} finally {
|
|
1685
|
+
try {
|
|
1686
|
+
check();
|
|
1687
|
+
// Includes the last caller-supplied clock/authority operation;
|
|
1688
|
+
// no result escapes before these checks and both FD closes.
|
|
1689
|
+
attestStoreDirectories(layout);
|
|
1690
|
+
} finally {
|
|
1691
|
+
active = false;
|
|
1692
|
+
}
|
|
1693
|
+
}
|
|
1694
|
+
},
|
|
1695
|
+
),
|
|
1696
|
+
);
|
|
1697
|
+
}
|
|
1698
|
+
|
|
1424
1699
|
function indexEntriesFingerprint(entries, label) {
|
|
1425
1700
|
assertDenseDataArray(entries, label, EVOLUTION_ARTIFACT_MAX_INDEX_ENTRIES);
|
|
1426
1701
|
const hash = crypto.createHash("sha256");
|
|
@@ -1435,38 +1710,216 @@ function indexEntriesFingerprint(entries, label) {
|
|
|
1435
1710
|
return `sha256:${hash.digest("hex")}`;
|
|
1436
1711
|
}
|
|
1437
1712
|
|
|
1438
|
-
|
|
1713
|
+
// Conservative accounting, not an exact VM heap size. Include UTF-16 strings,
|
|
1714
|
+
// parsed nodes/property storage and the per-row allowance for lookup maps.
|
|
1715
|
+
function parsedIndexMemory(value) {
|
|
1716
|
+
if (typeof value === "string") return 64 + value.length * 2;
|
|
1717
|
+
if (!value || typeof value !== "object") return 16;
|
|
1718
|
+
if (Array.isArray(value))
|
|
1719
|
+
return (
|
|
1720
|
+
64 + value.reduce((sum, child) => sum + 16 + parsedIndexMemory(child), 0)
|
|
1721
|
+
);
|
|
1722
|
+
let size = 64;
|
|
1723
|
+
for (const [key, child] of Object.entries(value))
|
|
1724
|
+
size += 64 + key.length * 2 + parsedIndexMemory(child);
|
|
1725
|
+
return size;
|
|
1726
|
+
}
|
|
1727
|
+
|
|
1728
|
+
function forgetTrustedIndexParse(layout) {
|
|
1729
|
+
const previous = TRUSTED_INDEX_PARSES.get(layout);
|
|
1730
|
+
if (!previous) return;
|
|
1731
|
+
retainedIndexMemory -= previous.memory + (previous.hot?.memory || 0);
|
|
1732
|
+
TRUSTED_INDEX_PARSES.delete(layout);
|
|
1733
|
+
}
|
|
1734
|
+
|
|
1735
|
+
function downgradeTrustedIndexParse(entry) {
|
|
1736
|
+
if (!entry.hot) return;
|
|
1737
|
+
retainedIndexMemory -= entry.hot.memory;
|
|
1738
|
+
entry.hot = null;
|
|
1739
|
+
}
|
|
1740
|
+
|
|
1741
|
+
function countTrustedIndexRows(bytes) {
|
|
1742
|
+
let count = 0;
|
|
1743
|
+
for (
|
|
1744
|
+
let offset = bytes.indexOf(10);
|
|
1745
|
+
offset !== -1;
|
|
1746
|
+
offset = bytes.indexOf(10, offset + 1)
|
|
1747
|
+
)
|
|
1748
|
+
count += 1;
|
|
1749
|
+
return count;
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
function reserveTrustedIndexParse(layout, bytes, prefix) {
|
|
1753
|
+
if (
|
|
1754
|
+
bytes.length > MAX_RETAINED_INDEX_BYTES ||
|
|
1755
|
+
countTrustedIndexRows(bytes) > MAX_RETAINED_INDEX_ROWS
|
|
1756
|
+
)
|
|
1757
|
+
return null;
|
|
1758
|
+
let scratchBytes = 0;
|
|
1759
|
+
let start = 0;
|
|
1760
|
+
for (
|
|
1761
|
+
let end = bytes.indexOf(10);
|
|
1762
|
+
end !== -1;
|
|
1763
|
+
end = bytes.indexOf(10, start)
|
|
1764
|
+
) {
|
|
1765
|
+
scratchBytes = Math.max(scratchBytes, end - start);
|
|
1766
|
+
start = end + 1;
|
|
1767
|
+
}
|
|
1768
|
+
// An oversized row is still rejected by the original parser, in its original
|
|
1769
|
+
// order. Cache admission must not allocate an oversized comparison buffer.
|
|
1770
|
+
if (scratchBytes > MAX_INDEX_ENTRY_BYTES) return null;
|
|
1771
|
+
// Charge the full raw backing store (including any shared slab), a potential
|
|
1772
|
+
// standalone canonical buffer, and the largest-row comparison scratch. The
|
|
1773
|
+
// canonical reservation is NOT allocated unless a byte difference is found.
|
|
1774
|
+
const compactMemory =
|
|
1775
|
+
bytes.buffer.byteLength + bytes.length + 4096 + parsedIndexMemory(layout);
|
|
1776
|
+
const memory = compactMemory + scratchBytes;
|
|
1777
|
+
if (memory > MAX_RETAINED_INDEX_MEMORY) return null;
|
|
1778
|
+
const needsRoom = () =>
|
|
1779
|
+
retainedIndexMemory + reservedIndexMemory + memory >
|
|
1780
|
+
MAX_RETAINED_INDEX_MEMORY;
|
|
1781
|
+
// No prefix entries have been captured yet. Dropping its optional hot tier
|
|
1782
|
+
// here is safe; the subsequent parser then reconstructs from compact bytes.
|
|
1783
|
+
for (const entry of TRUSTED_INDEX_PARSES.values()) {
|
|
1784
|
+
if (!needsRoom()) break;
|
|
1785
|
+
downgradeTrustedIndexParse(entry);
|
|
1786
|
+
}
|
|
1787
|
+
while (
|
|
1788
|
+
needsRoom() ||
|
|
1789
|
+
(!TRUSTED_INDEX_PARSES.has(layout) &&
|
|
1790
|
+
TRUSTED_INDEX_PARSES.size >= MAX_RETAINED_INDEX_LAYOUTS)
|
|
1791
|
+
) {
|
|
1792
|
+
const victim = [...TRUSTED_INDEX_PARSES].find(
|
|
1793
|
+
([, entry]) => entry !== prefix,
|
|
1794
|
+
);
|
|
1795
|
+
if (!victim) return null;
|
|
1796
|
+
forgetTrustedIndexParse(victim[0]);
|
|
1797
|
+
}
|
|
1798
|
+
reservedIndexMemory += memory;
|
|
1799
|
+
return {
|
|
1800
|
+
memory,
|
|
1801
|
+
compactMemory,
|
|
1802
|
+
scratchBytes,
|
|
1803
|
+
active: true,
|
|
1804
|
+
retain: true,
|
|
1805
|
+
canonicalBytes: null,
|
|
1806
|
+
scratch: null,
|
|
1807
|
+
retiring: null,
|
|
1808
|
+
};
|
|
1809
|
+
}
|
|
1810
|
+
|
|
1811
|
+
function releaseTrustedIndexReservation(reservation) {
|
|
1812
|
+
if (!reservation?.active) return;
|
|
1813
|
+
reservedIndexMemory -= reservation.memory;
|
|
1814
|
+
reservation.active = false;
|
|
1815
|
+
reservation.scratch = null;
|
|
1816
|
+
reservation.retiring = null;
|
|
1817
|
+
}
|
|
1818
|
+
|
|
1819
|
+
function promoteTrustedIndexParse(entry, parsed) {
|
|
1820
|
+
if (
|
|
1821
|
+
entry.hot ||
|
|
1822
|
+
parsed.memory + retainedIndexMemory + reservedIndexMemory >
|
|
1823
|
+
MAX_RETAINED_INDEX_MEMORY
|
|
1824
|
+
)
|
|
1825
|
+
return;
|
|
1826
|
+
entry.hot = parsed;
|
|
1827
|
+
retainedIndexMemory += parsed.memory;
|
|
1828
|
+
}
|
|
1829
|
+
|
|
1830
|
+
function retainTrustedIndexParse(layout, bytes, parsed, reservation) {
|
|
1831
|
+
const previous = TRUSTED_INDEX_PARSES.get(layout);
|
|
1832
|
+
if (previous) {
|
|
1833
|
+
// The parser may still hold the old prefix. Transfer its entire charge to
|
|
1834
|
+
// this transaction, rather than using replacement as free promotion room.
|
|
1835
|
+
const memory = previous.memory + (previous.hot?.memory || 0);
|
|
1836
|
+
retainedIndexMemory -= memory;
|
|
1837
|
+
reservedIndexMemory += memory;
|
|
1838
|
+
reservation.memory += memory;
|
|
1839
|
+
reservation.retiring = previous;
|
|
1840
|
+
TRUSTED_INDEX_PARSES.delete(layout);
|
|
1841
|
+
}
|
|
1842
|
+
// Transfer only compact ownership. Scratch and the retiring generation stay
|
|
1843
|
+
// reserved through promotion and are released at the outer finally boundary.
|
|
1844
|
+
reservedIndexMemory -= reservation.compactMemory;
|
|
1845
|
+
reservation.memory -= reservation.compactMemory;
|
|
1846
|
+
const entry = {
|
|
1847
|
+
bytes,
|
|
1848
|
+
canonicalBytes: reservation.canonicalBytes,
|
|
1849
|
+
rowCount: parsed.entries.length,
|
|
1850
|
+
entriesFingerprint: parsed.entriesFingerprint,
|
|
1851
|
+
hotMemoryEstimate: parsed.memory,
|
|
1852
|
+
memory: reservation.compactMemory,
|
|
1853
|
+
hot: null,
|
|
1854
|
+
};
|
|
1855
|
+
TRUSTED_INDEX_PARSES.set(layout, entry);
|
|
1856
|
+
retainedIndexMemory += entry.memory;
|
|
1857
|
+
promoteTrustedIndexParse(entry, parsed);
|
|
1858
|
+
}
|
|
1859
|
+
|
|
1860
|
+
function decodeTrustedIndexParse(entry) {
|
|
1861
|
+
// Only exact freshly read private-buffer matches reach this path. Decode the
|
|
1862
|
+
// original bytes, preserving property enumeration order for custom hooks.
|
|
1863
|
+
const entries = [];
|
|
1864
|
+
let start = 0;
|
|
1865
|
+
for (
|
|
1866
|
+
let end = entry.bytes.indexOf(10);
|
|
1867
|
+
end !== -1;
|
|
1868
|
+
end = entry.bytes.indexOf(10, start)
|
|
1869
|
+
) {
|
|
1870
|
+
const value = JSON.parse(entry.bytes.toString("utf8", start, end));
|
|
1871
|
+
deepFreeze(value);
|
|
1872
|
+
entries.push(value);
|
|
1873
|
+
start = end + 1;
|
|
1874
|
+
}
|
|
1875
|
+
return Object.freeze({
|
|
1876
|
+
entries: Object.freeze(entries),
|
|
1877
|
+
entriesFingerprint: entry.entriesFingerprint,
|
|
1878
|
+
memory: entry.hotMemoryEstimate,
|
|
1879
|
+
});
|
|
1880
|
+
}
|
|
1881
|
+
|
|
1882
|
+
function hashTrustedCanonicalRows(hash, bytes) {
|
|
1883
|
+
let start = 0;
|
|
1884
|
+
for (
|
|
1885
|
+
let end = bytes.indexOf(10);
|
|
1886
|
+
end !== -1;
|
|
1887
|
+
end = bytes.indexOf(10, start)
|
|
1888
|
+
) {
|
|
1889
|
+
hash.update(`${end - start}:`, "utf8");
|
|
1890
|
+
hash.update(bytes.subarray(start, end));
|
|
1891
|
+
start = end + 1;
|
|
1892
|
+
}
|
|
1893
|
+
}
|
|
1894
|
+
|
|
1895
|
+
function parseTrustedIndexBytes(bytes, prefix = null, reservation = null) {
|
|
1439
1896
|
if (!Buffer.isBuffer(bytes)) {
|
|
1440
1897
|
throw artifactError(
|
|
1441
1898
|
EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
|
|
1442
1899
|
"ArtifactStore index snapshot is not a Buffer",
|
|
1443
1900
|
);
|
|
1444
1901
|
}
|
|
1445
|
-
const
|
|
1446
|
-
|
|
1902
|
+
const suffix = prefix ? bytes.subarray(prefix.bytes.length) : bytes;
|
|
1903
|
+
const text = suffix.toString("utf8");
|
|
1904
|
+
if (!Buffer.from(text, "utf8").equals(suffix)) {
|
|
1447
1905
|
throw artifactError(
|
|
1448
1906
|
EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
|
|
1449
1907
|
"ArtifactStore index is not exact UTF-8",
|
|
1450
1908
|
);
|
|
1451
1909
|
}
|
|
1452
|
-
if (text
|
|
1453
|
-
return Object.freeze({
|
|
1454
|
-
entries: Object.freeze([]),
|
|
1455
|
-
entriesFingerprint: indexEntriesFingerprint(
|
|
1456
|
-
[],
|
|
1457
|
-
"trusted ArtifactStore index entries",
|
|
1458
|
-
),
|
|
1459
|
-
});
|
|
1460
|
-
}
|
|
1461
|
-
if (!text.endsWith("\n")) {
|
|
1910
|
+
if (text !== "" && !text.endsWith("\n")) {
|
|
1462
1911
|
throw artifactError(
|
|
1463
1912
|
EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
|
|
1464
1913
|
"ArtifactStore index must end at a complete JSONL record",
|
|
1465
1914
|
);
|
|
1466
1915
|
}
|
|
1467
|
-
const lines = text.slice(0, -1).split("\n");
|
|
1916
|
+
const lines = text === "" ? [] : text.slice(0, -1).split("\n");
|
|
1917
|
+
const entries = prefix
|
|
1918
|
+
? [...(prefix.hot || decodeTrustedIndexParse(prefix)).entries]
|
|
1919
|
+
: [];
|
|
1920
|
+
const rowCount = entries.length + lines.length;
|
|
1468
1921
|
if (
|
|
1469
|
-
|
|
1922
|
+
rowCount > EVOLUTION_ARTIFACT_MAX_INDEX_ENTRIES ||
|
|
1470
1923
|
lines.some(
|
|
1471
1924
|
(line) =>
|
|
1472
1925
|
line.length < 2 ||
|
|
@@ -1478,8 +1931,26 @@ function parseTrustedIndexBytes(bytes) {
|
|
|
1478
1931
|
"ArtifactStore index exceeds its trusted snapshot limits",
|
|
1479
1932
|
);
|
|
1480
1933
|
}
|
|
1481
|
-
|
|
1482
|
-
|
|
1934
|
+
let memory = prefix ? prefix.hotMemoryEstimate : 256;
|
|
1935
|
+
let canonicalOffset = prefix ? prefix.canonicalBytes.length : 0;
|
|
1936
|
+
let rawOffset = prefix ? prefix.bytes.length : 0;
|
|
1937
|
+
const allocateCanonical = () => {
|
|
1938
|
+
reservation.canonicalBytes = Buffer.allocUnsafeSlow(bytes.length);
|
|
1939
|
+
if (prefix && prefix.canonicalBytes !== prefix.bytes)
|
|
1940
|
+
prefix.canonicalBytes.copy(reservation.canonicalBytes);
|
|
1941
|
+
else bytes.copy(reservation.canonicalBytes, 0, 0, canonicalOffset);
|
|
1942
|
+
};
|
|
1943
|
+
if (reservation) {
|
|
1944
|
+
reservation.scratch = Buffer.allocUnsafeSlow(reservation.scratchBytes);
|
|
1945
|
+
// A dual prefix already proves a noncanonical row in this generation.
|
|
1946
|
+
if (prefix && prefix.canonicalBytes !== prefix.bytes) allocateCanonical();
|
|
1947
|
+
}
|
|
1948
|
+
// The new count is part of the original framing; never extend an old hash.
|
|
1949
|
+
const hash = crypto.createHash("sha256");
|
|
1950
|
+
hash.update(`${rowCount}\0`, "utf8");
|
|
1951
|
+
if (prefix) hashTrustedCanonicalRows(hash, prefix.canonicalBytes);
|
|
1952
|
+
for (const line of lines) {
|
|
1953
|
+
const index = entries.length;
|
|
1483
1954
|
let entry;
|
|
1484
1955
|
try {
|
|
1485
1956
|
entry = JSON.parse(line);
|
|
@@ -1512,25 +1983,126 @@ function parseTrustedIndexBytes(bytes) {
|
|
|
1512
1983
|
}
|
|
1513
1984
|
entries.push(entry);
|
|
1514
1985
|
}
|
|
1986
|
+
// Preserve the original JSON/row-before-canonical validation error precedence.
|
|
1987
|
+
for (let index = rowCount - lines.length; index < rowCount; index += 1) {
|
|
1988
|
+
const entry = entries[index];
|
|
1989
|
+
const canonical = canonicalJson(entry, MAX_INDEX_ENTRY_BYTES);
|
|
1990
|
+
const length = Buffer.byteLength(canonical, "utf8");
|
|
1991
|
+
hash.update(`${length}:`, "utf8");
|
|
1992
|
+
hash.update(canonical, "utf8");
|
|
1993
|
+
if (reservation) {
|
|
1994
|
+
const line = lines[index - (rowCount - lines.length)];
|
|
1995
|
+
const rawLength = Buffer.byteLength(line, "utf8");
|
|
1996
|
+
if (reservation.retain) {
|
|
1997
|
+
if (
|
|
1998
|
+
length !== rawLength ||
|
|
1999
|
+
length > reservation.scratchBytes ||
|
|
2000
|
+
canonicalOffset + length >= bytes.length
|
|
2001
|
+
) {
|
|
2002
|
+
// The budget relies on scalar encoding and key reordering preserving
|
|
2003
|
+
// row lengths. If that ever stops holding, only bypass this cache;
|
|
2004
|
+
// keep the original canonical validation and fingerprint semantics.
|
|
2005
|
+
reservation.retain = false;
|
|
2006
|
+
} else {
|
|
2007
|
+
const written = reservation.scratch.write(
|
|
2008
|
+
canonical,
|
|
2009
|
+
0,
|
|
2010
|
+
length,
|
|
2011
|
+
"utf8",
|
|
2012
|
+
);
|
|
2013
|
+
if (written !== length) reservation.retain = false;
|
|
2014
|
+
else {
|
|
2015
|
+
const same = reservation.scratch
|
|
2016
|
+
.subarray(0, length)
|
|
2017
|
+
.equals(bytes.subarray(rawOffset, rawOffset + length));
|
|
2018
|
+
if (!same && reservation.canonicalBytes === null)
|
|
2019
|
+
allocateCanonical();
|
|
2020
|
+
if (reservation.canonicalBytes !== null) {
|
|
2021
|
+
reservation.scratch.copy(
|
|
2022
|
+
reservation.canonicalBytes,
|
|
2023
|
+
canonicalOffset,
|
|
2024
|
+
0,
|
|
2025
|
+
length,
|
|
2026
|
+
);
|
|
2027
|
+
reservation.canonicalBytes[canonicalOffset + length] = 10;
|
|
2028
|
+
}
|
|
2029
|
+
}
|
|
2030
|
+
}
|
|
2031
|
+
}
|
|
2032
|
+
canonicalOffset += length + 1;
|
|
2033
|
+
rawOffset += rawLength + 1;
|
|
2034
|
+
memory += 1024 + parsedIndexMemory(entry);
|
|
2035
|
+
}
|
|
2036
|
+
deepFreeze(entry);
|
|
2037
|
+
}
|
|
2038
|
+
if (reservation?.retain) {
|
|
2039
|
+
if (canonicalOffset !== bytes.length || rawOffset !== bytes.length) {
|
|
2040
|
+
reservation.retain = false;
|
|
2041
|
+
} else if (reservation.canonicalBytes === null) {
|
|
2042
|
+
// Every row and LF has passed all original validation and exact byte
|
|
2043
|
+
// comparison. Release only the canonical backing that was NEVER allocated.
|
|
2044
|
+
reservation.canonicalBytes = bytes;
|
|
2045
|
+
reservation.memory -= bytes.length;
|
|
2046
|
+
reservation.compactMemory -= bytes.length;
|
|
2047
|
+
reservedIndexMemory -= bytes.length;
|
|
2048
|
+
}
|
|
2049
|
+
}
|
|
1515
2050
|
return Object.freeze({
|
|
1516
2051
|
entries: Object.freeze(entries),
|
|
1517
|
-
entriesFingerprint:
|
|
1518
|
-
|
|
1519
|
-
"trusted ArtifactStore index entries",
|
|
1520
|
-
),
|
|
2052
|
+
entriesFingerprint: `sha256:${hash.digest("hex")}`,
|
|
2053
|
+
memory,
|
|
1521
2054
|
});
|
|
1522
2055
|
}
|
|
1523
2056
|
|
|
1524
|
-
function
|
|
2057
|
+
function reuseTrustedIndexParse(layout, bytes) {
|
|
2058
|
+
let previous = TRUSTED_INDEX_PARSES.get(layout);
|
|
2059
|
+
if (previous && bytes.equals(previous.bytes)) {
|
|
2060
|
+
TRUSTED_INDEX_PARSES.delete(layout);
|
|
2061
|
+
TRUSTED_INDEX_PARSES.set(layout, previous);
|
|
2062
|
+
if (previous.hot) return previous.hot;
|
|
2063
|
+
const parsed = decodeTrustedIndexParse(previous);
|
|
2064
|
+
promoteTrustedIndexParse(previous, parsed);
|
|
2065
|
+
return parsed;
|
|
2066
|
+
}
|
|
2067
|
+
const prefix =
|
|
2068
|
+
previous &&
|
|
2069
|
+
previous.bytes.length > 0 &&
|
|
2070
|
+
previous.bytes[previous.bytes.length - 1] === 10 &&
|
|
2071
|
+
bytes.length > previous.bytes.length &&
|
|
2072
|
+
bytes.subarray(0, previous.bytes.length).equals(previous.bytes)
|
|
2073
|
+
? previous
|
|
2074
|
+
: null;
|
|
2075
|
+
if (!prefix) forgetTrustedIndexParse(layout);
|
|
2076
|
+
previous = null;
|
|
2077
|
+
const reservation = reserveTrustedIndexParse(layout, bytes, prefix);
|
|
2078
|
+
try {
|
|
2079
|
+
const parsed = parseTrustedIndexBytes(bytes, prefix, reservation);
|
|
2080
|
+
if (reservation?.retain)
|
|
2081
|
+
retainTrustedIndexParse(layout, bytes, parsed, reservation);
|
|
2082
|
+
return parsed;
|
|
2083
|
+
} finally {
|
|
2084
|
+
releaseTrustedIndexReservation(reservation);
|
|
2085
|
+
}
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
function readTrustedIndexSnapshot(layout, expectedSnapshot = null) {
|
|
1525
2089
|
return withEvolutionFileIdentity(
|
|
1526
2090
|
INDEX_DIRECTORY_FS,
|
|
1527
2091
|
layout.indexPath,
|
|
1528
2092
|
(samePathHandle) =>
|
|
1529
|
-
readTrustedIndexSnapshotWithIdentity(
|
|
2093
|
+
readTrustedIndexSnapshotWithIdentity(
|
|
2094
|
+
layout,
|
|
2095
|
+
samePathHandle,
|
|
2096
|
+
expectedSnapshot,
|
|
2097
|
+
),
|
|
1530
2098
|
);
|
|
1531
2099
|
}
|
|
1532
2100
|
|
|
1533
|
-
function readTrustedIndexSnapshotWithIdentity(
|
|
2101
|
+
function readTrustedIndexSnapshotWithIdentity(
|
|
2102
|
+
layout,
|
|
2103
|
+
samePathHandle,
|
|
2104
|
+
expectedSnapshot = null,
|
|
2105
|
+
) {
|
|
1534
2106
|
attestStoreDirectories(layout);
|
|
1535
2107
|
let beforePathStat;
|
|
1536
2108
|
let beforeDescriptorStat;
|
|
@@ -1625,9 +2197,20 @@ function readTrustedIndexSnapshotWithIdentity(layout, samePathHandle) {
|
|
|
1625
2197
|
);
|
|
1626
2198
|
}
|
|
1627
2199
|
attestStoreDirectories(layout);
|
|
1628
|
-
const
|
|
2200
|
+
const bytesDigest = sha256(bytes);
|
|
2201
|
+
if (expectedSnapshot !== null) {
|
|
2202
|
+
// Repeat every directory, handle, identity, mutation, size and byte-read
|
|
2203
|
+
// check above. Only identical freshly read bytes reuse their derived parse.
|
|
2204
|
+
if (bytesDigest !== expectedSnapshot.bytesDigest)
|
|
2205
|
+
throw artifactError(
|
|
2206
|
+
EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
|
|
2207
|
+
"ArtifactStore index changed during ledger batch readback",
|
|
2208
|
+
);
|
|
2209
|
+
return expectedSnapshot;
|
|
2210
|
+
}
|
|
2211
|
+
const parsed = reuseTrustedIndexParse(layout, bytes);
|
|
1629
2212
|
return Object.freeze({
|
|
1630
|
-
bytesDigest
|
|
2213
|
+
bytesDigest,
|
|
1631
2214
|
entries: parsed.entries,
|
|
1632
2215
|
entriesFingerprint: parsed.entriesFingerprint,
|
|
1633
2216
|
});
|
|
@@ -2214,10 +2797,9 @@ export class EvolutionArtifactPorts {
|
|
|
2214
2797
|
);
|
|
2215
2798
|
let storeFingerprint;
|
|
2216
2799
|
try {
|
|
2217
|
-
storeFingerprint =
|
|
2218
|
-
entries,
|
|
2219
|
-
|
|
2220
|
-
);
|
|
2800
|
+
storeFingerprint = this.#store.list
|
|
2801
|
+
? indexEntriesFingerprint(entries, "ArtifactStore.list entries")
|
|
2802
|
+
: trustedBefore.entriesFingerprint;
|
|
2221
2803
|
} catch (cause) {
|
|
2222
2804
|
if (isEvolutionArtifactPortError(cause)) throw cause;
|
|
2223
2805
|
throw artifactError(
|
|
@@ -2241,6 +2823,8 @@ export class EvolutionArtifactPorts {
|
|
|
2241
2823
|
}
|
|
2242
2824
|
|
|
2243
2825
|
#entriesByDataField(entries, field, expected) {
|
|
2826
|
+
const lookup = LEDGER_BATCH_INDEX_LOOKUPS.get(entries)?.get(field);
|
|
2827
|
+
if (lookup) return lookup.get(expected) || [];
|
|
2244
2828
|
const matches = [];
|
|
2245
2829
|
for (let index = 0; index < entries.length; index += 1) {
|
|
2246
2830
|
const entry = ownData(
|
|
@@ -2268,9 +2852,62 @@ export class EvolutionArtifactPorts {
|
|
|
2268
2852
|
return matches;
|
|
2269
2853
|
}
|
|
2270
2854
|
|
|
2271
|
-
#
|
|
2855
|
+
#indexLedgerBatchEntries(entries) {
|
|
2856
|
+
if (LEDGER_BATCH_INDEX_LOOKUPS.has(entries)) return;
|
|
2857
|
+
const lookups = new Map([
|
|
2858
|
+
["id", new Map()],
|
|
2859
|
+
["recordDigest", new Map()],
|
|
2860
|
+
]);
|
|
2861
|
+
// Retain every match, including duplicates. A last-wins table would hide
|
|
2862
|
+
// ambiguous ids or digests that the descriptor resolver must reject.
|
|
2863
|
+
for (let index = 0; index < entries.length; index += 1) {
|
|
2864
|
+
const entry = ownData(
|
|
2865
|
+
entries,
|
|
2866
|
+
String(index),
|
|
2867
|
+
"ArtifactStore index entries",
|
|
2868
|
+
);
|
|
2869
|
+
if (!entry || typeof entry !== "object") continue;
|
|
2870
|
+
rejectProxy(
|
|
2871
|
+
entry,
|
|
2872
|
+
`ArtifactStore index entry ${index}`,
|
|
2873
|
+
EVOLUTION_ARTIFACT_STORE_FAILED_CODE,
|
|
2874
|
+
);
|
|
2875
|
+
if (Array.isArray(entry)) continue;
|
|
2876
|
+
for (const [field, lookup] of lookups) {
|
|
2877
|
+
const descriptor = Object.getOwnPropertyDescriptor(entry, field);
|
|
2878
|
+
if (!descriptor) continue;
|
|
2879
|
+
if (!("value" in descriptor) || descriptor.enumerable !== true) {
|
|
2880
|
+
throw artifactError(
|
|
2881
|
+
EVOLUTION_ARTIFACT_STORE_FAILED_CODE,
|
|
2882
|
+
`ArtifactStore index ${field} must be an enumerable data property`,
|
|
2883
|
+
);
|
|
2884
|
+
}
|
|
2885
|
+
const matches = lookup.get(descriptor.value) || [];
|
|
2886
|
+
matches.push(entry);
|
|
2887
|
+
lookup.set(descriptor.value, matches);
|
|
2888
|
+
}
|
|
2889
|
+
}
|
|
2890
|
+
for (const lookup of lookups.values())
|
|
2891
|
+
for (const matches of lookup.values()) Object.freeze(matches);
|
|
2892
|
+
LEDGER_BATCH_INDEX_LOOKUPS.set(entries, lookups);
|
|
2893
|
+
}
|
|
2894
|
+
|
|
2895
|
+
#defaultReadbackSnapshot() {
|
|
2896
|
+
// One immutable descriptor snapshot may serve the pure default lookups
|
|
2897
|
+
// within this phase. Recheck dynamic methods independently before each
|
|
2898
|
+
// phase; never reuse this snapshot across payload I/O or authority calls.
|
|
2899
|
+
if (
|
|
2900
|
+
this.#store.list !== null ||
|
|
2901
|
+
this.#store.get !== null ||
|
|
2902
|
+
!this.#store.canUseSealedDirectoryRead()
|
|
2903
|
+
)
|
|
2904
|
+
return null;
|
|
2905
|
+
return readTrustedIndexSnapshot(this.#store.layout);
|
|
2906
|
+
}
|
|
2907
|
+
|
|
2908
|
+
#loadCurrentEntryById(artifactId, snapshot = null) {
|
|
2272
2909
|
const listed = this.#entriesByDataField(
|
|
2273
|
-
this.#boundedIndexEntries(),
|
|
2910
|
+
snapshot?.entries ?? this.#boundedIndexEntries(),
|
|
2274
2911
|
"id",
|
|
2275
2912
|
artifactId,
|
|
2276
2913
|
);
|
|
@@ -2288,6 +2925,7 @@ export class EvolutionArtifactPorts {
|
|
|
2288
2925
|
{ artifactId },
|
|
2289
2926
|
);
|
|
2290
2927
|
}
|
|
2928
|
+
if (snapshot !== null) return listed[0];
|
|
2291
2929
|
let current;
|
|
2292
2930
|
try {
|
|
2293
2931
|
current = rejectPromise(
|
|
@@ -2337,8 +2975,8 @@ export class EvolutionArtifactPorts {
|
|
|
2337
2975
|
return current;
|
|
2338
2976
|
}
|
|
2339
2977
|
|
|
2340
|
-
#assertEntryStable(artifactId, original) {
|
|
2341
|
-
const current = this.#loadCurrentEntryById(artifactId);
|
|
2978
|
+
#assertEntryStable(artifactId, original, snapshot = null) {
|
|
2979
|
+
const current = this.#loadCurrentEntryById(artifactId, snapshot);
|
|
2342
2980
|
if (canonicalJson(current) !== canonicalJson(original)) {
|
|
2343
2981
|
throw artifactError(
|
|
2344
2982
|
EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
|
|
@@ -2386,8 +3024,9 @@ export class EvolutionArtifactPorts {
|
|
|
2386
3024
|
#loadLedgerEntry(artifactId, recordDigest = null, snapshot = null) {
|
|
2387
3025
|
// Ledger resolution is read-only. Read the already attested physical index
|
|
2388
3026
|
// directly instead of invoking ArtifactStore.list/get (both acquire writer
|
|
2389
|
-
// locks). Each read/batch obtains
|
|
2390
|
-
//
|
|
3027
|
+
// locks). Each read/batch obtains fresh bounded descriptor bytes. Only an
|
|
3028
|
+
// identical parse may be shared within one synchronous read window;
|
|
3029
|
+
// signatures, current authority and payloads are always reauthenticated.
|
|
2391
3030
|
const { entries } =
|
|
2392
3031
|
snapshot ?? readTrustedIndexSnapshot(this.#store.layout);
|
|
2393
3032
|
const matches = this.#entriesByDataField(entries, "id", artifactId);
|
|
@@ -2507,8 +3146,15 @@ export class EvolutionArtifactPorts {
|
|
|
2507
3146
|
return Object.freeze({ createdAt, expiresAt, file, id, lineage, size });
|
|
2508
3147
|
}
|
|
2509
3148
|
|
|
2510
|
-
#readStoredBytes(
|
|
2511
|
-
|
|
3149
|
+
#readStoredBytes(
|
|
3150
|
+
entry,
|
|
3151
|
+
normalizedEntry,
|
|
3152
|
+
expectedDigest,
|
|
3153
|
+
boundedRead = null,
|
|
3154
|
+
directoryScope = null,
|
|
3155
|
+
) {
|
|
3156
|
+
if (directoryScope) directoryScope.check();
|
|
3157
|
+
else attestStoreDirectories(this.#store.layout);
|
|
2512
3158
|
const expectedHex = expectedDigest.slice("sha256:".length);
|
|
2513
3159
|
let storedPath;
|
|
2514
3160
|
try {
|
|
@@ -2543,21 +3189,32 @@ export class EvolutionArtifactPorts {
|
|
|
2543
3189
|
expectedSize: normalizedEntry.size,
|
|
2544
3190
|
maximumBytes: EVOLUTION_ARTIFACT_MAX_CANONICAL_BYTES,
|
|
2545
3191
|
});
|
|
2546
|
-
|
|
2547
|
-
|
|
2548
|
-
|
|
2549
|
-
|
|
2550
|
-
|
|
2551
|
-
|
|
2552
|
-
|
|
2553
|
-
|
|
2554
|
-
|
|
3192
|
+
const preserveIntegrityHooks = !this.#store.canUseSingleBoundedRead();
|
|
3193
|
+
directoryScope?.assertReader();
|
|
3194
|
+
if (preserveIntegrityHooks) {
|
|
3195
|
+
let integrityBefore;
|
|
3196
|
+
try {
|
|
3197
|
+
integrityBefore = this.#store.verifyIntegrity(entry, readBounds);
|
|
3198
|
+
} catch (cause) {
|
|
3199
|
+
throw artifactError(
|
|
3200
|
+
EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
|
|
3201
|
+
"ArtifactStore integrity verification threw",
|
|
3202
|
+
{ cause },
|
|
3203
|
+
);
|
|
3204
|
+
}
|
|
3205
|
+
validateIntegrityResult(integrityBefore, expectedHex);
|
|
2555
3206
|
}
|
|
2556
|
-
validateIntegrityResult(integrityBefore, expectedHex);
|
|
2557
3207
|
|
|
2558
3208
|
let bytes;
|
|
2559
3209
|
try {
|
|
2560
|
-
|
|
3210
|
+
directoryScope?.assertReader();
|
|
3211
|
+
const read =
|
|
3212
|
+
boundedRead !== null &&
|
|
3213
|
+
!preserveIntegrityHooks &&
|
|
3214
|
+
this.#store.canUseSingleBoundedRead()
|
|
3215
|
+
? boundedRead
|
|
3216
|
+
: readBoundedArtifactFile;
|
|
3217
|
+
bytes = read(
|
|
2561
3218
|
resolvedStoredPath,
|
|
2562
3219
|
readBounds.expectedSize,
|
|
2563
3220
|
readBounds.maximumBytes,
|
|
@@ -2579,18 +3236,22 @@ export class EvolutionArtifactPorts {
|
|
|
2579
3236
|
"stored artifact bytes do not match the signed recordDigest",
|
|
2580
3237
|
);
|
|
2581
3238
|
}
|
|
2582
|
-
|
|
2583
|
-
|
|
2584
|
-
integrityAfter
|
|
2585
|
-
|
|
2586
|
-
|
|
2587
|
-
|
|
2588
|
-
|
|
2589
|
-
|
|
2590
|
-
|
|
3239
|
+
directoryScope?.assertReader();
|
|
3240
|
+
if (preserveIntegrityHooks || !this.#store.canUseSingleBoundedRead()) {
|
|
3241
|
+
let integrityAfter;
|
|
3242
|
+
try {
|
|
3243
|
+
integrityAfter = this.#store.verifyIntegrity(entry, readBounds);
|
|
3244
|
+
} catch (cause) {
|
|
3245
|
+
throw artifactError(
|
|
3246
|
+
EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
|
|
3247
|
+
"ArtifactStore post-read integrity verification threw",
|
|
3248
|
+
{ cause },
|
|
3249
|
+
);
|
|
3250
|
+
}
|
|
3251
|
+
validateIntegrityResult(integrityAfter, expectedHex);
|
|
2591
3252
|
}
|
|
2592
|
-
|
|
2593
|
-
attestStoreDirectories(this.#store.layout);
|
|
3253
|
+
if (directoryScope) directoryScope.check(resolvedStoredPath);
|
|
3254
|
+
else attestStoreDirectories(this.#store.layout);
|
|
2594
3255
|
return bytes;
|
|
2595
3256
|
}
|
|
2596
3257
|
|
|
@@ -3011,10 +3672,20 @@ export class EvolutionArtifactPorts {
|
|
|
3011
3672
|
completionClock.milliseconds >= core.expiresAtMs) ||
|
|
3012
3673
|
completionClock.milliseconds >= authority.decisionExpiresAtMs
|
|
3013
3674
|
) {
|
|
3014
|
-
|
|
3675
|
+
const authenticationFailureReason =
|
|
3676
|
+
completionClock.milliseconds < verificationClock.milliseconds
|
|
3677
|
+
? "auth-clock-backward"
|
|
3678
|
+
: core.expiresAtMs !== null &&
|
|
3679
|
+
completionClock.milliseconds >= core.expiresAtMs
|
|
3680
|
+
? "auth-envelope-expired"
|
|
3681
|
+
: "auth-authority-lease-expired";
|
|
3682
|
+
const error = artifactError(
|
|
3015
3683
|
EVOLUTION_ARTIFACT_EXPIRED_CODE,
|
|
3016
3684
|
"artifact authority expired before envelope authentication completed",
|
|
3017
3685
|
);
|
|
3686
|
+
recordStorageFailureReason(error, authenticationFailureReason);
|
|
3687
|
+
recordStorageFailureStage(error, core.type, "authentication");
|
|
3688
|
+
throw error;
|
|
3018
3689
|
}
|
|
3019
3690
|
return Object.freeze({
|
|
3020
3691
|
authority,
|
|
@@ -3033,18 +3704,25 @@ export class EvolutionArtifactPorts {
|
|
|
3033
3704
|
ledgerRead = false,
|
|
3034
3705
|
capturedLedgerEntry = null,
|
|
3035
3706
|
batchWindow = null,
|
|
3707
|
+
boundedRead = null,
|
|
3708
|
+
observePhase = null,
|
|
3709
|
+
directoryScope = null,
|
|
3036
3710
|
) {
|
|
3711
|
+
observePhase?.("authentication");
|
|
3037
3712
|
const authentication =
|
|
3038
3713
|
preauthenticated || this.#authenticateEnvelope(envelope, options);
|
|
3714
|
+
observePhase?.("post-index");
|
|
3715
|
+
const beforeSnapshot = ledgerRead ? null : this.#defaultReadbackSnapshot();
|
|
3039
3716
|
if (!ledgerRead)
|
|
3040
3717
|
this.#assertUniqueRecordDigest(
|
|
3041
3718
|
authentication.core.recordDigest,
|
|
3042
3719
|
artifactId,
|
|
3720
|
+
beforeSnapshot?.entries,
|
|
3043
3721
|
);
|
|
3044
3722
|
const entry = ledgerRead
|
|
3045
3723
|
? (capturedLedgerEntry ??
|
|
3046
3724
|
this.#loadLedgerEntry(artifactId, authentication.core.recordDigest))
|
|
3047
|
-
: this.#loadCurrentEntryById(artifactId);
|
|
3725
|
+
: this.#loadCurrentEntryById(artifactId, beforeSnapshot);
|
|
3048
3726
|
const normalizedEntry = this.#validateIndexEntry(
|
|
3049
3727
|
entry,
|
|
3050
3728
|
envelope,
|
|
@@ -3057,12 +3735,16 @@ export class EvolutionArtifactPorts {
|
|
|
3057
3735
|
"ArtifactStore entry id does not match the requested locator",
|
|
3058
3736
|
);
|
|
3059
3737
|
}
|
|
3738
|
+
observePhase?.("read-bytes");
|
|
3060
3739
|
const bytes = this.#readStoredBytes(
|
|
3061
3740
|
entry,
|
|
3062
3741
|
normalizedEntry,
|
|
3063
3742
|
authentication.core.recordDigest,
|
|
3743
|
+
boundedRead,
|
|
3744
|
+
directoryScope,
|
|
3064
3745
|
);
|
|
3065
3746
|
const record = this.#parseRecordBytes(bytes, authentication.core);
|
|
3747
|
+
observePhase?.("post-index");
|
|
3066
3748
|
if (ledgerRead && batchWindow === null) {
|
|
3067
3749
|
const after = this.#loadLedgerEntry(
|
|
3068
3750
|
artifactId,
|
|
@@ -3076,24 +3758,34 @@ export class EvolutionArtifactPorts {
|
|
|
3076
3758
|
);
|
|
3077
3759
|
}
|
|
3078
3760
|
} else if (!ledgerRead) {
|
|
3079
|
-
this.#
|
|
3761
|
+
const afterSnapshot = this.#defaultReadbackSnapshot();
|
|
3762
|
+
this.#assertEntryStable(artifactId, entry, afterSnapshot);
|
|
3080
3763
|
this.#assertUniqueRecordDigest(
|
|
3081
3764
|
authentication.core.recordDigest,
|
|
3082
3765
|
artifactId,
|
|
3766
|
+
afterSnapshot?.entries,
|
|
3083
3767
|
);
|
|
3084
3768
|
}
|
|
3769
|
+
observePhase?.("final-lease");
|
|
3085
3770
|
const releaseClock = this.#clock();
|
|
3086
|
-
|
|
3087
|
-
releaseClock.milliseconds <
|
|
3088
|
-
|
|
3089
|
-
|
|
3090
|
-
|
|
3091
|
-
|
|
3092
|
-
|
|
3093
|
-
|
|
3771
|
+
const releaseFailureReason =
|
|
3772
|
+
releaseClock.milliseconds < new Date(authentication.resolvedAt).getTime()
|
|
3773
|
+
? "release-clock-backward"
|
|
3774
|
+
: authentication.core.expiresAtMs !== null &&
|
|
3775
|
+
releaseClock.milliseconds >= authentication.core.expiresAtMs
|
|
3776
|
+
? "release-envelope-expired"
|
|
3777
|
+
: releaseClock.milliseconds >=
|
|
3778
|
+
authentication.authority.decisionExpiresAtMs
|
|
3779
|
+
? "release-authority-lease-expired"
|
|
3780
|
+
: null;
|
|
3781
|
+
if (releaseFailureReason !== null) {
|
|
3782
|
+
const error = artifactError(
|
|
3094
3783
|
EVOLUTION_ARTIFACT_EXPIRED_CODE,
|
|
3095
3784
|
"artifact authority expired before canonical bytes could be released",
|
|
3096
3785
|
);
|
|
3786
|
+
recordStorageFailureReason(error, releaseFailureReason);
|
|
3787
|
+
recordStorageFailureStage(error, authentication.core.type, "final-lease");
|
|
3788
|
+
throw error;
|
|
3097
3789
|
}
|
|
3098
3790
|
const artifactRef = buildArtifactRef(
|
|
3099
3791
|
artifactId,
|
|
@@ -3172,6 +3864,18 @@ export class EvolutionArtifactPorts {
|
|
|
3172
3864
|
}
|
|
3173
3865
|
|
|
3174
3866
|
putCanonical(type, value, context) {
|
|
3867
|
+
let phase = "validate";
|
|
3868
|
+
try {
|
|
3869
|
+
return this.#putCanonical(type, value, context, (next) => {
|
|
3870
|
+
phase = next;
|
|
3871
|
+
});
|
|
3872
|
+
} catch (error) {
|
|
3873
|
+
recordStorageFailureStage(error, type, phase);
|
|
3874
|
+
throw error;
|
|
3875
|
+
}
|
|
3876
|
+
}
|
|
3877
|
+
|
|
3878
|
+
#putCanonical(type, value, context, observePhase) {
|
|
3175
3879
|
const normalizedType = normalizeType(type, this.#allowedTypes);
|
|
3176
3880
|
const normalizedContext = normalizePutContext(
|
|
3177
3881
|
context,
|
|
@@ -3191,6 +3895,7 @@ export class EvolutionArtifactPorts {
|
|
|
3191
3895
|
const recordJson = canonicalJson(record);
|
|
3192
3896
|
const recordBytes = Buffer.from(recordJson, "utf8");
|
|
3193
3897
|
const recordDigest = sha256(recordBytes);
|
|
3898
|
+
observePhase("pre-index");
|
|
3194
3899
|
readTrustedIndexSnapshot(this.#store.layout);
|
|
3195
3900
|
const issuanceClock = this.#clock();
|
|
3196
3901
|
const expiresAt =
|
|
@@ -3208,6 +3913,7 @@ export class EvolutionArtifactPorts {
|
|
|
3208
3913
|
1,
|
|
3209
3914
|
Math.ceil(normalizedContext.ttlMs / MILLISECONDS_PER_DAY),
|
|
3210
3915
|
);
|
|
3916
|
+
observePhase("authority");
|
|
3211
3917
|
const authority = this.#currentAuthority({
|
|
3212
3918
|
action: "publish",
|
|
3213
3919
|
digest: recordDigest,
|
|
@@ -3233,6 +3939,7 @@ export class EvolutionArtifactPorts {
|
|
|
3233
3939
|
tenantId: this.#tenantId,
|
|
3234
3940
|
type: normalizedType,
|
|
3235
3941
|
});
|
|
3942
|
+
observePhase("sign");
|
|
3236
3943
|
const signature = this.#sign(core, authority);
|
|
3237
3944
|
const signingClock = this.#clock();
|
|
3238
3945
|
if (
|
|
@@ -3278,7 +3985,9 @@ export class EvolutionArtifactPorts {
|
|
|
3278
3985
|
// Validate the captured physical store/index generation immediately before
|
|
3279
3986
|
// granting ArtifactStore the append. Post-publication readback re-attests
|
|
3280
3987
|
// it and cross-checks the repository API against a descriptor snapshot.
|
|
3988
|
+
observePhase("pre-index");
|
|
3281
3989
|
readTrustedIndexSnapshot(this.#store.layout);
|
|
3990
|
+
observePhase("publish");
|
|
3282
3991
|
let publication;
|
|
3283
3992
|
try {
|
|
3284
3993
|
publication = rejectPromise(
|
|
@@ -3348,6 +4057,7 @@ export class EvolutionArtifactPorts {
|
|
|
3348
4057
|
// A response-loss retry may find the prior immutable row. In that case its
|
|
3349
4058
|
// exact signed envelope is authoritative and must be returned, rather than
|
|
3350
4059
|
// replacing lineage with a newly issued envelope.
|
|
4060
|
+
observePhase("post-index");
|
|
3351
4061
|
const currentEntry = this.#loadCurrentEntryById(artifactId);
|
|
3352
4062
|
assertExactRecord(
|
|
3353
4063
|
currentEntry,
|
|
@@ -3387,7 +4097,14 @@ export class EvolutionArtifactPorts {
|
|
|
3387
4097
|
tenantId: this.#tenantId,
|
|
3388
4098
|
}),
|
|
3389
4099
|
artifactId,
|
|
3390
|
-
|
|
4100
|
+
null,
|
|
4101
|
+
false,
|
|
4102
|
+
null,
|
|
4103
|
+
null,
|
|
4104
|
+
null,
|
|
4105
|
+
observePhase,
|
|
4106
|
+
);
|
|
4107
|
+
observePhase("read-bytes");
|
|
3391
4108
|
if (
|
|
3392
4109
|
canonicalJson(resolved.record) !== recordJson ||
|
|
3393
4110
|
resolved.authentication.envelopeDigest !==
|
|
@@ -3398,6 +4115,7 @@ export class EvolutionArtifactPorts {
|
|
|
3398
4115
|
"canonical artifact readback does not match the requested value and lineage",
|
|
3399
4116
|
);
|
|
3400
4117
|
}
|
|
4118
|
+
observePhase("receipt");
|
|
3401
4119
|
const receiptCore = {
|
|
3402
4120
|
audience: this.#audience,
|
|
3403
4121
|
createdAt: ownData(
|
|
@@ -3481,7 +4199,14 @@ export class EvolutionArtifactPorts {
|
|
|
3481
4199
|
});
|
|
3482
4200
|
}
|
|
3483
4201
|
|
|
3484
|
-
#resolveLedgerRequest(
|
|
4202
|
+
#resolveLedgerRequest(
|
|
4203
|
+
request,
|
|
4204
|
+
purpose,
|
|
4205
|
+
snapshot = null,
|
|
4206
|
+
batchWindow = null,
|
|
4207
|
+
boundedRead = null,
|
|
4208
|
+
directoryScope = null,
|
|
4209
|
+
) {
|
|
3485
4210
|
assertExactRecord(
|
|
3486
4211
|
request,
|
|
3487
4212
|
LEDGER_REQUEST_KEYS,
|
|
@@ -3550,6 +4275,9 @@ export class EvolutionArtifactPorts {
|
|
|
3550
4275
|
true,
|
|
3551
4276
|
entry,
|
|
3552
4277
|
batchWindow,
|
|
4278
|
+
boundedRead,
|
|
4279
|
+
null,
|
|
4280
|
+
directoryScope,
|
|
3553
4281
|
);
|
|
3554
4282
|
const ledgerReceiptCore = {
|
|
3555
4283
|
artifactReceiptDigest: resolved.receipt.receiptDigest,
|
|
@@ -3574,10 +4302,14 @@ export class EvolutionArtifactPorts {
|
|
|
3574
4302
|
});
|
|
3575
4303
|
}
|
|
3576
4304
|
|
|
3577
|
-
#resolveLedgerBatch(input, purpose) {
|
|
4305
|
+
#resolveLedgerBatch(input, purpose, windowSnapshot = null) {
|
|
3578
4306
|
assertDenseDataArray(input, "ledger artifact batch", 128);
|
|
3579
4307
|
const requests = frozenCanonicalClone(input);
|
|
3580
|
-
const snapshot = readTrustedIndexSnapshot(
|
|
4308
|
+
const snapshot = readTrustedIndexSnapshot(
|
|
4309
|
+
this.#store.layout,
|
|
4310
|
+
windowSnapshot,
|
|
4311
|
+
);
|
|
4312
|
+
this.#indexLedgerBatchEntries(snapshot.entries);
|
|
3581
4313
|
const window = [];
|
|
3582
4314
|
const results = requests.map((request) =>
|
|
3583
4315
|
this.#resolveLedgerRequest(request, purpose, snapshot, window),
|
|
@@ -3585,7 +4317,7 @@ export class EvolutionArtifactPorts {
|
|
|
3585
4317
|
// One bounded synchronous read, not a cache: every envelope/current
|
|
3586
4318
|
// authority and payload is authenticated individually. Re-attest the full
|
|
3587
4319
|
// index after ALL reads, and all decision leases before releasing bytes.
|
|
3588
|
-
const after = readTrustedIndexSnapshot(this.#store.layout);
|
|
4320
|
+
const after = readTrustedIndexSnapshot(this.#store.layout, snapshot);
|
|
3589
4321
|
if (after.bytesDigest !== snapshot.bytesDigest)
|
|
3590
4322
|
throw artifactError(
|
|
3591
4323
|
EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
|
|
@@ -3604,6 +4336,152 @@ export class EvolutionArtifactPorts {
|
|
|
3604
4336
|
return Object.freeze(results);
|
|
3605
4337
|
}
|
|
3606
4338
|
|
|
4339
|
+
#resolveLedgerWholeHistory(input, purpose) {
|
|
4340
|
+
assertDenseDataArray(
|
|
4341
|
+
input,
|
|
4342
|
+
"ledger artifact history",
|
|
4343
|
+
EVOLUTION_ARTIFACT_MAX_INDEX_ENTRIES,
|
|
4344
|
+
);
|
|
4345
|
+
// Capture every request before invoking any authority port. Retain the
|
|
4346
|
+
// established per-chunk canonical budget rather than imposing a smaller
|
|
4347
|
+
// whole-history byte limit on an otherwise admitted read window.
|
|
4348
|
+
const chunks = [];
|
|
4349
|
+
for (let offset = 0; offset < input.length; offset += 128) {
|
|
4350
|
+
const chunk = [];
|
|
4351
|
+
for (
|
|
4352
|
+
let index = offset;
|
|
4353
|
+
index < Math.min(offset + 128, input.length);
|
|
4354
|
+
index += 1
|
|
4355
|
+
) {
|
|
4356
|
+
chunk.push(ownData(input, String(index), "ledger artifact history"));
|
|
4357
|
+
}
|
|
4358
|
+
chunks.push(frozenCanonicalClone(chunk));
|
|
4359
|
+
}
|
|
4360
|
+
const readHistory = (directoryScope) => {
|
|
4361
|
+
const snapshot = readTrustedIndexSnapshot(this.#store.layout);
|
|
4362
|
+
this.#indexLedgerBatchEntries(snapshot.entries);
|
|
4363
|
+
const window = [];
|
|
4364
|
+
const results = [];
|
|
4365
|
+
const resolveItems = (read) => {
|
|
4366
|
+
for (const requests of chunks) {
|
|
4367
|
+
for (const request of requests) {
|
|
4368
|
+
results.push(
|
|
4369
|
+
this.#resolveLedgerRequest(
|
|
4370
|
+
request,
|
|
4371
|
+
purpose,
|
|
4372
|
+
snapshot,
|
|
4373
|
+
window,
|
|
4374
|
+
read,
|
|
4375
|
+
directoryScope,
|
|
4376
|
+
),
|
|
4377
|
+
);
|
|
4378
|
+
}
|
|
4379
|
+
}
|
|
4380
|
+
};
|
|
4381
|
+
if (this.#store.canUseSingleBoundedRead()) {
|
|
4382
|
+
try {
|
|
4383
|
+
withBoundedArtifactDirectoryRead(
|
|
4384
|
+
this.#store.layout.filesDir,
|
|
4385
|
+
resolveItems,
|
|
4386
|
+
);
|
|
4387
|
+
} catch (cause) {
|
|
4388
|
+
if (isEvolutionArtifactPortError(cause)) throw cause;
|
|
4389
|
+
throw artifactError(
|
|
4390
|
+
EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
|
|
4391
|
+
"stored artifact history could not be opened safely",
|
|
4392
|
+
{ cause },
|
|
4393
|
+
);
|
|
4394
|
+
}
|
|
4395
|
+
} else {
|
|
4396
|
+
// A retained reader may outlive a prototype change. Preserve the
|
|
4397
|
+
// original per-file reads and integrity hooks when defaults changed.
|
|
4398
|
+
resolveItems(null);
|
|
4399
|
+
}
|
|
4400
|
+
// Unlike the public chunk callback API, this operation exposes no partial
|
|
4401
|
+
// results between chunks. Every item's payload, signature and current
|
|
4402
|
+
// authority are still read/verified above. Reattest the complete index and
|
|
4403
|
+
// ALL decision leases before releasing any result from the sealed read.
|
|
4404
|
+
const after = readTrustedIndexSnapshot(this.#store.layout, snapshot);
|
|
4405
|
+
if (after.bytesDigest !== snapshot.bytesDigest) {
|
|
4406
|
+
throw artifactError(
|
|
4407
|
+
EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
|
|
4408
|
+
"ArtifactStore index changed during ledger history readback",
|
|
4409
|
+
);
|
|
4410
|
+
}
|
|
4411
|
+
const completed = this.#clock().milliseconds;
|
|
4412
|
+
if (
|
|
4413
|
+
window.some(
|
|
4414
|
+
(entry) =>
|
|
4415
|
+
completed < entry.checkedAt || completed >= entry.expiresAt,
|
|
4416
|
+
)
|
|
4417
|
+
) {
|
|
4418
|
+
throw artifactError(
|
|
4419
|
+
EVOLUTION_ARTIFACT_EXPIRED_CODE,
|
|
4420
|
+
"artifact authority expired before ledger history release",
|
|
4421
|
+
);
|
|
4422
|
+
}
|
|
4423
|
+
return Object.freeze(results);
|
|
4424
|
+
};
|
|
4425
|
+
if (this.#store.canUseSealedDirectoryRead()) {
|
|
4426
|
+
try {
|
|
4427
|
+
return withSealedStoreDirectoryRead(this.#store, readHistory);
|
|
4428
|
+
} catch (cause) {
|
|
4429
|
+
if (isEvolutionArtifactPortError(cause)) throw cause;
|
|
4430
|
+
throw artifactError(
|
|
4431
|
+
EVOLUTION_ARTIFACT_INTEGRITY_FAILED_CODE,
|
|
4432
|
+
"stored artifact history directory identity changed",
|
|
4433
|
+
{ cause },
|
|
4434
|
+
);
|
|
4435
|
+
}
|
|
4436
|
+
}
|
|
4437
|
+
return readHistory(null);
|
|
4438
|
+
}
|
|
4439
|
+
|
|
4440
|
+
#withLedgerBatchRead(callback, purpose) {
|
|
4441
|
+
rejectProxy(
|
|
4442
|
+
callback,
|
|
4443
|
+
"ledger batch read callback",
|
|
4444
|
+
EVOLUTION_ARTIFACT_INVALID_CODE,
|
|
4445
|
+
);
|
|
4446
|
+
if (typeof callback !== "function")
|
|
4447
|
+
throw artifactError(
|
|
4448
|
+
EVOLUTION_ARTIFACT_INVALID_CODE,
|
|
4449
|
+
"ledger batch read callback is required",
|
|
4450
|
+
);
|
|
4451
|
+
const snapshot = readTrustedIndexSnapshot(this.#store.layout);
|
|
4452
|
+
let active = true;
|
|
4453
|
+
let requests = 0;
|
|
4454
|
+
const resolve = Object.freeze((input) => {
|
|
4455
|
+
if (!active)
|
|
4456
|
+
throw artifactError(
|
|
4457
|
+
EVOLUTION_ARTIFACT_INVALID_CODE,
|
|
4458
|
+
"ledger batch read window is closed",
|
|
4459
|
+
);
|
|
4460
|
+
assertDenseDataArray(input, "ledger artifact batch", 128);
|
|
4461
|
+
requests += input.length;
|
|
4462
|
+
if (requests > EVOLUTION_ARTIFACT_MAX_INDEX_ENTRIES)
|
|
4463
|
+
throw artifactError(
|
|
4464
|
+
EVOLUTION_ARTIFACT_INVALID_CODE,
|
|
4465
|
+
"ledger batch read window exceeds the index entry limit",
|
|
4466
|
+
);
|
|
4467
|
+
// Each chunk reopens and authenticates current index bytes before/after
|
|
4468
|
+
// resolving fresh payloads, envelopes and current authority. Parsed rows
|
|
4469
|
+
// alone survive within this synchronous window; decision leases do not.
|
|
4470
|
+
return this.#resolveLedgerBatch(input, purpose, snapshot);
|
|
4471
|
+
});
|
|
4472
|
+
try {
|
|
4473
|
+
const result = rejectPromise(
|
|
4474
|
+
callback(resolve),
|
|
4475
|
+
"ledger batch read callback",
|
|
4476
|
+
EVOLUTION_ARTIFACT_INVALID_CODE,
|
|
4477
|
+
);
|
|
4478
|
+
readTrustedIndexSnapshot(this.#store.layout, snapshot);
|
|
4479
|
+
return result;
|
|
4480
|
+
} finally {
|
|
4481
|
+
active = false;
|
|
4482
|
+
}
|
|
4483
|
+
}
|
|
4484
|
+
|
|
3607
4485
|
/** Return a frozen read-only function; it exposes no put/signing authority. */
|
|
3608
4486
|
createEvolutionLedgerArtifactResolver(options = {}) {
|
|
3609
4487
|
assertOptionalExactRecord(
|
|
@@ -3627,6 +4505,28 @@ export class EvolutionArtifactPorts {
|
|
|
3627
4505
|
EVOLUTION_LEDGER_BATCH_RESOLVERS.set(resolveReadOnly, (requests) =>
|
|
3628
4506
|
this.#resolveLedgerBatch(requests, normalizedPurpose),
|
|
3629
4507
|
);
|
|
4508
|
+
EVOLUTION_LEDGER_BATCH_READ_WINDOWS.set(resolveReadOnly, (callback) =>
|
|
4509
|
+
this.#withLedgerBatchRead(callback, normalizedPurpose),
|
|
4510
|
+
);
|
|
4511
|
+
if (Object.getPrototypeOf(this) === EvolutionArtifactPorts.prototype) {
|
|
4512
|
+
EVOLUTION_LEDGER_DEFAULT_PUBLICATIONS.set(
|
|
4513
|
+
resolveReadOnly,
|
|
4514
|
+
this.#store.canUseDefaultPublication,
|
|
4515
|
+
);
|
|
4516
|
+
EVOLUTION_LEDGER_WHOLE_HISTORY_READERS.set(
|
|
4517
|
+
resolveReadOnly,
|
|
4518
|
+
Object.freeze({
|
|
4519
|
+
eligible: () =>
|
|
4520
|
+
this.#store.repositoryInstance &&
|
|
4521
|
+
this.#store.list === null &&
|
|
4522
|
+
this.#store.get === null &&
|
|
4523
|
+
this.#store.canUseSingleBoundedRead(),
|
|
4524
|
+
read: Object.freeze((requests) =>
|
|
4525
|
+
this.#resolveLedgerWholeHistory(requests, normalizedPurpose),
|
|
4526
|
+
),
|
|
4527
|
+
}),
|
|
4528
|
+
);
|
|
4529
|
+
}
|
|
3630
4530
|
EVOLUTION_LEDGER_RESOLVER_BINDINGS.set(
|
|
3631
4531
|
resolveReadOnly,
|
|
3632
4532
|
Object.freeze({
|
|
@@ -3670,6 +4570,28 @@ export function captureEvolutionLedgerBatchResolver(value) {
|
|
|
3670
4570
|
return resolve;
|
|
3671
4571
|
}
|
|
3672
4572
|
|
|
4573
|
+
export function captureEvolutionLedgerBatchReadWindow(value) {
|
|
4574
|
+
const read = EVOLUTION_LEDGER_BATCH_READ_WINDOWS.get(value);
|
|
4575
|
+
if (!read)
|
|
4576
|
+
throw new TypeError("a branded ledger artifact resolver is required");
|
|
4577
|
+
return read;
|
|
4578
|
+
}
|
|
4579
|
+
|
|
4580
|
+
// Optional original-implementation capability. Overrides retain the existing
|
|
4581
|
+
// callback/chunk protocol, including its observable integrity-hook phases.
|
|
4582
|
+
export function captureEvolutionLedgerWholeHistoryReader(value) {
|
|
4583
|
+
if (!isEvolutionLedgerArtifactResolver(value)) {
|
|
4584
|
+
throw new TypeError("a branded ledger artifact resolver is required");
|
|
4585
|
+
}
|
|
4586
|
+
const reader = EVOLUTION_LEDGER_WHOLE_HISTORY_READERS.get(value);
|
|
4587
|
+
return reader?.eligible() ? reader.read : null;
|
|
4588
|
+
}
|
|
4589
|
+
|
|
4590
|
+
/** Optional append composition check; grants no publication or read authority. */
|
|
4591
|
+
export function hasEvolutionLedgerDefaultPublication(value) {
|
|
4592
|
+
return EVOLUTION_LEDGER_DEFAULT_PUBLICATIONS.get(value)?.() === true;
|
|
4593
|
+
}
|
|
4594
|
+
|
|
3673
4595
|
/** Private construction identity, independent of matching resolver descriptors. */
|
|
3674
4596
|
export function captureEvolutionLedgerArtifactResolverBinding(value) {
|
|
3675
4597
|
const binding = EVOLUTION_LEDGER_RESOLVER_BINDINGS.get(value);
|