@remnic/core 9.40.0 → 9.41.0
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 +10 -10
- package/dist/access-admin-ops-surface.js +5 -4
- package/dist/access-authorization-probe.d.ts +10 -10
- package/dist/access-authorization-probe.js +6 -5
- package/dist/access-boundary.d.ts +10 -10
- package/dist/access-boundary.js +5 -4
- package/dist/access-cli.js +16 -15
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.d.ts +10 -10
- package/dist/access-extraction-force-flush.js +5 -4
- package/dist/access-health-types.d.ts +1 -1
- package/dist/access-http-lcm-compaction.d.ts +10 -10
- package/dist/access-http-lifecycle-flush.d.ts +10 -10
- package/dist/access-http.d.ts +10 -10
- package/dist/access-http.js +10 -9
- package/dist/access-identity-continuity-surface.d.ts +8 -8
- package/dist/access-identity-continuity-surface.js +5 -4
- package/dist/access-lcm-surface.d.ts +10 -10
- package/dist/access-lcm-surface.js +5 -4
- package/dist/access-mcp.d.ts +10 -10
- package/dist/access-mcp.js +8 -7
- package/dist/access-memory-search-fanout.d.ts +2 -2
- package/dist/{access-namespace-preflight-BbDJBq0e.d.ts → access-namespace-preflight-47dOyty9.d.ts} +1 -1
- package/dist/access-namespace-preflight.d.ts +3 -3
- package/dist/access-namespace-preflight.js +5 -4
- package/dist/access-observe-write-surface.d.ts +10 -10
- package/dist/access-observe-write-surface.js +5 -4
- package/dist/access-operations-batch.js +6 -5
- package/dist/access-operations.d.ts +10 -10
- package/dist/access-operations.js +7 -6
- package/dist/access-recall-concurrency.d.ts +10 -10
- package/dist/access-recall-concurrency.js +5 -4
- package/dist/access-recall-response.d.ts +10 -10
- package/dist/access-recall-response.js +5 -4
- package/dist/access-recall-surface.d.ts +10 -10
- package/dist/access-recall-surface.js +5 -4
- package/dist/{access-service-B2h6rITp.d.ts → access-service-D1BkxwSf.d.ts} +7 -7
- package/dist/access-service-helpers.d.ts +10 -10
- package/dist/access-service.d.ts +10 -10
- package/dist/access-service.js +5 -4
- package/dist/access-surface-catalog.d.ts +10 -10
- package/dist/access-wearables-meetings-surface.d.ts +3 -3
- package/dist/action-confidence.d.ts +1 -1
- package/dist/active-memory-bridge.d.ts +1 -1
- package/dist/active-recall.d.ts +1 -1
- package/dist/active-recall.js +2 -2
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +8 -8
- package/dist/briefing.d.ts +2 -2
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer-turn-helpers.d.ts +1 -1
- package/dist/buffer.d.ts +2 -2
- package/dist/bulk-import/index.d.ts +3 -3
- package/dist/calibration.d.ts +1 -1
- package/dist/capabilities.d.ts +1 -1
- package/dist/{catalog-Bz2LxGNb.d.ts → catalog-DPQynQZG.d.ts} +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-trajectory-graph.d.ts +1 -1
- package/dist/{chunk-443XCR34.js → chunk-3SG4FGDV.js} +53 -41
- package/dist/chunk-3SG4FGDV.js.map +1 -0
- package/dist/{chunk-XPQ5ZTL6.js → chunk-6I2C3EOH.js} +6 -6
- package/dist/chunk-7VIB7PAE.js +142 -0
- package/dist/chunk-7VIB7PAE.js.map +1 -0
- package/dist/{chunk-AYICY7IE.js → chunk-AXUU7SG3.js} +2 -2
- package/dist/{chunk-RK6WMGTX.js → chunk-CQ5MR2HQ.js} +13 -9
- package/dist/chunk-CQ5MR2HQ.js.map +1 -0
- package/dist/{chunk-KW4ZFTUS.js → chunk-CWPDM66T.js} +5 -5
- package/dist/{chunk-NEO4QCID.js → chunk-FHEJ22B7.js} +10 -4
- package/dist/chunk-FHEJ22B7.js.map +1 -0
- package/dist/{chunk-EBWOOJEP.js → chunk-GDC4INBF.js} +2 -2
- package/dist/{chunk-UIVRPXRR.js → chunk-KXGN6G2Y.js} +3 -3
- package/dist/chunk-KXGN6G2Y.js.map +1 -0
- package/dist/{chunk-AXHMCRS3.js → chunk-OYFDEJZB.js} +23 -6
- package/dist/chunk-OYFDEJZB.js.map +1 -0
- package/dist/{chunk-SD4A6IZY.js → chunk-PWKZV4A3.js} +2 -2
- package/dist/{chunk-XYXYEDPL.js → chunk-Q4XL7LJW.js} +26 -5
- package/dist/chunk-Q4XL7LJW.js.map +1 -0
- package/dist/{chunk-MOHZIKZM.js → chunk-TM72UOX2.js} +2 -2
- package/dist/{chunk-GLBK35BT.js → chunk-UGCUVLOL.js} +14 -10
- package/dist/chunk-UGCUVLOL.js.map +1 -0
- package/dist/{chunk-Q6QYN56R.js → chunk-UQVGDEAJ.js} +2 -2
- package/dist/{chunk-KYYRJVE6.js → chunk-XRWVXPEM.js} +2 -2
- package/dist/{cli-CO8qKPM5.d.ts → cli-IUhV-lmk.d.ts} +5 -5
- package/dist/cli.d.ts +12 -12
- package/dist/cli.js +15 -14
- package/dist/compounding/engine.d.ts +2 -2
- package/dist/compounding/preference-consolidator.d.ts +1 -1
- package/dist/compression-optimizer.d.ts +1 -1
- package/dist/config.d.ts +1 -1
- package/dist/config.js +2 -2
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/consolidation-provenance-check.d.ts +2 -2
- package/dist/consolidation-undo.d.ts +2 -2
- package/dist/contradiction/index.d.ts +2 -2
- package/dist/conversation-index/backend.d.ts +1 -1
- package/dist/conversation-index/chunker.d.ts +1 -1
- package/dist/conversation-index/faiss-adapter.d.ts +1 -1
- package/dist/conversation-index/indexer.d.ts +1 -1
- package/dist/conversation-index/search.d.ts +1 -1
- package/dist/corpus-watermark.d.ts +7 -1
- package/dist/corpus-watermark.js +2 -2
- package/dist/day-summary.d.ts +1 -1
- package/dist/delinearize.d.ts +1 -1
- package/dist/direct-answer-wiring.d.ts +1 -1
- package/dist/direct-answer.d.ts +1 -1
- package/dist/embedding-fallback.d.ts +1 -1
- package/dist/enrichment/index.d.ts +1 -1
- package/dist/entity-retrieval.d.ts +2 -2
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +8 -8
- package/dist/extraction-error-classification.d.ts +1 -1
- package/dist/extraction-faithfulness.d.ts +1 -1
- package/dist/extraction-judge-telemetry.d.ts +1 -1
- package/dist/extraction-judge-training.d.ts +1 -1
- package/dist/extraction-judge.d.ts +1 -1
- package/dist/extraction-liveness.d.ts +1 -1
- package/dist/extraction-liveness.js +3 -1
- package/dist/extraction-source-grounding-rules.d.ts +1 -1
- package/dist/extraction-source-grounding.d.ts +1 -1
- package/dist/extraction.d.ts +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/graph-dashboard-diff.d.ts +1 -1
- package/dist/graph-dashboard-key.d.ts +1 -1
- package/dist/graph-dashboard-parser.d.ts +1 -1
- package/dist/graph-edge-reinforcement.d.ts +1 -1
- package/dist/graph-snapshot.d.ts +1 -1
- package/dist/graph.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/importers/index.d.ts +1 -1
- package/dist/in-flight-reads.d.ts +1 -1
- package/dist/index.d.ts +19 -19
- package/dist/index.js +16 -15
- package/dist/intent.d.ts +1 -1
- package/dist/lcm/engine.d.ts +1 -1
- package/dist/lcm/index.d.ts +1 -1
- package/dist/lcm/tools.d.ts +1 -1
- package/dist/lifecycle.d.ts +1 -1
- package/dist/live-connectors-runner.d.ts +1 -1
- package/dist/local-llm.d.ts +1 -1
- package/dist/local-model-endpoint.d.ts +1 -1
- package/dist/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +2 -2
- package/dist/maintenance/rebuild-memory-projection.d.ts +2 -2
- package/dist/{maintenance-D2jkiT9r.d.ts → maintenance-DVBLXkFw.d.ts} +3 -3
- package/dist/mcp-memory-inspector-app.d.ts +10 -10
- package/dist/memory-action-policy.d.ts +1 -1
- package/dist/memory-cache.d.ts +1 -1
- package/dist/memory-lifecycle-ledger-utils.d.ts +1 -1
- package/dist/memory-projection-store.d.ts +1 -1
- package/dist/memory-provenance.d.ts +1 -1
- package/dist/memory-worth-outcomes.d.ts +2 -2
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +4 -3
- package/dist/namespaces/migrate.js +1 -1
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +1 -1
- package/dist/namespaces/storage.d.ts +3 -3
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/offline-sync-impression-drain.d.ts +1 -1
- package/dist/operator-doctor-corpus.d.ts +1 -1
- package/dist/operator-doctor-corpus.js +3 -3
- package/dist/operator-doctor-replica.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +5 -4
- package/dist/operator-toolkit.js +7 -6
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -2
- package/dist/orchestration/maintenance.d.ts +4 -4
- package/dist/{orchestrator-SHaRXCUF.d.ts → orchestrator-fYTncnLy.d.ts} +7 -7
- package/dist/orchestrator.d.ts +8 -8
- package/dist/orchestrator.js +16 -15
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/{pipeline-B5_tS1Yc.d.ts → pipeline-Cs76XC-p.d.ts} +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/proactive-contention.d.ts +1 -1
- package/dist/provenance.d.ts +1 -1
- package/dist/{qmd-CkEV_SPG.d.ts → qmd-WJwvBgiF.d.ts} +1 -1
- package/dist/qmd-preflight.d.ts +2 -2
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +2 -2
- package/dist/recall-concurrency-config.d.ts +1 -1
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-memory-map.d.ts +1 -1
- package/dist/recall-planner-llm.d.ts +1 -1
- package/dist/recall-state.d.ts +1 -1
- package/dist/recall-tag-filter.d.ts +1 -1
- package/dist/recall-timings.d.ts +1 -1
- package/dist/recall-xray-cli.d.ts +1 -1
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray.d.ts +1 -1
- package/dist/replay/normalizers/chatgpt.d.ts +1 -1
- package/dist/replay/normalizers/claude.d.ts +1 -1
- package/dist/replay/normalizers/openclaw.d.ts +1 -1
- package/dist/replay/normalizers/shared.d.ts +1 -1
- package/dist/replay/runner.d.ts +1 -1
- package/dist/replay/types.d.ts +1 -1
- package/dist/replica-divergence.d.ts +1 -1
- package/dist/replica-peers-config.d.ts +1 -1
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/resume-bundles.js +3 -3
- package/dist/retrieval-agents.d.ts +2 -2
- package/dist/retrieval-tiers.d.ts +1 -1
- package/dist/routing/engine.d.ts +1 -1
- package/dist/routing/store.d.ts +1 -1
- package/dist/salvage-envelope.d.ts +1 -1
- package/dist/{scope-profiles-DeAdnocS.d.ts → scope-profiles-CQ0NAleS.d.ts} +1 -1
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/index.d.ts +1 -1
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/port.d.ts +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-DR_h-JUD.d.ts → semantic-consolidation-BWxOBMtz.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-rule-verifier.d.ts +1 -1
- package/dist/{service-BTb94UPJ.d.ts → service-CLuMO-pa.d.ts} +2 -2
- package/dist/session-observer-bands.d.ts +1 -1
- package/dist/session-observer-state.d.ts +1 -1
- package/dist/shared-context/manager.d.ts +1 -1
- package/dist/signal.d.ts +1 -1
- package/dist/source-agent-qualifier.d.ts +1 -1
- package/dist/{storage-ax2EIsVS.d.ts → storage-Blr71GpS.d.ts} +1 -1
- package/dist/storage.d.ts +2 -2
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/temporal-supersession.d.ts +2 -2
- package/dist/temporal-timeline-recall.d.ts +1 -1
- package/dist/temporal-validity.d.ts +1 -1
- package/dist/threading.d.ts +1 -1
- package/dist/tier-migration.d.ts +2 -2
- package/dist/tier-routing.d.ts +1 -1
- package/dist/topics.d.ts +1 -1
- package/dist/transcript.d.ts +1 -1
- package/dist/trust-score-stage.d.ts +1 -1
- package/dist/trust-score.d.ts +1 -1
- package/dist/{types-CCGL-9NX.d.ts → types-CqWwMPr_.d.ts} +41 -59
- package/dist/types.d.ts +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/write-envelope.d.ts +1 -1
- package/package.json +2 -2
- package/src/access-http.test.ts +2 -2
- package/src/access-service-health.test.ts +39 -17
- package/src/access-service.ts +25 -3
- package/src/cli.ts +3 -3
- package/src/corpus-watermark.test.ts +16 -0
- package/src/corpus-watermark.ts +28 -5
- package/src/extraction-liveness.test.ts +81 -19
- package/src/extraction-liveness.ts +96 -98
- package/src/extraction-watermark.test.ts +290 -0
- package/src/namespaces/migrate.ts +10 -2
- package/src/operator-doctor-corpus.test.ts +7 -0
- package/src/operator-doctor-corpus.ts +1 -1
- package/src/operator-toolkit.ts +8 -8
- package/src/orchestration/extraction-watermark.ts +188 -0
- package/dist/chunk-443XCR34.js.map +0 -1
- package/dist/chunk-AXHMCRS3.js.map +0 -1
- package/dist/chunk-GLBK35BT.js.map +0 -1
- package/dist/chunk-NEO4QCID.js.map +0 -1
- package/dist/chunk-RK6WMGTX.js.map +0 -1
- package/dist/chunk-UIVRPXRR.js.map +0 -1
- package/dist/chunk-XYXYEDPL.js.map +0 -1
- /package/dist/{chunk-XPQ5ZTL6.js.map → chunk-6I2C3EOH.js.map} +0 -0
- /package/dist/{chunk-AYICY7IE.js.map → chunk-AXUU7SG3.js.map} +0 -0
- /package/dist/{chunk-KW4ZFTUS.js.map → chunk-CWPDM66T.js.map} +0 -0
- /package/dist/{chunk-EBWOOJEP.js.map → chunk-GDC4INBF.js.map} +0 -0
- /package/dist/{chunk-SD4A6IZY.js.map → chunk-PWKZV4A3.js.map} +0 -0
- /package/dist/{chunk-MOHZIKZM.js.map → chunk-TM72UOX2.js.map} +0 -0
- /package/dist/{chunk-Q6QYN56R.js.map → chunk-UQVGDEAJ.js.map} +0 -0
- /package/dist/{chunk-KYYRJVE6.js.map → chunk-XRWVXPEM.js.map} +0 -0
package/dist/write-envelope.d.ts
CHANGED
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/core",
|
|
3
|
-
"version": "9.
|
|
3
|
+
"version": "9.41.0",
|
|
4
4
|
"description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -3086,7 +3086,7 @@
|
|
|
3086
3086
|
"core"
|
|
3087
3087
|
],
|
|
3088
3088
|
"peerDependencies": {
|
|
3089
|
-
"@remnic/coding-graph": "^9.
|
|
3089
|
+
"@remnic/coding-graph": "^9.41.0"
|
|
3090
3090
|
},
|
|
3091
3091
|
"peerDependenciesMeta": {
|
|
3092
3092
|
"@remnic/coding-graph": {
|
package/src/access-http.test.ts
CHANGED
|
@@ -1993,7 +1993,7 @@ test("HTTP externalRequestHandler runs pre-auth, can end responses, and falls th
|
|
|
1993
1993
|
oldestBufferedTurnAgeMs: null,
|
|
1994
1994
|
degraded: false,
|
|
1995
1995
|
degradedReason: null,
|
|
1996
|
-
watermarkScope: "
|
|
1996
|
+
watermarkScope: "aggregate",
|
|
1997
1997
|
},
|
|
1998
1998
|
corpus: [],
|
|
1999
1999
|
corpusComplete: true,
|
|
@@ -2120,7 +2120,7 @@ test("HTTP authTokenEntriesGetter is authoritative: scope policy binds connector
|
|
|
2120
2120
|
oldestBufferedTurnAgeMs: null,
|
|
2121
2121
|
degraded: false,
|
|
2122
2122
|
degradedReason: null,
|
|
2123
|
-
watermarkScope: "
|
|
2123
|
+
watermarkScope: "aggregate",
|
|
2124
2124
|
},
|
|
2125
2125
|
corpus: [],
|
|
2126
2126
|
corpusComplete: true,
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
-
import { mkdtemp, rm } from "node:fs/promises";
|
|
2
|
+
import { mkdir, mkdtemp, rm } from "node:fs/promises";
|
|
3
3
|
import os from "node:os";
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import test from "node:test";
|
|
6
6
|
|
|
7
7
|
import { EngramAccessService } from "./access-service.js";
|
|
8
8
|
import { parseConfig } from "./config.js";
|
|
9
|
+
import { TOKEN_CAPABILITIES_VERSION, tokenCapabilityStore } from "./access-token-capabilities.js";
|
|
9
10
|
import { namespaceCollectionName } from "./namespaces/search.js";
|
|
10
11
|
import type { Orchestrator } from "./orchestrator.js";
|
|
11
12
|
import type { SearchBackend } from "./search/port.js";
|
|
@@ -548,16 +549,27 @@ test("health reports extraction liveness ok when the buffer is empty (nothing to
|
|
|
548
549
|
assert.equal(health.extraction.degraded, false);
|
|
549
550
|
assert.equal(health.extraction.bufferedSessionCount, 0);
|
|
550
551
|
assert.equal(health.extraction.lastExtractionAt, oldTs);
|
|
552
|
+
const scopedHealth = await tokenCapabilityStore.run(
|
|
553
|
+
{ version: TOKEN_CAPABILITIES_VERSION, namespaces: ["default"] },
|
|
554
|
+
() => service.health(),
|
|
555
|
+
);
|
|
556
|
+
assert.equal(scopedHealth.extraction.degraded, health.extraction.degraded);
|
|
557
|
+
assert.equal(scopedHealth.extraction.lastExtractionAt, null);
|
|
551
558
|
} finally {
|
|
552
559
|
await rm(memoryDir, { recursive: true, force: true });
|
|
553
560
|
}
|
|
554
561
|
});
|
|
555
562
|
|
|
556
|
-
test("health returns
|
|
563
|
+
test("health returns one daemon-global aggregate extraction verdict for every namespace", async () => {
|
|
557
564
|
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-health-liveness-ns-"));
|
|
558
565
|
try {
|
|
559
|
-
const
|
|
560
|
-
|
|
566
|
+
const namespaceDirs: Record<string, string> = {
|
|
567
|
+
fresh: path.join(memoryDir, "namespaces", "fresh"),
|
|
568
|
+
stale: path.join(memoryDir, "namespaces", "stale"),
|
|
569
|
+
};
|
|
570
|
+
await Promise.all(Object.values(namespaceDirs).map((dir) => mkdir(dir, { recursive: true })));
|
|
571
|
+
const freshTs = new Date().toISOString();
|
|
572
|
+
const staleTs = new Date(Date.now() - 7_200_000).toISOString();
|
|
561
573
|
const config = parseConfig({
|
|
562
574
|
memoryDir,
|
|
563
575
|
qmdEnabled: false,
|
|
@@ -565,20 +577,25 @@ test("health returns the same daemon-global extraction verdict for every namespa
|
|
|
565
577
|
defaultNamespace: "default",
|
|
566
578
|
extractionLiveness: { staleWindowMs: 3_600_000 },
|
|
567
579
|
});
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
580
|
+
const perNamespaceMeta: Record<string, string | null> = {
|
|
581
|
+
default: staleTs,
|
|
582
|
+
fresh: freshTs,
|
|
583
|
+
stale: staleTs,
|
|
584
|
+
};
|
|
573
585
|
const service = new EngramAccessService({
|
|
574
586
|
config,
|
|
575
587
|
qmd: makeQmd({}),
|
|
576
588
|
buffer: {
|
|
577
|
-
getBufferSnapshot: async () => ({
|
|
589
|
+
getBufferSnapshot: async () => ({
|
|
590
|
+
bufferedSessionCount: 3,
|
|
591
|
+
pendingTurnCount: 12,
|
|
592
|
+
oldestTurnTimestamp: staleTs,
|
|
593
|
+
}),
|
|
578
594
|
},
|
|
579
595
|
storage: {
|
|
596
|
+
dir: memoryDir,
|
|
580
597
|
loadMeta: async () => ({
|
|
581
|
-
lastExtractionAt:
|
|
598
|
+
lastExtractionAt: staleTs,
|
|
582
599
|
extractionCount: 4,
|
|
583
600
|
lastConsolidationAt: null,
|
|
584
601
|
totalMemories: 0,
|
|
@@ -586,10 +603,11 @@ test("health returns the same daemon-global extraction verdict for every namespa
|
|
|
586
603
|
}),
|
|
587
604
|
},
|
|
588
605
|
async getStorage(namespace?: string) {
|
|
606
|
+
const resolved = namespace ?? "default";
|
|
589
607
|
return {
|
|
590
|
-
dir: memoryDir,
|
|
608
|
+
dir: namespaceDirs[resolved] ?? memoryDir,
|
|
591
609
|
loadMeta: async () => ({
|
|
592
|
-
lastExtractionAt:
|
|
610
|
+
lastExtractionAt: perNamespaceMeta[resolved] ?? null,
|
|
593
611
|
extractionCount: 4,
|
|
594
612
|
lastConsolidationAt: null,
|
|
595
613
|
totalMemories: 0,
|
|
@@ -599,19 +617,23 @@ test("health returns the same daemon-global extraction verdict for every namespa
|
|
|
599
617
|
},
|
|
600
618
|
} as unknown as Orchestrator);
|
|
601
619
|
|
|
620
|
+
await service.health();
|
|
621
|
+
const serviceWithCache = service as unknown as {
|
|
622
|
+
corpusWatermarkCache: { whenIdle(): Promise<void> };
|
|
623
|
+
};
|
|
624
|
+
await serviceWithCache.corpusWatermarkCache.whenIdle();
|
|
625
|
+
|
|
602
626
|
const fresh = (await service.health("fresh")).extraction;
|
|
603
627
|
const stale = (await service.health("stale")).extraction;
|
|
604
628
|
const def = (await service.health()).extraction;
|
|
605
|
-
// The namespace-governed fields must not vary with the namespace argument
|
|
606
|
-
// (oldestBufferedTurnAgeMs is time-derived and intentionally excluded).
|
|
607
629
|
assert.equal(stale.degraded, fresh.degraded, "degraded must not vary by namespace");
|
|
608
630
|
assert.equal(def.degraded, fresh.degraded, "default health matches namespaced health");
|
|
609
631
|
assert.equal(stale.lastExtractionAt, fresh.lastExtractionAt, "watermark must not vary by namespace");
|
|
610
632
|
assert.equal(def.lastExtractionAt, fresh.lastExtractionAt);
|
|
611
633
|
assert.equal(stale.degradedReason, fresh.degradedReason);
|
|
612
|
-
// Sourced from the fresh root watermark → healthy despite stale per-namespace stores.
|
|
613
634
|
assert.equal(fresh.degraded, false);
|
|
614
|
-
assert.equal(fresh.lastExtractionAt,
|
|
635
|
+
assert.equal(fresh.lastExtractionAt, freshTs);
|
|
636
|
+
assert.equal(fresh.watermarkScope, "aggregate");
|
|
615
637
|
} finally {
|
|
616
638
|
await rm(memoryDir, { recursive: true, force: true });
|
|
617
639
|
}
|
package/src/access-service.ts
CHANGED
|
@@ -7,6 +7,7 @@ import { createHash } from "node:crypto";
|
|
|
7
7
|
import { ZodError } from "zod";
|
|
8
8
|
import { AccessIdempotencyStore, hashAccessIdempotencyPayload } from "./access-idempotency.js";
|
|
9
9
|
import { computeExtractionLivenessStatus, ExtractionLivenessWarnThrottle } from "./extraction-liveness.js";
|
|
10
|
+
import { readAggregateExtractionWatermark } from "./orchestration/extraction-watermark.js";
|
|
10
11
|
import { enforceNamespaceAllowList, tokenCapabilityStore } from "./access-token-capabilities.js";
|
|
11
12
|
import {
|
|
12
13
|
recordCitationUsage as recordCitationUsageForAccess,
|
|
@@ -2369,10 +2370,31 @@ export class EngramAccessService {
|
|
|
2369
2370
|
const storage = await this.orchestrator.getStorage(resolvedNamespace);
|
|
2370
2371
|
const searchBackend = this.orchestrator.config.searchBackend ?? "qmd";
|
|
2371
2372
|
const qmdEnabled = resolveQmdCapabilities(this.orchestrator.config).qmd === true;
|
|
2372
|
-
const
|
|
2373
|
+
const caps = tokenCapabilityStore.getStore();
|
|
2374
|
+
const extractionWatermark = await readAggregateExtractionWatermark({
|
|
2375
|
+
config: this.orchestrator.config,
|
|
2376
|
+
rootStorage: this.orchestrator.storage,
|
|
2377
|
+
storageForNamespace: (candidate) => this.orchestrator.getStorage(candidate),
|
|
2378
|
+
rootsCache: this.corpusWatermarkCache,
|
|
2379
|
+
});
|
|
2380
|
+
const extraction = await computeExtractionLivenessStatus(
|
|
2381
|
+
this.orchestrator,
|
|
2382
|
+
extractionWatermark,
|
|
2383
|
+
caps?.namespaces !== undefined ? undefined : this.extractionLivenessWarn,
|
|
2384
|
+
);
|
|
2385
|
+
const visibleExtraction =
|
|
2386
|
+
caps?.namespaces !== undefined
|
|
2387
|
+
? {
|
|
2388
|
+
...extraction,
|
|
2389
|
+
lastExtractionAt: null,
|
|
2390
|
+
bufferedSessionCount: 0,
|
|
2391
|
+
pendingTurnCount: 0,
|
|
2392
|
+
oldestBufferedTurnAgeMs: null,
|
|
2393
|
+
degradedReason: extraction.degraded ? "daemon extraction pipeline degraded" : null,
|
|
2394
|
+
}
|
|
2395
|
+
: extraction;
|
|
2373
2396
|
// ONE call: the corpus array and the flag describing it must not come from
|
|
2374
2397
|
// two independently-cached scans (round 8, codex P1).
|
|
2375
|
-
const caps = tokenCapabilityStore.getStore();
|
|
2376
2398
|
const corpusCensus = await computeServiceCorpusCensus(this.orchestrator, {
|
|
2377
2399
|
cache: this.corpusWatermarkCache, caps });
|
|
2378
2400
|
let projectionAvailable = false;
|
|
@@ -2398,7 +2420,7 @@ export class EngramAccessService {
|
|
|
2398
2420
|
),
|
|
2399
2421
|
nativeKnowledgeEnabled: this.orchestrator.config.nativeKnowledge?.enabled === true,
|
|
2400
2422
|
projectionAvailable,
|
|
2401
|
-
extraction,
|
|
2423
|
+
extraction: visibleExtraction,
|
|
2402
2424
|
corpus: corpusCensus.watermarks,
|
|
2403
2425
|
// Describes THIS response's array (round 8): a separate scan could report
|
|
2404
2426
|
// complete while the shipped corpus was partial, and peers trust it.
|
package/src/cli.ts
CHANGED
|
@@ -31,6 +31,7 @@ import { chunkContent } from "./chunking.js";
|
|
|
31
31
|
import { rescoreMemoryImportance } from "./importance.js";
|
|
32
32
|
import { renderQmdBacklogStatus } from "./qmd-status.js";
|
|
33
33
|
import { renderExtractionLivenessStats } from "./extraction-liveness.js";
|
|
34
|
+
import { readAggregateExtractionWatermark } from "./orchestration/extraction-watermark.js";
|
|
34
35
|
import { exportJsonBundle } from "./transfer/export-json.js";
|
|
35
36
|
import { exportMarkdownBundle } from "./transfer/export-md.js";
|
|
36
37
|
import { backupMemoryDir } from "./transfer/backup.js";
|
|
@@ -3653,9 +3654,7 @@ export function registerCli(
|
|
|
3653
3654
|
.command("stats")
|
|
3654
3655
|
.description("Show memory system statistics")
|
|
3655
3656
|
.action(async () => {
|
|
3656
|
-
// Ensure QMD is probed before checking availability
|
|
3657
3657
|
await orchestrator.qmd.probe();
|
|
3658
|
-
|
|
3659
3658
|
const memories = await orchestrator.storage.readAllMemories();
|
|
3660
3659
|
const entities = await orchestrator.storage.readEntities();
|
|
3661
3660
|
const profile = await orchestrator.storage.readProfile();
|
|
@@ -3664,7 +3663,8 @@ export function registerCli(
|
|
|
3664
3663
|
console.log(`Total memories: ${memories.length}`);
|
|
3665
3664
|
console.log(`Total entities: ${entities.length}`);
|
|
3666
3665
|
console.log(`Profile size: ${profile.length} chars`);
|
|
3667
|
-
|
|
3666
|
+
const extractionWatermark = await readAggregateExtractionWatermark({ config: orchestrator.config, rootStorage: orchestrator.storage, storageForNamespace: (ns) => orchestrator.getStorage(ns) });
|
|
3667
|
+
for (const line of await renderExtractionLivenessStats(orchestrator, extractionWatermark)) console.log(line);
|
|
3668
3668
|
console.log(`QMD: ${orchestrator.qmd.isAvailable() ? "available" : "not available"}`);
|
|
3669
3669
|
for (const line of await renderQmdBacklogStatus(orchestrator.qmd, orchestrator.config.qmdEmbeddingBacklogThreshold)) console.log(line);
|
|
3670
3670
|
|
|
@@ -780,6 +780,22 @@ test("finding round-7: namespace enumeration is cached (resolved once per TTL, s
|
|
|
780
780
|
assert.equal(resolveCalls, 2, "re-enumerated once the TTL elapsed");
|
|
781
781
|
});
|
|
782
782
|
|
|
783
|
+
test("computeServiceCorpusCensus returns incomplete when roots refresh error is set in cache", async () => {
|
|
784
|
+
const cache = new CorpusWatermarkCache();
|
|
785
|
+
const host = {
|
|
786
|
+
config: parseConfig({ namespacesEnabled: true }),
|
|
787
|
+
getStorage: async () => ({ readAllMemories: async () => [] } as unknown as StorageManager),
|
|
788
|
+
};
|
|
789
|
+
const failingResolve = async (): Promise<CorpusNamespaceRoot[]> => {
|
|
790
|
+
throw new Error("disk unreadable");
|
|
791
|
+
};
|
|
792
|
+
cache.getResolvedRootsStatus(failingResolve);
|
|
793
|
+
await cache.whenIdle();
|
|
794
|
+
const census = await computeServiceCorpusCensus(host, { cache });
|
|
795
|
+
assert.equal(census.complete, false);
|
|
796
|
+
assert.deepEqual(census.watermarks, []);
|
|
797
|
+
});
|
|
798
|
+
|
|
783
799
|
test("finding round-9: background refresh scans are bounded to maxConcurrentRefreshes", async () => {
|
|
784
800
|
const cache = new CorpusWatermarkCache({ maxConcurrentRefreshes: 2 });
|
|
785
801
|
let active = 0;
|
package/src/corpus-watermark.ts
CHANGED
|
@@ -312,9 +312,13 @@ export interface CorpusNamespaceRoot {
|
|
|
312
312
|
*/
|
|
313
313
|
export async function resolveCorpusNamespaceRoots(options: {
|
|
314
314
|
config: PluginConfig;
|
|
315
|
+
propagateDiscoveryErrors?: boolean;
|
|
315
316
|
}): Promise<CorpusNamespaceRoot[]> {
|
|
316
317
|
const { config } = options;
|
|
317
|
-
const configDriven = await listNamespaces({
|
|
318
|
+
const configDriven = await listNamespaces({
|
|
319
|
+
config,
|
|
320
|
+
propagateDiscoveryErrors: options.propagateDiscoveryErrors,
|
|
321
|
+
});
|
|
318
322
|
// Default namespace first so it wins the representative label when several
|
|
319
323
|
// configured names collapse onto one root (namespaces disabled → memoryDir).
|
|
320
324
|
const ordered = [
|
|
@@ -376,6 +380,7 @@ export class CorpusWatermarkCache {
|
|
|
376
380
|
private readonly clock: () => number;
|
|
377
381
|
private rootsEntry: { value: CorpusNamespaceRoot[]; expiresAt: number } | undefined;
|
|
378
382
|
private rootsInFlight: Promise<void> | undefined;
|
|
383
|
+
private rootsRefreshError: unknown;
|
|
379
384
|
private readonly queued = new Map<string, () => void>();
|
|
380
385
|
private activeRefreshes = 0;
|
|
381
386
|
private readonly maxConcurrentRefreshes: number;
|
|
@@ -444,9 +449,12 @@ export class CorpusWatermarkCache {
|
|
|
444
449
|
this.rootsInFlight = compute()
|
|
445
450
|
.then((value) => {
|
|
446
451
|
this.rootsEntry = { value, expiresAt: this.clock() + this.ttlMs };
|
|
452
|
+
this.rootsRefreshError = undefined;
|
|
447
453
|
})
|
|
448
|
-
.catch(() => {
|
|
449
|
-
|
|
454
|
+
.catch((error: unknown) => {
|
|
455
|
+
this.rootsRefreshError = error;
|
|
456
|
+
// Keep serving any stale roots; callers that require completeness
|
|
457
|
+
// inspect the refresh status before trusting them.
|
|
450
458
|
})
|
|
451
459
|
.finally(() => {
|
|
452
460
|
this.rootsInFlight = undefined;
|
|
@@ -454,6 +462,14 @@ export class CorpusWatermarkCache {
|
|
|
454
462
|
}
|
|
455
463
|
return this.rootsEntry?.value;
|
|
456
464
|
}
|
|
465
|
+
|
|
466
|
+
getResolvedRootsStatus(compute: () => Promise<CorpusNamespaceRoot[]>): {
|
|
467
|
+
roots: CorpusNamespaceRoot[] | undefined;
|
|
468
|
+
refreshError: unknown;
|
|
469
|
+
} {
|
|
470
|
+
const roots = this.getResolvedRoots(compute);
|
|
471
|
+
return { roots, refreshError: this.rootsRefreshError };
|
|
472
|
+
}
|
|
457
473
|
/** Await every background refresh — in-flight AND queued (issue #2156 round-9) —
|
|
458
474
|
* plus any roots refresh (shutdown / deterministic tests). */
|
|
459
475
|
async whenIdle(): Promise<void> {
|
|
@@ -518,10 +534,17 @@ export async function computeServiceCorpusCensus(
|
|
|
518
534
|
): Promise<{ watermarks: CorpusWatermark[]; complete: boolean }> {
|
|
519
535
|
let roots: CorpusNamespaceRoot[] | undefined;
|
|
520
536
|
if (options.cache) {
|
|
521
|
-
roots = options.cache.
|
|
537
|
+
const { roots: cachedRoots, refreshError } = options.cache.getResolvedRootsStatus(() =>
|
|
538
|
+
resolveCorpusNamespaceRoots({ config: host.config, propagateDiscoveryErrors: true }),
|
|
539
|
+
);
|
|
540
|
+
if (refreshError !== undefined) return { watermarks: [], complete: false };
|
|
541
|
+
roots = cachedRoots;
|
|
522
542
|
} else {
|
|
523
543
|
try {
|
|
524
|
-
roots = await resolveCorpusNamespaceRoots({
|
|
544
|
+
roots = await resolveCorpusNamespaceRoots({
|
|
545
|
+
config: host.config,
|
|
546
|
+
propagateDiscoveryErrors: true,
|
|
547
|
+
});
|
|
525
548
|
} catch {
|
|
526
549
|
return { watermarks: [], complete: false }; // never enumerated — not an empty corpus
|
|
527
550
|
}
|
|
@@ -194,6 +194,30 @@ test("evaluate: an unreadable buffer degrades with a distinct reason, even with
|
|
|
194
194
|
assert.equal(disabled.degraded, false, "disabled gate suppresses even a read-failure degradation");
|
|
195
195
|
});
|
|
196
196
|
|
|
197
|
+
test("evaluate: a warming aggregate watermark remains pending without degrading", () => {
|
|
198
|
+
const status = evaluateExtractionLiveness({
|
|
199
|
+
config: ENABLED,
|
|
200
|
+
lastExtractionAt: null,
|
|
201
|
+
snapshot: snapshot({ bufferedSessionCount: 1, pendingTurnCount: 2 }),
|
|
202
|
+
nowMs: NOW,
|
|
203
|
+
watermarkPending: true,
|
|
204
|
+
});
|
|
205
|
+
assert.equal(status.degraded, false);
|
|
206
|
+
assert.equal(status.watermarkPending, true);
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("evaluate: warming watermark does not suppress an independent buffer read failure", () => {
|
|
210
|
+
const status = evaluateExtractionLiveness({
|
|
211
|
+
config: ENABLED,
|
|
212
|
+
lastExtractionAt: null,
|
|
213
|
+
snapshot: snapshot({ readFailed: true, readError: "buffer unavailable" }),
|
|
214
|
+
nowMs: NOW,
|
|
215
|
+
watermarkPending: true,
|
|
216
|
+
});
|
|
217
|
+
assert.equal(status.degraded, true);
|
|
218
|
+
assert.match(status.degradedReason ?? "", /buffer unavailable/);
|
|
219
|
+
});
|
|
220
|
+
|
|
197
221
|
test("evaluate: an unreadable watermark degrades with a distinct reason, even with an empty buffer (§22)", () => {
|
|
198
222
|
const status = evaluateExtractionLiveness({
|
|
199
223
|
config: ENABLED,
|
|
@@ -311,22 +335,14 @@ test("getBufferSnapshot: counts retained (deferred) turns as pending, not just a
|
|
|
311
335
|
assert.equal(snap.oldestTurnTimestamp, "2026-04-01T00:00:00.000Z", "oldest is the first retained turn");
|
|
312
336
|
});
|
|
313
337
|
|
|
314
|
-
test("evaluate: emits
|
|
315
|
-
const
|
|
316
|
-
config: ENABLED,
|
|
317
|
-
lastExtractionAt: null,
|
|
318
|
-
snapshot: snapshot(),
|
|
319
|
-
nowMs: NOW,
|
|
320
|
-
});
|
|
321
|
-
assert.equal(rootScoped.watermarkScope, "root-store", "defaults to root-store today (#2159)");
|
|
322
|
-
const aggregate = evaluateExtractionLiveness({
|
|
338
|
+
test("evaluate: emits only the aggregate watermark scope", () => {
|
|
339
|
+
const status = evaluateExtractionLiveness({
|
|
323
340
|
config: ENABLED,
|
|
324
341
|
lastExtractionAt: null,
|
|
325
342
|
snapshot: snapshot(),
|
|
326
343
|
nowMs: NOW,
|
|
327
|
-
watermarkScope: "aggregate",
|
|
328
344
|
});
|
|
329
|
-
assert.equal(
|
|
345
|
+
assert.equal(status.watermarkScope, "aggregate");
|
|
330
346
|
});
|
|
331
347
|
|
|
332
348
|
// ── ExtractionLivenessWarnThrottle ───────────────────────────────────────────
|
|
@@ -339,7 +355,7 @@ test("throttle: warns once per staleness window and again after it elapses; rese
|
|
|
339
355
|
oldestBufferedTurnAgeMs: null,
|
|
340
356
|
degraded: true,
|
|
341
357
|
degradedReason: "stalled",
|
|
342
|
-
watermarkScope: "
|
|
358
|
+
watermarkScope: "aggregate",
|
|
343
359
|
};
|
|
344
360
|
const healthy: ExtractionLivenessStatus = { ...degraded, degraded: false, degradedReason: null };
|
|
345
361
|
const t = new ExtractionLivenessWarnThrottle();
|
|
@@ -351,13 +367,31 @@ test("throttle: warns once per staleness window and again after it elapses; rese
|
|
|
351
367
|
assert.equal(t.maybeWarn(degraded, WINDOW, NOW + WINDOW + 2), true, "recovery reset lets a fresh episode warn at once");
|
|
352
368
|
});
|
|
353
369
|
|
|
370
|
+
test("summarizeExtractionLiveness: fails closed for legacy root-store-only storage argument", async () => {
|
|
371
|
+
const storage = {
|
|
372
|
+
readMetadata: async () => ({ lastExtractionAt: "2026-07-29T10:00:00.000Z" }),
|
|
373
|
+
};
|
|
374
|
+
const doctorCheck = await summarizeExtractionLiveness(
|
|
375
|
+
{ extractionLiveness: { enabled: true, staleWindowMs: WINDOW } },
|
|
376
|
+
storage,
|
|
377
|
+
undefined,
|
|
378
|
+
NOW,
|
|
379
|
+
);
|
|
380
|
+
assert.equal(doctorCheck.status, "warn");
|
|
381
|
+
assert.ok(doctorCheck.summary.includes("aggregate watermark unavailable"));
|
|
382
|
+
});
|
|
383
|
+
|
|
354
384
|
// ── renderExtractionLivenessStats ────────────────────────────────────────────
|
|
355
385
|
|
|
356
386
|
test("renderExtractionLivenessStats: emits watermark, backlog, and a degraded verdict", async () => {
|
|
387
|
+
let rootReads = 0;
|
|
357
388
|
const orchestrator = {
|
|
358
389
|
config: { extractionLiveness: { enabled: true, staleWindowMs: WINDOW } },
|
|
359
390
|
storage: {
|
|
360
|
-
loadMeta: async () =>
|
|
391
|
+
loadMeta: async () => {
|
|
392
|
+
rootReads += 1;
|
|
393
|
+
return { extractionCount: 7, lastExtractionAt: ancient, lastConsolidationAt: null };
|
|
394
|
+
},
|
|
361
395
|
},
|
|
362
396
|
buffer: {
|
|
363
397
|
getBufferSnapshot: async (): Promise<ExtractionBufferSnapshot> => ({
|
|
@@ -367,14 +401,32 @@ test("renderExtractionLivenessStats: emits watermark, backlog, and a degraded ve
|
|
|
367
401
|
}),
|
|
368
402
|
},
|
|
369
403
|
};
|
|
370
|
-
const lines = await renderExtractionLivenessStats(
|
|
404
|
+
const lines = await renderExtractionLivenessStats(
|
|
405
|
+
orchestrator,
|
|
406
|
+
{
|
|
407
|
+
lastExtractionAt: ancient,
|
|
408
|
+
readFailed: false,
|
|
409
|
+
rootStats: { extractionCount: 7, lastConsolidationAt: null },
|
|
410
|
+
},
|
|
411
|
+
NOW,
|
|
412
|
+
);
|
|
371
413
|
assert.ok(lines.includes("Extractions: 7"), "reports extraction count");
|
|
414
|
+
assert.equal(rootReads, 0, "rendering reuses the aggregate reader's root metadata");
|
|
372
415
|
assert.ok(lines.includes("Buffered sessions: 2 (5 turns pending)"), "reports backlog");
|
|
373
|
-
assert.ok(lines.includes("Extraction watermark scope:
|
|
416
|
+
assert.ok(lines.includes("Extraction watermark scope: aggregate"), "reports the watermark scope");
|
|
374
417
|
assert.ok(
|
|
375
418
|
lines.some((l) => l.startsWith("Extraction liveness: DEGRADED")),
|
|
376
419
|
"reports a degraded verdict when the watermark is stale",
|
|
377
420
|
);
|
|
421
|
+
const warmingLines = await renderExtractionLivenessStats(
|
|
422
|
+
orchestrator,
|
|
423
|
+
{ lastExtractionAt: null, readFailed: false, pending: true },
|
|
424
|
+
NOW,
|
|
425
|
+
);
|
|
426
|
+
assert.ok(
|
|
427
|
+
warmingLines.some((line) => line === "Extraction liveness: ok"),
|
|
428
|
+
"cache warming is pending rather than degraded",
|
|
429
|
+
);
|
|
378
430
|
});
|
|
379
431
|
|
|
380
432
|
test("renderExtractionLivenessStats: reports DEGRADED when the buffer read fails (§22)", async () => {
|
|
@@ -393,7 +445,11 @@ test("renderExtractionLivenessStats: reports DEGRADED when the buffer read fails
|
|
|
393
445
|
},
|
|
394
446
|
},
|
|
395
447
|
};
|
|
396
|
-
const lines = await renderExtractionLivenessStats(
|
|
448
|
+
const lines = await renderExtractionLivenessStats(
|
|
449
|
+
orchestrator,
|
|
450
|
+
{ lastExtractionAt: new Date(NOW).toISOString(), readFailed: false },
|
|
451
|
+
NOW,
|
|
452
|
+
);
|
|
397
453
|
const verdict = lines.find((l) => l.startsWith("Extraction liveness:"));
|
|
398
454
|
assert.ok(verdict, "emits a liveness verdict line");
|
|
399
455
|
assert.match(verdict, /DEGRADED/);
|
|
@@ -418,7 +474,11 @@ test("renderExtractionLivenessStats: reports DEGRADED + unavailable counts when
|
|
|
418
474
|
}),
|
|
419
475
|
},
|
|
420
476
|
};
|
|
421
|
-
const lines = await renderExtractionLivenessStats(
|
|
477
|
+
const lines = await renderExtractionLivenessStats(
|
|
478
|
+
orchestrator,
|
|
479
|
+
{ lastExtractionAt: null, readFailed: true, readError: "meta.json corrupt" },
|
|
480
|
+
NOW,
|
|
481
|
+
);
|
|
422
482
|
assert.ok(lines.includes("Extractions: unavailable"), "meta counts read as unavailable, not 0/never");
|
|
423
483
|
const verdict = lines.find((l) => l.startsWith("Extraction liveness:"));
|
|
424
484
|
assert.match(verdict ?? "", /DEGRADED/);
|
|
@@ -448,12 +508,14 @@ test("resolveExtractionLivenessConfig: absent or partial block defaults to enabl
|
|
|
448
508
|
});
|
|
449
509
|
|
|
450
510
|
test("summarizeExtractionLiveness: no throw when the extractionLiveness block is absent or partial (host/legacy config)", async () => {
|
|
451
|
-
const storage = { loadMeta: async () => ({ lastExtractionAt: null }) };
|
|
452
511
|
// A PluginConfig built directly (host adapter / older persisted config) can omit
|
|
453
512
|
// the block or leave it partial; every liveness surface must return a sane status
|
|
454
513
|
// rather than crash (#2155 review).
|
|
455
514
|
for (const config of [{}, { extractionLiveness: {} }, { extractionLiveness: { enabled: true } }]) {
|
|
456
|
-
const check = await summarizeExtractionLiveness(
|
|
515
|
+
const check = await summarizeExtractionLiveness(
|
|
516
|
+
config as never,
|
|
517
|
+
{ lastExtractionAt: null, readFailed: false },
|
|
518
|
+
);
|
|
457
519
|
assert.equal(check.key, "extraction_liveness");
|
|
458
520
|
assert.ok(check.status === "ok" || check.status === "warn", `sane status for ${JSON.stringify(config)}`);
|
|
459
521
|
assert.ok(!check.summary.includes("liveness check disabled"), "missing/partial block reads as enabled default");
|