@remnic/core 9.40.0 → 9.42.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 +13 -13
- 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-schema.d.ts +84 -84
- 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 +407 -407
- 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/schemas.d.ts +76 -76
- 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 +9 -9
- 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/transfer/types.d.ts +66 -66
- 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
|
@@ -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");
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
import { coerceBool, coerceNumber } from "./connectors/coerce.js";
|
|
22
22
|
import { log } from "./logger.js";
|
|
23
23
|
import type { OperatorDoctorCheck } from "./operator-doctor-types.js";
|
|
24
|
-
import type {
|
|
24
|
+
import type { PluginConfig } from "./types.js";
|
|
25
25
|
|
|
26
26
|
/** Default staleness window: 24h. A watermark older than this with a non-empty buffer is degraded. */
|
|
27
27
|
const DEFAULT_STALE_WINDOW_MS = 86_400_000;
|
|
@@ -60,23 +60,57 @@ export interface ExtractionBufferSource {
|
|
|
60
60
|
getBufferSnapshot(): Promise<ExtractionBufferSnapshot>;
|
|
61
61
|
}
|
|
62
62
|
|
|
63
|
+
export interface ExtractionRootStats {
|
|
64
|
+
extractionCount?: number;
|
|
65
|
+
lastConsolidationAt?: string | null;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export interface ExtractionWatermarkRead {
|
|
69
|
+
lastExtractionAt: string | null;
|
|
70
|
+
readFailed: boolean;
|
|
71
|
+
readError?: string;
|
|
72
|
+
pending?: boolean;
|
|
73
|
+
rootStats?: ExtractionRootStats;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
export type ExtractionWatermarkOrStorage =
|
|
77
|
+
| ExtractionWatermarkRead
|
|
78
|
+
| { readMetadata(): Promise<{ lastExtractionAt?: string | null } | null> }
|
|
79
|
+
| { loadMeta(): Promise<{ lastExtractionAt?: string | null }> };
|
|
80
|
+
|
|
81
|
+
export async function coerceExtractionWatermark(
|
|
82
|
+
watermark: ExtractionWatermarkOrStorage,
|
|
83
|
+
): Promise<ExtractionWatermarkRead> {
|
|
84
|
+
if (watermark && "readFailed" in watermark && typeof watermark.readFailed === "boolean") {
|
|
85
|
+
return watermark as ExtractionWatermarkRead;
|
|
86
|
+
}
|
|
87
|
+
try {
|
|
88
|
+
let lastExtractionAt: string | null = null;
|
|
89
|
+
if ("loadMeta" in watermark && typeof watermark.loadMeta === "function") {
|
|
90
|
+
const meta = await watermark.loadMeta();
|
|
91
|
+
lastExtractionAt = meta?.lastExtractionAt ?? null;
|
|
92
|
+
} else if ("readMetadata" in watermark && typeof watermark.readMetadata === "function") {
|
|
93
|
+
const meta = await watermark.readMetadata();
|
|
94
|
+
lastExtractionAt = meta?.lastExtractionAt ?? null;
|
|
95
|
+
}
|
|
96
|
+
return {
|
|
97
|
+
lastExtractionAt,
|
|
98
|
+
readFailed: true,
|
|
99
|
+
readError: "aggregate watermark unavailable from legacy root-store-only storage argument",
|
|
100
|
+
};
|
|
101
|
+
} catch (error) {
|
|
102
|
+
return {
|
|
103
|
+
lastExtractionAt: null,
|
|
104
|
+
readFailed: true,
|
|
105
|
+
readError: error instanceof Error ? error.message : String(error),
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
|
|
63
110
|
/**
|
|
64
|
-
* The liveness payload surfaced on `/health.extraction` and in
|
|
65
|
-
* details.
|
|
66
|
-
*
|
|
67
|
-
* DAEMON-SCOPED SIGNAL: a daemon exposes a single extraction verdict; the
|
|
68
|
-
* last-extraction watermark is read from the daemon's default/root store, so
|
|
69
|
-
* `/health` returns the same `extraction` block for every namespace argument.
|
|
70
|
-
*
|
|
71
|
-
* KNOWN LIMITATION (issue #2159): extraction stamps `lastExtractionAt` on the
|
|
72
|
-
* PER-NAMESPACE store (`storageFor(selfNamespace)`), so this watermark reflects
|
|
73
|
-
* only extraction that stamped the root store (default-namespace work). On a
|
|
74
|
-
* single-namespace deployment (the common case) the watermark and buffer share
|
|
75
|
-
* one scope and the verdict is exact. On a multi-namespace deployment the verdict
|
|
76
|
-
* is NOT guaranteed correct for work isolated to a non-default namespace: it can
|
|
77
|
-
* over-report `degraded` (the default namespace idle while another is extracting)
|
|
78
|
-
* or miss a stall confined to a non-default namespace. Aggregating the watermark
|
|
79
|
-
* across namespace stores is tracked in #2159.
|
|
111
|
+
* The daemon-scoped liveness payload surfaced on `/health.extraction` and in
|
|
112
|
+
* doctor/stats details. Its watermark is the maximum successful extraction
|
|
113
|
+
* across every distinct namespace store, matching the daemon-wide buffer scope.
|
|
80
114
|
*/
|
|
81
115
|
export interface ExtractionLivenessStatus {
|
|
82
116
|
lastExtractionAt: string | null;
|
|
@@ -85,18 +119,8 @@ export interface ExtractionLivenessStatus {
|
|
|
85
119
|
oldestBufferedTurnAgeMs: number | null;
|
|
86
120
|
degraded: boolean;
|
|
87
121
|
degradedReason: string | null;
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
* daemon's default/root store only), `aggregate` once it is resolved across
|
|
91
|
-
* every namespace store (#2159). A monitor can alert on
|
|
92
|
-
* `watermarkScope !== "aggregate"` while namespaces are enabled instead of
|
|
93
|
-
* trusting a number that may be wrong in either direction.
|
|
94
|
-
*/
|
|
95
|
-
watermarkScope: "root-store" | "aggregate";
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
interface ExtractionLivenessStorageLike {
|
|
99
|
-
loadMeta(): Promise<Pick<MetaState, "lastExtractionAt">>;
|
|
122
|
+
watermarkPending?: boolean;
|
|
123
|
+
watermarkScope: "aggregate";
|
|
100
124
|
}
|
|
101
125
|
|
|
102
126
|
interface ExtractionLivenessOrchestratorLike {
|
|
@@ -190,7 +214,7 @@ export function evaluateExtractionLiveness(input: {
|
|
|
190
214
|
nowMs: number;
|
|
191
215
|
metaReadFailed?: boolean;
|
|
192
216
|
metaReadError?: string;
|
|
193
|
-
|
|
217
|
+
watermarkPending?: boolean;
|
|
194
218
|
}): ExtractionLivenessStatus {
|
|
195
219
|
const { config, lastExtractionAt, snapshot, nowMs } = input;
|
|
196
220
|
const lastMs = lastExtractionAt !== null ? Date.parse(lastExtractionAt) : Number.NaN;
|
|
@@ -204,8 +228,10 @@ export function evaluateExtractionLiveness(input: {
|
|
|
204
228
|
// (§22): a storage/pipeline outage must not read as healthy, and must stay
|
|
205
229
|
// distinct from a pipeline that has genuinely never extracted.
|
|
206
230
|
const metaReadFailed = input.metaReadFailed === true;
|
|
231
|
+
const watermarkPending = input.watermarkPending === true;
|
|
207
232
|
const readFailed = snapshot.readFailed === true;
|
|
208
|
-
const degraded =
|
|
233
|
+
const degraded =
|
|
234
|
+
config.enabled && (metaReadFailed || readFailed || (!watermarkPending && hasBacklog && stale));
|
|
209
235
|
|
|
210
236
|
let degradedReason: string | null = null;
|
|
211
237
|
if (degraded) {
|
|
@@ -230,7 +256,8 @@ export function evaluateExtractionLiveness(input: {
|
|
|
230
256
|
oldestBufferedTurnAgeMs: ageMsFrom(snapshot.oldestTurnTimestamp, nowMs),
|
|
231
257
|
degraded,
|
|
232
258
|
degradedReason,
|
|
233
|
-
|
|
259
|
+
...(watermarkPending ? { watermarkPending: true } : {}),
|
|
260
|
+
watermarkScope: "aggregate",
|
|
234
261
|
};
|
|
235
262
|
}
|
|
236
263
|
|
|
@@ -262,40 +289,27 @@ async function readBufferSnapshot(
|
|
|
262
289
|
}
|
|
263
290
|
|
|
264
291
|
/**
|
|
265
|
-
* Gather the liveness status from
|
|
266
|
-
*
|
|
267
|
-
*
|
|
268
|
-
* swallowed into a healthy-looking verdict; no failure path throws.
|
|
292
|
+
* Gather the liveness status from an aggregate watermark read and an optional
|
|
293
|
+
* buffer. A read failure on either side is surfaced as a degraded status with a
|
|
294
|
+
* distinct reason rather than swallowed into a healthy-looking verdict.
|
|
269
295
|
*/
|
|
270
296
|
export async function gatherExtractionLivenessStatus(input: {
|
|
271
297
|
config: ExtractionLivenessConfig;
|
|
272
|
-
|
|
298
|
+
watermark: ExtractionWatermarkOrStorage;
|
|
273
299
|
buffer: ExtractionBufferSource | undefined;
|
|
274
300
|
nowMs: number;
|
|
275
301
|
}): Promise<ExtractionLivenessStatus> {
|
|
276
|
-
const { config,
|
|
277
|
-
|
|
278
|
-
let metaReadFailed = false;
|
|
279
|
-
let metaReadError: string | undefined;
|
|
280
|
-
try {
|
|
281
|
-
lastExtractionAt = (await storage.loadMeta()).lastExtractionAt ?? null;
|
|
282
|
-
} catch (err) {
|
|
283
|
-
// A swallowed loadMeta() failure that reads as "never extracted" is the
|
|
284
|
-
// exact §22 conflation this feature exists to kill — surface it explicitly.
|
|
285
|
-
metaReadFailed = true;
|
|
286
|
-
metaReadError = err instanceof Error ? err.message : String(err);
|
|
287
|
-
}
|
|
302
|
+
const { config, buffer, nowMs } = input;
|
|
303
|
+
const watermarkRead = await coerceExtractionWatermark(input.watermark);
|
|
288
304
|
const snapshot = await readBufferSnapshot(buffer);
|
|
289
305
|
return evaluateExtractionLiveness({
|
|
290
306
|
config,
|
|
291
|
-
lastExtractionAt,
|
|
307
|
+
lastExtractionAt: watermarkRead.lastExtractionAt,
|
|
292
308
|
snapshot,
|
|
293
309
|
nowMs,
|
|
294
|
-
metaReadFailed,
|
|
295
|
-
metaReadError,
|
|
296
|
-
|
|
297
|
-
// "aggregate" when the resolver reads across every namespace store.
|
|
298
|
-
watermarkScope: "root-store",
|
|
310
|
+
metaReadFailed: watermarkRead.readFailed,
|
|
311
|
+
metaReadError: watermarkRead.readError,
|
|
312
|
+
watermarkPending: watermarkRead.pending,
|
|
299
313
|
});
|
|
300
314
|
}
|
|
301
315
|
|
|
@@ -349,25 +363,19 @@ export function resolveExtractionLivenessConfig(
|
|
|
349
363
|
}
|
|
350
364
|
|
|
351
365
|
/**
|
|
352
|
-
* Compute the liveness status for the authenticated `/health`
|
|
353
|
-
* the throttled
|
|
354
|
-
*
|
|
355
|
-
* The watermark is read from the daemon's default/root store (`orchestrator.storage`),
|
|
356
|
-
* which is why `/health` returns the same `extraction` block for any namespace
|
|
357
|
-
* argument. KNOWN LIMITATION (issue #2159): extraction writes `lastExtractionAt`
|
|
358
|
-
* per-namespace, so this watermark reflects only default-namespace extraction and
|
|
359
|
-
* the verdict is exact only for a single-namespace daemon (see
|
|
360
|
-
* `ExtractionLivenessStatus`).
|
|
366
|
+
* Compute the daemon-wide liveness status for the authenticated `/health`
|
|
367
|
+
* payload and emit the throttled aggregate warning.
|
|
361
368
|
*/
|
|
362
369
|
export async function computeExtractionLivenessStatus(
|
|
363
|
-
orchestrator: ExtractionLivenessOrchestratorLike
|
|
370
|
+
orchestrator: ExtractionLivenessOrchestratorLike,
|
|
371
|
+
watermark: ExtractionWatermarkOrStorage,
|
|
364
372
|
throttle?: ExtractionLivenessWarnThrottle,
|
|
365
373
|
nowMs: number = Date.now(),
|
|
366
374
|
): Promise<ExtractionLivenessStatus> {
|
|
367
375
|
const config = resolveExtractionLivenessConfig(orchestrator.config.extractionLiveness);
|
|
368
376
|
const status = await gatherExtractionLivenessStatus({
|
|
369
377
|
config,
|
|
370
|
-
|
|
378
|
+
watermark,
|
|
371
379
|
buffer: orchestrator.buffer,
|
|
372
380
|
nowMs,
|
|
373
381
|
});
|
|
@@ -381,22 +389,18 @@ export async function computeExtractionLivenessStatus(
|
|
|
381
389
|
*/
|
|
382
390
|
export async function summarizeExtractionLiveness(
|
|
383
391
|
config: Pick<PluginConfig, "extractionLiveness">,
|
|
384
|
-
|
|
392
|
+
watermark: ExtractionWatermarkOrStorage,
|
|
385
393
|
buffer?: ExtractionBufferSource,
|
|
386
394
|
nowMs: number = Date.now(),
|
|
387
395
|
): Promise<OperatorDoctorCheck> {
|
|
388
396
|
const livenessConfig = resolveExtractionLivenessConfig(config.extractionLiveness);
|
|
389
397
|
const status = await gatherExtractionLivenessStatus({
|
|
390
398
|
config: livenessConfig,
|
|
391
|
-
|
|
399
|
+
watermark,
|
|
392
400
|
buffer,
|
|
393
401
|
nowMs,
|
|
394
402
|
});
|
|
395
|
-
const scopeNote =
|
|
396
|
-
` Watermark scope: ${status.watermarkScope}` +
|
|
397
|
-
(status.watermarkScope === "root-store"
|
|
398
|
-
? " (default-namespace only; multi-namespace aggregation tracked in #2159)."
|
|
399
|
-
: ".");
|
|
403
|
+
const scopeNote = ` Watermark scope: ${status.watermarkScope}.`;
|
|
400
404
|
const summary =
|
|
401
405
|
(status.degraded
|
|
402
406
|
? `Extraction pipeline degraded: ${status.degradedReason}.`
|
|
@@ -416,44 +420,38 @@ export async function summarizeExtractionLiveness(
|
|
|
416
420
|
};
|
|
417
421
|
}
|
|
418
422
|
|
|
419
|
-
/** Meta fields the `remnic stats` view renders directly, beyond the liveness watermark. */
|
|
420
|
-
type ExtractionStatsMeta = Pick<MetaState, "extractionCount" | "lastExtractionAt" | "lastConsolidationAt">;
|
|
421
|
-
|
|
422
423
|
/**
|
|
423
|
-
* Lines for `remnic stats`: extraction watermark,
|
|
424
|
-
*
|
|
425
|
-
*
|
|
426
|
-
*
|
|
427
|
-
* crashing the whole `stats` command.
|
|
424
|
+
* Lines for `remnic stats`: the aggregate extraction watermark, root metadata
|
|
425
|
+
* counters, buffer backlog, and liveness verdict. Root counter failures render
|
|
426
|
+
* those counters unavailable; the injected aggregate read alone determines
|
|
427
|
+
* watermark liveness so stats cannot diverge from health and doctor.
|
|
428
428
|
*/
|
|
429
429
|
export async function renderExtractionLivenessStats(
|
|
430
|
-
orchestrator: ExtractionLivenessOrchestratorLike
|
|
430
|
+
orchestrator: ExtractionLivenessOrchestratorLike,
|
|
431
|
+
watermark: ExtractionWatermarkRead,
|
|
431
432
|
nowMs: number = Date.now(),
|
|
432
433
|
): Promise<string[]> {
|
|
433
|
-
let meta: ExtractionStatsMeta | null = null;
|
|
434
|
-
let metaReadFailed = false;
|
|
435
|
-
let metaReadError: string | undefined;
|
|
436
|
-
try {
|
|
437
|
-
meta = await orchestrator.storage.loadMeta();
|
|
438
|
-
} catch (err) {
|
|
439
|
-
metaReadFailed = true;
|
|
440
|
-
metaReadError = err instanceof Error ? err.message : String(err);
|
|
441
|
-
}
|
|
442
434
|
const status = evaluateExtractionLiveness({
|
|
443
435
|
config: resolveExtractionLivenessConfig(orchestrator.config.extractionLiveness),
|
|
444
|
-
lastExtractionAt:
|
|
436
|
+
lastExtractionAt: watermark.lastExtractionAt,
|
|
445
437
|
snapshot: await readBufferSnapshot(orchestrator.buffer),
|
|
446
438
|
nowMs,
|
|
447
|
-
metaReadFailed,
|
|
448
|
-
metaReadError,
|
|
439
|
+
metaReadFailed: watermark.readFailed,
|
|
440
|
+
metaReadError: watermark.readError,
|
|
441
|
+
watermarkPending: watermark.pending,
|
|
449
442
|
});
|
|
450
443
|
const oldestAge =
|
|
451
444
|
status.oldestBufferedTurnAgeMs !== null ? formatAgeMs(status.oldestBufferedTurnAgeMs) : "n/a";
|
|
452
|
-
const
|
|
445
|
+
const rootStats = watermark.rootStats;
|
|
446
|
+
const watermarkDisplay = watermark.readFailed
|
|
447
|
+
? "unavailable"
|
|
448
|
+
: watermark.pending
|
|
449
|
+
? "pending"
|
|
450
|
+
: status.lastExtractionAt ?? "never";
|
|
453
451
|
return [
|
|
454
|
-
`Extractions: ${
|
|
455
|
-
`Last extraction: ${
|
|
456
|
-
`Last consolidation: ${
|
|
452
|
+
`Extractions: ${rootStats?.extractionCount ?? "unavailable"}`,
|
|
453
|
+
`Last extraction: ${watermarkDisplay}`,
|
|
454
|
+
`Last consolidation: ${rootStats ? rootStats.lastConsolidationAt ?? "never" : "unavailable"}`,
|
|
457
455
|
`Buffered sessions: ${status.bufferedSessionCount} (${status.pendingTurnCount} turns pending)`,
|
|
458
456
|
`Oldest buffered turn age: ${oldestAge}`,
|
|
459
457
|
`Extraction watermark scope: ${status.watermarkScope}`,
|