@remnic/core 9.3.654 → 9.3.656
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-cli.js +29 -29
- package/dist/access-http.d.ts +4 -4
- package/dist/access-http.js +17 -17
- package/dist/access-mcp.d.ts +4 -4
- package/dist/access-mcp.js +16 -16
- package/dist/access-schema.d.ts +10 -10
- package/dist/{access-service-C8A5hoXJ.d.ts → access-service-D_nbpexW.d.ts} +33 -2
- package/dist/access-service.d.ts +4 -4
- package/dist/access-service.js +15 -15
- 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 +1 -1
- package/dist/behavior-learner.d.ts +1 -1
- package/dist/behavior-signals.d.ts +1 -1
- package/dist/bootstrap.d.ts +3 -3
- package/dist/briefing.d.ts +1 -1
- package/dist/briefing.js +3 -3
- package/dist/buffer-surprise-report.d.ts +1 -1
- package/dist/buffer.d.ts +1 -1
- package/dist/calibration.d.ts +1 -1
- package/dist/causal-behavior.d.ts +1 -1
- package/dist/causal-consolidation.d.ts +1 -1
- package/dist/causal-consolidation.js +4 -4
- package/dist/{chunk-JMQSYGXS.js → chunk-2BD7DG37.js} +2 -2
- package/dist/{chunk-FVRBLJP6.js → chunk-2MXEVL75.js} +2 -2
- package/dist/{chunk-LJCEWTG3.js → chunk-4UL7VPTD.js} +277 -58
- package/dist/chunk-4UL7VPTD.js.map +1 -0
- package/dist/{chunk-JYN7QNTA.js → chunk-54XF2FY7.js} +17 -17
- package/dist/{chunk-7WEB3FLJ.js → chunk-5PLUC5OB.js} +2 -2
- package/dist/{chunk-JX2RINDR.js → chunk-6G5JEN55.js} +2 -2
- package/dist/{chunk-ZCORQM74.js → chunk-AGJKWOKV.js} +2 -2
- package/dist/{chunk-NE2JBMLN.js → chunk-AZBV4RRY.js} +1 -1
- package/dist/chunk-AZBV4RRY.js.map +1 -0
- package/dist/{chunk-YLZLPVKK.js → chunk-CTAV55JM.js} +344 -1
- package/dist/chunk-CTAV55JM.js.map +1 -0
- package/dist/{chunk-2DSTAWNZ.js → chunk-DIBWFCLA.js} +3 -3
- package/dist/{chunk-NAZWHTYV.js → chunk-DR67OK4E.js} +5 -5
- package/dist/{chunk-XBIACVCO.js → chunk-EC2AYKRX.js} +2 -2
- package/dist/{chunk-JVRPJ7D4.js → chunk-EKQMQQ3U.js} +48 -12
- package/dist/chunk-EKQMQQ3U.js.map +1 -0
- package/dist/{chunk-RGPUQ66K.js → chunk-GCYFUTUC.js} +2 -2
- package/dist/{chunk-JBHXMCYN.js → chunk-GRYAECRV.js} +2 -2
- package/dist/{chunk-BJA6DQOC.js → chunk-GSHW5VVD.js} +5 -5
- package/dist/chunk-GYSYLGNE.js +650 -0
- package/dist/chunk-GYSYLGNE.js.map +1 -0
- package/dist/{chunk-NCGWXCSW.js → chunk-IOZ5WBWD.js} +2 -2
- package/dist/{chunk-QKK64Z6M.js → chunk-JSVFEHLL.js} +7 -5
- package/dist/chunk-JSVFEHLL.js.map +1 -0
- package/dist/{chunk-7LWRCOP7.js → chunk-LZTFCAKE.js} +2 -2
- package/dist/{chunk-2DGQLOOM.js → chunk-M3VYPE2H.js} +1 -1
- package/dist/{chunk-2DGQLOOM.js.map → chunk-M3VYPE2H.js.map} +1 -1
- package/dist/{chunk-6CVI6BP6.js → chunk-NXCK7DO7.js} +2 -2
- package/dist/{chunk-Z5MQI7K2.js → chunk-PEPHBH2W.js} +2 -2
- package/dist/{chunk-PYWNNF2I.js → chunk-QRSKPI62.js} +99 -66
- package/dist/chunk-QRSKPI62.js.map +1 -0
- package/dist/{chunk-XWQ6ERUG.js → chunk-QZRKNA5F.js} +2 -2
- package/dist/{chunk-PS3SYNHP.js → chunk-R5DB26G6.js} +2 -2
- package/dist/{chunk-OL2364SB.js → chunk-RDW5G6DO.js} +1502 -335
- package/dist/chunk-RDW5G6DO.js.map +1 -0
- package/dist/{chunk-YM3LR4LS.js → chunk-SSSXWIBP.js} +5 -5
- package/dist/{chunk-T2C6QJG2.js → chunk-SWDHVH2P.js} +2 -2
- package/dist/{chunk-DBM2BD22.js → chunk-SXYCVRLK.js} +3 -3
- package/dist/{chunk-K6X553JB.js → chunk-TFFZUFEP.js} +7 -5
- package/dist/chunk-TFFZUFEP.js.map +1 -0
- package/dist/{chunk-ENV6RDTD.js → chunk-TIJYQXDI.js} +2 -2
- package/dist/{chunk-BP2EV6W5.js → chunk-VAEAGTEQ.js} +4 -4
- package/dist/{chunk-3RACUBII.js → chunk-WIKMCJUR.js} +2 -2
- package/dist/{chunk-QW6JZO5P.js → chunk-WWMHAMAY.js} +2 -2
- package/dist/{chunk-GPW2E4LN.js → chunk-YEZHZCUO.js} +4 -4
- package/dist/{chunk-5FOCXX5E.js → chunk-YVVQUAOO.js} +3 -3
- package/dist/{chunk-5FOCXX5E.js.map → chunk-YVVQUAOO.js.map} +1 -1
- package/dist/{chunk-3XGWCZ63.js → chunk-YXLT4EMM.js} +2 -2
- package/dist/{chunk-Y2RIIF6H.js → chunk-Z6UDTNY6.js} +2 -2
- package/dist/{cli-uQgvDFNE.d.ts → cli-aYxSuPvP.d.ts} +3 -3
- package/dist/cli.d.ts +5 -5
- package/dist/cli.js +29 -29
- package/dist/compounding/engine.d.ts +1 -1
- package/dist/compounding/engine.js +3 -3
- 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 +1 -1
- package/dist/connectors/codex-materialize-runner.d.ts +1 -1
- package/dist/connectors/codex-materialize-runner.js +3 -3
- package/dist/connectors/codex-materialize.d.ts +1 -1
- package/dist/connectors/index.d.ts +1 -1
- package/dist/connectors/index.js +3 -3
- package/dist/consolidation-provenance-check.d.ts +1 -1
- package/dist/consolidation-undo.d.ts +1 -1
- package/dist/contradiction/index.d.ts +1 -1
- 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/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 +1 -1
- package/dist/entity-retrieval.js +3 -3
- package/dist/entity-schema.d.ts +1 -1
- package/dist/explicit-capture.d.ts +3 -3
- package/dist/explicit-cue-recall.js +2 -2
- 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.d.ts +1 -1
- package/dist/fallback-llm.d.ts +1 -1
- package/dist/focused-list-recall.js +2 -2
- package/dist/identity-continuity.d.ts +1 -1
- package/dist/importance.d.ts +1 -1
- package/dist/index.d.ts +121 -121
- package/dist/index.js +39 -39
- 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/lcm-fallback-read.js +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/maintenance/memory-governance.d.ts +1 -1
- package/dist/maintenance/memory-governance.js +3 -3
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +3 -3
- package/dist/maintenance/rebuild-memory-projection.js +4 -4
- package/dist/mcp-memory-inspector-app.d.ts +4 -4
- 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 +1 -1
- package/dist/models-json.d.ts +1 -1
- package/dist/namespaces/migrate.d.ts +1 -1
- package/dist/namespaces/migrate.js +11 -11
- package/dist/namespaces/principal.d.ts +1 -1
- package/dist/namespaces/search.d.ts +15 -4
- package/dist/namespaces/search.js +7 -7
- package/dist/namespaces/storage.d.ts +1 -1
- package/dist/namespaces/storage.js +3 -3
- package/dist/native-knowledge.d.ts +1 -1
- package/dist/operator-toolkit.d.ts +1 -1
- package/dist/operator-toolkit.js +14 -14
- package/dist/{orchestrator-B4Y4sWQH.d.ts → orchestrator-D1wcmPNj.d.ts} +17 -14
- package/dist/orchestrator.d.ts +3 -3
- package/dist/orchestrator.js +25 -25
- package/dist/patterns-cli.d.ts +1 -1
- package/dist/policy-runtime.d.ts +1 -1
- package/dist/qmd-recall-cache.d.ts +1 -1
- package/dist/qmd.d.ts +5 -1
- package/dist/qmd.js +2 -2
- package/dist/recall-disclosure-escalation.d.ts +1 -1
- package/dist/recall-explain-renderer.d.ts +1 -1
- package/dist/recall-explain-renderer.js +3 -3
- 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-xray-cli.d.ts +1 -1
- package/dist/recall-xray-cli.js +4 -4
- package/dist/recall-xray-renderer.d.ts +1 -1
- package/dist/recall-xray-renderer.js +3 -3
- package/dist/recall-xray.d.ts +1 -1
- package/dist/recall-xray.js +2 -2
- package/dist/resolve-auth-token.d.ts +1 -1
- package/dist/response-guidance-recall.js +2 -2
- package/dist/resume-bundles.js +2 -2
- package/dist/retrieval-agents.d.ts +1 -1
- 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/schemas.d.ts +22 -22
- package/dist/search/embed-helper.d.ts +1 -1
- package/dist/search/factory.d.ts +1 -1
- package/dist/search/factory.js +6 -6
- package/dist/search/index.d.ts +1 -1
- package/dist/search/index.js +6 -6
- package/dist/search/lancedb-backend.d.ts +1 -1
- package/dist/search/lancedb-backend.js +2 -2
- package/dist/search/meilisearch-backend.d.ts +1 -1
- package/dist/search/meilisearch-backend.js +2 -2
- package/dist/search/noop-backend.d.ts +1 -1
- package/dist/search/orama-backend.d.ts +1 -1
- package/dist/search/orama-backend.js +2 -2
- package/dist/search/port.d.ts +17 -1
- package/dist/search/port.js +1 -1
- package/dist/search/remote-backend.d.ts +1 -1
- package/dist/{semantic-consolidation-BKd0Pype.d.ts → semantic-consolidation-MWOdNtSE.d.ts} +1 -1
- package/dist/semantic-consolidation.d.ts +2 -2
- package/dist/semantic-consolidation.js +4 -4
- package/dist/semantic-rule-promotion.js +3 -3
- package/dist/semantic-rule-verifier.d.ts +3 -2
- package/dist/semantic-rule-verifier.js +5 -3
- 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/storage.d.ts +1 -1
- package/dist/storage.js +2 -2
- package/dist/summarizer.d.ts +1 -1
- package/dist/summary-snapshot.d.ts +1 -1
- package/dist/targeted-fact-recall.js +2 -2
- package/dist/temporal-supersession.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 +1 -1
- 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 +12 -12
- package/dist/{types-BgChEr0M.d.ts → types-CgcCpUrf.d.ts} +51 -1
- package/dist/types.d.ts +1 -1
- package/dist/types.js +1 -1
- package/dist/utility-runtime.d.ts +1 -1
- package/dist/verified-recall.d.ts +2 -1
- package/dist/verified-recall.js +5 -3
- package/package.json +1 -1
- package/src/access-service-observe-lcm-parity.test.ts +86 -1
- package/src/access-service-observe-scope.test.ts +283 -1
- package/src/access-service-raw-excerpt-read-gate.test.ts +53 -0
- package/src/access-service.ts +391 -93
- package/src/coding/coding-namespace.ts +0 -3
- package/src/config.test.ts +69 -0
- package/src/config.ts +417 -0
- package/src/lcm-fallback-read.ts +2 -6
- package/src/maintenance/namespace-planner.test.ts +1120 -0
- package/src/maintenance/namespace-planner.ts +893 -0
- package/src/namespaces/scope-profiles.test.ts +1074 -0
- package/src/namespaces/scope-profiles.ts +456 -0
- package/src/namespaces/search.test.ts +130 -2
- package/src/namespaces/search.ts +71 -10
- package/src/orchestrator-flush.test.ts +606 -44
- package/src/orchestrator-source-attribution.test.ts +73 -0
- package/src/orchestrator.ts +932 -229
- package/src/qmd-client.test.ts +59 -0
- package/src/qmd.ts +124 -84
- package/src/search/port.ts +16 -0
- package/src/semantic-rule-verifier.ts +13 -6
- package/src/types.ts +64 -0
- package/src/verified-recall.ts +10 -6
- package/dist/chunk-JVRPJ7D4.js.map +0 -1
- package/dist/chunk-K6X553JB.js.map +0 -1
- package/dist/chunk-LJCEWTG3.js.map +0 -1
- package/dist/chunk-MMJANTJX.js +0 -339
- package/dist/chunk-MMJANTJX.js.map +0 -1
- package/dist/chunk-NE2JBMLN.js.map +0 -1
- package/dist/chunk-OL2364SB.js.map +0 -1
- package/dist/chunk-PYWNNF2I.js.map +0 -1
- package/dist/chunk-QKK64Z6M.js.map +0 -1
- package/dist/chunk-YLZLPVKK.js.map +0 -1
- /package/dist/{chunk-JMQSYGXS.js.map → chunk-2BD7DG37.js.map} +0 -0
- /package/dist/{chunk-FVRBLJP6.js.map → chunk-2MXEVL75.js.map} +0 -0
- /package/dist/{chunk-JYN7QNTA.js.map → chunk-54XF2FY7.js.map} +0 -0
- /package/dist/{chunk-7WEB3FLJ.js.map → chunk-5PLUC5OB.js.map} +0 -0
- /package/dist/{chunk-JX2RINDR.js.map → chunk-6G5JEN55.js.map} +0 -0
- /package/dist/{chunk-ZCORQM74.js.map → chunk-AGJKWOKV.js.map} +0 -0
- /package/dist/{chunk-2DSTAWNZ.js.map → chunk-DIBWFCLA.js.map} +0 -0
- /package/dist/{chunk-NAZWHTYV.js.map → chunk-DR67OK4E.js.map} +0 -0
- /package/dist/{chunk-XBIACVCO.js.map → chunk-EC2AYKRX.js.map} +0 -0
- /package/dist/{chunk-RGPUQ66K.js.map → chunk-GCYFUTUC.js.map} +0 -0
- /package/dist/{chunk-JBHXMCYN.js.map → chunk-GRYAECRV.js.map} +0 -0
- /package/dist/{chunk-BJA6DQOC.js.map → chunk-GSHW5VVD.js.map} +0 -0
- /package/dist/{chunk-NCGWXCSW.js.map → chunk-IOZ5WBWD.js.map} +0 -0
- /package/dist/{chunk-7LWRCOP7.js.map → chunk-LZTFCAKE.js.map} +0 -0
- /package/dist/{chunk-6CVI6BP6.js.map → chunk-NXCK7DO7.js.map} +0 -0
- /package/dist/{chunk-Z5MQI7K2.js.map → chunk-PEPHBH2W.js.map} +0 -0
- /package/dist/{chunk-XWQ6ERUG.js.map → chunk-QZRKNA5F.js.map} +0 -0
- /package/dist/{chunk-PS3SYNHP.js.map → chunk-R5DB26G6.js.map} +0 -0
- /package/dist/{chunk-YM3LR4LS.js.map → chunk-SSSXWIBP.js.map} +0 -0
- /package/dist/{chunk-T2C6QJG2.js.map → chunk-SWDHVH2P.js.map} +0 -0
- /package/dist/{chunk-DBM2BD22.js.map → chunk-SXYCVRLK.js.map} +0 -0
- /package/dist/{chunk-ENV6RDTD.js.map → chunk-TIJYQXDI.js.map} +0 -0
- /package/dist/{chunk-BP2EV6W5.js.map → chunk-VAEAGTEQ.js.map} +0 -0
- /package/dist/{chunk-3RACUBII.js.map → chunk-WIKMCJUR.js.map} +0 -0
- /package/dist/{chunk-QW6JZO5P.js.map → chunk-WWMHAMAY.js.map} +0 -0
- /package/dist/{chunk-GPW2E4LN.js.map → chunk-YEZHZCUO.js.map} +0 -0
- /package/dist/{chunk-3XGWCZ63.js.map → chunk-YXLT4EMM.js.map} +0 -0
- /package/dist/{chunk-Y2RIIF6H.js.map → chunk-Z6UDTNY6.js.map} +0 -0
|
@@ -8,9 +8,11 @@ import {
|
|
|
8
8
|
Orchestrator,
|
|
9
9
|
} from "./orchestrator.js";
|
|
10
10
|
import { parseConfig } from "./config.js";
|
|
11
|
+
import { stableHash } from "./coding/git-context.js";
|
|
11
12
|
import type { BufferTurn } from "./types.js";
|
|
12
13
|
import type { ImportTurn } from "./bulk-import/types.js";
|
|
13
14
|
import { namespaceIdentityToken } from "./namespaces/identity.js";
|
|
15
|
+
import { readNamespaceMaintenanceStatuses } from "./maintenance/namespace-planner.js";
|
|
14
16
|
|
|
15
17
|
function makeTurn(sessionKey: string, content: string): BufferTurn {
|
|
16
18
|
return {
|
|
@@ -634,6 +636,147 @@ test("flushSession drains every discovered buffer for the session", async () =>
|
|
|
634
636
|
]);
|
|
635
637
|
});
|
|
636
638
|
|
|
639
|
+
test("runExtraction skips active scope profile writes when no layer is writable", async () => {
|
|
640
|
+
const config = parseConfig({
|
|
641
|
+
namespacesEnabled: true,
|
|
642
|
+
defaultNamespace: "default",
|
|
643
|
+
sharedNamespace: "shared",
|
|
644
|
+
defaultScopeProfile: "teamCoding",
|
|
645
|
+
codingMode: { projectScope: true },
|
|
646
|
+
principalFromSessionKeyMode: "prefix",
|
|
647
|
+
principalFromSessionKeyRules: [{ match: "pi-observer:", principal: "pi-observer" }],
|
|
648
|
+
namespacePolicies: [
|
|
649
|
+
{ name: "pi-observer", readPrincipals: ["pi-observer"], writePrincipals: [] },
|
|
650
|
+
],
|
|
651
|
+
scopeProfiles: {
|
|
652
|
+
teamCoding: {
|
|
653
|
+
readOrder: ["teamProject"],
|
|
654
|
+
writeDefault: "teamProject",
|
|
655
|
+
promotionTargets: ["teamProject"],
|
|
656
|
+
teamProject: { namespaceTemplate: "team-{teamId}-project-{projectHash}" },
|
|
657
|
+
},
|
|
658
|
+
},
|
|
659
|
+
teams: {
|
|
660
|
+
pi: {
|
|
661
|
+
principals: ["pi-observer"],
|
|
662
|
+
read: ["pi-observer"],
|
|
663
|
+
write: [],
|
|
664
|
+
promote: [],
|
|
665
|
+
},
|
|
666
|
+
},
|
|
667
|
+
});
|
|
668
|
+
config.extractionMinChars = 0;
|
|
669
|
+
config.extractionMinUserTurns = 1;
|
|
670
|
+
|
|
671
|
+
let clearCalls = 0;
|
|
672
|
+
const orchestrator = Object.create(Orchestrator.prototype) as any;
|
|
673
|
+
orchestrator.config = config;
|
|
674
|
+
orchestrator.buffer = {
|
|
675
|
+
clearAfterExtraction: async () => {
|
|
676
|
+
clearCalls += 1;
|
|
677
|
+
},
|
|
678
|
+
};
|
|
679
|
+
orchestrator.getCodingContextForSession = () => ({
|
|
680
|
+
projectId: "tag:remnic",
|
|
681
|
+
branch: null,
|
|
682
|
+
rootPath: "tag:remnic",
|
|
683
|
+
defaultBranch: "main",
|
|
684
|
+
});
|
|
685
|
+
orchestrator.storageRouter = {
|
|
686
|
+
storageFor: async () => {
|
|
687
|
+
throw new Error("unauthorized profile write must not choose fallback storage");
|
|
688
|
+
},
|
|
689
|
+
};
|
|
690
|
+
|
|
691
|
+
const result = await orchestrator.runExtraction(
|
|
692
|
+
[makeTurn("pi-observer:abc123", "remember unauthorized profile target")],
|
|
693
|
+
{ bufferKey: "pi-observer:abc123" },
|
|
694
|
+
);
|
|
695
|
+
|
|
696
|
+
assert.equal(result.status, "skipped");
|
|
697
|
+
assert.equal(result.reason, "scope_profile_no_writable_layer");
|
|
698
|
+
assert.equal(clearCalls, 1);
|
|
699
|
+
});
|
|
700
|
+
|
|
701
|
+
test("runExtraction writes buffered turns to active scope profile write layer", async () => {
|
|
702
|
+
const config = parseConfig({
|
|
703
|
+
namespacesEnabled: true,
|
|
704
|
+
defaultNamespace: "default",
|
|
705
|
+
sharedNamespace: "shared",
|
|
706
|
+
defaultScopeProfile: "teamCoding",
|
|
707
|
+
codingMode: { projectScope: true },
|
|
708
|
+
principalFromSessionKeyMode: "prefix",
|
|
709
|
+
principalFromSessionKeyRules: [{ match: "pi-observer:", principal: "pi-observer" }],
|
|
710
|
+
namespacePolicies: [
|
|
711
|
+
{ name: "pi-observer", readPrincipals: ["pi-observer"], writePrincipals: ["pi-observer"] },
|
|
712
|
+
],
|
|
713
|
+
scopeProfiles: {
|
|
714
|
+
teamCoding: {
|
|
715
|
+
readOrder: ["teamProject"],
|
|
716
|
+
writeDefault: "teamProject",
|
|
717
|
+
promotionTargets: ["teamProject"],
|
|
718
|
+
teamProject: { namespaceTemplate: "team-{teamId}-project-{projectHash}" },
|
|
719
|
+
},
|
|
720
|
+
},
|
|
721
|
+
teams: {
|
|
722
|
+
pi: {
|
|
723
|
+
principals: ["pi-observer"],
|
|
724
|
+
read: ["pi-observer"],
|
|
725
|
+
write: ["pi-observer"],
|
|
726
|
+
promote: ["pi-observer"],
|
|
727
|
+
},
|
|
728
|
+
},
|
|
729
|
+
});
|
|
730
|
+
config.extractionMinChars = 0;
|
|
731
|
+
config.extractionMinUserTurns = 1;
|
|
732
|
+
|
|
733
|
+
const turn = {
|
|
734
|
+
...makeTurn("pi-observer:abc123", "remember the team profile target"),
|
|
735
|
+
persistProcessedFingerprint: true,
|
|
736
|
+
};
|
|
737
|
+
const orchestrator = Object.create(Orchestrator.prototype) as any;
|
|
738
|
+
orchestrator.config = config;
|
|
739
|
+
orchestrator.buffer = { clearAfterExtraction: async () => undefined };
|
|
740
|
+
orchestrator.getCodingContextForSession = () => ({
|
|
741
|
+
projectId: "tag:remnic",
|
|
742
|
+
branch: null,
|
|
743
|
+
rootPath: "tag:remnic",
|
|
744
|
+
defaultBranch: "main",
|
|
745
|
+
});
|
|
746
|
+
let requestedNamespace: string | undefined;
|
|
747
|
+
orchestrator.storageRouter = {
|
|
748
|
+
storageFor: async (namespace: string) => {
|
|
749
|
+
requestedNamespace = namespace;
|
|
750
|
+
return {
|
|
751
|
+
listEntityNames: async () => [],
|
|
752
|
+
loadMeta: async () => ({
|
|
753
|
+
extractionCount: 0,
|
|
754
|
+
lastExtractionAt: null,
|
|
755
|
+
lastConsolidationAt: null,
|
|
756
|
+
totalMemories: 0,
|
|
757
|
+
totalEntities: 0,
|
|
758
|
+
processedExtractionFingerprints: [
|
|
759
|
+
{
|
|
760
|
+
fingerprint: orchestrator.buildExtractionFingerprint([turn], "pi-observer:abc123"),
|
|
761
|
+
observedAt: "2026-04-15T00:00:00.000Z",
|
|
762
|
+
},
|
|
763
|
+
],
|
|
764
|
+
}),
|
|
765
|
+
saveMeta: async () => undefined,
|
|
766
|
+
};
|
|
767
|
+
},
|
|
768
|
+
};
|
|
769
|
+
orchestrator.extraction = {
|
|
770
|
+
extract: async () => {
|
|
771
|
+
throw new Error("extraction should be skipped by processed fingerprint");
|
|
772
|
+
},
|
|
773
|
+
};
|
|
774
|
+
|
|
775
|
+
await orchestrator.runExtraction([turn], { bufferKey: "pi-observer:abc123" });
|
|
776
|
+
|
|
777
|
+
assert.equal(requestedNamespace, `team-pi-project-${stableHash("tag:remnic")}`);
|
|
778
|
+
});
|
|
779
|
+
|
|
637
780
|
test("runExtraction skips batches whose persisted fingerprint already exists in storage meta", async () => {
|
|
638
781
|
const config = parseConfig({});
|
|
639
782
|
config.extractionMinChars = 0;
|
|
@@ -1780,8 +1923,10 @@ test("runExtraction still clears the session buffer after persistence even if re
|
|
|
1780
1923
|
// cataloged namespaces.
|
|
1781
1924
|
test("runQmdMaintenance updates and embeds cataloged dynamic namespaces (NGnei)", async () => {
|
|
1782
1925
|
const orchestrator = Object.create(Orchestrator.prototype) as any;
|
|
1783
|
-
|
|
1784
|
-
|
|
1926
|
+
const updateArgs: string[] = [];
|
|
1927
|
+
const updateCalls: Array<{ namespaces: string[]; strict: boolean | undefined }> = [];
|
|
1928
|
+
const embedArgs: string[] = [];
|
|
1929
|
+
const embedCalls: string[][] = [];
|
|
1785
1930
|
const memoryDir = path.join(os.tmpdir(), "remnic-qmd-maintenance-ngnei");
|
|
1786
1931
|
const dynamicNamespace = "project-origin-dynamic";
|
|
1787
1932
|
const dynamicStorageDir = path.join(
|
|
@@ -1797,6 +1942,7 @@ test("runQmdMaintenance updates and embeds cataloged dynamic namespaces (NGnei)"
|
|
|
1797
1942
|
defaultNamespace: "default",
|
|
1798
1943
|
sharedNamespace: "shared",
|
|
1799
1944
|
namespacePolicies: [],
|
|
1945
|
+
maintenanceNamespaceLockStaleMs: 100,
|
|
1800
1946
|
qmdAutoEmbedEnabled: true,
|
|
1801
1947
|
qmdEmbedMinIntervalMs: 0,
|
|
1802
1948
|
};
|
|
@@ -1820,35 +1966,98 @@ test("runQmdMaintenance updates and embeds cataloged dynamic namespaces (NGnei)"
|
|
|
1820
1966
|
},
|
|
1821
1967
|
};
|
|
1822
1968
|
orchestrator.namespaceSearchRouter = {
|
|
1823
|
-
async
|
|
1824
|
-
|
|
1825
|
-
|
|
1969
|
+
async updateNamespacesDetailed(ns: string[], _execution?: unknown, options?: { strict?: boolean }) {
|
|
1970
|
+
updateCalls.push({ namespaces: [...ns], strict: options?.strict });
|
|
1971
|
+
updateArgs.push(...ns);
|
|
1972
|
+
return { backendCount: ns.length, eligibleNamespaces: ns };
|
|
1826
1973
|
},
|
|
1827
1974
|
async embedNamespaces(ns: string[]) {
|
|
1828
|
-
|
|
1975
|
+
embedCalls.push([...ns]);
|
|
1976
|
+
embedArgs.push(...ns);
|
|
1829
1977
|
},
|
|
1830
1978
|
};
|
|
1831
1979
|
|
|
1832
1980
|
await orchestrator.runQmdMaintenance();
|
|
1833
1981
|
|
|
1834
|
-
assert.ok(
|
|
1982
|
+
assert.ok(updateArgs.length > 0, "updateNamespaces must be called");
|
|
1983
|
+
assert.equal(updateCalls.length, 1, "global QMD maintenance must batch selected namespaces into one update call");
|
|
1984
|
+
assert.equal(updateCalls[0]?.strict, true, "recurring QMD maintenance must use strict update semantics");
|
|
1835
1985
|
assert.ok(
|
|
1836
|
-
|
|
1986
|
+
updateArgs.includes(dynamicNamespace),
|
|
1837
1987
|
"QMD update must cover the cataloged dynamic namespace, not just configured ones",
|
|
1838
1988
|
);
|
|
1839
1989
|
assert.ok(
|
|
1840
|
-
|
|
1990
|
+
updateArgs.includes("default") && updateArgs.includes("shared"),
|
|
1841
1991
|
"configured namespaces remain covered",
|
|
1842
1992
|
);
|
|
1843
1993
|
assert.ok(
|
|
1844
|
-
|
|
1994
|
+
embedArgs.includes(dynamicNamespace),
|
|
1845
1995
|
"QMD embed must cover the cataloged dynamic namespace",
|
|
1846
1996
|
);
|
|
1997
|
+
assert.equal(embedCalls.length, 1, "QMD embed must batch all selected namespaces into one router call");
|
|
1998
|
+
assert.deepEqual(new Set(embedCalls[0]), new Set(["default", "shared", dynamicNamespace]));
|
|
1999
|
+
});
|
|
2000
|
+
|
|
2001
|
+
test("runQmdMaintenance tracks namespace embed cadence across budget rotation", async () => {
|
|
2002
|
+
const orchestrator = Object.create(Orchestrator.prototype) as any;
|
|
2003
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-namespace-embed-cadence-"));
|
|
2004
|
+
const updateCalls: string[][] = [];
|
|
2005
|
+
const embedCalls: string[][] = [];
|
|
2006
|
+
|
|
2007
|
+
try {
|
|
2008
|
+
orchestrator.config = {
|
|
2009
|
+
memoryDir,
|
|
2010
|
+
namespacesEnabled: true,
|
|
2011
|
+
defaultNamespace: "default",
|
|
2012
|
+
sharedNamespace: "shared",
|
|
2013
|
+
namespacePolicies: [{ name: "project-a" }, { name: "project-b" }],
|
|
2014
|
+
maintenanceMaxNamespacesPerCycle: 3,
|
|
2015
|
+
maintenanceNamespaceLockStaleMs: 100,
|
|
2016
|
+
qmdAutoEmbedEnabled: true,
|
|
2017
|
+
qmdEmbedMinIntervalMs: 60_000,
|
|
2018
|
+
};
|
|
2019
|
+
orchestrator.qmdMaintenanceInFlight = false;
|
|
2020
|
+
orchestrator.qmdMaintenancePending = true;
|
|
2021
|
+
orchestrator.lastQmdEmbedAtMs = 0;
|
|
2022
|
+
orchestrator.lastQmdEmbedAtMsByNamespace = new Map();
|
|
2023
|
+
orchestrator.namespaceCatalog = {
|
|
2024
|
+
enabled: false,
|
|
2025
|
+
async listNamespaces() {
|
|
2026
|
+
throw new Error("catalog disabled - must not be read");
|
|
2027
|
+
},
|
|
2028
|
+
};
|
|
2029
|
+
orchestrator.namespaceSearchRouter = {
|
|
2030
|
+
async updateNamespacesDetailed(ns: string[]) {
|
|
2031
|
+
updateCalls.push([...ns]);
|
|
2032
|
+
return { backendCount: ns.length, eligibleNamespaces: ns };
|
|
2033
|
+
},
|
|
2034
|
+
async embedNamespaces(ns: string[]) {
|
|
2035
|
+
embedCalls.push([...ns]);
|
|
2036
|
+
},
|
|
2037
|
+
};
|
|
2038
|
+
|
|
2039
|
+
await orchestrator.runQmdMaintenance();
|
|
2040
|
+
orchestrator.qmdMaintenancePending = true;
|
|
2041
|
+
await orchestrator.runQmdMaintenance();
|
|
2042
|
+
|
|
2043
|
+
assert.deepEqual(updateCalls, [
|
|
2044
|
+
["default", "shared", "project-a"],
|
|
2045
|
+
["default", "shared", "project-b"],
|
|
2046
|
+
]);
|
|
2047
|
+
assert.deepEqual(
|
|
2048
|
+
embedCalls,
|
|
2049
|
+
[["default", "shared", "project-a"], ["project-b"]],
|
|
2050
|
+
"a global embed timestamp must not suppress embeddings for newly budgeted namespaces",
|
|
2051
|
+
);
|
|
2052
|
+
} finally {
|
|
2053
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
2054
|
+
}
|
|
1847
2055
|
});
|
|
1848
2056
|
|
|
1849
2057
|
test("runQmdMaintenance skips cataloged dynamic namespaces whose live root is unsafe", async () => {
|
|
1850
2058
|
const orchestrator = Object.create(Orchestrator.prototype) as any;
|
|
1851
|
-
|
|
2059
|
+
const updateArgs: string[] = [];
|
|
2060
|
+
const updateCalls: string[][] = [];
|
|
1852
2061
|
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-unsafe-root-"));
|
|
1853
2062
|
const outsideDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-unsafe-target-"));
|
|
1854
2063
|
try {
|
|
@@ -1868,6 +2077,7 @@ test("runQmdMaintenance skips cataloged dynamic namespaces whose live root is un
|
|
|
1868
2077
|
defaultNamespace: "default",
|
|
1869
2078
|
sharedNamespace: "shared",
|
|
1870
2079
|
namespacePolicies: [],
|
|
2080
|
+
maintenanceNamespaceLockStaleMs: 100,
|
|
1871
2081
|
qmdAutoEmbedEnabled: false,
|
|
1872
2082
|
qmdEmbedMinIntervalMs: 0,
|
|
1873
2083
|
};
|
|
@@ -1890,18 +2100,20 @@ test("runQmdMaintenance skips cataloged dynamic namespaces whose live root is un
|
|
|
1890
2100
|
},
|
|
1891
2101
|
};
|
|
1892
2102
|
orchestrator.namespaceSearchRouter = {
|
|
1893
|
-
async
|
|
1894
|
-
|
|
1895
|
-
|
|
2103
|
+
async updateNamespacesDetailed(ns: string[]) {
|
|
2104
|
+
updateCalls.push([...ns]);
|
|
2105
|
+
updateArgs.push(...ns);
|
|
2106
|
+
return { backendCount: ns.length, eligibleNamespaces: ns };
|
|
1896
2107
|
},
|
|
1897
2108
|
async embedNamespaces() {},
|
|
1898
2109
|
};
|
|
1899
2110
|
|
|
1900
2111
|
await orchestrator.runQmdMaintenance();
|
|
1901
2112
|
|
|
1902
|
-
assert.ok(
|
|
2113
|
+
assert.ok(updateArgs.length > 0, "updateNamespaces must be called");
|
|
2114
|
+
assert.equal(updateCalls.length, 1, "global QMD maintenance must update once for the locked namespace set");
|
|
1903
2115
|
assert.deepEqual(
|
|
1904
|
-
[...
|
|
2116
|
+
[...updateArgs].sort(),
|
|
1905
2117
|
["default", "shared"],
|
|
1906
2118
|
"cataloged dynamic namespaces are skipped when the live router root differs from the catalog-sanitized root",
|
|
1907
2119
|
);
|
|
@@ -1911,46 +2123,395 @@ test("runQmdMaintenance skips cataloged dynamic namespaces whose live root is un
|
|
|
1911
2123
|
}
|
|
1912
2124
|
});
|
|
1913
2125
|
|
|
2126
|
+
test("runQmdMaintenance treats zero namespace updates as failed maintenance", async () => {
|
|
2127
|
+
const orchestrator = Object.create(Orchestrator.prototype) as any;
|
|
2128
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-zero-update-"));
|
|
2129
|
+
let markMaintenanceCalls = 0;
|
|
2130
|
+
let embedCalls = 0;
|
|
2131
|
+
try {
|
|
2132
|
+
orchestrator.config = {
|
|
2133
|
+
memoryDir,
|
|
2134
|
+
namespacesEnabled: true,
|
|
2135
|
+
defaultNamespace: "default",
|
|
2136
|
+
sharedNamespace: "shared",
|
|
2137
|
+
namespacePolicies: [],
|
|
2138
|
+
maintenanceNamespaceLockStaleMs: 100,
|
|
2139
|
+
qmdAutoEmbedEnabled: false,
|
|
2140
|
+
qmdEmbedMinIntervalMs: 0,
|
|
2141
|
+
};
|
|
2142
|
+
orchestrator.qmdMaintenanceInFlight = false;
|
|
2143
|
+
orchestrator.qmdMaintenancePending = true;
|
|
2144
|
+
orchestrator.lastQmdEmbedAtMs = 0;
|
|
2145
|
+
orchestrator.namespaceCatalog = {
|
|
2146
|
+
enabled: true,
|
|
2147
|
+
async listNamespaces() {
|
|
2148
|
+
return [{ namespace: "default" }];
|
|
2149
|
+
},
|
|
2150
|
+
async markMaintenance() {
|
|
2151
|
+
markMaintenanceCalls += 1;
|
|
2152
|
+
},
|
|
2153
|
+
};
|
|
2154
|
+
orchestrator.namespaceSearchRouter = {
|
|
2155
|
+
async updateNamespacesDetailed() {
|
|
2156
|
+
return { backendCount: 0, eligibleNamespaces: [] };
|
|
2157
|
+
},
|
|
2158
|
+
async embedNamespaces() {
|
|
2159
|
+
embedCalls += 1;
|
|
2160
|
+
},
|
|
2161
|
+
};
|
|
2162
|
+
|
|
2163
|
+
await orchestrator.runQmdMaintenance();
|
|
2164
|
+
|
|
2165
|
+
const statuses = await readNamespaceMaintenanceStatuses(orchestrator.config);
|
|
2166
|
+
assert.ok(
|
|
2167
|
+
statuses.some((status) => status.namespace === "default" && status.state === "failed"),
|
|
2168
|
+
"zero updates should be recorded as failed maintenance, not a successful run",
|
|
2169
|
+
);
|
|
2170
|
+
assert.equal(markMaintenanceCalls, 0);
|
|
2171
|
+
assert.equal(embedCalls, 0);
|
|
2172
|
+
assert.equal(orchestrator.lastQmdEmbedAtMs, 0);
|
|
2173
|
+
} finally {
|
|
2174
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
2175
|
+
}
|
|
2176
|
+
});
|
|
2177
|
+
|
|
2178
|
+
test("runQmdMaintenance treats partial namespace update eligibility as failed maintenance", async () => {
|
|
2179
|
+
const orchestrator = Object.create(Orchestrator.prototype) as any;
|
|
2180
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-partial-update-"));
|
|
2181
|
+
let markMaintenanceCalls = 0;
|
|
2182
|
+
let embedCalls = 0;
|
|
2183
|
+
try {
|
|
2184
|
+
orchestrator.config = {
|
|
2185
|
+
memoryDir,
|
|
2186
|
+
namespacesEnabled: true,
|
|
2187
|
+
defaultNamespace: "default",
|
|
2188
|
+
sharedNamespace: "shared",
|
|
2189
|
+
namespacePolicies: [],
|
|
2190
|
+
maintenanceNamespaceLockStaleMs: 100,
|
|
2191
|
+
qmdAutoEmbedEnabled: false,
|
|
2192
|
+
qmdEmbedMinIntervalMs: 0,
|
|
2193
|
+
};
|
|
2194
|
+
orchestrator.qmdMaintenanceInFlight = false;
|
|
2195
|
+
orchestrator.qmdMaintenancePending = true;
|
|
2196
|
+
orchestrator.lastQmdEmbedAtMs = 0;
|
|
2197
|
+
orchestrator.namespaceCatalog = {
|
|
2198
|
+
enabled: true,
|
|
2199
|
+
async listNamespaces() {
|
|
2200
|
+
return [{ namespace: "default" }];
|
|
2201
|
+
},
|
|
2202
|
+
async markMaintenance() {
|
|
2203
|
+
markMaintenanceCalls += 1;
|
|
2204
|
+
},
|
|
2205
|
+
};
|
|
2206
|
+
orchestrator.namespaceSearchRouter = {
|
|
2207
|
+
async updateNamespacesDetailed(ns: string[]) {
|
|
2208
|
+
assert.ok(ns.includes("default") && ns.includes("shared"));
|
|
2209
|
+
return { backendCount: 1, eligibleNamespaces: ["default"] };
|
|
2210
|
+
},
|
|
2211
|
+
async embedNamespaces() {
|
|
2212
|
+
embedCalls += 1;
|
|
2213
|
+
},
|
|
2214
|
+
};
|
|
2215
|
+
|
|
2216
|
+
await orchestrator.runQmdMaintenance();
|
|
2217
|
+
|
|
2218
|
+
const statuses = await readNamespaceMaintenanceStatuses(orchestrator.config);
|
|
2219
|
+
assert.ok(
|
|
2220
|
+
statuses.some((status) => status.namespace === "default" && status.state === "failed"),
|
|
2221
|
+
"partial update eligibility should not be recorded as successful maintenance",
|
|
2222
|
+
);
|
|
2223
|
+
assert.ok(
|
|
2224
|
+
statuses.some((status) => status.namespace === "shared" && status.state === "failed"),
|
|
2225
|
+
"ineligible selected namespaces should not be rotated as maintained",
|
|
2226
|
+
);
|
|
2227
|
+
assert.equal(markMaintenanceCalls, 0);
|
|
2228
|
+
assert.equal(embedCalls, 0);
|
|
2229
|
+
assert.equal(orchestrator.lastQmdEmbedAtMs, 0);
|
|
2230
|
+
} finally {
|
|
2231
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
2232
|
+
}
|
|
2233
|
+
});
|
|
2234
|
+
|
|
2235
|
+
test("runQmdMaintenance treats namespace embed errors as failed maintenance", async () => {
|
|
2236
|
+
const orchestrator = Object.create(Orchestrator.prototype) as any;
|
|
2237
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-embed-failure-"));
|
|
2238
|
+
let markMaintenanceCalls = 0;
|
|
2239
|
+
try {
|
|
2240
|
+
orchestrator.config = {
|
|
2241
|
+
memoryDir,
|
|
2242
|
+
namespacesEnabled: true,
|
|
2243
|
+
defaultNamespace: "default",
|
|
2244
|
+
sharedNamespace: "shared",
|
|
2245
|
+
namespacePolicies: [],
|
|
2246
|
+
maintenanceNamespaceLockStaleMs: 100,
|
|
2247
|
+
qmdAutoEmbedEnabled: true,
|
|
2248
|
+
qmdEmbedMinIntervalMs: 0,
|
|
2249
|
+
};
|
|
2250
|
+
orchestrator.qmdMaintenanceInFlight = false;
|
|
2251
|
+
orchestrator.qmdMaintenancePending = true;
|
|
2252
|
+
orchestrator.lastQmdEmbedAtMs = 0;
|
|
2253
|
+
orchestrator.namespaceCatalog = {
|
|
2254
|
+
enabled: true,
|
|
2255
|
+
async listNamespaces() {
|
|
2256
|
+
return [{ namespace: "default" }];
|
|
2257
|
+
},
|
|
2258
|
+
async markMaintenance() {
|
|
2259
|
+
markMaintenanceCalls += 1;
|
|
2260
|
+
},
|
|
2261
|
+
};
|
|
2262
|
+
orchestrator.namespaceSearchRouter = {
|
|
2263
|
+
async updateNamespacesDetailed(ns: string[]) {
|
|
2264
|
+
return { backendCount: 1, eligibleNamespaces: ns };
|
|
2265
|
+
},
|
|
2266
|
+
async embedNamespaces() {
|
|
2267
|
+
throw Object.assign(new Error("embed failed"), { code: "EQMD" });
|
|
2268
|
+
},
|
|
2269
|
+
};
|
|
2270
|
+
|
|
2271
|
+
await orchestrator.runQmdMaintenance();
|
|
2272
|
+
|
|
2273
|
+
const statuses = await readNamespaceMaintenanceStatuses(orchestrator.config);
|
|
2274
|
+
assert.ok(
|
|
2275
|
+
statuses.some((status) => status.namespace === "default" && status.state === "failed"),
|
|
2276
|
+
"embed failures should not be recorded as successful namespace maintenance",
|
|
2277
|
+
);
|
|
2278
|
+
assert.equal(markMaintenanceCalls, 0);
|
|
2279
|
+
assert.equal(orchestrator.lastQmdEmbedAtMs, 0);
|
|
2280
|
+
} finally {
|
|
2281
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
2282
|
+
}
|
|
2283
|
+
});
|
|
2284
|
+
|
|
2285
|
+
test("runQmdMaintenance records QMD min-interval throttles as skipped maintenance", async () => {
|
|
2286
|
+
const orchestrator = Object.create(Orchestrator.prototype) as any;
|
|
2287
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-throttled-update-"));
|
|
2288
|
+
let markMaintenanceCalls = 0;
|
|
2289
|
+
let embedCalls = 0;
|
|
2290
|
+
let updateCalls = 0;
|
|
2291
|
+
try {
|
|
2292
|
+
orchestrator.config = {
|
|
2293
|
+
memoryDir,
|
|
2294
|
+
namespacesEnabled: true,
|
|
2295
|
+
defaultNamespace: "default",
|
|
2296
|
+
sharedNamespace: "shared",
|
|
2297
|
+
namespacePolicies: [],
|
|
2298
|
+
maintenanceNamespaceLockStaleMs: 100,
|
|
2299
|
+
qmdAutoEmbedEnabled: false,
|
|
2300
|
+
qmdEmbedMinIntervalMs: 0,
|
|
2301
|
+
};
|
|
2302
|
+
orchestrator.qmdMaintenanceInFlight = false;
|
|
2303
|
+
orchestrator.qmdMaintenancePending = true;
|
|
2304
|
+
orchestrator.lastQmdEmbedAtMs = 0;
|
|
2305
|
+
orchestrator.namespaceCatalog = {
|
|
2306
|
+
enabled: true,
|
|
2307
|
+
async listNamespaces() {
|
|
2308
|
+
return [{ namespace: "default" }];
|
|
2309
|
+
},
|
|
2310
|
+
async markMaintenance() {
|
|
2311
|
+
markMaintenanceCalls += 1;
|
|
2312
|
+
},
|
|
2313
|
+
};
|
|
2314
|
+
orchestrator.namespaceSearchRouter = {
|
|
2315
|
+
async updateNamespacesDetailed(_ns: string[], _execution?: unknown, options?: { strict?: boolean }) {
|
|
2316
|
+
updateCalls += 1;
|
|
2317
|
+
assert.equal(options?.strict, true, "recurring maintenance must use strict QMD updates");
|
|
2318
|
+
throw new Error("QMD update skipped by global min-interval gate");
|
|
2319
|
+
},
|
|
2320
|
+
async embedNamespaces() {
|
|
2321
|
+
embedCalls += 1;
|
|
2322
|
+
},
|
|
2323
|
+
};
|
|
2324
|
+
|
|
2325
|
+
await orchestrator.runQmdMaintenance();
|
|
2326
|
+
|
|
2327
|
+
const statuses = await readNamespaceMaintenanceStatuses(orchestrator.config);
|
|
2328
|
+
assert.equal(updateCalls, 1, "strict global QMD maintenance should be attempted once");
|
|
2329
|
+
assert.ok(
|
|
2330
|
+
statuses.some(
|
|
2331
|
+
(status) =>
|
|
2332
|
+
status.namespace === "default" &&
|
|
2333
|
+
status.state === "skipped" &&
|
|
2334
|
+
status.reason === "throttled",
|
|
2335
|
+
),
|
|
2336
|
+
"QMD min-interval throttles should be recorded as skipped maintenance",
|
|
2337
|
+
);
|
|
2338
|
+
assert.ok(
|
|
2339
|
+
statuses.some(
|
|
2340
|
+
(status) =>
|
|
2341
|
+
status.namespace === "shared" &&
|
|
2342
|
+
status.state === "skipped" &&
|
|
2343
|
+
status.reason === "throttled",
|
|
2344
|
+
),
|
|
2345
|
+
"every selected namespace should receive the throttled skip status",
|
|
2346
|
+
);
|
|
2347
|
+
assert.equal(markMaintenanceCalls, 0);
|
|
2348
|
+
assert.equal(embedCalls, 0);
|
|
2349
|
+
assert.equal(orchestrator.lastQmdEmbedAtMs, 0);
|
|
2350
|
+
} finally {
|
|
2351
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
2352
|
+
}
|
|
2353
|
+
});
|
|
2354
|
+
|
|
2355
|
+
test("runQmdMaintenance still embeds when due update is throttled", async () => {
|
|
2356
|
+
const orchestrator = Object.create(Orchestrator.prototype) as any;
|
|
2357
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-throttled-update-embed-"));
|
|
2358
|
+
let markMaintenanceCalls = 0;
|
|
2359
|
+
let updateCalls = 0;
|
|
2360
|
+
const embedCalls: string[][] = [];
|
|
2361
|
+
try {
|
|
2362
|
+
orchestrator.config = {
|
|
2363
|
+
memoryDir,
|
|
2364
|
+
namespacesEnabled: true,
|
|
2365
|
+
defaultNamespace: "default",
|
|
2366
|
+
sharedNamespace: "shared",
|
|
2367
|
+
namespacePolicies: [],
|
|
2368
|
+
maintenanceNamespaceLockStaleMs: 100,
|
|
2369
|
+
qmdAutoEmbedEnabled: true,
|
|
2370
|
+
qmdEmbedMinIntervalMs: 0,
|
|
2371
|
+
};
|
|
2372
|
+
orchestrator.qmdMaintenanceInFlight = false;
|
|
2373
|
+
orchestrator.qmdMaintenancePending = true;
|
|
2374
|
+
orchestrator.lastQmdEmbedAtMs = 0;
|
|
2375
|
+
orchestrator.namespaceCatalog = {
|
|
2376
|
+
enabled: true,
|
|
2377
|
+
async listNamespaces() {
|
|
2378
|
+
return [{ namespace: "default" }];
|
|
2379
|
+
},
|
|
2380
|
+
async markMaintenance() {
|
|
2381
|
+
markMaintenanceCalls += 1;
|
|
2382
|
+
},
|
|
2383
|
+
};
|
|
2384
|
+
orchestrator.namespaceSearchRouter = {
|
|
2385
|
+
async updateNamespacesDetailed(_ns: string[], _execution?: unknown, options?: { strict?: boolean }) {
|
|
2386
|
+
updateCalls += 1;
|
|
2387
|
+
assert.equal(options?.strict, true, "recurring maintenance must use strict QMD updates");
|
|
2388
|
+
throw new Error("QMD update skipped by global min-interval gate");
|
|
2389
|
+
},
|
|
2390
|
+
async embedNamespaces(ns: string[], options?: { strict?: boolean }) {
|
|
2391
|
+
assert.equal(options?.strict, true, "due embed retries must surface embed failures");
|
|
2392
|
+
embedCalls.push([...ns]);
|
|
2393
|
+
},
|
|
2394
|
+
};
|
|
2395
|
+
|
|
2396
|
+
await orchestrator.runQmdMaintenance();
|
|
2397
|
+
|
|
2398
|
+
const statuses = await readNamespaceMaintenanceStatuses(orchestrator.config);
|
|
2399
|
+
assert.equal(updateCalls, 1, "strict global QMD maintenance should be attempted once");
|
|
2400
|
+
assert.deepEqual(embedCalls, [["default", "shared"]]);
|
|
2401
|
+
assert.ok(
|
|
2402
|
+
statuses.every((status) => status.state === "skipped" && status.reason === "throttled"),
|
|
2403
|
+
"a throttled update should still be recorded as skipped after the due embed retry",
|
|
2404
|
+
);
|
|
2405
|
+
assert.equal(markMaintenanceCalls, 0);
|
|
2406
|
+
assert.notEqual(orchestrator.lastQmdEmbedAtMs, 0);
|
|
2407
|
+
} finally {
|
|
2408
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
2409
|
+
}
|
|
2410
|
+
});
|
|
2411
|
+
|
|
2412
|
+
test("runQmdMaintenance treats strict namespace update errors as failed maintenance", async () => {
|
|
2413
|
+
const orchestrator = Object.create(Orchestrator.prototype) as any;
|
|
2414
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-update-failure-"));
|
|
2415
|
+
let markMaintenanceCalls = 0;
|
|
2416
|
+
let embedCalls = 0;
|
|
2417
|
+
let updateCalls = 0;
|
|
2418
|
+
try {
|
|
2419
|
+
orchestrator.config = {
|
|
2420
|
+
memoryDir,
|
|
2421
|
+
namespacesEnabled: true,
|
|
2422
|
+
defaultNamespace: "default",
|
|
2423
|
+
sharedNamespace: "shared",
|
|
2424
|
+
namespacePolicies: [],
|
|
2425
|
+
maintenanceNamespaceLockStaleMs: 100,
|
|
2426
|
+
qmdAutoEmbedEnabled: true,
|
|
2427
|
+
qmdEmbedMinIntervalMs: 0,
|
|
2428
|
+
};
|
|
2429
|
+
orchestrator.qmdMaintenanceInFlight = false;
|
|
2430
|
+
orchestrator.qmdMaintenancePending = true;
|
|
2431
|
+
orchestrator.lastQmdEmbedAtMs = 0;
|
|
2432
|
+
orchestrator.namespaceCatalog = {
|
|
2433
|
+
enabled: true,
|
|
2434
|
+
async listNamespaces() {
|
|
2435
|
+
return [{ namespace: "default" }];
|
|
2436
|
+
},
|
|
2437
|
+
async markMaintenance() {
|
|
2438
|
+
markMaintenanceCalls += 1;
|
|
2439
|
+
},
|
|
2440
|
+
};
|
|
2441
|
+
orchestrator.namespaceSearchRouter = {
|
|
2442
|
+
async updateNamespacesDetailed(_ns: string[], _execution?: unknown, options?: { strict?: boolean }) {
|
|
2443
|
+
updateCalls += 1;
|
|
2444
|
+
assert.equal(options?.strict, true, "recurring maintenance must require strict update failure propagation");
|
|
2445
|
+
throw new Error("qmd exploded");
|
|
2446
|
+
},
|
|
2447
|
+
async embedNamespaces() {
|
|
2448
|
+
embedCalls += 1;
|
|
2449
|
+
},
|
|
2450
|
+
};
|
|
2451
|
+
|
|
2452
|
+
await orchestrator.runQmdMaintenance();
|
|
2453
|
+
|
|
2454
|
+
const statuses = await readNamespaceMaintenanceStatuses(orchestrator.config);
|
|
2455
|
+
assert.equal(updateCalls, 1, "strict global QMD maintenance should be attempted once");
|
|
2456
|
+
assert.ok(
|
|
2457
|
+
statuses.some((status) => status.namespace === "default" && status.state === "failed"),
|
|
2458
|
+
"strict update errors should be recorded as failed maintenance",
|
|
2459
|
+
);
|
|
2460
|
+
assert.equal(markMaintenanceCalls, 0);
|
|
2461
|
+
assert.equal(embedCalls, 0);
|
|
2462
|
+
assert.equal(orchestrator.lastQmdEmbedAtMs, 0);
|
|
2463
|
+
} finally {
|
|
2464
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
2465
|
+
}
|
|
2466
|
+
});
|
|
2467
|
+
|
|
1914
2468
|
// NGnei fallback: when the catalog is disabled, maintenance covers exactly the
|
|
1915
2469
|
// configured set (no catalog read), and a catalog read failure degrades to the
|
|
1916
2470
|
// configured set rather than breaking maintenance.
|
|
1917
2471
|
test("runQmdMaintenance falls back to configured namespaces when the catalog is disabled (NGnei)", async () => {
|
|
1918
2472
|
const orchestrator = Object.create(Orchestrator.prototype) as any;
|
|
1919
|
-
|
|
2473
|
+
const updateArgs: string[] = [];
|
|
2474
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-disabled-catalog-"));
|
|
1920
2475
|
|
|
1921
|
-
|
|
1922
|
-
|
|
1923
|
-
|
|
1924
|
-
|
|
2476
|
+
try {
|
|
2477
|
+
orchestrator.config = {
|
|
2478
|
+
memoryDir,
|
|
2479
|
+
namespacesEnabled: true,
|
|
2480
|
+
defaultNamespace: "default",
|
|
2481
|
+
sharedNamespace: "shared",
|
|
1925
2482
|
namespacePolicies: [],
|
|
2483
|
+
maintenanceNamespaceLockStaleMs: 100,
|
|
1926
2484
|
qmdAutoEmbedEnabled: false,
|
|
1927
|
-
|
|
1928
|
-
|
|
1929
|
-
|
|
1930
|
-
|
|
1931
|
-
|
|
1932
|
-
|
|
1933
|
-
|
|
1934
|
-
|
|
1935
|
-
|
|
1936
|
-
|
|
2485
|
+
qmdEmbedMinIntervalMs: 0,
|
|
2486
|
+
};
|
|
2487
|
+
orchestrator.qmdMaintenanceInFlight = false;
|
|
2488
|
+
orchestrator.qmdMaintenancePending = true;
|
|
2489
|
+
orchestrator.lastQmdEmbedAtMs = 0;
|
|
2490
|
+
orchestrator.namespaceCatalog = {
|
|
2491
|
+
enabled: false,
|
|
2492
|
+
async listNamespaces() {
|
|
2493
|
+
throw new Error("catalog disabled — must not be read");
|
|
2494
|
+
},
|
|
1937
2495
|
};
|
|
1938
2496
|
orchestrator.namespaceSearchRouter = {
|
|
1939
|
-
async
|
|
1940
|
-
|
|
1941
|
-
return ns.length;
|
|
2497
|
+
async updateNamespacesDetailed(ns: string[]) {
|
|
2498
|
+
updateArgs.push(...ns);
|
|
2499
|
+
return { backendCount: ns.length, eligibleNamespaces: ns };
|
|
1942
2500
|
},
|
|
1943
|
-
|
|
1944
|
-
|
|
2501
|
+
async embedNamespaces() {},
|
|
2502
|
+
};
|
|
1945
2503
|
|
|
1946
|
-
|
|
2504
|
+
await orchestrator.runQmdMaintenance();
|
|
1947
2505
|
|
|
1948
|
-
|
|
1949
|
-
|
|
1950
|
-
|
|
1951
|
-
|
|
1952
|
-
|
|
1953
|
-
|
|
2506
|
+
assert.ok(updateArgs.length > 0, "updateNamespaces must be called");
|
|
2507
|
+
assert.deepEqual(
|
|
2508
|
+
[...updateArgs].sort(),
|
|
2509
|
+
["default", "shared"],
|
|
2510
|
+
"a disabled catalog covers exactly the configured set",
|
|
2511
|
+
);
|
|
2512
|
+
} finally {
|
|
2513
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
2514
|
+
}
|
|
1954
2515
|
});
|
|
1955
2516
|
|
|
1956
2517
|
// ── NHZEV (codex P2): the QMD STARTUP sync in deferredInitialize() must cover
|
|
@@ -1981,6 +2542,7 @@ test("deferredInitialize startup sync covers cataloged dynamic namespaces (NHZEV
|
|
|
1981
2542
|
sharedNamespace: "shared",
|
|
1982
2543
|
namespacePolicies: [],
|
|
1983
2544
|
qmdMaintenanceEnabled: true,
|
|
2545
|
+
maintenanceMaxNamespacesPerCycle: 2,
|
|
1984
2546
|
};
|
|
1985
2547
|
orchestrator.qmd = {
|
|
1986
2548
|
isAvailable: () => true,
|
|
@@ -2017,7 +2579,7 @@ test("deferredInitialize startup sync covers cataloged dynamic namespaces (NHZEV
|
|
|
2017
2579
|
assert.ok(updateArg, "startup updateNamespaces must be called");
|
|
2018
2580
|
assert.ok(
|
|
2019
2581
|
updateArg!.includes(dynamicNamespace),
|
|
2020
|
-
"startup sync must cover the cataloged dynamic namespace
|
|
2582
|
+
"startup sync must cover the cataloged dynamic namespace even when it exceeds the recurring maintenance cycle budget",
|
|
2021
2583
|
);
|
|
2022
2584
|
assert.ok(
|
|
2023
2585
|
updateArg!.includes("default") && updateArg!.includes("shared"),
|