@remnic/core 9.69.0 → 9.69.1
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/dist/access-admin-ops-surface.d.ts +1 -1
- package/dist/access-admin-ops-surface.js +19 -19
- package/dist/access-authorization-probe.d.ts +1 -1
- package/dist/access-authorization-probe.js +20 -20
- package/dist/access-boundary.d.ts +1 -1
- package/dist/access-boundary.js +19 -19
- package/dist/access-cli.js +65 -65
- package/dist/access-extraction-force-flush.d.ts +1 -1
- package/dist/access-extraction-force-flush.js +19 -19
- package/dist/access-http-lcm-compaction.d.ts +1 -1
- package/dist/access-http-lifecycle-flush.d.ts +1 -1
- package/dist/access-http-offline-stream.d.ts +1 -1
- package/dist/access-http-query.js +21 -21
- package/dist/access-http.d.ts +2 -2
- package/dist/access-http.js +33 -33
- package/dist/access-identity-continuity-surface.js +19 -19
- package/dist/access-lcm-surface.d.ts +1 -1
- package/dist/access-lcm-surface.js +19 -19
- package/dist/access-mcp.d.ts +1 -1
- package/dist/access-mcp.js +26 -26
- package/dist/access-namespace-preflight.js +19 -19
- package/dist/access-observe-write-surface.d.ts +1 -1
- package/dist/access-observe-write-surface.js +19 -19
- package/dist/access-operations-batch.js +20 -20
- package/dist/access-operations.d.ts +2 -2
- package/dist/access-operations.js +25 -25
- package/dist/access-recall-concurrency.d.ts +1 -1
- package/dist/access-recall-concurrency.js +19 -19
- package/dist/access-recall-response.d.ts +1 -1
- package/dist/access-recall-response.js +19 -19
- package/dist/access-recall-surface.d.ts +1 -1
- package/dist/access-recall-surface.js +19 -19
- package/dist/access-service-helpers.d.ts +1 -1
- package/dist/access-service.d.ts +1 -1
- package/dist/access-service.js +19 -19
- package/dist/access-surface-catalog.d.ts +1 -1
- package/dist/access-surface-catalog.js +21 -21
- package/dist/briefing.js +10 -10
- package/dist/causal-consolidation.js +11 -11
- package/dist/{chunk-KAY2C6VX.js → chunk-2D7S3FZW.js} +2 -2
- package/dist/{chunk-BZ26443Y.js → chunk-2FEBD4LN.js} +89 -41
- package/dist/chunk-2FEBD4LN.js.map +1 -0
- package/dist/{chunk-HDQQRRCZ.js → chunk-5DSPAWXQ.js} +3 -3
- package/dist/{chunk-VLSUKXCJ.js → chunk-5JUSOMNO.js} +3 -3
- package/dist/{chunk-P342D5BP.js → chunk-626ILV6J.js} +2 -2
- package/dist/{chunk-I6T4SYFW.js → chunk-63Z7D2YJ.js} +3 -3
- package/dist/{chunk-XW3G4BJI.js → chunk-6DPAZEGK.js} +7 -7
- package/dist/{chunk-XPDWNWH6.js → chunk-6LIILOP3.js} +3 -3
- package/dist/{chunk-3IRH5MYM.js → chunk-6OX6SNRC.js} +5 -5
- package/dist/{chunk-FOT5GP2U.js → chunk-73CA45TG.js} +2 -2
- package/dist/{chunk-2MHMHF76.js → chunk-7GFLU63V.js} +2 -2
- package/dist/{chunk-A7NMS5II.js → chunk-7HAZ6GZE.js} +3 -3
- package/dist/{chunk-X2NFFBTO.js → chunk-A7GQBJP5.js} +4 -4
- package/dist/{chunk-7KG5V4VV.js → chunk-BZFHCRO6.js} +2 -2
- package/dist/{chunk-4PEZMGZD.js → chunk-CUQI6DR3.js} +2 -2
- package/dist/{chunk-NFITGYMQ.js → chunk-CX6J53S5.js} +3 -3
- package/dist/{chunk-DJ7TZLGU.js → chunk-DHXPZZ5U.js} +2 -2
- package/dist/{chunk-XZBXINXZ.js → chunk-GIC6BASP.js} +4 -4
- package/dist/{chunk-ABPCF4MW.js → chunk-HB3GEHGF.js} +1 -1
- package/dist/{chunk-6FUMA6AA.js → chunk-JFPUQ3E2.js} +2 -2
- package/dist/{chunk-BTRY6UH3.js → chunk-JMMG5K7H.js} +2 -2
- package/dist/{chunk-ZDJMCXC7.js → chunk-JRLDQ6WS.js} +12 -12
- package/dist/{chunk-BGD3T743.js → chunk-JRYINMLF.js} +2 -2
- package/dist/{chunk-F25CL3IQ.js → chunk-KSTLZCR5.js} +2 -2
- package/dist/{chunk-363VZTVH.js → chunk-L5QVIATR.js} +2 -2
- package/dist/{chunk-BS5BD3DF.js → chunk-LFGSIMFS.js} +2 -2
- package/dist/{chunk-YYAWJRGL.js → chunk-MYMME2VL.js} +2 -2
- package/dist/{chunk-RACZB5HT.js → chunk-N37X5RU7.js} +4 -4
- package/dist/{chunk-Z2FXSE5P.js → chunk-NFZUZL44.js} +2 -2
- package/dist/{chunk-AG46P24C.js → chunk-NNBZS3DY.js} +4 -4
- package/dist/{chunk-L7MJJAG2.js → chunk-NRDLOFAI.js} +2 -2
- package/dist/{chunk-PIBQIYF2.js → chunk-NX2ECBRG.js} +2 -2
- package/dist/{chunk-QOE3EM5Z.js → chunk-PVN6DVWU.js} +8 -8
- package/dist/{chunk-327JMA2N.js → chunk-PZY4DLLO.js} +2 -2
- package/dist/{chunk-ZVW724ZK.js → chunk-Q4OITLPA.js} +2 -2
- package/dist/{chunk-ND6NL2YR.js → chunk-QBZMUF54.js} +2 -2
- package/dist/{chunk-25P3LW6U.js → chunk-QLT74C2M.js} +2 -2
- package/dist/{chunk-KJUH3QIQ.js → chunk-TITZPONM.js} +2 -2
- package/dist/{chunk-YFK64FYK.js → chunk-TPIO36PJ.js} +2 -2
- package/dist/{chunk-5UI7WQA6.js → chunk-TX55UTJT.js} +2 -2
- package/dist/{chunk-D35XLYQ7.js → chunk-UF65P4GF.js} +6 -6
- package/dist/{chunk-BHH736UQ.js → chunk-UJYNT3R2.js} +3 -3
- package/dist/{chunk-4PMPOZK3.js → chunk-V5I36Y2F.js} +2 -2
- package/dist/{chunk-WQCET3N3.js → chunk-VK7YWP5Q.js} +2 -2
- package/dist/{chunk-USQAH6PN.js → chunk-VPYAQYD5.js} +2 -2
- package/dist/{chunk-KRNFJKMH.js → chunk-VTEO5T6Z.js} +2 -2
- package/dist/{chunk-WSYACIKD.js → chunk-W6PZQY4L.js} +2 -2
- package/dist/{chunk-52GQNSLC.js → chunk-WM2N5DRB.js} +7 -7
- package/dist/{chunk-ERKFG4RV.js → chunk-XRT7ZG5B.js} +33 -33
- package/dist/{chunk-NYQHD7H4.js → chunk-YTWEK6CU.js} +17 -17
- package/dist/{cli-CFpMhqz5.d.ts → cli-DDHGtWI1.d.ts} +1 -1
- package/dist/cli.d.ts +2 -2
- package/dist/cli.js +48 -48
- package/dist/coding/export-okf-codegraph.js +10 -10
- package/dist/compounding/engine.js +10 -10
- package/dist/connectors/codex-materialize-runner.js +10 -10
- package/dist/connectors/index.js +10 -10
- package/dist/convergence-refresh.js +12 -12
- package/dist/corpus-watermark.js +12 -12
- package/dist/entity-retrieval.js +10 -10
- package/dist/external-wiki-access.d.ts +1 -1
- package/dist/external-wiki-access.js +20 -20
- package/dist/external-wiki-mcp-tools.d.ts +1 -1
- package/dist/extraction.js +10 -10
- package/dist/{first-start-migration-KHIMCBIC.js → first-start-migration-UG32BSG2.js} +4 -4
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/index.d.ts +28 -7
- package/dist/index.js +78 -72
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +7 -7
- package/dist/location/index.js +12 -12
- package/dist/maintenance/memory-governance.js +10 -10
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -10
- package/dist/maintenance/rebuild-memory-projection.js +11 -11
- package/dist/mcp-memory-inspector-app.d.ts +1 -1
- package/dist/memory-subject-cli.d.ts +1 -1
- package/dist/memory-subject.d.ts +1 -1
- package/dist/namespaces/migrate.js +11 -11
- package/dist/namespaces/search.js +7 -7
- package/dist/namespaces/storage.js +10 -10
- package/dist/operator-doctor-corpus.js +13 -13
- package/dist/operator-toolkit.js +18 -18
- package/dist/orchestration/maintenance.js +18 -18
- package/dist/orchestrator.js +65 -65
- package/dist/{public-http-C9-CHc40.d.ts → public-http-Bab7S0ED.d.ts} +1 -1
- package/dist/recall-navigate-disclosure.d.ts +44 -0
- package/dist/recall-navigate-disclosure.js +30 -0
- package/dist/recall-navigate-disclosure.js.map +1 -0
- package/dist/recall-state-view-packet.d.ts +30 -0
- package/dist/recall-state-view-packet.js +69 -0
- package/dist/recall-state-view-packet.js.map +1 -0
- package/dist/schemas.d.ts +42 -42
- package/dist/search/factory.js +6 -6
- package/dist/search/index.js +10 -10
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/orama-backend.js +2 -2
- package/dist/semantic-consolidation.js +11 -11
- package/dist/semantic-rule-promotion.js +10 -10
- package/dist/semantic-rule-verifier.js +10 -10
- package/dist/source-agent-qualifier.js +10 -10
- package/dist/standup/build.js +10 -10
- package/dist/storage.js +9 -9
- package/dist/support-passport/index.d.ts +4 -4
- package/dist/support-passport/index.js +22 -22
- package/dist/transfer/export-okf.js +11 -11
- package/dist/transfer/types.d.ts +12 -12
- package/dist/verified-recall.js +10 -10
- package/dist/who-knows-cli.d.ts +1 -1
- package/dist/who-knows-cli.js +11 -11
- package/dist/who-knows.d.ts +1 -1
- package/dist/who-knows.js +10 -10
- package/package.json +2 -2
- package/src/activity/timeline/index.ts +1 -0
- package/src/activity/timeline/week-recurring.test.ts +177 -0
- package/src/activity/timeline/week-recurring.ts +88 -0
- package/src/dedup/index.ts +7 -0
- package/src/dedup/merge-provenance.test.ts +185 -0
- package/src/dedup/merge-provenance.ts +151 -0
- package/src/recall-navigate-disclosure.test.ts +128 -0
- package/src/recall-navigate-disclosure.ts +45 -0
- package/src/recall-state-view-packet.test.ts +158 -0
- package/src/recall-state-view-packet.ts +128 -0
- package/dist/chunk-BZ26443Y.js.map +0 -1
- package/dist/{access-service-DcfM0Vf0.d.ts → access-service-CRZHXClX.d.ts} +6 -6
- /package/dist/{chunk-KAY2C6VX.js.map → chunk-2D7S3FZW.js.map} +0 -0
- /package/dist/{chunk-HDQQRRCZ.js.map → chunk-5DSPAWXQ.js.map} +0 -0
- /package/dist/{chunk-VLSUKXCJ.js.map → chunk-5JUSOMNO.js.map} +0 -0
- /package/dist/{chunk-P342D5BP.js.map → chunk-626ILV6J.js.map} +0 -0
- /package/dist/{chunk-I6T4SYFW.js.map → chunk-63Z7D2YJ.js.map} +0 -0
- /package/dist/{chunk-XW3G4BJI.js.map → chunk-6DPAZEGK.js.map} +0 -0
- /package/dist/{chunk-XPDWNWH6.js.map → chunk-6LIILOP3.js.map} +0 -0
- /package/dist/{chunk-3IRH5MYM.js.map → chunk-6OX6SNRC.js.map} +0 -0
- /package/dist/{chunk-FOT5GP2U.js.map → chunk-73CA45TG.js.map} +0 -0
- /package/dist/{chunk-2MHMHF76.js.map → chunk-7GFLU63V.js.map} +0 -0
- /package/dist/{chunk-A7NMS5II.js.map → chunk-7HAZ6GZE.js.map} +0 -0
- /package/dist/{chunk-X2NFFBTO.js.map → chunk-A7GQBJP5.js.map} +0 -0
- /package/dist/{chunk-7KG5V4VV.js.map → chunk-BZFHCRO6.js.map} +0 -0
- /package/dist/{chunk-4PEZMGZD.js.map → chunk-CUQI6DR3.js.map} +0 -0
- /package/dist/{chunk-NFITGYMQ.js.map → chunk-CX6J53S5.js.map} +0 -0
- /package/dist/{chunk-DJ7TZLGU.js.map → chunk-DHXPZZ5U.js.map} +0 -0
- /package/dist/{chunk-XZBXINXZ.js.map → chunk-GIC6BASP.js.map} +0 -0
- /package/dist/{chunk-ABPCF4MW.js.map → chunk-HB3GEHGF.js.map} +0 -0
- /package/dist/{chunk-6FUMA6AA.js.map → chunk-JFPUQ3E2.js.map} +0 -0
- /package/dist/{chunk-BTRY6UH3.js.map → chunk-JMMG5K7H.js.map} +0 -0
- /package/dist/{chunk-ZDJMCXC7.js.map → chunk-JRLDQ6WS.js.map} +0 -0
- /package/dist/{chunk-BGD3T743.js.map → chunk-JRYINMLF.js.map} +0 -0
- /package/dist/{chunk-F25CL3IQ.js.map → chunk-KSTLZCR5.js.map} +0 -0
- /package/dist/{chunk-363VZTVH.js.map → chunk-L5QVIATR.js.map} +0 -0
- /package/dist/{chunk-BS5BD3DF.js.map → chunk-LFGSIMFS.js.map} +0 -0
- /package/dist/{chunk-YYAWJRGL.js.map → chunk-MYMME2VL.js.map} +0 -0
- /package/dist/{chunk-RACZB5HT.js.map → chunk-N37X5RU7.js.map} +0 -0
- /package/dist/{chunk-Z2FXSE5P.js.map → chunk-NFZUZL44.js.map} +0 -0
- /package/dist/{chunk-AG46P24C.js.map → chunk-NNBZS3DY.js.map} +0 -0
- /package/dist/{chunk-L7MJJAG2.js.map → chunk-NRDLOFAI.js.map} +0 -0
- /package/dist/{chunk-PIBQIYF2.js.map → chunk-NX2ECBRG.js.map} +0 -0
- /package/dist/{chunk-QOE3EM5Z.js.map → chunk-PVN6DVWU.js.map} +0 -0
- /package/dist/{chunk-327JMA2N.js.map → chunk-PZY4DLLO.js.map} +0 -0
- /package/dist/{chunk-ZVW724ZK.js.map → chunk-Q4OITLPA.js.map} +0 -0
- /package/dist/{chunk-ND6NL2YR.js.map → chunk-QBZMUF54.js.map} +0 -0
- /package/dist/{chunk-25P3LW6U.js.map → chunk-QLT74C2M.js.map} +0 -0
- /package/dist/{chunk-KJUH3QIQ.js.map → chunk-TITZPONM.js.map} +0 -0
- /package/dist/{chunk-YFK64FYK.js.map → chunk-TPIO36PJ.js.map} +0 -0
- /package/dist/{chunk-5UI7WQA6.js.map → chunk-TX55UTJT.js.map} +0 -0
- /package/dist/{chunk-D35XLYQ7.js.map → chunk-UF65P4GF.js.map} +0 -0
- /package/dist/{chunk-BHH736UQ.js.map → chunk-UJYNT3R2.js.map} +0 -0
- /package/dist/{chunk-4PMPOZK3.js.map → chunk-V5I36Y2F.js.map} +0 -0
- /package/dist/{chunk-WQCET3N3.js.map → chunk-VK7YWP5Q.js.map} +0 -0
- /package/dist/{chunk-USQAH6PN.js.map → chunk-VPYAQYD5.js.map} +0 -0
- /package/dist/{chunk-KRNFJKMH.js.map → chunk-VTEO5T6Z.js.map} +0 -0
- /package/dist/{chunk-WSYACIKD.js.map → chunk-W6PZQY4L.js.map} +0 -0
- /package/dist/{chunk-52GQNSLC.js.map → chunk-WM2N5DRB.js.map} +0 -0
- /package/dist/{chunk-ERKFG4RV.js.map → chunk-XRT7ZG5B.js.map} +0 -0
- /package/dist/{chunk-NYQHD7H4.js.map → chunk-YTWEK6CU.js.map} +0 -0
- /package/dist/{first-start-migration-KHIMCBIC.js.map → first-start-migration-UG32BSG2.js.map} +0 -0
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import {
|
|
5
|
+
disclosureRank,
|
|
6
|
+
planDisclosureStep,
|
|
7
|
+
} from "./recall-navigate-disclosure.js";
|
|
8
|
+
import { RECALL_DISCLOSURE_LEVELS } from "./types.js";
|
|
9
|
+
|
|
10
|
+
// The declared input type is strings; agents send arbitrary JSON. Cast only
|
|
11
|
+
// at this boundary so the invalid-shape cases stay one line each.
|
|
12
|
+
const plan = (from: unknown, to: unknown) =>
|
|
13
|
+
planDisclosureStep({ from, to } as unknown as { from: string; to: string });
|
|
14
|
+
|
|
15
|
+
test("disclosureRank returns the index of every level, shallowest first", () => {
|
|
16
|
+
for (const [i, level] of RECALL_DISCLOSURE_LEVELS.entries()) {
|
|
17
|
+
assert.equal(disclosureRank(level), i);
|
|
18
|
+
}
|
|
19
|
+
assert.ok(
|
|
20
|
+
disclosureRank(RECALL_DISCLOSURE_LEVELS[0]) < disclosureRank(RECALL_DISCLOSURE_LEVELS[1]),
|
|
21
|
+
);
|
|
22
|
+
assert.ok(
|
|
23
|
+
disclosureRank(RECALL_DISCLOSURE_LEVELS[1]) < disclosureRank(RECALL_DISCLOSURE_LEVELS[2]),
|
|
24
|
+
);
|
|
25
|
+
});
|
|
26
|
+
|
|
27
|
+
test("disclosureRank throws a RangeError listing allowed values", () => {
|
|
28
|
+
assert.throws(
|
|
29
|
+
() => disclosureRank("full"),
|
|
30
|
+
(err: unknown) =>
|
|
31
|
+
err instanceof RangeError &&
|
|
32
|
+
/disclosure level/.test(err.message) &&
|
|
33
|
+
err.message.includes("chunk") &&
|
|
34
|
+
err.message.includes("section") &&
|
|
35
|
+
err.message.includes("raw"),
|
|
36
|
+
);
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
test("chunk→section is one step", () => {
|
|
40
|
+
assert.deepEqual(planDisclosureStep({ from: "chunk", to: "section" }), {
|
|
41
|
+
ok: true,
|
|
42
|
+
from: "chunk",
|
|
43
|
+
to: "section",
|
|
44
|
+
steps: 1,
|
|
45
|
+
});
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("section→raw is one step", () => {
|
|
49
|
+
assert.deepEqual(planDisclosureStep({ from: "section", to: "raw" }), {
|
|
50
|
+
ok: true,
|
|
51
|
+
from: "section",
|
|
52
|
+
to: "raw",
|
|
53
|
+
steps: 1,
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
test("chunk→raw is two steps", () => {
|
|
58
|
+
assert.deepEqual(planDisclosureStep({ from: "chunk", to: "raw" }), {
|
|
59
|
+
ok: true,
|
|
60
|
+
from: "chunk",
|
|
61
|
+
to: "raw",
|
|
62
|
+
steps: 2,
|
|
63
|
+
});
|
|
64
|
+
});
|
|
65
|
+
|
|
66
|
+
test("every level pair is only deeper in one direction", () => {
|
|
67
|
+
for (const shallower of RECALL_DISCLOSURE_LEVELS) {
|
|
68
|
+
for (const deeper of RECALL_DISCLOSURE_LEVELS) {
|
|
69
|
+
const result = planDisclosureStep({ from: shallower, to: deeper });
|
|
70
|
+
if (disclosureRank(shallower) < disclosureRank(deeper)) {
|
|
71
|
+
assert.deepEqual(result, {
|
|
72
|
+
ok: true,
|
|
73
|
+
from: shallower,
|
|
74
|
+
to: deeper,
|
|
75
|
+
steps: disclosureRank(deeper) - disclosureRank(shallower),
|
|
76
|
+
});
|
|
77
|
+
} else {
|
|
78
|
+
assert.deepEqual(result, { ok: false, error: "not_deeper" });
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("equal levels are not_deeper", () => {
|
|
85
|
+
for (const level of RECALL_DISCLOSURE_LEVELS) {
|
|
86
|
+
assert.deepEqual(planDisclosureStep({ from: level, to: level }), {
|
|
87
|
+
ok: false,
|
|
88
|
+
error: "not_deeper",
|
|
89
|
+
});
|
|
90
|
+
}
|
|
91
|
+
});
|
|
92
|
+
|
|
93
|
+
test("shallower pairs are not_deeper", () => {
|
|
94
|
+
for (const [from, to] of [
|
|
95
|
+
["section", "chunk"],
|
|
96
|
+
["raw", "section"],
|
|
97
|
+
["raw", "chunk"],
|
|
98
|
+
] as const) {
|
|
99
|
+
assert.deepEqual(planDisclosureStep({ from, to }), {
|
|
100
|
+
ok: false,
|
|
101
|
+
error: "not_deeper",
|
|
102
|
+
});
|
|
103
|
+
}
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("unknown level on either side is unknown_level, not a throw", () => {
|
|
107
|
+
assert.deepEqual(plan("full", "raw"), { ok: false, error: "unknown_level" });
|
|
108
|
+
assert.deepEqual(plan("chunk", "full"), { ok: false, error: "unknown_level" });
|
|
109
|
+
assert.deepEqual(plan("full", "worse"), { ok: false, error: "unknown_level" });
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
test("non-string level on either side is unknown_level", () => {
|
|
113
|
+
for (const bad of [5, null, undefined, true, { level: "raw" }, ["raw"]]) {
|
|
114
|
+
assert.deepEqual(plan(bad, "raw"), { ok: false, error: "unknown_level" });
|
|
115
|
+
assert.deepEqual(plan("chunk", bad), { ok: false, error: "unknown_level" });
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
|
|
119
|
+
test("empty string level is unknown_level", () => {
|
|
120
|
+
assert.deepEqual(plan("", "raw"), { ok: false, error: "unknown_level" });
|
|
121
|
+
assert.deepEqual(plan("chunk", ""), { ok: false, error: "unknown_level" });
|
|
122
|
+
});
|
|
123
|
+
|
|
124
|
+
test("levels match exactly — no case folding, no trimming", () => {
|
|
125
|
+
assert.deepEqual(plan("Raw", "chunk"), { ok: false, error: "unknown_level" });
|
|
126
|
+
assert.deepEqual(plan(" raw", "raw"), { ok: false, error: "unknown_level" });
|
|
127
|
+
assert.deepEqual(plan("raw", "RAW"), { ok: false, error: "unknown_level" });
|
|
128
|
+
});
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Disclosure-step planning for recall navigation (issue #1956).
|
|
3
|
+
*
|
|
4
|
+
* The ladder itself is NOT redefined here: `types.ts` designates
|
|
5
|
+
* `RECALL_DISCLOSURE_LEVELS` the single source of truth and forbids
|
|
6
|
+
* hard-coding disclosure strings elsewhere, so a second copy could drift out
|
|
7
|
+
* of agreement with recall validation and rendering. This module only adds
|
|
8
|
+
* the rule that an expansion must go strictly deeper, so a caller cannot
|
|
9
|
+
* re-pay recall budget for output it already holds. Pure — no I/O.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import {
|
|
13
|
+
isRecallDisclosure,
|
|
14
|
+
RECALL_DISCLOSURE_LEVELS,
|
|
15
|
+
type RecallDisclosure,
|
|
16
|
+
} from "./types.js";
|
|
17
|
+
|
|
18
|
+
export type DisclosureStepResult =
|
|
19
|
+
| { ok: true; from: RecallDisclosure; to: RecallDisclosure; steps: number }
|
|
20
|
+
| { ok: false; error: "unknown_level" | "not_deeper" };
|
|
21
|
+
|
|
22
|
+
/** Index in the canonical shallow-to-deep ladder. Throws for a non-level. */
|
|
23
|
+
export function disclosureRank(level: string): number {
|
|
24
|
+
const index = (RECALL_DISCLOSURE_LEVELS as readonly string[]).indexOf(level);
|
|
25
|
+
if (index < 0) {
|
|
26
|
+
throw new RangeError(
|
|
27
|
+
`unknown disclosure level ${JSON.stringify(level)}; expected one of ${RECALL_DISCLOSURE_LEVELS.join(", ")}`,
|
|
28
|
+
);
|
|
29
|
+
}
|
|
30
|
+
return index;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
/**
|
|
34
|
+
* Plan one expansion. A bad level from an agent is a typed refusal rather
|
|
35
|
+
* than a throw; equal or shallower targets are `not_deeper`.
|
|
36
|
+
*/
|
|
37
|
+
export function planDisclosureStep(input: { from: string; to: string }): DisclosureStepResult {
|
|
38
|
+
if (!isRecallDisclosure(input.from) || !isRecallDisclosure(input.to)) {
|
|
39
|
+
return { ok: false, error: "unknown_level" };
|
|
40
|
+
}
|
|
41
|
+
const fromRank = disclosureRank(input.from);
|
|
42
|
+
const toRank = disclosureRank(input.to);
|
|
43
|
+
if (toRank <= fromRank) return { ok: false, error: "not_deeper" };
|
|
44
|
+
return { ok: true, from: input.from, to: input.to, steps: toRank - fromRank };
|
|
45
|
+
}
|
|
@@ -0,0 +1,158 @@
|
|
|
1
|
+
import assert from "node:assert/strict";
|
|
2
|
+
import test from "node:test";
|
|
3
|
+
|
|
4
|
+
import { buildStateEvidencePackets } from "./recall-state-view-packet.js";
|
|
5
|
+
import type { StateViewEntry } from "./recall-state-view-packet.js";
|
|
6
|
+
|
|
7
|
+
test("two-step predecessor chain is ordered nearest to head first", () => {
|
|
8
|
+
const result = buildStateEvidencePackets([
|
|
9
|
+
{ memoryId: "head", stateLabel: "current" },
|
|
10
|
+
{ memoryId: "old-2", stateLabel: "historical", supersededById: "old-1" },
|
|
11
|
+
{ memoryId: "old-1", stateLabel: "historical", supersededById: "head" },
|
|
12
|
+
]);
|
|
13
|
+
assert.deepEqual(result, {
|
|
14
|
+
packets: [{ headId: "head", historyIds: ["old-1", "old-2"] }],
|
|
15
|
+
orphanHistoryIds: [],
|
|
16
|
+
});
|
|
17
|
+
});
|
|
18
|
+
|
|
19
|
+
test("two independent packets never interleave", () => {
|
|
20
|
+
const result = buildStateEvidencePackets([
|
|
21
|
+
{ memoryId: "b-head", stateLabel: "transition" },
|
|
22
|
+
{ memoryId: "a-head", stateLabel: "current" },
|
|
23
|
+
{ memoryId: "b-old", stateLabel: "historical", supersededById: "b-head" },
|
|
24
|
+
{ memoryId: "a-old", stateLabel: "historical", supersededById: "a-head" },
|
|
25
|
+
]);
|
|
26
|
+
assert.deepEqual(result, {
|
|
27
|
+
packets: [
|
|
28
|
+
{ headId: "a-head", historyIds: ["a-old"] },
|
|
29
|
+
{ headId: "b-head", historyIds: ["b-old"] },
|
|
30
|
+
],
|
|
31
|
+
orphanHistoryIds: [],
|
|
32
|
+
});
|
|
33
|
+
});
|
|
34
|
+
|
|
35
|
+
test("historical entry pointing at an absent successor becomes an orphan", () => {
|
|
36
|
+
const result = buildStateEvidencePackets([
|
|
37
|
+
{ memoryId: "head", stateLabel: "current" },
|
|
38
|
+
{ memoryId: "dangling", stateLabel: "historical", supersededById: "missing" },
|
|
39
|
+
]);
|
|
40
|
+
assert.deepEqual(result, {
|
|
41
|
+
packets: [{ headId: "head", historyIds: [] }],
|
|
42
|
+
orphanHistoryIds: ["dangling"],
|
|
43
|
+
});
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
test("historical entry with no supersededById becomes an orphan", () => {
|
|
47
|
+
const result = buildStateEvidencePackets([
|
|
48
|
+
{ memoryId: "rootless", stateLabel: "historical" },
|
|
49
|
+
]);
|
|
50
|
+
assert.deepEqual(result, {
|
|
51
|
+
packets: [],
|
|
52
|
+
orphanHistoryIds: ["rootless"],
|
|
53
|
+
});
|
|
54
|
+
});
|
|
55
|
+
|
|
56
|
+
test("supersededById cycle terminates and yields orphans", () => {
|
|
57
|
+
const result = buildStateEvidencePackets([
|
|
58
|
+
{ memoryId: "head", stateLabel: "current" },
|
|
59
|
+
{ memoryId: "loop-a", stateLabel: "historical", supersededById: "loop-b" },
|
|
60
|
+
{ memoryId: "loop-b", stateLabel: "historical", supersededById: "loop-a" },
|
|
61
|
+
{ memoryId: "into-loop", stateLabel: "historical", supersededById: "loop-a" },
|
|
62
|
+
]);
|
|
63
|
+
assert.deepEqual(result, {
|
|
64
|
+
packets: [{ headId: "head", historyIds: [] }],
|
|
65
|
+
orphanHistoryIds: ["into-loop", "loop-a", "loop-b"],
|
|
66
|
+
});
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
test("lone current entry yields a packet with empty history", () => {
|
|
70
|
+
const result = buildStateEvidencePackets([
|
|
71
|
+
{ memoryId: "solo", stateLabel: "current" },
|
|
72
|
+
]);
|
|
73
|
+
assert.deepEqual(result, {
|
|
74
|
+
packets: [{ headId: "solo", historyIds: [] }],
|
|
75
|
+
orphanHistoryIds: [],
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
test("unknown state label throws TypeError listing the allowed values", () => {
|
|
80
|
+
assert.throws(
|
|
81
|
+
() =>
|
|
82
|
+
buildStateEvidencePackets([
|
|
83
|
+
{ memoryId: "x", stateLabel: "archived" as StateViewEntry["stateLabel"] },
|
|
84
|
+
]),
|
|
85
|
+
(error: unknown) => {
|
|
86
|
+
assert.ok(error instanceof TypeError);
|
|
87
|
+
const message = String((error as Error).message);
|
|
88
|
+
assert.match(message, /state label/);
|
|
89
|
+
assert.ok(message.includes('"current"'));
|
|
90
|
+
assert.ok(message.includes('"historical"'));
|
|
91
|
+
assert.ok(message.includes('"transition"'));
|
|
92
|
+
return true;
|
|
93
|
+
},
|
|
94
|
+
);
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("blank memoryId is ignored", () => {
|
|
98
|
+
const result = buildStateEvidencePackets([
|
|
99
|
+
{ memoryId: "", stateLabel: "historical", supersededById: "head" },
|
|
100
|
+
{ memoryId: " ", stateLabel: "current" },
|
|
101
|
+
{ memoryId: "head", stateLabel: "current" },
|
|
102
|
+
]);
|
|
103
|
+
assert.deepEqual(result, {
|
|
104
|
+
packets: [{ headId: "head", historyIds: [] }],
|
|
105
|
+
orphanHistoryIds: [],
|
|
106
|
+
});
|
|
107
|
+
});
|
|
108
|
+
|
|
109
|
+
test("duplicate memoryId keeps the first entry", () => {
|
|
110
|
+
const result = buildStateEvidencePackets([
|
|
111
|
+
{ memoryId: "dup", stateLabel: "current" },
|
|
112
|
+
{ memoryId: "dup", stateLabel: "historical", supersededById: "other" },
|
|
113
|
+
{ memoryId: "old", stateLabel: "historical", supersededById: "dup" },
|
|
114
|
+
]);
|
|
115
|
+
assert.deepEqual(result, {
|
|
116
|
+
packets: [{ headId: "dup", historyIds: ["old"] }],
|
|
117
|
+
orphanHistoryIds: [],
|
|
118
|
+
});
|
|
119
|
+
});
|
|
120
|
+
|
|
121
|
+
test("shuffled input produces a deep-equal result", () => {
|
|
122
|
+
const entries: StateViewEntry[] = [
|
|
123
|
+
{ memoryId: "h2", stateLabel: "transition" },
|
|
124
|
+
{ memoryId: "h1", stateLabel: "current" },
|
|
125
|
+
{ memoryId: "a1", stateLabel: "historical", supersededById: "h1" },
|
|
126
|
+
{ memoryId: "a2", stateLabel: "historical", supersededById: "a1" },
|
|
127
|
+
{ memoryId: "b1", stateLabel: "historical", supersededById: "h2" },
|
|
128
|
+
{ memoryId: "lost", stateLabel: "historical" },
|
|
129
|
+
];
|
|
130
|
+
const expected = {
|
|
131
|
+
packets: [
|
|
132
|
+
{ headId: "h1", historyIds: ["a1", "a2"] },
|
|
133
|
+
{ headId: "h2", historyIds: ["b1"] },
|
|
134
|
+
],
|
|
135
|
+
orphanHistoryIds: ["lost"],
|
|
136
|
+
};
|
|
137
|
+
assert.deepEqual(buildStateEvidencePackets(entries), expected);
|
|
138
|
+
const byId = new Map(entries.map((entry) => [entry.memoryId, entry]));
|
|
139
|
+
const orders = [
|
|
140
|
+
["lost", "b1", "a2", "a1", "h2", "h1"],
|
|
141
|
+
["h2", "lost", "h1", "b1", "a2", "a1"],
|
|
142
|
+
["a2", "h1", "lost", "h2", "a1", "b1"],
|
|
143
|
+
];
|
|
144
|
+
for (const order of orders) {
|
|
145
|
+
const shuffled = order.map((id) => byId.get(id) as StateViewEntry);
|
|
146
|
+
assert.deepEqual(buildStateEvidencePackets(shuffled), expected);
|
|
147
|
+
}
|
|
148
|
+
});
|
|
149
|
+
|
|
150
|
+
test("input entries are not mutated", () => {
|
|
151
|
+
const entries: StateViewEntry[] = [
|
|
152
|
+
{ memoryId: "head", stateLabel: "current" },
|
|
153
|
+
{ memoryId: "old", stateLabel: "historical", supersededById: "head" },
|
|
154
|
+
];
|
|
155
|
+
const snapshot = structuredClone(entries);
|
|
156
|
+
buildStateEvidencePackets(entries);
|
|
157
|
+
assert.deepEqual(entries, snapshot);
|
|
158
|
+
});
|
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Recall state-view evidence packets (issue #1952).
|
|
3
|
+
*
|
|
4
|
+
* Groups labeled state-view entries into per-head evidence packets so
|
|
5
|
+
* superseded records stay visible as ordered history instead of being
|
|
6
|
+
* hidden or interleaved with another packet's entries. Pure. Surfaces wait.
|
|
7
|
+
*/
|
|
8
|
+
import type { StateLabel } from "./recall-state-view.js";
|
|
9
|
+
|
|
10
|
+
export type { StateLabel };
|
|
11
|
+
|
|
12
|
+
export interface StateViewEntry {
|
|
13
|
+
memoryId: string;
|
|
14
|
+
stateLabel: StateLabel;
|
|
15
|
+
/** Id of the entry that superseded this one, when any. */
|
|
16
|
+
supersededById?: string;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
export interface StateEvidencePacket {
|
|
20
|
+
/** The current (or transition) entry this packet is about. */
|
|
21
|
+
headId: string;
|
|
22
|
+
/** Predecessors, nearest to head first. Never interleaved with other packets. */
|
|
23
|
+
historyIds: string[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
export interface StateEvidencePackets {
|
|
27
|
+
packets: StateEvidencePacket[];
|
|
28
|
+
/** Historical entries whose successor is absent from the input. */
|
|
29
|
+
orphanHistoryIds: string[];
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const STATE_LABELS: readonly StateLabel[] = ["current", "historical", "transition"];
|
|
33
|
+
|
|
34
|
+
interface HeadResolution {
|
|
35
|
+
headId: string;
|
|
36
|
+
depth: number;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
interface Attachment {
|
|
40
|
+
headId: string;
|
|
41
|
+
historyId: string;
|
|
42
|
+
depth: number;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/**
|
|
46
|
+
* Walk supersededById forward through intermediate historical entries and
|
|
47
|
+
* stop at the first current/transition successor. Returns undefined when the
|
|
48
|
+
* walk dead-ends (no or blank successor, successor id absent from the input)
|
|
49
|
+
* or revisits an id (cycle), so callers treat the entry as an orphan.
|
|
50
|
+
*/
|
|
51
|
+
function resolveHead(
|
|
52
|
+
entry: StateViewEntry,
|
|
53
|
+
byId: ReadonlyMap<string, StateViewEntry>,
|
|
54
|
+
): HeadResolution | undefined {
|
|
55
|
+
const visited = new Set<string>();
|
|
56
|
+
let current = entry;
|
|
57
|
+
let depth = 0;
|
|
58
|
+
for (;;) {
|
|
59
|
+
const successorId = current.supersededById;
|
|
60
|
+
if (successorId === undefined || successorId.trim() === "") return undefined;
|
|
61
|
+
const successor = byId.get(successorId);
|
|
62
|
+
if (successor === undefined) return undefined;
|
|
63
|
+
depth += 1;
|
|
64
|
+
if (successor.stateLabel !== "historical") return { headId: successorId, depth };
|
|
65
|
+
if (visited.has(successorId)) return undefined;
|
|
66
|
+
visited.add(successorId);
|
|
67
|
+
current = successor;
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
function compareStrings(a: string, b: string): number {
|
|
72
|
+
return a < b ? -1 : a > b ? 1 : 0;
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
function compareAttachments(a: Attachment, b: Attachment): number {
|
|
76
|
+
if (a.headId !== b.headId) return compareStrings(a.headId, b.headId);
|
|
77
|
+
if (a.depth !== b.depth) return a.depth < b.depth ? -1 : 1;
|
|
78
|
+
return compareStrings(a.historyId, b.historyId);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
export function buildStateEvidencePackets(
|
|
82
|
+
entries: readonly StateViewEntry[],
|
|
83
|
+
): StateEvidencePackets {
|
|
84
|
+
// Validate every label before any skip, dedup, or walk.
|
|
85
|
+
for (const entry of entries) {
|
|
86
|
+
if (!(STATE_LABELS as readonly string[]).includes(entry.stateLabel)) {
|
|
87
|
+
throw new TypeError(
|
|
88
|
+
`unknown state label ${JSON.stringify(entry.stateLabel)}; expected one of "current", "historical", "transition"`,
|
|
89
|
+
);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
// Blank ids are ignored; on duplicate ids the first entry wins.
|
|
94
|
+
const byId = new Map<string, StateViewEntry>();
|
|
95
|
+
for (const entry of entries) {
|
|
96
|
+
if (typeof entry.memoryId !== "string" || entry.memoryId.trim() === "") continue;
|
|
97
|
+
if (!byId.has(entry.memoryId)) byId.set(entry.memoryId, entry);
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// ponytail: per-entry chain walks are O(n^2) on one long chain; recall-sized
|
|
101
|
+
// result sets stay tiny, memoize if packet building ever moves offline.
|
|
102
|
+
const packets: StateEvidencePacket[] = [];
|
|
103
|
+
const packetByHead = new Map<string, StateEvidencePacket>();
|
|
104
|
+
const attachments: Attachment[] = [];
|
|
105
|
+
const orphanHistoryIds: string[] = [];
|
|
106
|
+
|
|
107
|
+
for (const [id, entry] of byId) {
|
|
108
|
+
if (entry.stateLabel !== "historical") {
|
|
109
|
+
const packet: StateEvidencePacket = { headId: id, historyIds: [] };
|
|
110
|
+
packetByHead.set(id, packet);
|
|
111
|
+
packets.push(packet);
|
|
112
|
+
continue;
|
|
113
|
+
}
|
|
114
|
+
const head = resolveHead(entry, byId);
|
|
115
|
+
if (head === undefined) orphanHistoryIds.push(id);
|
|
116
|
+
else attachments.push({ headId: head.headId, historyId: id, depth: head.depth });
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
attachments.sort(compareAttachments);
|
|
120
|
+
for (const attachment of attachments) {
|
|
121
|
+
const packet = packetByHead.get(attachment.headId);
|
|
122
|
+
if (packet) packet.historyIds.push(attachment.historyId);
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
packets.sort((a, b) => compareStrings(a.headId, b.headId));
|
|
126
|
+
orphanHistoryIds.sort(compareStrings);
|
|
127
|
+
return { packets, orphanHistoryIds };
|
|
128
|
+
}
|