chainlesschain 0.166.86 → 0.166.87
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +1 -1
- package/src/assets/linux-subreaper/linux-arm64/manifest.json +1 -1
- package/src/assets/linux-subreaper/linux-x64/manifest.json +1 -1
- package/src/assets/web-panel/assets/{AIOps-DZwiXAIh.js → AIOps-DdQjhXg9.js} +1 -1
- package/src/assets/web-panel/assets/{ActionButton-aNzXoIJP.js → ActionButton-CuwA3hPA.js} +1 -1
- package/src/assets/web-panel/assets/{Analytics-ETQk4ZXt.js → Analytics-DTVXJSAC.js} +3 -3
- package/src/assets/web-panel/assets/{AppLayout-D5oEbeEC.js → AppLayout-j06JBfEQ.js} +5 -5
- package/src/assets/web-panel/assets/{Artifacts-ja48FAne.js → Artifacts-BFAK8LEW.js} +1 -1
- package/src/assets/web-panel/assets/{Audit-DlN0YnDt.js → Audit-BiGxRpOf.js} +1 -1
- package/src/assets/web-panel/assets/{BackgroundAgents-CzvyNsRg.js → BackgroundAgents-B6uHaPkZ.js} +1 -1
- package/src/assets/web-panel/assets/{Backup-DXgEC2MQ.js → Backup-BNAesB8q.js} +1 -1
- package/src/assets/web-panel/assets/{BaseInput-D9XqejLx.js → BaseInput-BOuetfCd.js} +1 -1
- package/src/assets/web-panel/assets/{Chat-BTwuYUlN.js → Chat-cVzo3m_D.js} +4 -4
- package/src/assets/web-panel/assets/{ChatBubbleRenderer-0anIC2Wn.js → ChatBubbleRenderer-DZ2Ys-CI.js} +1 -1
- package/src/assets/web-panel/assets/{Checkbox-CLuaQbDt.js → Checkbox-BaCTS-H8.js} +1 -1
- package/src/assets/web-panel/assets/{Codegen-KwQUZs8A.js → Codegen-CBE8NiYh.js} +1 -1
- package/src/assets/web-panel/assets/{Col-BCmROkzc.js → Col-C9wLiUTN.js} +1 -1
- package/src/assets/web-panel/assets/{Community-CXlUEHR4.js → Community-jdClBSIp.js} +1 -1
- package/src/assets/web-panel/assets/{Compact-Dzx-lWI2.js → Compact-BIjauIlM.js} +1 -1
- package/src/assets/web-panel/assets/{Compliance-CfXW82FF.js → Compliance-CZ-AoaNj.js} +1 -1
- package/src/assets/web-panel/assets/{Cowork-DIISTRm2.js → Cowork-BbTKtDS4.js} +2 -2
- package/src/assets/web-panel/assets/{Cron-nyJ-KcFE.js → Cron-ImKdtwNi.js} +2 -2
- package/src/assets/web-panel/assets/{Crosschain-Dtv1E4cV.js → Crosschain-B54GSHf5.js} +1 -1
- package/src/assets/web-panel/assets/{DID-BCTpektS.js → DID-BQt5WUzs.js} +2 -2
- package/src/assets/web-panel/assets/{Dashboard-C1Q_m0So.js → Dashboard--gNOffif.js} +2 -2
- package/src/assets/web-panel/assets/{Dropdown-BrQ5_PPL.js → Dropdown-342A5YJw.js} +1 -1
- package/src/assets/web-panel/assets/{EmailListRenderer-DjoNlONP.js → EmailListRenderer-CtIZ3ZHd.js} +1 -1
- package/src/assets/web-panel/assets/{EvolutionSettings-DaGaE6bX.js → EvolutionSettings-CyZPoTio.js} +1 -1
- package/src/assets/web-panel/assets/{FamilyGuardDashboard-nJDWBECt.js → FamilyGuardDashboard-B1MCkFEu.js} +1 -1
- package/src/assets/web-panel/assets/{Federation-DhMUY_wG.js → Federation-DZWoERUy.js} +1 -1
- package/src/assets/web-panel/assets/{FormItemContext-CbONJPuc.js → FormItemContext-Onmb0mp3.js} +1 -1
- package/src/assets/web-panel/assets/{GenericCardRenderer-CDdX4dX-.js → GenericCardRenderer-u1qhNNc7.js} +1 -1
- package/src/assets/web-panel/assets/{Git-DXbDVJdR.js → Git-BEowR1C5.js} +2 -2
- package/src/assets/web-panel/assets/{Governance-DbAfAvs1.js → Governance-BiYd3AAg.js} +1 -1
- package/src/assets/web-panel/assets/{Inference-Dy-fGea2.js → Inference-CsC4Vjh5.js} +1 -1
- package/src/assets/web-panel/assets/{KnowledgeGraph-DAxRpGUF.js → KnowledgeGraph-BXA7ikPM.js} +1 -1
- package/src/assets/web-panel/assets/{Logs-5L1UCR8X.js → Logs-C9WoplQm.js} +2 -2
- package/src/assets/web-panel/assets/{MarkdownRenderer-u0FPafvT.js → MarkdownRenderer-DecEsXsy.js} +1 -1
- package/src/assets/web-panel/assets/{Marketplace-DUk_McDG.js → Marketplace-CCoJBfRL.js} +1 -1
- package/src/assets/web-panel/assets/{McpTools-B6PNsZ_p.js → McpTools-CzaOWBoA.js} +3 -3
- package/src/assets/web-panel/assets/{Memory-BUUs8j5F.js → Memory-BPwh7u1-.js} +2 -2
- package/src/assets/web-panel/assets/{MobileBridge-pXRsgyuO.js → MobileBridge-Bk47v6z2.js} +1 -1
- package/src/assets/web-panel/assets/{MobileProjects-D7v-2Hwm.js → MobileProjects-D-CY--Sb.js} +1 -1
- package/src/assets/web-panel/assets/{Mtc-Deaj4faB.js → Mtc-ExVyAGlW.js} +4 -4
- package/src/assets/web-panel/assets/{MtcAudit-wUV0VUH1.js → MtcAudit-Rrw10csE.js} +5 -5
- package/src/assets/web-panel/assets/{Multisig-C67IIkts.js → Multisig-8TxxwxZZ.js} +3 -3
- package/src/assets/web-panel/assets/{NLProgramming-BCA_CWrW.js → NLProgramming-CNckjtnY.js} +1 -1
- package/src/assets/web-panel/assets/{Notes-D9eu14hQ.js → Notes-C4j6cZTu.js} +3 -3
- package/src/assets/web-panel/assets/{NotificationSettings-Cnd-J4LM.js → NotificationSettings-CchU8rWd.js} +1 -1
- package/src/assets/web-panel/assets/{OrderTableRenderer-C4pHUuyZ.js → OrderTableRenderer-ZJBoyJ16.js} +1 -1
- package/src/assets/web-panel/assets/{Organization-4YExAbnp.js → Organization-F0Je7EdZ.js} +4 -4
- package/src/assets/web-panel/assets/{Overflow-BF-wFuZz.js → Overflow-B2k-JA3u.js} +1 -1
- package/src/assets/web-panel/assets/{P2P-CdiajqCT.js → P2P-yui9Xxe6.js} +2 -2
- package/src/assets/web-panel/assets/{PdhVaultBrowser-BkjfS6Th.js → PdhVaultBrowser-CegM7hT6.js} +3 -3
- package/src/assets/web-panel/assets/{Permissions-DrZ4D78_.js → Permissions-CSFL0uvh.js} +4 -4
- package/src/assets/web-panel/assets/{PersonalDataHub-lDc6OXjz.js → PersonalDataHub-BrMcd-vr.js} +5 -5
- package/src/assets/web-panel/assets/{Pipeline-DqDS9ON1.js → Pipeline-fzrliaVP.js} +1 -1
- package/src/assets/web-panel/assets/{Privacy-BMekZRZu.js → Privacy-BrkUjJ_g.js} +1 -1
- package/src/assets/web-panel/assets/{ProjectInit-LYS7NFvJ.js → ProjectInit-CF_QCsW9.js} +2 -2
- package/src/assets/web-panel/assets/{ProjectSettings-CcJNHk9A.js → ProjectSettings-Duwrd8aC.js} +2 -2
- package/src/assets/web-panel/assets/{Projects-DrfAHgdF.js → Projects-B0dLVXzU.js} +1 -1
- package/src/assets/web-panel/assets/{Providers-aqsly9SR.js → Providers-Db8iS1xQ.js} +1 -1
- package/src/assets/web-panel/assets/{QrScannerModal-ZWvR3zqg.js → QrScannerModal-CHbNzEQs.js} +1 -1
- package/src/assets/web-panel/assets/{QuickAsk-BT5CgsvW.js → QuickAsk-CIESMfeR.js} +1 -1
- package/src/assets/web-panel/assets/{Recommend-Cz8Dz2RN.js → Recommend-BTMbCMtR.js} +1 -1
- package/src/assets/web-panel/assets/{RemoteSession-BlxPVbtQ.js → RemoteSession-DavyK6J-.js} +3 -3
- package/src/assets/web-panel/assets/{Reputation-B3VGBE-1.js → Reputation-BFKtMelA.js} +1 -1
- package/src/assets/web-panel/assets/{Row-Bwv7vSEA.js → Row-BI9AZUvw.js} +1 -1
- package/src/assets/web-panel/assets/{RssFeed-DHI085r_.js → RssFeed-1nwXt8NY.js} +2 -2
- package/src/assets/web-panel/assets/{Search-DBler5T0.js → Search-Ck8PDYbk.js} +1 -1
- package/src/assets/web-panel/assets/{Security-BZdw43NW.js → Security-BgDqpCbq.js} +3 -3
- package/src/assets/web-panel/assets/{Services-Cslk5lUj.js → Services-DF8anXm4.js} +2 -2
- package/src/assets/web-panel/assets/{Skeleton-Bur-C-25.js → Skeleton-CoHyY4ZX.js} +1 -1
- package/src/assets/web-panel/assets/{Skills-v0zgLy0X.js → Skills-dOaD4Ztl.js} +1 -1
- package/src/assets/web-panel/assets/{Sla-B1OWgQ51.js → Sla-XuOawvQY.js} +1 -1
- package/src/assets/web-panel/assets/{SpeechSettings-CaBcEaV4.js → SpeechSettings-DFJ7nAiu.js} +1 -1
- package/src/assets/web-panel/assets/{SyncSettings-CBbG8L6J.js → SyncSettings-B2xYj-Pi.js} +2 -2
- package/src/assets/web-panel/assets/{Tasks-C7EjXrnN.js → Tasks-D_SzNTLi.js} +1 -1
- package/src/assets/web-panel/assets/{Templates-VBBOZ1H5.js → Templates-BXFmKh7W.js} +1 -1
- package/src/assets/web-panel/assets/{Tenant-CtQTj7IF.js → Tenant-CRg2AXbd.js} +1 -1
- package/src/assets/web-panel/assets/{Terminal-DXVTQjnl.js → Terminal-DQgxnuep.js} +2 -2
- package/src/assets/web-panel/assets/{TimelineRenderer-BRIno0aN.js → TimelineRenderer-BrPQhQVc.js} +1 -1
- package/src/assets/web-panel/assets/{Tokens-uiiAfkYN.js → Tokens-RByy4USy.js} +1 -1
- package/src/assets/web-panel/assets/{Trigger-CPApN8o6.js → Trigger-pE3y8u73.js} +1 -1
- package/src/assets/web-panel/assets/{Trust-T2jKVwCE.js → Trust-DhID9C33.js} +1 -1
- package/src/assets/web-panel/assets/{UkeySign-C2U_gbzP.js → UkeySign-CPxEPNBH.js} +1 -1
- package/src/assets/web-panel/assets/{VideoEditing-BCdlr3Kj.js → VideoEditing-ZD8DUxeQ.js} +1 -1
- package/src/assets/web-panel/assets/{Wallet-CWnjoSc1.js → Wallet-Br9JREEd.js} +4 -4
- package/src/assets/web-panel/assets/{WebAuthn-CqSbYqHq.js → WebAuthn-Cn-YacJg.js} +4 -4
- package/src/assets/web-panel/assets/{WorkflowEditor-Dr_I3jcr.js → WorkflowEditor-BTu0oqMp.js} +1 -1
- package/src/assets/web-panel/assets/{chat-Cg21FKdE.js → chat-CEjws_QF.js} +1 -1
- package/src/assets/web-panel/assets/{colors-CK85tY9e.js → colors-Bv2RNebW.js} +1 -1
- package/src/assets/web-panel/assets/{compact-item-m-XYPC7W.js → compact-item-gSc3EofP.js} +1 -1
- package/src/assets/web-panel/assets/{createContext-BmiEeNvs.js → createContext-kyFDaqWC.js} +1 -1
- package/src/assets/web-panel/assets/devWarning-Dcc8pILJ.js +1 -0
- package/src/assets/web-panel/assets/{hasIn-CCw4KAoc.js → hasIn-BuMWcjhx.js} +1 -1
- package/src/assets/web-panel/assets/{index-ByVvAXdl.js → index--nrQmk8X.js} +1 -1
- package/src/assets/web-panel/assets/index-05NB95_V.js +1 -0
- package/src/assets/web-panel/assets/{index-BIOTRzaF.js → index-42PTBO7a.js} +1 -1
- package/src/assets/web-panel/assets/{index-8KzhlvJ9.js → index-BCS5j4SQ.js} +1 -1
- package/src/assets/web-panel/assets/{index-lhoexBDH.js → index-BSSIJ-rv.js} +1 -1
- package/src/assets/web-panel/assets/{index-6jXf-Lln.js → index-BUOCcXou.js} +1 -1
- package/src/assets/web-panel/assets/{index-D7gS_TJl.js → index-Bg5jis0q.js} +1 -1
- package/src/assets/web-panel/assets/{index-BtvVupUL.js → index-C-LPpwe9.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGb1j2z9.js → index-C56Drzol.js} +1 -1
- package/src/assets/web-panel/assets/{index-DpIy2HzE.js → index-C7Jo20JB.js} +1 -1
- package/src/assets/web-panel/assets/{index-Cf5arJie.js → index-C9U9SjcT.js} +1 -1
- package/src/assets/web-panel/assets/{index-CBcgIWOe.js → index-CF3TjDNz.js} +1 -1
- package/src/assets/web-panel/assets/{index-J81uvUMN.js → index-CMS3Mk93.js} +1 -1
- package/src/assets/web-panel/assets/{index-BGUYgc96.js → index-CNt2X-aK.js} +1 -1
- package/src/assets/web-panel/assets/index-Cb6q8wlI.js +1 -0
- package/src/assets/web-panel/assets/{index-CN2xbLGz.js → index-Cj4RA7oA.js} +1 -1
- package/src/assets/web-panel/assets/{index-D1sU5-XA.js → index-CkfvP2dN.js} +1 -1
- package/src/assets/web-panel/assets/{index-DmSgfXuz.js → index-Cm8K8Evn.js} +1 -1
- package/src/assets/web-panel/assets/{index-DMbrl43d.js → index-CvSuM_CB.js} +1 -1
- package/src/assets/web-panel/assets/{index-B7xJkhtJ.js → index-Czo_hw97.js} +1 -1
- package/src/assets/web-panel/assets/{index-BvjC3t35.js → index-D3m3EXbL.js} +1 -1
- package/src/assets/web-panel/assets/{index-DAlGVix-.js → index-DR561Sqc.js} +1 -1
- package/src/assets/web-panel/assets/{index-B8IC9ZeZ.js → index-DRa4R7vv.js} +1 -1
- package/src/assets/web-panel/assets/{index-yYmjkf38.js → index-DYKSuD02.js} +3 -3
- package/src/assets/web-panel/assets/{index-Br9t_I1X.js → index-DYcPWVN9.js} +1 -1
- package/src/assets/web-panel/assets/{index-Bv90WEWa.js → index-DbVUA5l3.js} +1 -1
- package/src/assets/web-panel/assets/{index-BiVPFsat.js → index-DbkyTMj-.js} +1 -1
- package/src/assets/web-panel/assets/{index-IskAKXhx.js → index-Dej4AGOz.js} +1 -1
- package/src/assets/web-panel/assets/{index-CYEzWoyZ.js → index-EpxWlmip.js} +1 -1
- package/src/assets/web-panel/assets/{index-Br0ZNdu1.js → index-GpvWtvw3.js} +1 -1
- package/src/assets/web-panel/assets/{index-DIlGxS2W.js → index-NQ0c4hJ1.js} +1 -1
- package/src/assets/web-panel/assets/{index-sWUmvT8H.js → index-OjZUHz5z.js} +1 -1
- package/src/assets/web-panel/assets/{index-OIbfsi-o.js → index-U-ytt84A.js} +1 -1
- package/src/assets/web-panel/assets/{index-BUYyhYP6.js → index-_0bfDTDu.js} +1 -1
- package/src/assets/web-panel/assets/{index-DxRhnsEC.js → index-jmMAqBAm.js} +1 -1
- package/src/assets/web-panel/assets/{index-DI9GJFcn.js → index-nLemnBZ2.js} +1 -1
- package/src/assets/web-panel/assets/{index-BStyjmLG.js → index-q65c1kp8.js} +1 -1
- package/src/assets/web-panel/assets/{index-BHsiSoGY.js → index-sJ1F5Txa.js} +1 -1
- package/src/assets/web-panel/assets/{index-BcfVHmFK.js → index-xfS_WO9b.js} +1 -1
- package/src/assets/web-panel/assets/{initDefaultProps-Cc1XOmNe.js → initDefaultProps-B-wqiSu7.js} +1 -1
- package/src/assets/web-panel/assets/{motion-BehsX88u.js → motion-BK4_B4MD.js} +1 -1
- package/src/assets/web-panel/assets/{move-BPx2YsqY.js → move-BU2TgDDO.js} +1 -1
- package/src/assets/web-panel/assets/{mtc-parser-BSFJgFQA.js → mtc-parser-DA3E4_EX.js} +1 -1
- package/src/assets/web-panel/assets/{omit-DBN5KUeo.js → omit-CaHz1e_q.js} +1 -1
- package/src/assets/web-panel/assets/{pickAttrs-BDK1yZ-9.js → pickAttrs-NXZ19amf.js} +1 -1
- package/src/assets/web-panel/assets/{placementArrow-C4rMmKNJ.js → placementArrow-BCXI9AHO.js} +1 -1
- package/src/assets/web-panel/assets/{responsiveObserve-BoeoOnxX.js → responsiveObserve-DdlZ9tLk.js} +1 -1
- package/src/assets/web-panel/assets/{slide-BKlr1bl9.js → slide-DCZm_Vc4.js} +1 -1
- package/src/assets/web-panel/assets/{statusUtils-Ky1apWPI.js → statusUtils-CuEULKQr.js} +1 -1
- package/src/assets/web-panel/assets/{styleChecker-DxxR7uXk.js → styleChecker-Bo_UXG8x.js} +1 -1
- package/src/assets/web-panel/assets/{useFlexGapSupport-DmsUqVe9.js → useFlexGapSupport-B9PaTcFH.js} +1 -1
- package/src/assets/web-panel/assets/{useFs-CwkG7sOg.js → useFs-DwSTGdVR.js} +1 -1
- package/src/assets/web-panel/assets/{usePersonalDataHub-CddgeEfF.js → usePersonalDataHub-FswSxnDC.js} +1 -1
- package/src/assets/web-panel/assets/{vnode-C_-H8i4Y.js → vnode-EUztvYTj.js} +1 -1
- package/src/assets/web-panel/assets/{zoom-aAsHUMNE.js → zoom-D41MAgV-.js} +1 -1
- package/src/assets/web-panel/index.html +1 -1
- package/src/command-help-index.json +1 -1
- package/src/commands/eval.js +118 -18
- package/src/commands/memory.js +34 -15
- package/src/commands/sandbox.js +9 -0
- package/src/data/changelog.json +13 -0
- package/src/lib/causal-observability.js +6 -45
- package/src/lib/context-memory-kernel/durable-memory-port.js +49 -10
- package/src/lib/context-memory-kernel/memory-query-index.js +278 -0
- package/src/lib/context-memory-kernel/memory-service.js +17 -2
- package/src/lib/context-memory-kernel/segmented-memory-port.js +277 -9
- package/src/lib/eval/outcomes.js +20 -10
- package/src/lib/eval/runner.js +49 -5
- package/src/lib/eval/tasks.js +8 -3
- package/src/lib/eval/verify01-contracts.js +349 -0
- package/src/lib/eval/verify01-execution.js +605 -0
- package/src/lib/execution-support.js +58 -0
- package/src/lib/llm-pricing.js +98 -11
- package/src/lib/model-catalog-review.js +116 -0
- package/src/lib/model-context-catalog.js +23 -1
- package/src/lib/openai-responses.js +39 -0
- package/src/lib/process-execution-broker/index.js +8 -0
- package/src/lib/process-execution-broker/linux-subreaper-process.js +24 -2
- package/src/lib/process-execution-broker/process-ownership-journal.js +168 -7
- package/src/lib/process-execution-broker/process-ownership-quarantine.js +56 -1
- package/src/lib/process-execution-broker/process-recovery-cgroup.js +272 -0
- package/src/runtime/agent-core.js +9 -24
- package/src/runtime/permission-authority-host.js +14 -0
- package/src/assets/web-panel/assets/devWarning-CkMEODFC.js +0 -1
- package/src/assets/web-panel/assets/index-BjE5qaKn.js +0 -1
- package/src/assets/web-panel/assets/index-CsFH-2Zk.js +0 -1
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
import { createHash } from "node:crypto";
|
|
2
|
+
|
|
3
|
+
export const MEMORY_QUERY_INDEX_SCHEMA = "chainlesschain.memory-query-index/v1";
|
|
4
|
+
const CURSOR_SCHEMA = "chainlesschain.memory-list-cursor/v1";
|
|
5
|
+
|
|
6
|
+
export function queryDigest(bytes) {
|
|
7
|
+
return `sha256:${createHash("sha256").update(bytes).digest("hex")}`;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function compareMemoryRows(left, right) {
|
|
11
|
+
return (
|
|
12
|
+
right.importance - left.importance ||
|
|
13
|
+
right.updatedAt.localeCompare(left.updatedAt, "en") ||
|
|
14
|
+
left.memoryId.localeCompare(right.memoryId, "en")
|
|
15
|
+
);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// No content, evidence, or audit is copied into the derived index. The manifest
|
|
19
|
+
// binds these bytes to the immutable authority shard from which they were made.
|
|
20
|
+
export function encodeQueryIndex(state) {
|
|
21
|
+
const rows = Object.values(state.records)
|
|
22
|
+
.map((record) => ({
|
|
23
|
+
memoryId: record.memoryId,
|
|
24
|
+
category: record.category,
|
|
25
|
+
scope: record.scope,
|
|
26
|
+
scopeId: record.scopeId ?? null,
|
|
27
|
+
state: record.state,
|
|
28
|
+
importance: record.importance,
|
|
29
|
+
updatedAt: record.updatedAt,
|
|
30
|
+
tags: record.tags,
|
|
31
|
+
allowedSinks: record.allowedSinks,
|
|
32
|
+
}))
|
|
33
|
+
.sort(compareMemoryRows);
|
|
34
|
+
return Buffer.from(
|
|
35
|
+
JSON.stringify({
|
|
36
|
+
schema: MEMORY_QUERY_INDEX_SCHEMA,
|
|
37
|
+
shardDigest: state.digest,
|
|
38
|
+
rows,
|
|
39
|
+
}),
|
|
40
|
+
);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function scopeKey(scope, scopeId) {
|
|
44
|
+
return JSON.stringify([scope, scopeId ?? null]);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
export function compileQueryIndex(bytes, descriptor) {
|
|
48
|
+
const value = JSON.parse(bytes.toString("utf8"));
|
|
49
|
+
if (
|
|
50
|
+
value.schema !== MEMORY_QUERY_INDEX_SCHEMA ||
|
|
51
|
+
value.shardDigest !== descriptor.digest ||
|
|
52
|
+
!Array.isArray(value.rows) ||
|
|
53
|
+
value.rows.length !== descriptor.recordCount
|
|
54
|
+
) {
|
|
55
|
+
throw new Error("query index does not match authority shard");
|
|
56
|
+
}
|
|
57
|
+
const fields = ["category", "scope", "state", "tag", "sink"];
|
|
58
|
+
const postings = Object.fromEntries(
|
|
59
|
+
fields.map((field) => [field, new Map()]),
|
|
60
|
+
);
|
|
61
|
+
const add = (field, key, index) => {
|
|
62
|
+
const map = postings[field];
|
|
63
|
+
if (!map.has(key)) map.set(key, []);
|
|
64
|
+
map.get(key).push(index);
|
|
65
|
+
};
|
|
66
|
+
value.rows.forEach((row, index) => {
|
|
67
|
+
add("category", row.category, index);
|
|
68
|
+
add("scope", scopeKey(row.scope, row.scopeId), index);
|
|
69
|
+
add("state", row.state, index);
|
|
70
|
+
for (const tag of row.tags) add("tag", tag, index);
|
|
71
|
+
for (const sink of row.allowedSinks) add("sink", sink, index);
|
|
72
|
+
});
|
|
73
|
+
return { rows: value.rows, postings };
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export function normalizeListQuery(options = {}) {
|
|
77
|
+
const result = { includeTombstones: options.includeTombstones === true };
|
|
78
|
+
if (options.category) result.category = String(options.category);
|
|
79
|
+
if (options.sink) result.sink = String(options.sink);
|
|
80
|
+
if (options.scopeAdmissions !== undefined) {
|
|
81
|
+
if (!Array.isArray(options.scopeAdmissions))
|
|
82
|
+
throw new TypeError("scopeAdmissions must be an array");
|
|
83
|
+
result.scopeAdmissions = options.scopeAdmissions.map(
|
|
84
|
+
({ scope, scopeId }) => ({
|
|
85
|
+
scope,
|
|
86
|
+
scopeId: scopeId ?? null,
|
|
87
|
+
}),
|
|
88
|
+
);
|
|
89
|
+
}
|
|
90
|
+
if (options.tags !== undefined) {
|
|
91
|
+
if (!Array.isArray(options.tags))
|
|
92
|
+
throw new TypeError("tags must be an array");
|
|
93
|
+
result.tags = [...new Set(options.tags.map(String))].sort();
|
|
94
|
+
}
|
|
95
|
+
if (options.states !== undefined) {
|
|
96
|
+
if (!Array.isArray(options.states))
|
|
97
|
+
throw new TypeError("states must be an array");
|
|
98
|
+
result.states = [...new Set(options.states.map(String))].sort();
|
|
99
|
+
}
|
|
100
|
+
return result;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
export function matchesListQuery(row, query) {
|
|
104
|
+
return (
|
|
105
|
+
(query.includeTombstones || !["deleted", "purged"].includes(row.state)) &&
|
|
106
|
+
(!query.category || row.category === query.category) &&
|
|
107
|
+
(!query.states || query.states.includes(row.state)) &&
|
|
108
|
+
(!query.scopeAdmissions ||
|
|
109
|
+
query.scopeAdmissions.some(
|
|
110
|
+
(scope) =>
|
|
111
|
+
scope.scope === row.scope &&
|
|
112
|
+
(scope.scopeId ?? null) === (row.scopeId ?? null),
|
|
113
|
+
)) &&
|
|
114
|
+
(!query.sink ||
|
|
115
|
+
row.allowedSinks.includes("*") ||
|
|
116
|
+
row.allowedSinks.includes(query.sink)) &&
|
|
117
|
+
(!query.tags?.length || row.tags.some((tag) => query.tags.includes(tag)))
|
|
118
|
+
);
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
// Each posting is already in the public list order. Pick the narrowest posting
|
|
122
|
+
// before applying the remaining predicates; no full-record scan or top-k before
|
|
123
|
+
// permission filtering. Recall uses these gates but retains kernel ranking.
|
|
124
|
+
export function* selectQueryRows(index, query, after = null) {
|
|
125
|
+
const candidates = [];
|
|
126
|
+
const union = (field, keys) => {
|
|
127
|
+
const lists = keys
|
|
128
|
+
.map((key) => index.postings[field].get(key))
|
|
129
|
+
.filter(Boolean);
|
|
130
|
+
return lists.length < 2
|
|
131
|
+
? lists[0] || []
|
|
132
|
+
: [...new Set(lists.flat())].sort((a, b) => a - b);
|
|
133
|
+
};
|
|
134
|
+
if (query.category)
|
|
135
|
+
candidates.push(index.postings.category.get(query.category) || []);
|
|
136
|
+
if (query.scopeAdmissions)
|
|
137
|
+
candidates.push(
|
|
138
|
+
union(
|
|
139
|
+
"scope",
|
|
140
|
+
query.scopeAdmissions.map((scope) =>
|
|
141
|
+
scopeKey(scope.scope, scope.scopeId),
|
|
142
|
+
),
|
|
143
|
+
),
|
|
144
|
+
);
|
|
145
|
+
if (query.states) candidates.push(union("state", query.states));
|
|
146
|
+
if (query.tags?.length) candidates.push(union("tag", query.tags));
|
|
147
|
+
if (query.sink) candidates.push(union("sink", ["*", query.sink]));
|
|
148
|
+
candidates.sort((a, b) => a.length - b.length);
|
|
149
|
+
const positions = candidates[0];
|
|
150
|
+
const length = positions ? positions.length : index.rows.length;
|
|
151
|
+
const rowAt = (offset) => index.rows[positions ? positions[offset] : offset];
|
|
152
|
+
let start = 0;
|
|
153
|
+
if (after) {
|
|
154
|
+
let end = length;
|
|
155
|
+
while (start < end) {
|
|
156
|
+
const middle = Math.floor((start + end) / 2);
|
|
157
|
+
if (compareMemoryRows(rowAt(middle), after) <= 0) start = middle + 1;
|
|
158
|
+
else end = middle;
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
for (let offset = start; offset < length; offset++) {
|
|
162
|
+
const row = rowAt(offset);
|
|
163
|
+
if (matchesListQuery(row, query)) yield row;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
export function* mergeQueryRows(heads) {
|
|
168
|
+
const heap = [];
|
|
169
|
+
const less = (left, right) => compareMemoryRows(left.row, right.row) < 0;
|
|
170
|
+
const push = (head) => {
|
|
171
|
+
let at = heap.length;
|
|
172
|
+
heap.push(head);
|
|
173
|
+
while (at > 0) {
|
|
174
|
+
const parent = Math.floor((at - 1) / 2);
|
|
175
|
+
if (!less(head, heap[parent])) break;
|
|
176
|
+
heap[at] = heap[parent];
|
|
177
|
+
at = parent;
|
|
178
|
+
}
|
|
179
|
+
heap[at] = head;
|
|
180
|
+
};
|
|
181
|
+
for (const head of heads) push(head);
|
|
182
|
+
while (heap.length) {
|
|
183
|
+
const first = heap[0];
|
|
184
|
+
const last = heap.pop();
|
|
185
|
+
if (heap.length) {
|
|
186
|
+
let at = 0;
|
|
187
|
+
while (at * 2 + 1 < heap.length) {
|
|
188
|
+
let child = at * 2 + 1;
|
|
189
|
+
if (child + 1 < heap.length && less(heap[child + 1], heap[child]))
|
|
190
|
+
child++;
|
|
191
|
+
if (!less(heap[child], last)) break;
|
|
192
|
+
heap[at] = heap[child];
|
|
193
|
+
at = child;
|
|
194
|
+
}
|
|
195
|
+
heap[at] = last;
|
|
196
|
+
}
|
|
197
|
+
yield { bucket: first.bucket, row: first.row };
|
|
198
|
+
const next = first.iterator.next();
|
|
199
|
+
if (!next.done) push({ ...first, row: next.value });
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
export function cursorError(reason) {
|
|
204
|
+
return Object.assign(new Error(`Memory list cursor is invalid: ${reason}`), {
|
|
205
|
+
code: "CONTEXT_MEMORY_CURSOR_INVALID",
|
|
206
|
+
});
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
export function listCursorBinding(filePath, generation, query) {
|
|
210
|
+
return queryDigest(
|
|
211
|
+
JSON.stringify({
|
|
212
|
+
filePath,
|
|
213
|
+
generation,
|
|
214
|
+
query,
|
|
215
|
+
order: "importance-desc,updatedAt-desc,memoryId-en-asc/v1",
|
|
216
|
+
}),
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export function decodeListCursor(cursor, binding) {
|
|
221
|
+
if (cursor == null) return null;
|
|
222
|
+
if (
|
|
223
|
+
typeof cursor !== "string" ||
|
|
224
|
+
cursor.length > 4096 ||
|
|
225
|
+
!/^[\w-]+$/u.test(cursor)
|
|
226
|
+
)
|
|
227
|
+
throw cursorError("encoding");
|
|
228
|
+
let decoded;
|
|
229
|
+
try {
|
|
230
|
+
decoded = JSON.parse(Buffer.from(cursor, "base64url").toString("utf8"));
|
|
231
|
+
} catch {
|
|
232
|
+
throw cursorError("encoding");
|
|
233
|
+
}
|
|
234
|
+
if (
|
|
235
|
+
!decoded ||
|
|
236
|
+
decoded.schema !== CURSOR_SCHEMA ||
|
|
237
|
+
decoded.binding !== binding ||
|
|
238
|
+
!decoded.after ||
|
|
239
|
+
typeof decoded.after.memoryId !== "string" ||
|
|
240
|
+
typeof decoded.after.updatedAt !== "string" ||
|
|
241
|
+
!Number.isFinite(decoded.after.importance) ||
|
|
242
|
+
decoded.digest !==
|
|
243
|
+
queryDigest(
|
|
244
|
+
JSON.stringify({
|
|
245
|
+
schema: decoded.schema,
|
|
246
|
+
binding: decoded.binding,
|
|
247
|
+
after: decoded.after,
|
|
248
|
+
}),
|
|
249
|
+
)
|
|
250
|
+
) {
|
|
251
|
+
throw cursorError("generation, filters, or checksum changed");
|
|
252
|
+
}
|
|
253
|
+
return decoded.after;
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
export function encodeListCursor(binding, row) {
|
|
257
|
+
const value = {
|
|
258
|
+
schema: CURSOR_SCHEMA,
|
|
259
|
+
binding,
|
|
260
|
+
after: {
|
|
261
|
+
memoryId: row.memoryId,
|
|
262
|
+
updatedAt: row.updatedAt,
|
|
263
|
+
importance: row.importance,
|
|
264
|
+
},
|
|
265
|
+
};
|
|
266
|
+
return Buffer.from(
|
|
267
|
+
JSON.stringify({ ...value, digest: queryDigest(JSON.stringify(value)) }),
|
|
268
|
+
).toString("base64url");
|
|
269
|
+
}
|
|
270
|
+
|
|
271
|
+
export function pageLimit(value, fallback = 20) {
|
|
272
|
+
const number = value == null ? fallback : Number(value);
|
|
273
|
+
if (!Number.isSafeInteger(number) || number < 1 || number > 10000)
|
|
274
|
+
throw new RangeError(
|
|
275
|
+
"memory page limit must be an integer from 1 to 10000",
|
|
276
|
+
);
|
|
277
|
+
return number;
|
|
278
|
+
}
|
|
@@ -403,14 +403,29 @@ export class CliCanonicalMemoryService {
|
|
|
403
403
|
return result;
|
|
404
404
|
}
|
|
405
405
|
|
|
406
|
-
async list({ limit = 20, category } = {}) {
|
|
407
|
-
const records = await this.runtime.memoryPort.listRecords(
|
|
406
|
+
async list({ limit = 20, category, scopeAdmissions, sink, tags } = {}) {
|
|
407
|
+
const records = await this.runtime.memoryPort.listRecords({
|
|
408
|
+
limit: Math.max(1, Number(limit) || 20),
|
|
409
|
+
category,
|
|
410
|
+
scopeAdmissions,
|
|
411
|
+
sink,
|
|
412
|
+
tags,
|
|
413
|
+
});
|
|
408
414
|
return records
|
|
409
415
|
.filter((record) => !category || record.category === category)
|
|
410
416
|
.slice(0, Math.max(1, Number(limit) || 20))
|
|
411
417
|
.map((record) => publicEntry(record));
|
|
412
418
|
}
|
|
413
419
|
|
|
420
|
+
async listPage(options = {}) {
|
|
421
|
+
const page = await this.runtime.memoryPort.listPage(options);
|
|
422
|
+
return {
|
|
423
|
+
entries: page.records.map((record) => publicEntry(record)),
|
|
424
|
+
nextCursor: page.nextCursor,
|
|
425
|
+
storeRevision: page.storeRevision,
|
|
426
|
+
};
|
|
427
|
+
}
|
|
428
|
+
|
|
414
429
|
async add(content, options = {}) {
|
|
415
430
|
const now = new Date(Number(this.now())).toISOString();
|
|
416
431
|
const evidenceId = `add-${canonicalDigest(
|
|
@@ -26,6 +26,20 @@ import {
|
|
|
26
26
|
stateDigest,
|
|
27
27
|
syncDirectory,
|
|
28
28
|
} from "./durable-memory-port.js";
|
|
29
|
+
import {
|
|
30
|
+
compareMemoryRows,
|
|
31
|
+
compileQueryIndex,
|
|
32
|
+
decodeListCursor,
|
|
33
|
+
encodeListCursor,
|
|
34
|
+
encodeQueryIndex,
|
|
35
|
+
listCursorBinding,
|
|
36
|
+
matchesListQuery,
|
|
37
|
+
mergeQueryRows,
|
|
38
|
+
normalizeListQuery,
|
|
39
|
+
pageLimit,
|
|
40
|
+
queryDigest,
|
|
41
|
+
selectQueryRows,
|
|
42
|
+
} from "./memory-query-index.js";
|
|
29
43
|
|
|
30
44
|
export const SEGMENTED_STORE_SCHEMA =
|
|
31
45
|
"chainlesschain.cli-context-memory-store/v2";
|
|
@@ -147,6 +161,7 @@ export class SegmentedMemoryPort extends DurableJsonMemoryPort {
|
|
|
147
161
|
this.readOnly = Boolean(readOnly);
|
|
148
162
|
this.filePath = resolve(this.filePath);
|
|
149
163
|
this.shardDirectory = `${this.filePath}.shards`;
|
|
164
|
+
this._queryIndexes = new Map();
|
|
150
165
|
}
|
|
151
166
|
|
|
152
167
|
_locked(operation) {
|
|
@@ -194,6 +209,13 @@ export class SegmentedMemoryPort extends DurableJsonMemoryPort {
|
|
|
194
209
|
);
|
|
195
210
|
}
|
|
196
211
|
|
|
212
|
+
_queryIndexPath(bucket, descriptor) {
|
|
213
|
+
return join(
|
|
214
|
+
this.shardDirectory,
|
|
215
|
+
`query-${bucket}-${descriptor.queryDigest.slice(7)}.json`,
|
|
216
|
+
);
|
|
217
|
+
}
|
|
218
|
+
|
|
197
219
|
_validateManifest(input) {
|
|
198
220
|
const invalid = () => {
|
|
199
221
|
throw corruptStore(this.filePath, "segmented manifest is invalid");
|
|
@@ -219,13 +241,23 @@ export class SegmentedMemoryPort extends DurableJsonMemoryPort {
|
|
|
219
241
|
if (
|
|
220
242
|
!BUCKET.test(bucket) ||
|
|
221
243
|
!entry ||
|
|
222
|
-
|
|
223
|
-
"bytes,digest,eventCount,recordCount"
|
|
244
|
+
![
|
|
245
|
+
"bytes,digest,eventCount,recordCount",
|
|
246
|
+
"bytes,digest,eventCount,queryBytes,queryDigest,recordCount",
|
|
247
|
+
].includes(Object.keys(entry).sort().join()) ||
|
|
224
248
|
!HEX.test(entry.digest)
|
|
225
249
|
)
|
|
226
250
|
invalid();
|
|
227
251
|
for (const key of ["bytes", "eventCount", "recordCount"])
|
|
228
252
|
if (!Number.isSafeInteger(entry[key]) || entry[key] < 0) invalid();
|
|
253
|
+
if (
|
|
254
|
+
entry.queryDigest !== undefined &&
|
|
255
|
+
(!HEX.test(entry.queryDigest) ||
|
|
256
|
+
!Number.isSafeInteger(entry.queryBytes) ||
|
|
257
|
+
entry.queryBytes < 1 ||
|
|
258
|
+
entry.queryBytes > this.maxStoreBytes)
|
|
259
|
+
)
|
|
260
|
+
invalid();
|
|
229
261
|
if (entry.bytes > this.maxStoreBytes)
|
|
230
262
|
throw limit("shard exceeds configured byte limit");
|
|
231
263
|
bytes += entry.bytes;
|
|
@@ -241,6 +273,15 @@ export class SegmentedMemoryPort extends DurableJsonMemoryPort {
|
|
|
241
273
|
invalid();
|
|
242
274
|
if (bytes > this.maxTotalBytes || events > this.maxEvents)
|
|
243
275
|
throw limit("aggregate capacity exceeded");
|
|
276
|
+
if (
|
|
277
|
+
bytes +
|
|
278
|
+
Object.values(input.shards).reduce(
|
|
279
|
+
(sum, entry) => sum + (entry.queryBytes || 0),
|
|
280
|
+
0,
|
|
281
|
+
) >
|
|
282
|
+
this.maxTotalBytes
|
|
283
|
+
)
|
|
284
|
+
throw limit("authority and query indexes exceed aggregate capacity");
|
|
244
285
|
return input;
|
|
245
286
|
}
|
|
246
287
|
|
|
@@ -339,9 +380,12 @@ export class SegmentedMemoryPort extends DurableJsonMemoryPort {
|
|
|
339
380
|
_collect(manifest) {
|
|
340
381
|
this._checkDirectories();
|
|
341
382
|
const live = new Set(
|
|
342
|
-
Object.entries(manifest.shards).
|
|
343
|
-
|
|
344
|
-
|
|
383
|
+
Object.entries(manifest.shards).flatMap(([bucket, entry]) => [
|
|
384
|
+
`${bucket}-${entry.digest.slice(7)}.json`,
|
|
385
|
+
...(entry.queryDigest
|
|
386
|
+
? [`query-${bucket}-${entry.queryDigest.slice(7)}.json`]
|
|
387
|
+
: []),
|
|
388
|
+
]),
|
|
345
389
|
);
|
|
346
390
|
// All readers hold the same authority lock. No reader can still be using
|
|
347
391
|
// a superseded shard here. Unknown files are never treated as our garbage.
|
|
@@ -355,6 +399,8 @@ export class SegmentedMemoryPort extends DurableJsonMemoryPort {
|
|
|
355
399
|
throw limit("shard directory inventory exceeds 4096 entries");
|
|
356
400
|
const owned =
|
|
357
401
|
/^[a-f0-9]{2}-[a-f0-9]{64}\.json$/u.test(entry.name) ||
|
|
402
|
+
/^query-[a-f0-9]{2}-[a-f0-9]{64}\.json$/u.test(entry.name) ||
|
|
403
|
+
/^query-rebuild-[a-f0-9-]{36}\.tmp$/u.test(entry.name) ||
|
|
358
404
|
/^manifest-[a-f0-9-]{36}\.tmp$/u.test(entry.name);
|
|
359
405
|
if (owned && !live.has(entry.name)) {
|
|
360
406
|
const file = join(this.shardDirectory, entry.name);
|
|
@@ -388,7 +434,12 @@ export class SegmentedMemoryPort extends DurableJsonMemoryPort {
|
|
|
388
434
|
for (const [bucket, state] of changed) {
|
|
389
435
|
state.digest = stateDigest(state);
|
|
390
436
|
const bytes = `${JSON.stringify(state)}\n`;
|
|
391
|
-
const
|
|
437
|
+
const unchanged = manifest.shards[bucket]?.digest === state.digest;
|
|
438
|
+
// Canonical digests do not encode JSON property order. During a derived
|
|
439
|
+
// index upgrade retain the original immutable authority bytes verbatim.
|
|
440
|
+
const size = unchanged
|
|
441
|
+
? manifest.shards[bucket].bytes
|
|
442
|
+
: Buffer.byteLength(bytes);
|
|
392
443
|
if (size > this.maxStoreBytes)
|
|
393
444
|
throw limit("shard exceeds configured byte limit");
|
|
394
445
|
pendingBytes += size - (next.shards[bucket]?.bytes || 0);
|
|
@@ -402,9 +453,19 @@ export class SegmentedMemoryPort extends DurableJsonMemoryPort {
|
|
|
402
453
|
eventCount: state.events.length,
|
|
403
454
|
recordCount: Object.keys(state.records).length,
|
|
404
455
|
};
|
|
456
|
+
const queryBytes = encodeQueryIndex(state);
|
|
457
|
+
if (queryBytes.length > this.maxStoreBytes)
|
|
458
|
+
throw limit("query index exceeds configured byte limit");
|
|
459
|
+
next.shards[bucket].queryDigest = queryDigest(queryBytes);
|
|
460
|
+
next.shards[bucket].queryBytes = queryBytes.length;
|
|
461
|
+
if (!unchanged)
|
|
462
|
+
pending.push({
|
|
463
|
+
file: this._shardPath(bucket, next.shards[bucket]),
|
|
464
|
+
bytes,
|
|
465
|
+
});
|
|
405
466
|
pending.push({
|
|
406
|
-
file: this.
|
|
407
|
-
bytes,
|
|
467
|
+
file: this._queryIndexPath(bucket, next.shards[bucket]),
|
|
468
|
+
bytes: queryBytes,
|
|
408
469
|
});
|
|
409
470
|
}
|
|
410
471
|
next.totalBytes = Object.values(next.shards).reduce(
|
|
@@ -557,7 +618,214 @@ export class SegmentedMemoryPort extends DurableJsonMemoryPort {
|
|
|
557
618
|
),
|
|
558
619
|
);
|
|
559
620
|
}
|
|
560
|
-
|
|
621
|
+
|
|
622
|
+
_ensureQueryIndexes(manifest) {
|
|
623
|
+
if (manifest.schema === STORE_SCHEMA || this.readOnly) return manifest;
|
|
624
|
+
const missing = Object.keys(manifest.shards).filter(
|
|
625
|
+
(bucket) => !manifest.shards[bucket].queryDigest,
|
|
626
|
+
);
|
|
627
|
+
if (!missing.length) return manifest;
|
|
628
|
+
// Upgrade older v2 snapshots without changing records, revisions or audit.
|
|
629
|
+
// Old strict readers reject these extended descriptors instead of writing
|
|
630
|
+
// a second authority. Shadow readers derive indexes only in memory.
|
|
631
|
+
return this._publish(
|
|
632
|
+
manifest,
|
|
633
|
+
new Map(
|
|
634
|
+
missing.map((bucket) => [bucket, this._readShard(manifest, bucket)]),
|
|
635
|
+
),
|
|
636
|
+
);
|
|
637
|
+
}
|
|
638
|
+
|
|
639
|
+
_readQueryIndex(manifest, bucket) {
|
|
640
|
+
const descriptor = manifest.shards[bucket];
|
|
641
|
+
let bytes;
|
|
642
|
+
if (descriptor.queryDigest) {
|
|
643
|
+
const file = this._queryIndexPath(bucket, descriptor);
|
|
644
|
+
this._checkDirectories();
|
|
645
|
+
if (existsSync(file)) {
|
|
646
|
+
// Unsafe filesystem objects are authority errors, never repair targets.
|
|
647
|
+
regular(file);
|
|
648
|
+
try {
|
|
649
|
+
bytes = readBoundedState(file, descriptor.queryBytes, true);
|
|
650
|
+
} catch (error) {
|
|
651
|
+
if (error.code !== "CONTEXT_MEMORY_STORE_CORRUPT") throw error;
|
|
652
|
+
}
|
|
653
|
+
}
|
|
654
|
+
if (
|
|
655
|
+
!bytes ||
|
|
656
|
+
bytes.length !== descriptor.queryBytes ||
|
|
657
|
+
queryDigest(bytes) !== descriptor.queryDigest
|
|
658
|
+
) {
|
|
659
|
+
bytes = encodeQueryIndex(this._readShard(manifest, bucket));
|
|
660
|
+
if (
|
|
661
|
+
bytes.length !== descriptor.queryBytes ||
|
|
662
|
+
queryDigest(bytes) !== descriptor.queryDigest
|
|
663
|
+
)
|
|
664
|
+
throw corruptStore(
|
|
665
|
+
file,
|
|
666
|
+
"query index cannot be reproduced from authority",
|
|
667
|
+
);
|
|
668
|
+
if (!this.readOnly) {
|
|
669
|
+
const temporary = join(
|
|
670
|
+
this.shardDirectory,
|
|
671
|
+
`query-rebuild-${randomUUID()}.tmp`,
|
|
672
|
+
);
|
|
673
|
+
const fd = openSync(temporary, "wx", 0o600);
|
|
674
|
+
try {
|
|
675
|
+
writeFileSync(fd, bytes);
|
|
676
|
+
fsyncSync(fd);
|
|
677
|
+
} finally {
|
|
678
|
+
closeSync(fd);
|
|
679
|
+
}
|
|
680
|
+
this._checkDirectories();
|
|
681
|
+
if (existsSync(file)) regular(file);
|
|
682
|
+
renameSync(temporary, file);
|
|
683
|
+
syncDirectory(this.shardDirectory);
|
|
684
|
+
}
|
|
685
|
+
}
|
|
686
|
+
} else {
|
|
687
|
+
bytes = encodeQueryIndex(this._readShard(manifest, bucket));
|
|
688
|
+
}
|
|
689
|
+
const digest = descriptor.queryDigest || queryDigest(bytes);
|
|
690
|
+
const cached = this._queryIndexes.get(bucket);
|
|
691
|
+
if (cached?.digest === digest) return cached.index;
|
|
692
|
+
let index;
|
|
693
|
+
try {
|
|
694
|
+
index = compileQueryIndex(bytes, descriptor);
|
|
695
|
+
} catch (cause) {
|
|
696
|
+
throw corruptStore(
|
|
697
|
+
this.filePath,
|
|
698
|
+
"query index structure is invalid",
|
|
699
|
+
cause,
|
|
700
|
+
);
|
|
701
|
+
}
|
|
702
|
+
this._queryIndexes.set(bucket, { digest, index });
|
|
703
|
+
return index;
|
|
704
|
+
}
|
|
705
|
+
|
|
706
|
+
async _indexedRecords(
|
|
707
|
+
options = {},
|
|
708
|
+
{ limit: count = Infinity, cursor = null } = {},
|
|
709
|
+
) {
|
|
710
|
+
const query = normalizeListQuery(options);
|
|
711
|
+
const captured = await this._locked(() => {
|
|
712
|
+
const manifest = this._ensureQueryIndexes(this._loadManifest());
|
|
713
|
+
const binding = listCursorBinding(
|
|
714
|
+
this.filePath,
|
|
715
|
+
manifest.digest || queryDigest(JSON.stringify(manifest)),
|
|
716
|
+
query,
|
|
717
|
+
);
|
|
718
|
+
const after = decodeListCursor(cursor, binding);
|
|
719
|
+
if (manifest.schema === STORE_SCHEMA) {
|
|
720
|
+
const matches = Object.values(manifest.records)
|
|
721
|
+
.filter(
|
|
722
|
+
(row) =>
|
|
723
|
+
matchesListQuery(row, query) &&
|
|
724
|
+
(!after || compareMemoryRows(row, after) > 0),
|
|
725
|
+
)
|
|
726
|
+
.sort(compareMemoryRows);
|
|
727
|
+
const records = matches.slice(0, count);
|
|
728
|
+
return {
|
|
729
|
+
legacy: records,
|
|
730
|
+
storeRevision: manifest.storeRevision,
|
|
731
|
+
nextCursor:
|
|
732
|
+
matches.length > count
|
|
733
|
+
? encodeListCursor(binding, records.at(-1))
|
|
734
|
+
: null,
|
|
735
|
+
};
|
|
736
|
+
}
|
|
737
|
+
// Merge each sorted posting lazily. Only the requested page is captured;
|
|
738
|
+
// every candidate has already passed scope, sink and lifecycle filters.
|
|
739
|
+
const heads = [];
|
|
740
|
+
for (const bucket of Object.keys(manifest.shards)) {
|
|
741
|
+
const iterator = selectQueryRows(
|
|
742
|
+
this._readQueryIndex(manifest, bucket),
|
|
743
|
+
query,
|
|
744
|
+
after,
|
|
745
|
+
);
|
|
746
|
+
const next = iterator.next();
|
|
747
|
+
if (!next.done) heads.push({ bucket, iterator, row: next.value });
|
|
748
|
+
}
|
|
749
|
+
const selected = [];
|
|
750
|
+
for (const row of mergeQueryRows(heads)) {
|
|
751
|
+
selected.push(row);
|
|
752
|
+
if (selected.length > count) break;
|
|
753
|
+
}
|
|
754
|
+
const more = selected.length > count;
|
|
755
|
+
if (more) selected.pop();
|
|
756
|
+
const shards = new Map();
|
|
757
|
+
for (const { bucket } of selected)
|
|
758
|
+
if (!shards.has(bucket))
|
|
759
|
+
shards.set(bucket, this._readShardBytes(manifest, bucket));
|
|
760
|
+
return {
|
|
761
|
+
manifest,
|
|
762
|
+
selected,
|
|
763
|
+
shards,
|
|
764
|
+
storeRevision: manifest.storeRevision,
|
|
765
|
+
nextCursor: more
|
|
766
|
+
? encodeListCursor(binding, selected.at(-1).row)
|
|
767
|
+
: null,
|
|
768
|
+
};
|
|
769
|
+
});
|
|
770
|
+
if (captured.legacy)
|
|
771
|
+
return {
|
|
772
|
+
records: captured.legacy,
|
|
773
|
+
nextCursor: captured.nextCursor,
|
|
774
|
+
storeRevision: captured.storeRevision,
|
|
775
|
+
};
|
|
776
|
+
const decoded = new Map(
|
|
777
|
+
[...captured.shards].map(([bucket, bytes]) => [
|
|
778
|
+
bucket,
|
|
779
|
+
this._decodeShard(captured.manifest, bucket, bytes),
|
|
780
|
+
]),
|
|
781
|
+
);
|
|
782
|
+
const records = captured.selected.map(({ bucket, row }) => {
|
|
783
|
+
const record = own(decoded.get(bucket).records, row.memoryId);
|
|
784
|
+
if (
|
|
785
|
+
!record ||
|
|
786
|
+
!matchesListQuery(record, query) ||
|
|
787
|
+
compareMemoryRows(record, row) !== 0
|
|
788
|
+
)
|
|
789
|
+
throw corruptStore(
|
|
790
|
+
this.filePath,
|
|
791
|
+
"query index result disagrees with authority",
|
|
792
|
+
);
|
|
793
|
+
return record;
|
|
794
|
+
});
|
|
795
|
+
return {
|
|
796
|
+
records,
|
|
797
|
+
nextCursor: captured.nextCursor,
|
|
798
|
+
storeRevision: captured.storeRevision,
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
async listRecords(options = {}) {
|
|
803
|
+
const count =
|
|
804
|
+
options.limit == null
|
|
805
|
+
? Infinity
|
|
806
|
+
: Math.max(1, Number(options.limit) || 20);
|
|
807
|
+
return (await this._indexedRecords(options, { limit: count })).records;
|
|
808
|
+
}
|
|
809
|
+
|
|
810
|
+
async listPage(options = {}) {
|
|
811
|
+
return this._indexedRecords(options, {
|
|
812
|
+
limit: pageLimit(options.limit),
|
|
813
|
+
cursor: options.cursor,
|
|
814
|
+
});
|
|
815
|
+
}
|
|
816
|
+
|
|
817
|
+
async query(request) {
|
|
818
|
+
if (request?.scopeAdmissions || request?.sink) {
|
|
819
|
+
// Do not push recall's limit before lexical/semantic ranking in the
|
|
820
|
+
// kernel. Push only gates that cannot discard an admissible result.
|
|
821
|
+
return (
|
|
822
|
+
await this._indexedRecords({
|
|
823
|
+
scopeAdmissions: request.scopeAdmissions,
|
|
824
|
+
sink: request.sink,
|
|
825
|
+
states: ["active", "reinforced"],
|
|
826
|
+
})
|
|
827
|
+
).records;
|
|
828
|
+
}
|
|
561
829
|
const snapshot = await this._locked(() => {
|
|
562
830
|
const manifest = this._loadManifest();
|
|
563
831
|
if (manifest.schema === STORE_SCHEMA) return { legacy: manifest };
|