@remnic/core 9.69.38 → 9.69.39
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 +2 -2
- package/dist/access-admin-ops-surface.js +18 -18
- package/dist/access-authorization-probe.d.ts +2 -2
- package/dist/access-authorization-probe.js +19 -19
- package/dist/access-boundary.d.ts +2 -2
- package/dist/access-boundary.js +18 -18
- package/dist/access-cli.js +67 -66
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.d.ts +2 -2
- package/dist/access-extraction-force-flush.js +18 -18
- package/dist/access-http-briefing.d.ts +2 -2
- package/dist/access-http-briefing.js +19 -19
- package/dist/access-http-lcm-compaction.d.ts +2 -2
- package/dist/access-http-lifecycle-flush.d.ts +2 -2
- package/dist/access-http-offline-stream.d.ts +2 -2
- package/dist/access-http-query.js +20 -20
- package/dist/access-http.d.ts +3 -3
- package/dist/access-http.js +35 -35
- package/dist/access-identity-continuity-surface.d.ts +1 -1
- package/dist/access-identity-continuity-surface.js +18 -18
- package/dist/access-lcm-surface.d.ts +2 -2
- package/dist/access-lcm-surface.js +18 -18
- package/dist/access-mcp.d.ts +2 -2
- package/dist/access-mcp.js +25 -25
- package/dist/access-namespace-preflight.js +18 -18
- package/dist/access-observe-transcript.d.ts +1 -1
- package/dist/access-observe-write-surface.d.ts +2 -2
- package/dist/access-observe-write-surface.js +18 -18
- package/dist/access-offline-manifest.d.ts +1 -1
- package/dist/access-operations-batch.js +19 -19
- package/dist/access-operations.d.ts +2 -2
- package/dist/access-operations.js +24 -24
- package/dist/access-recall-concurrency.d.ts +2 -2
- package/dist/access-recall-concurrency.js +18 -18
- package/dist/access-recall-response.d.ts +2 -2
- package/dist/access-recall-response.js +18 -18
- package/dist/access-recall-surface.d.ts +2 -2
- package/dist/access-recall-surface.js +18 -18
- package/dist/{access-service-gBcdRUHY.d.ts → access-service-MY4bsr_Q.d.ts} +1 -1
- package/dist/access-service-helpers.d.ts +2 -2
- package/dist/access-service.d.ts +2 -2
- package/dist/access-service.js +18 -18
- package/dist/access-surface-catalog.d.ts +2 -2
- package/dist/access-surface-catalog.js +20 -20
- package/dist/active-recall.js +2 -2
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/bootstrap.d.ts +1 -1
- package/dist/briefing.js +7 -7
- package/dist/causal-consolidation.js +8 -8
- package/dist/{chunk-2PTNJTBE.js → chunk-2ZTDRNCS.js} +2 -2
- package/dist/{chunk-BX4EOMKT.js → chunk-3EKVN2OP.js} +2 -2
- package/dist/{chunk-DPMTLJCT.js → chunk-3NPNBVFR.js} +2 -2
- package/dist/{chunk-OKO57IXB.js → chunk-4JVQRFZD.js} +4 -4
- package/dist/{chunk-6PRY6CLC.js → chunk-4RZLTZVM.js} +1 -1
- package/dist/{chunk-PPQEP4OH.js → chunk-54NX5EG4.js} +2 -2
- package/dist/{chunk-2OGTN37H.js → chunk-5SPCMAUC.js} +3 -3
- package/dist/{chunk-TUKGXQ5T.js → chunk-5YCNDC3C.js} +2 -2
- package/dist/chunk-6AGWBKQU.js +94 -0
- package/dist/chunk-6AGWBKQU.js.map +1 -0
- package/dist/{chunk-BJEJUOGM.js → chunk-6YAIN5XJ.js} +2 -2
- package/dist/{chunk-X2NFFBTO.js → chunk-A7GQBJP5.js} +4 -4
- package/dist/{chunk-ZKZFFWLV.js → chunk-AAZEKXUP.js} +2 -2
- package/dist/{chunk-6ZMNTRU3.js → chunk-AENM6GKU.js} +2 -2
- package/dist/{chunk-7S53BPTU.js → chunk-CZTPU65V.js} +8 -8
- package/dist/{chunk-UQNYPK7G.js → chunk-D545Z7UH.js} +2 -2
- package/dist/{chunk-GT4ZHOKT.js → chunk-ENBPQEVU.js} +2 -2
- package/dist/{chunk-TRHBQVVO.js → chunk-EYKIDK33.js} +11 -11
- package/dist/{chunk-EY5JAWZC.js → chunk-F4EHXVP7.js} +2 -2
- package/dist/{chunk-474DL7UB.js → chunk-FNHLI2RE.js} +3 -3
- package/dist/{chunk-2MT4ADLQ.js → chunk-FWWWUPOE.js} +2 -2
- package/dist/{chunk-M3MYMWXG.js → chunk-FXHOZGSE.js} +2 -2
- package/dist/{chunk-VHOSM5G4.js → chunk-GG5FHZJJ.js} +2 -2
- package/dist/{chunk-CBFYCDYO.js → chunk-GI4ZBWPQ.js} +3 -3
- package/dist/{chunk-KDFDXEXZ.js → chunk-GMIQV6DD.js} +2 -2
- package/dist/{chunk-XGRXQR6T.js → chunk-IL6ODG6M.js} +2 -2
- package/dist/{chunk-XWLLFKHM.js → chunk-IQEJIOEU.js} +7 -7
- package/dist/{chunk-MLWMR2GE.js → chunk-JKKII6GX.js} +2 -2
- package/dist/{chunk-T32B72QX.js → chunk-JTQR6L2N.js} +2 -2
- package/dist/{chunk-BRZWWUCJ.js → chunk-LMLGBG6H.js} +2 -2
- package/dist/{chunk-3OYSIVOO.js → chunk-LPYYY4AP.js} +152 -112
- package/dist/chunk-LPYYY4AP.js.map +1 -0
- package/dist/{chunk-A6CKMIPK.js → chunk-MUTVRKKY.js} +8 -8
- package/dist/{chunk-JPOX2AJB.js → chunk-NXGSONWM.js} +2 -2
- package/dist/{chunk-GGAXFTNH.js → chunk-O4M4BDIJ.js} +2 -2
- package/dist/{chunk-VV4ZVUE6.js → chunk-O76F5QPP.js} +2 -2
- package/dist/{chunk-G43PMSME.js → chunk-OPBFP4TF.js} +3 -3
- package/dist/{chunk-JNSENHV5.js → chunk-PCOVEDPK.js} +3 -3
- package/dist/{chunk-VMNCZ627.js → chunk-Q432REND.js} +2 -2
- package/dist/{chunk-QMWKENNK.js → chunk-Q4AUIFUA.js} +4 -4
- package/dist/{chunk-74AHCL56.js → chunk-Q7CLGFA7.js} +2 -2
- package/dist/{chunk-5AERZWJD.js → chunk-QTQ6XKGT.js} +3 -3
- package/dist/{chunk-K5DA5TRG.js → chunk-RLJHJQUY.js} +2 -2
- package/dist/{chunk-GQQVEH6V.js → chunk-TA4C3XB7.js} +2 -2
- package/dist/{chunk-V6G2PSXA.js → chunk-TNZNSMVJ.js} +26 -26
- package/dist/{chunk-JA2QJOAX.js → chunk-TSP5WH72.js} +2 -2
- package/dist/{chunk-NWMUSKED.js → chunk-UHRZ324M.js} +3 -3
- package/dist/{chunk-62QIQEUC.js → chunk-UZEHHXYC.js} +13 -13
- package/dist/{chunk-TTSJ7WUC.js → chunk-VBSNUH4J.js} +2 -2
- package/dist/{chunk-R4VAUNNC.js → chunk-VJIE4D2I.js} +2 -2
- package/dist/{chunk-BYZKLEWG.js → chunk-VSL63CFJ.js} +5 -5
- package/dist/{chunk-4B3HBIFX.js → chunk-WBM2MVBK.js} +2 -2
- package/dist/{chunk-ULDBGA5W.js → chunk-WIWLX65T.js} +2 -2
- package/dist/{chunk-XN6XT6GR.js → chunk-X4TD4O2F.js} +2 -2
- package/dist/{chunk-UIOIDW3L.js → chunk-XH3HGI2N.js} +2 -2
- package/dist/chunk-YDVAM7S4.js +221 -0
- package/dist/chunk-YDVAM7S4.js.map +1 -0
- package/dist/{chunk-VELCIWJZ.js → chunk-YKREX63Z.js} +19 -10
- package/dist/chunk-YKREX63Z.js.map +1 -0
- package/dist/{chunk-HVS7TH2Y.js → chunk-ZBW7A667.js} +2 -2
- package/dist/{cli-BNg-TAlz.d.ts → cli-DGq0MNpO.d.ts} +2 -2
- package/dist/cli.d.ts +3 -3
- package/dist/cli.js +49 -49
- package/dist/coding/export-okf-codegraph.js +6 -6
- package/dist/compounding/engine.js +7 -7
- package/dist/config.js +2 -2
- package/dist/connectors/codex-materialize-runner.js +7 -7
- package/dist/connectors/index.js +7 -7
- package/dist/convergence-refresh.js +9 -9
- package/dist/corpus-watermark.js +9 -9
- package/dist/deep-recall-http-glue.d.ts +2 -2
- package/dist/deep-recall-mcp-tools.d.ts +2 -2
- package/dist/entity-retrieval.js +7 -7
- package/dist/explicit-capture.d.ts +1 -1
- package/dist/external-wiki-access.d.ts +2 -2
- package/dist/external-wiki-access.js +19 -19
- package/dist/external-wiki-mcp-tools.d.ts +2 -2
- package/dist/extraction.js +7 -7
- package/dist/harmonic-retrieval.js +1 -1
- package/dist/index.d.ts +6 -6
- package/dist/index.js +78 -69
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +4 -4
- package/dist/location/index.js +9 -9
- package/dist/maintenance/memory-governance.js +7 -7
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +7 -7
- package/dist/maintenance/rebuild-memory-projection.js +8 -8
- package/dist/mcp-memory-inspector-app.d.ts +2 -2
- package/dist/memory-subject-cli.d.ts +2 -2
- package/dist/memory-subject.d.ts +2 -2
- package/dist/namespaces/migrate.js +8 -8
- package/dist/namespaces/search.js +4 -4
- package/dist/namespaces/storage.js +7 -7
- package/dist/operator-doctor-corpus.js +10 -10
- package/dist/operator-toolkit.js +17 -17
- package/dist/orchestration/maintenance.js +15 -15
- package/dist/{orchestrator-DocO0_hw.d.ts → orchestrator-ComHLm49.d.ts} +13 -2
- package/dist/orchestrator.d.ts +1 -1
- package/dist/orchestrator.js +67 -66
- package/dist/{public-http-C0FoTiL_.d.ts → public-http-CuK4_Cb1.d.ts} +1 -1
- package/dist/recall-state-view-admission.js +4 -4
- package/dist/recall-state-view-anchors.d.ts +85 -0
- package/dist/recall-state-view-anchors.js +21 -0
- package/dist/recall-state-view-anchors.js.map +1 -0
- package/dist/recall-state-view-inject.js +2 -2
- package/dist/recall-state-view-render.js +2 -2
- package/dist/recall-state-view-widen.js +2 -2
- package/dist/recall-state-view-wire.js +3 -3
- package/dist/recall-state-view.d.ts +37 -1
- package/dist/recall-state-view.js +11 -3
- package/dist/resume-bundles.js +3 -3
- package/dist/search/factory.js +3 -3
- package/dist/search/index.js +7 -7
- package/dist/semantic-consolidation.js +8 -8
- package/dist/semantic-rule-promotion.js +7 -7
- package/dist/semantic-rule-verifier.js +7 -7
- package/dist/source-agent-qualifier.js +7 -7
- package/dist/standup/build.js +7 -7
- package/dist/storage.js +6 -6
- package/dist/support-passport/index.d.ts +5 -5
- package/dist/support-passport/index.js +21 -21
- package/dist/transfer/export-okf.js +7 -7
- package/dist/verified-recall.js +7 -7
- package/dist/who-knows-cli.d.ts +2 -2
- package/dist/who-knows-cli.js +8 -8
- package/dist/who-knows.d.ts +2 -2
- package/dist/who-knows.js +7 -7
- package/package.json +2 -2
- package/src/orchestration/recall-internal.ts +91 -74
- package/src/orchestration/recall-rerank-coordinator.ts +41 -4
- package/src/orchestration/recall-search-pipeline.ts +25 -18
- package/src/orchestration/recall-state-view-pipeline.test.ts +89 -0
- package/src/recall-rerank-coordinator.test.ts +153 -0
- package/src/recall-state-view-anchors.ts +163 -0
- package/src/recall-state-view-widen.ts +21 -7
- package/src/recall-state-view-zero-diff.test.ts +30 -0
- package/src/recall-state-view.test.ts +161 -0
- package/src/recall-state-view.ts +206 -35
- package/src/trust-score-recall-paths.test.ts +1 -1
- package/dist/chunk-255PC37A.js +0 -120
- package/dist/chunk-255PC37A.js.map +0 -1
- package/dist/chunk-3OYSIVOO.js.map +0 -1
- package/dist/chunk-VELCIWJZ.js.map +0 -1
- /package/dist/{chunk-2PTNJTBE.js.map → chunk-2ZTDRNCS.js.map} +0 -0
- /package/dist/{chunk-BX4EOMKT.js.map → chunk-3EKVN2OP.js.map} +0 -0
- /package/dist/{chunk-DPMTLJCT.js.map → chunk-3NPNBVFR.js.map} +0 -0
- /package/dist/{chunk-OKO57IXB.js.map → chunk-4JVQRFZD.js.map} +0 -0
- /package/dist/{chunk-6PRY6CLC.js.map → chunk-4RZLTZVM.js.map} +0 -0
- /package/dist/{chunk-PPQEP4OH.js.map → chunk-54NX5EG4.js.map} +0 -0
- /package/dist/{chunk-2OGTN37H.js.map → chunk-5SPCMAUC.js.map} +0 -0
- /package/dist/{chunk-TUKGXQ5T.js.map → chunk-5YCNDC3C.js.map} +0 -0
- /package/dist/{chunk-BJEJUOGM.js.map → chunk-6YAIN5XJ.js.map} +0 -0
- /package/dist/{chunk-X2NFFBTO.js.map → chunk-A7GQBJP5.js.map} +0 -0
- /package/dist/{chunk-ZKZFFWLV.js.map → chunk-AAZEKXUP.js.map} +0 -0
- /package/dist/{chunk-6ZMNTRU3.js.map → chunk-AENM6GKU.js.map} +0 -0
- /package/dist/{chunk-7S53BPTU.js.map → chunk-CZTPU65V.js.map} +0 -0
- /package/dist/{chunk-UQNYPK7G.js.map → chunk-D545Z7UH.js.map} +0 -0
- /package/dist/{chunk-GT4ZHOKT.js.map → chunk-ENBPQEVU.js.map} +0 -0
- /package/dist/{chunk-TRHBQVVO.js.map → chunk-EYKIDK33.js.map} +0 -0
- /package/dist/{chunk-EY5JAWZC.js.map → chunk-F4EHXVP7.js.map} +0 -0
- /package/dist/{chunk-474DL7UB.js.map → chunk-FNHLI2RE.js.map} +0 -0
- /package/dist/{chunk-2MT4ADLQ.js.map → chunk-FWWWUPOE.js.map} +0 -0
- /package/dist/{chunk-M3MYMWXG.js.map → chunk-FXHOZGSE.js.map} +0 -0
- /package/dist/{chunk-VHOSM5G4.js.map → chunk-GG5FHZJJ.js.map} +0 -0
- /package/dist/{chunk-CBFYCDYO.js.map → chunk-GI4ZBWPQ.js.map} +0 -0
- /package/dist/{chunk-KDFDXEXZ.js.map → chunk-GMIQV6DD.js.map} +0 -0
- /package/dist/{chunk-XGRXQR6T.js.map → chunk-IL6ODG6M.js.map} +0 -0
- /package/dist/{chunk-XWLLFKHM.js.map → chunk-IQEJIOEU.js.map} +0 -0
- /package/dist/{chunk-MLWMR2GE.js.map → chunk-JKKII6GX.js.map} +0 -0
- /package/dist/{chunk-T32B72QX.js.map → chunk-JTQR6L2N.js.map} +0 -0
- /package/dist/{chunk-BRZWWUCJ.js.map → chunk-LMLGBG6H.js.map} +0 -0
- /package/dist/{chunk-A6CKMIPK.js.map → chunk-MUTVRKKY.js.map} +0 -0
- /package/dist/{chunk-JPOX2AJB.js.map → chunk-NXGSONWM.js.map} +0 -0
- /package/dist/{chunk-GGAXFTNH.js.map → chunk-O4M4BDIJ.js.map} +0 -0
- /package/dist/{chunk-VV4ZVUE6.js.map → chunk-O76F5QPP.js.map} +0 -0
- /package/dist/{chunk-G43PMSME.js.map → chunk-OPBFP4TF.js.map} +0 -0
- /package/dist/{chunk-JNSENHV5.js.map → chunk-PCOVEDPK.js.map} +0 -0
- /package/dist/{chunk-VMNCZ627.js.map → chunk-Q432REND.js.map} +0 -0
- /package/dist/{chunk-QMWKENNK.js.map → chunk-Q4AUIFUA.js.map} +0 -0
- /package/dist/{chunk-74AHCL56.js.map → chunk-Q7CLGFA7.js.map} +0 -0
- /package/dist/{chunk-5AERZWJD.js.map → chunk-QTQ6XKGT.js.map} +0 -0
- /package/dist/{chunk-K5DA5TRG.js.map → chunk-RLJHJQUY.js.map} +0 -0
- /package/dist/{chunk-GQQVEH6V.js.map → chunk-TA4C3XB7.js.map} +0 -0
- /package/dist/{chunk-V6G2PSXA.js.map → chunk-TNZNSMVJ.js.map} +0 -0
- /package/dist/{chunk-JA2QJOAX.js.map → chunk-TSP5WH72.js.map} +0 -0
- /package/dist/{chunk-NWMUSKED.js.map → chunk-UHRZ324M.js.map} +0 -0
- /package/dist/{chunk-62QIQEUC.js.map → chunk-UZEHHXYC.js.map} +0 -0
- /package/dist/{chunk-TTSJ7WUC.js.map → chunk-VBSNUH4J.js.map} +0 -0
- /package/dist/{chunk-R4VAUNNC.js.map → chunk-VJIE4D2I.js.map} +0 -0
- /package/dist/{chunk-BYZKLEWG.js.map → chunk-VSL63CFJ.js.map} +0 -0
- /package/dist/{chunk-4B3HBIFX.js.map → chunk-WBM2MVBK.js.map} +0 -0
- /package/dist/{chunk-ULDBGA5W.js.map → chunk-WIWLX65T.js.map} +0 -0
- /package/dist/{chunk-XN6XT6GR.js.map → chunk-X4TD4O2F.js.map} +0 -0
- /package/dist/{chunk-UIOIDW3L.js.map → chunk-XH3HGI2N.js.map} +0 -0
- /package/dist/{chunk-HVS7TH2Y.js.map → chunk-ZBW7A667.js.map} +0 -0
|
@@ -3,9 +3,11 @@ import test from "node:test";
|
|
|
3
3
|
|
|
4
4
|
import {
|
|
5
5
|
annotateStateView,
|
|
6
|
+
capStateViewPackets,
|
|
6
7
|
formatSupersededPrefix,
|
|
7
8
|
isChangeOrientedQuery,
|
|
8
9
|
parseRecallStateViews,
|
|
10
|
+
reconcileStateViewPairs,
|
|
9
11
|
shouldWidenSuperseded,
|
|
10
12
|
type StateViewChain,
|
|
11
13
|
type StateViewResult,
|
|
@@ -271,3 +273,162 @@ test("transitive orphan chains collapse: A→B→(C absent) drops both A and B",
|
|
|
271
273
|
"the fixpoint must drop A once B is dropped, not render a dangling historical",
|
|
272
274
|
);
|
|
273
275
|
});
|
|
276
|
+
|
|
277
|
+
test("#2859 reverse chain derives from the successor supersedes back-pointer when supersededBy is absent", () => {
|
|
278
|
+
const results = [
|
|
279
|
+
fact("new-job", { status: "active", supersedes: "old-job" }),
|
|
280
|
+
// Corrected row: no supersededBy, and even no "superseded" status —
|
|
281
|
+
// the ONLY link is the successor's back-pointer.
|
|
282
|
+
fact("old-job", {}),
|
|
283
|
+
];
|
|
284
|
+
const labeled = annotateStateView(results, "when did the job title change", [], { enabled: true });
|
|
285
|
+
assert.deepEqual(
|
|
286
|
+
labeled.map((row) => [row.id, row.stateLabel, row.supersededBy]),
|
|
287
|
+
[
|
|
288
|
+
["new-job", "current", undefined],
|
|
289
|
+
["old-job", "historical", "new-job"],
|
|
290
|
+
],
|
|
291
|
+
"the back-pointer must link the pair, admit the predecessor, and carry derived supersededBy",
|
|
292
|
+
);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
test("#2859 back-pointer pair survives the orphan fixpoint in both directions", () => {
|
|
296
|
+
const results = [
|
|
297
|
+
fact("v3", { status: "active", supersedes: "v2" }),
|
|
298
|
+
fact("v2", { status: "superseded", supersedes: "v1" }),
|
|
299
|
+
fact("v1", {}),
|
|
300
|
+
];
|
|
301
|
+
const labeled = annotateStateView(results, "what changed about the database", [], { enabled: true });
|
|
302
|
+
assert.deepEqual(
|
|
303
|
+
labeled.map((row) => [row.id, row.stateLabel]),
|
|
304
|
+
[
|
|
305
|
+
["v3", "current"],
|
|
306
|
+
["v2", "transition"],
|
|
307
|
+
["v1", "historical"],
|
|
308
|
+
],
|
|
309
|
+
);
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
test("#2859 asOf label uses the validity boundary, not the supersededAt write time", () => {
|
|
313
|
+
const pin = Date.parse("2026-08-05T00:00:00.000Z");
|
|
314
|
+
// Write time January, validity flip September: at an August pin the row
|
|
315
|
+
// was still the live fact even though supersededAt PREDATES the pin.
|
|
316
|
+
const predecessor = fact("old-job", {
|
|
317
|
+
status: "superseded",
|
|
318
|
+
supersededBy: "new-job",
|
|
319
|
+
supersededAt: "2026-01-01T00:00:00.000Z",
|
|
320
|
+
invalidAt: "2026-09-01T00:00:00.000Z",
|
|
321
|
+
});
|
|
322
|
+
const labeled = annotateStateView([predecessor], "when did the job title change", [], {
|
|
323
|
+
enabled: true,
|
|
324
|
+
asOfMs: pin,
|
|
325
|
+
});
|
|
326
|
+
assert.equal(
|
|
327
|
+
labeled[0]?.stateLabel,
|
|
328
|
+
"current",
|
|
329
|
+
"the boundary is invalidAt, so a pin before the flip labels the row current-at-snapshot",
|
|
330
|
+
);
|
|
331
|
+
|
|
332
|
+
// Past the boundary the same row labels historical (boundary <= pin).
|
|
333
|
+
const afterFlip = annotateStateView([predecessor], "when did the job title change", [], {
|
|
334
|
+
enabled: true,
|
|
335
|
+
asOfMs: Date.parse("2026-09-05T00:00:00.000Z"),
|
|
336
|
+
});
|
|
337
|
+
assert.equal(
|
|
338
|
+
afterFlip[0]?.stateLabel,
|
|
339
|
+
"historical",
|
|
340
|
+
"a pin past the validity flip labels the row historical",
|
|
341
|
+
);
|
|
342
|
+
|
|
343
|
+
// Legacy row without a validity window keeps the supersededAt fallback.
|
|
344
|
+
const legacy = fact("old-legacy", {
|
|
345
|
+
status: "superseded",
|
|
346
|
+
supersededBy: "new-legacy",
|
|
347
|
+
supersededAt: "2026-01-01T00:00:00.000Z",
|
|
348
|
+
});
|
|
349
|
+
const legacyLabel = annotateStateView([legacy], "when did the job title change", [], {
|
|
350
|
+
enabled: true,
|
|
351
|
+
asOfMs: pin,
|
|
352
|
+
});
|
|
353
|
+
assert.equal(
|
|
354
|
+
legacyLabel[0]?.stateLabel,
|
|
355
|
+
"historical",
|
|
356
|
+
"no invalidAt → the supersededAt write time is the only recorded boundary",
|
|
357
|
+
);
|
|
358
|
+
});
|
|
359
|
+
|
|
360
|
+
test("#2859 namespace-qualified identities: same id across namespaces never cross-anchors", () => {
|
|
361
|
+
// ns-a has the superseded predecessor; ns-b has an UNRELATED row that
|
|
362
|
+
// happens to reuse the successor id. Bare-id matching would admit the
|
|
363
|
+
// ns-a predecessor on the ns-b anchor.
|
|
364
|
+
const results = [
|
|
365
|
+
fact("m-1", { namespace: "ns-b", status: "active" }),
|
|
366
|
+
fact("m-0", { namespace: "ns-a", status: "superseded", supersededBy: "m-1" }),
|
|
367
|
+
];
|
|
368
|
+
const labeled = annotateStateView(results, "when did this change", [], { enabled: true });
|
|
369
|
+
assert.deepEqual(
|
|
370
|
+
labeled.map((row) => row.id),
|
|
371
|
+
["m-1"],
|
|
372
|
+
"a foreign-namespace successor must not anchor the superseded row",
|
|
373
|
+
);
|
|
374
|
+
|
|
375
|
+
// Positive control: the same-namespace successor anchors it.
|
|
376
|
+
const anchored = annotateStateView(
|
|
377
|
+
[
|
|
378
|
+
fact("m-1", { namespace: "ns-a", status: "active" }),
|
|
379
|
+
fact("m-0", { namespace: "ns-a", status: "superseded", supersededBy: "m-1" }),
|
|
380
|
+
],
|
|
381
|
+
"when did this change",
|
|
382
|
+
[],
|
|
383
|
+
{ enabled: true },
|
|
384
|
+
);
|
|
385
|
+
assert.deepEqual(
|
|
386
|
+
anchored.map((row) => [row.id, row.stateLabel]),
|
|
387
|
+
[
|
|
388
|
+
["m-1", "current"],
|
|
389
|
+
["m-0", "historical"],
|
|
390
|
+
],
|
|
391
|
+
);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
test("#2859 namespace-qualified chains: a chain link never crosses namespaces", () => {
|
|
395
|
+
const chains: StateViewChain[] = [
|
|
396
|
+
{ predecessorId: "old", successorId: "new", namespace: "ns-a" },
|
|
397
|
+
];
|
|
398
|
+
const results = [
|
|
399
|
+
fact("new", { namespace: "ns-b", status: "active" }),
|
|
400
|
+
fact("old", { namespace: "ns-a", status: "superseded", supersededAt: "2026-02-01" }),
|
|
401
|
+
];
|
|
402
|
+
const labeled = annotateStateView(results, "before the correction what was it", chains, {
|
|
403
|
+
enabled: true,
|
|
404
|
+
});
|
|
405
|
+
assert.deepEqual(
|
|
406
|
+
labeled.map((row) => row.id),
|
|
407
|
+
["new"],
|
|
408
|
+
"the ns-a chain link must not pair against the ns-b successor",
|
|
409
|
+
);
|
|
410
|
+
});
|
|
411
|
+
|
|
412
|
+
test("reconcile drops a linkless superseded row so it cannot consume a packet slot", () => {
|
|
413
|
+
const rows = [
|
|
414
|
+
fact("legacy", { status: "superseded", supersededAt: "2026-01-01" }),
|
|
415
|
+
fact("live", { status: "active" }),
|
|
416
|
+
];
|
|
417
|
+
assert.deepEqual(
|
|
418
|
+
reconcileStateViewPairs(rows).map((row) => row.id),
|
|
419
|
+
["live"],
|
|
420
|
+
"audit/kill-switch status-only rows have no successor and must leave before the cap",
|
|
421
|
+
);
|
|
422
|
+
assert.deepEqual(
|
|
423
|
+
capStateViewPackets(reconcileStateViewPairs(rows), 1).map((row) => row.id),
|
|
424
|
+
["live"],
|
|
425
|
+
);
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
test("reconcile still keeps an unlinked active row", () => {
|
|
429
|
+
const rows = [fact("solo", { status: "active" })];
|
|
430
|
+
assert.deepEqual(
|
|
431
|
+
reconcileStateViewPairs(rows).map((row) => row.id),
|
|
432
|
+
["solo"],
|
|
433
|
+
);
|
|
434
|
+
});
|
package/src/recall-state-view.ts
CHANGED
|
@@ -8,6 +8,13 @@
|
|
|
8
8
|
*
|
|
9
9
|
* Change-intent morphology lives here (do not grow intent.ts). A superseded
|
|
10
10
|
* memory is admitted only when its successor is also in the candidate set.
|
|
11
|
+
*
|
|
12
|
+
* #2859 — chain identities are namespace-qualified (`namespace\0id`): the
|
|
13
|
+
* same id in two namespaces never cross-anchors. The reverse chain derives
|
|
14
|
+
* from the successor's `supersedes` back-pointer when a predecessor lacks
|
|
15
|
+
* `supersededBy`, and asOf snapshot labels use the temporal validity
|
|
16
|
+
* boundary (predecessor `invalidAt` / successor `validAt`), not the
|
|
17
|
+
* `supersededAt` write time.
|
|
11
18
|
*/
|
|
12
19
|
import { coerceBooleanLike } from "./connectors/coerce.js";
|
|
13
20
|
|
|
@@ -17,14 +24,24 @@ export interface StateViewChain {
|
|
|
17
24
|
predecessorId: string;
|
|
18
25
|
successorId: string;
|
|
19
26
|
supersededAt?: string;
|
|
27
|
+
/** #2859 — namespace scope; a chain link never crosses namespaces. */
|
|
28
|
+
namespace?: string;
|
|
20
29
|
}
|
|
21
30
|
|
|
22
31
|
export interface StateViewResult {
|
|
23
32
|
id?: string;
|
|
24
33
|
docid?: string;
|
|
25
34
|
status?: string;
|
|
35
|
+
/** #2859 — namespace that owns this row; qualifies every chain identity. */
|
|
36
|
+
namespace?: string;
|
|
37
|
+
/** #2859 — back-pointer: id of the row this one replaced. */
|
|
38
|
+
supersedes?: string;
|
|
26
39
|
supersededBy?: string;
|
|
27
40
|
supersededAt?: string;
|
|
41
|
+
/** #2859 — fact validity start (frontmatter `valid_at`). */
|
|
42
|
+
validAt?: string;
|
|
43
|
+
/** #2859 — fact validity end (frontmatter `invalid_at`). */
|
|
44
|
+
invalidAt?: string;
|
|
28
45
|
stateLabel?: StateLabel;
|
|
29
46
|
}
|
|
30
47
|
|
|
@@ -66,32 +83,94 @@ export function resultStateViewId(result: StateViewResult): string {
|
|
|
66
83
|
return result.id ?? result.docid ?? "";
|
|
67
84
|
}
|
|
68
85
|
|
|
69
|
-
|
|
86
|
+
/**
|
|
87
|
+
* #2859 — namespace-qualified chain identity. Namespace fanout can surface
|
|
88
|
+
* the same id from two namespaces; every successor/admission match runs on
|
|
89
|
+
* `namespace\0id` keys so a row in one namespace can never anchor (or be
|
|
90
|
+
* anchored by) an identically-named row in another.
|
|
91
|
+
*/
|
|
92
|
+
export function stateViewKey(namespace: string | undefined, id: string): string {
|
|
93
|
+
return `${typeof namespace === "string" ? namespace : ""}\u0000${id}`;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function resultStateViewKey(result: StateViewResult): string {
|
|
97
|
+
const id = resultStateViewId(result);
|
|
98
|
+
return id ? stateViewKey(result.namespace, id) : "";
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
/** Inverse of {@link stateViewKey}: the id after the namespace separator. */
|
|
102
|
+
function stateViewIdFromKey(key: string): string {
|
|
103
|
+
const sep = key.indexOf("\u0000");
|
|
104
|
+
return sep === -1 ? key : key.slice(sep + 1);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
/**
|
|
108
|
+
* predecessorKey → successorKey, namespace-qualified. Precedence: explicit
|
|
109
|
+
* chains, then successor rows' `supersedes` back-pointers (#2859 — derives
|
|
110
|
+
* the reverse chain when the predecessor lacks `supersededBy`), then the
|
|
111
|
+
* predecessor's own `supersededBy` (authoritative when present).
|
|
112
|
+
*/
|
|
113
|
+
function buildSuccessorMap(
|
|
114
|
+
results: readonly StateViewResult[],
|
|
115
|
+
chains: readonly StateViewChain[],
|
|
116
|
+
): Map<string, string> {
|
|
70
117
|
const byPred = new Map<string, string>();
|
|
118
|
+
const link = (predKey: string, succKey: string, overwrite: boolean): void => {
|
|
119
|
+
if (!predKey || !succKey) return;
|
|
120
|
+
if (overwrite || !byPred.has(predKey)) byPred.set(predKey, succKey);
|
|
121
|
+
};
|
|
71
122
|
for (const chain of chains) {
|
|
72
123
|
if (chain.predecessorId && chain.successorId) {
|
|
73
|
-
|
|
124
|
+
link(
|
|
125
|
+
stateViewKey(chain.namespace, chain.predecessorId),
|
|
126
|
+
stateViewKey(chain.namespace, chain.successorId),
|
|
127
|
+
false,
|
|
128
|
+
);
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
for (const result of results) {
|
|
132
|
+
const key = resultStateViewKey(result);
|
|
133
|
+
if (key && result.supersedes) {
|
|
134
|
+
link(stateViewKey(result.namespace, result.supersedes), key, false);
|
|
74
135
|
}
|
|
75
136
|
}
|
|
76
137
|
for (const result of results) {
|
|
77
|
-
const
|
|
78
|
-
if (
|
|
138
|
+
const key = resultStateViewKey(result);
|
|
139
|
+
if (key && result.supersededBy) {
|
|
140
|
+
link(key, stateViewKey(result.namespace, result.supersededBy), true);
|
|
141
|
+
}
|
|
79
142
|
}
|
|
80
143
|
return byPred;
|
|
81
144
|
}
|
|
82
145
|
|
|
146
|
+
function successorLinkOf(
|
|
147
|
+
result: StateViewResult,
|
|
148
|
+
byPred: ReadonlyMap<string, string>,
|
|
149
|
+
): { superseded: boolean; successorKey: string | undefined } {
|
|
150
|
+
const key = resultStateViewKey(result);
|
|
151
|
+
return {
|
|
152
|
+
superseded:
|
|
153
|
+
result.status === "superseded" ||
|
|
154
|
+
Boolean(result.supersededBy) ||
|
|
155
|
+
byPred.has(key),
|
|
156
|
+
successorKey: result.supersededBy
|
|
157
|
+
? stateViewKey(result.namespace, result.supersededBy)
|
|
158
|
+
: byPred.get(key),
|
|
159
|
+
};
|
|
160
|
+
}
|
|
161
|
+
|
|
83
162
|
function labelFor(
|
|
84
|
-
|
|
163
|
+
key: string,
|
|
85
164
|
result: StateViewResult,
|
|
86
165
|
byPred: Map<string, string>,
|
|
87
166
|
admittedIds: ReadonlySet<string>,
|
|
88
167
|
asOfMs: number | null,
|
|
89
168
|
): StateLabel {
|
|
90
|
-
const
|
|
91
|
-
const isPred = shouldWidenSuperseded(
|
|
169
|
+
const { successorKey } = successorLinkOf(result, byPred);
|
|
170
|
+
const isPred = shouldWidenSuperseded(successorKey, admittedIds);
|
|
92
171
|
let isSucc = false;
|
|
93
172
|
for (const [predId, succId] of byPred) {
|
|
94
|
-
if (succId ===
|
|
173
|
+
if (succId === key && admittedIds.has(predId)) {
|
|
95
174
|
isSucc = true;
|
|
96
175
|
break;
|
|
97
176
|
}
|
|
@@ -101,19 +180,25 @@ function labelFor(
|
|
|
101
180
|
if (isSucc) return "current";
|
|
102
181
|
if (result.status === "superseded") {
|
|
103
182
|
// #1952 asOf mode: the successor is legitimately absent under the
|
|
104
|
-
// historical pin
|
|
105
|
-
//
|
|
106
|
-
//
|
|
183
|
+
// historical pin (a linked successor takes the pair branches above).
|
|
184
|
+
// The row was the live fact at the snapshot when the temporal
|
|
185
|
+
// validity boundary postdates the pin ("current" relative to the
|
|
186
|
+
// snapshot); already replaced at the pin is "historical". #2859: the
|
|
187
|
+
// boundary is the fact's validity flip — `invalidAt` (the supersession
|
|
188
|
+
// pipeline sets it to the successor's valid_at) — not the
|
|
189
|
+
// `supersededAt` write time; legacy rows without a window fall back
|
|
190
|
+
// to supersededAt.
|
|
107
191
|
return asOfMs === null ? "historical" : snapshotStateLabel(result, asOfMs);
|
|
108
192
|
}
|
|
109
193
|
return "current";
|
|
110
194
|
}
|
|
111
195
|
|
|
112
196
|
function snapshotStateLabel(result: StateViewResult, asOfMs: number): StateLabel {
|
|
113
|
-
const
|
|
114
|
-
|
|
115
|
-
?
|
|
116
|
-
: "
|
|
197
|
+
const invalidAtMs = Date.parse(result.invalidAt ?? "");
|
|
198
|
+
const boundaryMs = Number.isFinite(invalidAtMs)
|
|
199
|
+
? invalidAtMs
|
|
200
|
+
: Date.parse(result.supersededAt ?? "");
|
|
201
|
+
return Number.isFinite(boundaryMs) && boundaryMs <= asOfMs ? "historical" : "current";
|
|
117
202
|
}
|
|
118
203
|
|
|
119
204
|
export function annotateStateView<T extends StateViewResult>(
|
|
@@ -136,26 +221,17 @@ export function annotateStateView<T extends StateViewResult>(
|
|
|
136
221
|
const byPred = buildSuccessorMap(results, chains);
|
|
137
222
|
const candidateIds = new Set<string>();
|
|
138
223
|
for (const result of results) {
|
|
139
|
-
const
|
|
140
|
-
if (
|
|
224
|
+
const key = resultStateViewKey(result);
|
|
225
|
+
if (key) candidateIds.add(key);
|
|
141
226
|
}
|
|
142
|
-
|
|
143
|
-
const isSupersededRow = (result: T): { superseded: boolean; successorId: string | undefined } => {
|
|
144
|
-
const id = resultStateViewId(result);
|
|
145
|
-
return {
|
|
146
|
-
superseded: result.status === "superseded" || Boolean(result.supersededBy) || byPred.has(id),
|
|
147
|
-
successorId: result.supersededBy ?? byPred.get(id),
|
|
148
|
-
};
|
|
149
|
-
};
|
|
150
|
-
|
|
151
227
|
let admitted: T[];
|
|
152
228
|
if (asOfMs !== null) {
|
|
153
229
|
admitted = results.slice();
|
|
154
230
|
} else {
|
|
155
231
|
admitted = [];
|
|
156
232
|
for (const result of results) {
|
|
157
|
-
const { superseded,
|
|
158
|
-
if (!superseded || shouldWidenSuperseded(
|
|
233
|
+
const { superseded, successorKey } = successorLinkOf(result, byPred);
|
|
234
|
+
if (!superseded || shouldWidenSuperseded(successorKey, candidateIds)) {
|
|
159
235
|
admitted.push(result);
|
|
160
236
|
}
|
|
161
237
|
}
|
|
@@ -163,24 +239,119 @@ export function annotateStateView<T extends StateViewResult>(
|
|
|
163
239
|
// #1952 contract: superseded never appears without its successor. A
|
|
164
240
|
// successor can itself be dropped (filtered elsewhere, or transitively
|
|
165
241
|
// orphaned), so reconcile to a fixpoint instead of trusting the first pass.
|
|
166
|
-
let admittedIds = new Set(admitted.map(
|
|
242
|
+
let admittedIds = new Set(admitted.map(resultStateViewKey).filter(Boolean));
|
|
167
243
|
for (let changed = true; changed; ) {
|
|
168
244
|
changed = false;
|
|
169
245
|
for (let i = admitted.length - 1; i >= 0; i -= 1) {
|
|
170
246
|
const row = admitted[i]!;
|
|
171
|
-
const { superseded,
|
|
172
|
-
if (superseded && !shouldWidenSuperseded(
|
|
247
|
+
const { superseded, successorKey } = successorLinkOf(row, byPred);
|
|
248
|
+
if (superseded && !shouldWidenSuperseded(successorKey, admittedIds)) {
|
|
173
249
|
admitted.splice(i, 1);
|
|
174
250
|
changed = true;
|
|
175
251
|
}
|
|
176
252
|
}
|
|
177
|
-
if (changed) admittedIds = new Set(admitted.map(
|
|
253
|
+
if (changed) admittedIds = new Set(admitted.map(resultStateViewKey).filter(Boolean));
|
|
178
254
|
}
|
|
179
255
|
}
|
|
180
256
|
|
|
181
|
-
const admittedIds = new Set(admitted.map(
|
|
257
|
+
const admittedIds = new Set(admitted.map(resultStateViewKey).filter(Boolean));
|
|
182
258
|
return admitted.map((result) => {
|
|
183
|
-
const
|
|
184
|
-
|
|
259
|
+
const key = resultStateViewKey(result);
|
|
260
|
+
const { successorKey } = successorLinkOf(result, byPred);
|
|
261
|
+
const stateLabel = labelFor(key, result, byPred, admittedIds, asOfMs);
|
|
262
|
+
if (result.supersededBy || !successorKey) return { ...result, stateLabel };
|
|
263
|
+
return { ...result, stateLabel, supersededBy: stateViewIdFromKey(successorKey) };
|
|
185
264
|
});
|
|
186
265
|
}
|
|
266
|
+
|
|
267
|
+
/**
|
|
268
|
+
* #2859 — drop superseded rows whose successor is not in the pool BEFORE
|
|
269
|
+
* the user cap/MMR runs, so post-cap orphan removal can never underfill
|
|
270
|
+
* the result set. Reconciles to a fixpoint (a dropped successor orphans
|
|
271
|
+
* its own predecessors). Rows with no successor links pass through in
|
|
272
|
+
* order. ponytail: repeated full-pool passes are O(n²) on one long chain;
|
|
273
|
+
* recall pools stay small, memoize per-key if pools ever grow.
|
|
274
|
+
*/
|
|
275
|
+
export function reconcileStateViewPairs<T extends StateViewResult>(results: readonly T[]): T[] {
|
|
276
|
+
const byPred = buildSuccessorMap(results, []);
|
|
277
|
+
let kept = results.slice();
|
|
278
|
+
for (let changed = true; changed; ) {
|
|
279
|
+
changed = false;
|
|
280
|
+
const keptKeys = new Set(kept.map(resultStateViewKey).filter(Boolean));
|
|
281
|
+
const next: T[] = [];
|
|
282
|
+
for (const row of kept) {
|
|
283
|
+
const { superseded, successorKey } = successorLinkOf(row, byPred);
|
|
284
|
+
if (superseded && !shouldWidenSuperseded(successorKey, keptKeys)) {
|
|
285
|
+
changed = true;
|
|
286
|
+
continue;
|
|
287
|
+
}
|
|
288
|
+
next.push(row);
|
|
289
|
+
}
|
|
290
|
+
if (changed) kept = next;
|
|
291
|
+
}
|
|
292
|
+
return kept;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
/**
|
|
296
|
+
* #2859 — packet-counting cap. A predecessor/successor chain admitted
|
|
297
|
+
* together is ONE complete evidence packet: it consumes a single slot of
|
|
298
|
+
* the user cap, and the slice never splits a packet at the boundary (no
|
|
299
|
+
* underfill from post-cap orphan removal). Rows keep their incoming
|
|
300
|
+
* (MMR) order; packets are ordered by first appearance. Call
|
|
301
|
+
* reconcileStateViewPairs first so orphans never consume a slot.
|
|
302
|
+
*/
|
|
303
|
+
export function capStateViewPackets<T extends StateViewResult>(
|
|
304
|
+
ordered: readonly T[],
|
|
305
|
+
limit: number,
|
|
306
|
+
): T[] {
|
|
307
|
+
const safeLimit =
|
|
308
|
+
typeof limit === "number" && Number.isFinite(limit) ? Math.max(0, Math.floor(limit)) : 0;
|
|
309
|
+
if (safeLimit === 0) return [];
|
|
310
|
+
if (ordered.length === 0) return [];
|
|
311
|
+
|
|
312
|
+
const byPred = buildSuccessorMap(ordered, []);
|
|
313
|
+
// Union-find over row keys; blank-key rows become per-index singletons.
|
|
314
|
+
const parent = new Map<string, string>();
|
|
315
|
+
const root = (key: string): string => {
|
|
316
|
+
let current = key;
|
|
317
|
+
for (;;) {
|
|
318
|
+
const up = parent.get(current);
|
|
319
|
+
if (up === undefined || up === current) {
|
|
320
|
+
parent.set(current, current);
|
|
321
|
+
return current;
|
|
322
|
+
}
|
|
323
|
+
current = up;
|
|
324
|
+
}
|
|
325
|
+
};
|
|
326
|
+
const union = (a: string, b: string): void => {
|
|
327
|
+
const ra = root(a);
|
|
328
|
+
const rb = root(b);
|
|
329
|
+
if (ra !== rb) parent.set(ra, rb);
|
|
330
|
+
};
|
|
331
|
+
const keys: string[] = [];
|
|
332
|
+
const keyToIndex = new Map<string, number>();
|
|
333
|
+
for (let i = 0; i < ordered.length; i += 1) {
|
|
334
|
+
// ponytail: blank-key rows must stay distinct singletons; index-keyed.
|
|
335
|
+
const key = resultStateViewKey(ordered[i]!) || `#idx${i}`;
|
|
336
|
+
keys.push(key);
|
|
337
|
+
if (!parent.has(key)) parent.set(key, key);
|
|
338
|
+
const first = keyToIndex.get(key);
|
|
339
|
+
if (first !== undefined) union(keys[first]!, key);
|
|
340
|
+
else keyToIndex.set(key, i);
|
|
341
|
+
}
|
|
342
|
+
for (const [predKey, succKey] of byPred) {
|
|
343
|
+
const predIdx = keyToIndex.get(predKey);
|
|
344
|
+
const succIdx = keyToIndex.get(succKey);
|
|
345
|
+
if (predIdx !== undefined && succIdx !== undefined) union(keys[predIdx]!, keys[succIdx]!);
|
|
346
|
+
}
|
|
347
|
+
// Packets ordered by first appearance; admit whole packets until the cap.
|
|
348
|
+
const selectedRoots = new Set<string>();
|
|
349
|
+
for (let i = 0; i < ordered.length && selectedRoots.size < safeLimit; i += 1) {
|
|
350
|
+
selectedRoots.add(root(keys[i]!));
|
|
351
|
+
}
|
|
352
|
+
const selected: T[] = [];
|
|
353
|
+
for (let i = 0; i < ordered.length; i += 1) {
|
|
354
|
+
if (selectedRoots.has(root(keys[i]!))) selected.push(ordered[i]!);
|
|
355
|
+
}
|
|
356
|
+
return selected;
|
|
357
|
+
}
|
|
@@ -175,7 +175,7 @@ test("TrustScore quarantine excludes a contradicted memory from injection but su
|
|
|
175
175
|
|
|
176
176
|
test("X-ray capture preserves recent-scan serving while recording post-trust applied and pre-trust headroom", async () => {
|
|
177
177
|
const { orchestrator, memoryDir } = await makeOrchestrator({
|
|
178
|
-
qmdMaxResults:
|
|
178
|
+
qmdMaxResults: 1,
|
|
179
179
|
});
|
|
180
180
|
const now = Date.now();
|
|
181
181
|
try {
|
package/dist/chunk-255PC37A.js
DELETED
|
@@ -1,120 +0,0 @@
|
|
|
1
|
-
import {
|
|
2
|
-
coerceBooleanLike
|
|
3
|
-
} from "./chunk-PQVCH2AE.js";
|
|
4
|
-
|
|
5
|
-
// src/recall-state-view.ts
|
|
6
|
-
var DEFAULT_RECALL_STATE_VIEWS = false;
|
|
7
|
-
var CHANGE_WORDS = ["switch", "switches", "switched", "switching", "change", "changes", "changed", "changing"];
|
|
8
|
-
var CHANGE_PHRASE_RE = /\bwhen\s+did\b|\bused\s+to\b/;
|
|
9
|
-
var CHANGE_WORD_RE = new RegExp(`\\b(?:${CHANGE_WORDS.join("|")})\\b`);
|
|
10
|
-
var EVENT_POINTER_RE = /\b(?:before|after)\s+(?:the|a|an|this|that|these|those)\b/;
|
|
11
|
-
function parseRecallStateViews(raw) {
|
|
12
|
-
return coerceBooleanLike(raw, "recallStateViews") === true;
|
|
13
|
-
}
|
|
14
|
-
function isChangeOrientedQuery(query) {
|
|
15
|
-
const lower = query.toLowerCase();
|
|
16
|
-
return CHANGE_PHRASE_RE.test(lower) || CHANGE_WORD_RE.test(lower) || EVENT_POINTER_RE.test(lower);
|
|
17
|
-
}
|
|
18
|
-
function shouldWidenSuperseded(successorId, candidateIds) {
|
|
19
|
-
return typeof successorId === "string" && successorId.length > 0 && candidateIds.has(successorId);
|
|
20
|
-
}
|
|
21
|
-
function formatSupersededPrefix(date, successorId) {
|
|
22
|
-
return `[superseded ${date} by ${successorId}]`;
|
|
23
|
-
}
|
|
24
|
-
function resultStateViewId(result) {
|
|
25
|
-
return result.id ?? result.docid ?? "";
|
|
26
|
-
}
|
|
27
|
-
function buildSuccessorMap(results, chains) {
|
|
28
|
-
const byPred = /* @__PURE__ */ new Map();
|
|
29
|
-
for (const chain of chains) {
|
|
30
|
-
if (chain.predecessorId && chain.successorId) {
|
|
31
|
-
byPred.set(chain.predecessorId, chain.successorId);
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
for (const result of results) {
|
|
35
|
-
const id = resultStateViewId(result);
|
|
36
|
-
if (id && result.supersededBy) byPred.set(id, result.supersededBy);
|
|
37
|
-
}
|
|
38
|
-
return byPred;
|
|
39
|
-
}
|
|
40
|
-
function labelFor(id, result, byPred, admittedIds, asOfMs) {
|
|
41
|
-
const successorId = result.supersededBy ?? byPred.get(id);
|
|
42
|
-
const isPred = shouldWidenSuperseded(successorId, admittedIds);
|
|
43
|
-
let isSucc = false;
|
|
44
|
-
for (const [predId, succId] of byPred) {
|
|
45
|
-
if (succId === id && admittedIds.has(predId)) {
|
|
46
|
-
isSucc = true;
|
|
47
|
-
break;
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
if (isPred && isSucc) return "transition";
|
|
51
|
-
if (isPred) return "historical";
|
|
52
|
-
if (isSucc) return "current";
|
|
53
|
-
if (result.status === "superseded") {
|
|
54
|
-
return asOfMs === null ? "historical" : snapshotStateLabel(result, asOfMs);
|
|
55
|
-
}
|
|
56
|
-
return "current";
|
|
57
|
-
}
|
|
58
|
-
function snapshotStateLabel(result, asOfMs) {
|
|
59
|
-
const supersededAtMs = Date.parse(result.supersededAt ?? "");
|
|
60
|
-
return Number.isFinite(supersededAtMs) && supersededAtMs <= asOfMs ? "historical" : "current";
|
|
61
|
-
}
|
|
62
|
-
function annotateStateView(results, query, chains, options = {}) {
|
|
63
|
-
const enabled = options.enabled === true;
|
|
64
|
-
if (!enabled || !isChangeOrientedQuery(query)) return results;
|
|
65
|
-
const asOfMs = typeof options.asOfMs === "number" && Number.isFinite(options.asOfMs) ? options.asOfMs : null;
|
|
66
|
-
const byPred = buildSuccessorMap(results, chains);
|
|
67
|
-
const candidateIds = /* @__PURE__ */ new Set();
|
|
68
|
-
for (const result of results) {
|
|
69
|
-
const id = resultStateViewId(result);
|
|
70
|
-
if (id) candidateIds.add(id);
|
|
71
|
-
}
|
|
72
|
-
const isSupersededRow = (result) => {
|
|
73
|
-
const id = resultStateViewId(result);
|
|
74
|
-
return {
|
|
75
|
-
superseded: result.status === "superseded" || Boolean(result.supersededBy) || byPred.has(id),
|
|
76
|
-
successorId: result.supersededBy ?? byPred.get(id)
|
|
77
|
-
};
|
|
78
|
-
};
|
|
79
|
-
let admitted;
|
|
80
|
-
if (asOfMs !== null) {
|
|
81
|
-
admitted = results.slice();
|
|
82
|
-
} else {
|
|
83
|
-
admitted = [];
|
|
84
|
-
for (const result of results) {
|
|
85
|
-
const { superseded, successorId } = isSupersededRow(result);
|
|
86
|
-
if (!superseded || shouldWidenSuperseded(successorId, candidateIds)) {
|
|
87
|
-
admitted.push(result);
|
|
88
|
-
}
|
|
89
|
-
}
|
|
90
|
-
let admittedIds2 = new Set(admitted.map(resultStateViewId).filter(Boolean));
|
|
91
|
-
for (let changed = true; changed; ) {
|
|
92
|
-
changed = false;
|
|
93
|
-
for (let i = admitted.length - 1; i >= 0; i -= 1) {
|
|
94
|
-
const row = admitted[i];
|
|
95
|
-
const { superseded, successorId } = isSupersededRow(row);
|
|
96
|
-
if (superseded && !shouldWidenSuperseded(successorId, admittedIds2)) {
|
|
97
|
-
admitted.splice(i, 1);
|
|
98
|
-
changed = true;
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
if (changed) admittedIds2 = new Set(admitted.map(resultStateViewId).filter(Boolean));
|
|
102
|
-
}
|
|
103
|
-
}
|
|
104
|
-
const admittedIds = new Set(admitted.map(resultStateViewId).filter(Boolean));
|
|
105
|
-
return admitted.map((result) => {
|
|
106
|
-
const id = resultStateViewId(result);
|
|
107
|
-
return { ...result, stateLabel: labelFor(id, result, byPred, admittedIds, asOfMs) };
|
|
108
|
-
});
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
export {
|
|
112
|
-
DEFAULT_RECALL_STATE_VIEWS,
|
|
113
|
-
parseRecallStateViews,
|
|
114
|
-
isChangeOrientedQuery,
|
|
115
|
-
shouldWidenSuperseded,
|
|
116
|
-
formatSupersededPrefix,
|
|
117
|
-
resultStateViewId,
|
|
118
|
-
annotateStateView
|
|
119
|
-
};
|
|
120
|
-
//# sourceMappingURL=chunk-255PC37A.js.map
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/recall-state-view.ts"],"sourcesContent":["/**\n * Recall state views (issue #1952).\n *\n * Config key: `recallStateViews` (default false). When false, annotateStateView\n * is identity: same array reference, no `stateLabel`. config.ts is at its\n * fileSizeGrandfather ceiling, so parseConfig wiring waits for a later PR.\n * Call `parseRecallStateViews` at the recall seam when that lands.\n *\n * Change-intent morphology lives here (do not grow intent.ts). A superseded\n * memory is admitted only when its successor is also in the candidate set.\n */\nimport { coerceBooleanLike } from \"./connectors/coerce.js\";\n\nexport type StateLabel = \"current\" | \"historical\" | \"transition\";\n\nexport interface StateViewChain {\n predecessorId: string;\n successorId: string;\n supersededAt?: string;\n}\n\nexport interface StateViewResult {\n id?: string;\n docid?: string;\n status?: string;\n supersededBy?: string;\n supersededAt?: string;\n stateLabel?: StateLabel;\n}\n\nexport const DEFAULT_RECALL_STATE_VIEWS = false;\n\nconst CHANGE_WORDS = [\"switch\", \"switches\", \"switched\", \"switching\", \"change\", \"changes\", \"changed\", \"changing\"];\n\n// Phrase matches are token-bounded: \"when Didi\" must not fire the\n// \"when did\" phrase, and \"confused to\" must not fire \"used to\".\nconst CHANGE_PHRASE_RE = /\\bwhen\\s+did\\b|\\bused\\s+to\\b/;\nconst CHANGE_WORD_RE = new RegExp(`\\\\b(?:${CHANGE_WORDS.join(\"|\")})\\\\b`);\n// Bare \"before\"/\"after\" are ordinary sequencing (\"before lunch\",\n// \"after install\"). They carry change intent only when they point at a\n// specific event through a determiner (\"before the move\", \"after the\n// cutover\").\nconst EVENT_POINTER_RE = /\\b(?:before|after)\\s+(?:the|a|an|this|that|these|those)\\b/;\n\nexport function parseRecallStateViews(raw: unknown): boolean {\n return coerceBooleanLike(raw, \"recallStateViews\") === true;\n}\n\nexport function isChangeOrientedQuery(query: string): boolean {\n const lower = query.toLowerCase();\n return CHANGE_PHRASE_RE.test(lower) || CHANGE_WORD_RE.test(lower) || EVENT_POINTER_RE.test(lower);\n}\n\nexport function shouldWidenSuperseded(\n successorId: string | undefined,\n candidateIds: ReadonlySet<string>,\n): boolean {\n return typeof successorId === \"string\" && successorId.length > 0 && candidateIds.has(successorId);\n}\n\nexport function formatSupersededPrefix(date: string, successorId: string): string {\n return `[superseded ${date} by ${successorId}]`;\n}\n\nexport function resultStateViewId(result: StateViewResult): string {\n return result.id ?? result.docid ?? \"\";\n}\n\nfunction buildSuccessorMap(results: readonly StateViewResult[], chains: readonly StateViewChain[]): Map<string, string> {\n const byPred = new Map<string, string>();\n for (const chain of chains) {\n if (chain.predecessorId && chain.successorId) {\n byPred.set(chain.predecessorId, chain.successorId);\n }\n }\n for (const result of results) {\n const id = resultStateViewId(result);\n if (id && result.supersededBy) byPred.set(id, result.supersededBy);\n }\n return byPred;\n}\n\nfunction labelFor(\n id: string,\n result: StateViewResult,\n byPred: Map<string, string>,\n admittedIds: ReadonlySet<string>,\n asOfMs: number | null,\n): StateLabel {\n const successorId = result.supersededBy ?? byPred.get(id);\n const isPred = shouldWidenSuperseded(successorId, admittedIds);\n let isSucc = false;\n for (const [predId, succId] of byPred) {\n if (succId === id && admittedIds.has(predId)) {\n isSucc = true;\n break;\n }\n }\n if (isPred && isSucc) return \"transition\";\n if (isPred) return \"historical\";\n if (isSucc) return \"current\";\n if (result.status === \"superseded\") {\n // #1952 asOf mode: the successor is legitimately absent under the\n // historical pin. The row was the live fact at the snapshot when the\n // recorded supersession postdates the pin (\"current\" relative to\n // the snapshot); already replaced at the pin is \"historical\".\n return asOfMs === null ? \"historical\" : snapshotStateLabel(result, asOfMs);\n }\n return \"current\";\n}\n\nfunction snapshotStateLabel(result: StateViewResult, asOfMs: number): StateLabel {\n const supersededAtMs = Date.parse(result.supersededAt ?? \"\");\n return Number.isFinite(supersededAtMs) && supersededAtMs <= asOfMs\n ? \"historical\"\n : \"current\";\n}\n\nexport function annotateStateView<T extends StateViewResult>(\n results: T[],\n query: string,\n chains: readonly StateViewChain[],\n options: { enabled?: boolean; asOfMs?: number } = {},\n): T[] {\n const enabled = options.enabled === true;\n if (!enabled || !isChangeOrientedQuery(query)) return results;\n // #1952 asOf mode: under a historical pin the pipeline's isValidAsOf\n // gate already decided validity at the pinned instant, so a predecessor\n // must not be discarded merely because its successor is absent under\n // the same filter. A valid asOf result is never emptied here.\n const asOfMs =\n typeof options.asOfMs === \"number\" && Number.isFinite(options.asOfMs)\n ? options.asOfMs\n : null;\n\n const byPred = buildSuccessorMap(results, chains);\n const candidateIds = new Set<string>();\n for (const result of results) {\n const id = resultStateViewId(result);\n if (id) candidateIds.add(id);\n }\n\n const isSupersededRow = (result: T): { superseded: boolean; successorId: string | undefined } => {\n const id = resultStateViewId(result);\n return {\n superseded: result.status === \"superseded\" || Boolean(result.supersededBy) || byPred.has(id),\n successorId: result.supersededBy ?? byPred.get(id),\n };\n };\n\n let admitted: T[];\n if (asOfMs !== null) {\n admitted = results.slice();\n } else {\n admitted = [];\n for (const result of results) {\n const { superseded, successorId } = isSupersededRow(result);\n if (!superseded || shouldWidenSuperseded(successorId, candidateIds)) {\n admitted.push(result);\n }\n }\n\n // #1952 contract: superseded never appears without its successor. A\n // successor can itself be dropped (filtered elsewhere, or transitively\n // orphaned), so reconcile to a fixpoint instead of trusting the first pass.\n let admittedIds = new Set(admitted.map(resultStateViewId).filter(Boolean));\n for (let changed = true; changed; ) {\n changed = false;\n for (let i = admitted.length - 1; i >= 0; i -= 1) {\n const row = admitted[i]!;\n const { superseded, successorId } = isSupersededRow(row);\n if (superseded && !shouldWidenSuperseded(successorId, admittedIds)) {\n admitted.splice(i, 1);\n changed = true;\n }\n }\n if (changed) admittedIds = new Set(admitted.map(resultStateViewId).filter(Boolean));\n }\n }\n\n const admittedIds = new Set(admitted.map(resultStateViewId).filter(Boolean));\n return admitted.map((result) => {\n const id = resultStateViewId(result);\n return { ...result, stateLabel: labelFor(id, result, byPred, admittedIds, asOfMs) };\n });\n}\n"],"mappings":"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","names":["admittedIds"]}
|