@remnic/core 9.47.1 → 9.48.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/access-admin-ops-surface.d.ts +6 -5
- package/dist/access-admin-ops-surface.js +27 -25
- package/dist/access-audit.js +2 -2
- package/dist/access-authorization-probe.d.ts +6 -5
- package/dist/access-authorization-probe.js +28 -26
- package/dist/access-boundary.d.ts +6 -5
- package/dist/access-boundary.js +27 -25
- package/dist/access-cli.js +87 -85
- package/dist/access-cli.js.map +1 -1
- package/dist/access-extraction-force-flush.d.ts +6 -5
- package/dist/access-extraction-force-flush.js +27 -25
- package/dist/access-http-lcm-compaction.d.ts +6 -5
- package/dist/access-http-lifecycle-flush.d.ts +6 -5
- package/dist/access-http-offline-stream.d.ts +6 -5
- package/dist/access-http-query.js +27 -25
- package/dist/access-http.d.ts +6 -5
- package/dist/access-http.js +36 -34
- package/dist/access-identity-continuity-surface.d.ts +5 -4
- package/dist/access-identity-continuity-surface.js +27 -25
- package/dist/access-lcm-surface.d.ts +6 -5
- package/dist/access-lcm-surface.js +27 -25
- package/dist/access-mcp.d.ts +6 -5
- package/dist/access-mcp.js +32 -30
- package/dist/access-namespace-preflight.js +27 -25
- package/dist/access-observe-write-surface.d.ts +6 -5
- package/dist/access-observe-write-surface.js +27 -25
- package/dist/access-offline-manifest.d.ts +5 -4
- package/dist/access-offline-manifest.js +4 -4
- package/dist/access-operations-batch.js +29 -27
- package/dist/access-operations.d.ts +6 -5
- package/dist/access-operations.js +31 -29
- package/dist/access-recall-concurrency.d.ts +6 -5
- package/dist/access-recall-concurrency.js +27 -25
- package/dist/access-recall-response.d.ts +6 -5
- package/dist/access-recall-response.js +27 -25
- package/dist/access-recall-surface.d.ts +11 -10
- package/dist/access-recall-surface.js +27 -25
- package/dist/access-schema.d.ts +2 -2
- package/dist/{access-service-DNBYHK33.d.ts → access-service-andNjwfD.d.ts} +3 -3
- package/dist/access-service-helpers.d.ts +6 -5
- package/dist/access-service.d.ts +10 -9
- package/dist/access-service.js +27 -25
- package/dist/access-surface-catalog.d.ts +6 -5
- package/dist/access-wearables-meetings-surface.d.ts +3 -2
- package/dist/adapters/index.js +4 -4
- package/dist/adapters/registry.js +2 -2
- package/dist/artifact-search.d.ts +37 -0
- package/dist/artifact-search.js +13 -0
- package/dist/artifact-search.js.map +1 -0
- package/dist/bootstrap.d.ts +5 -4
- package/dist/briefing.d.ts +2 -1
- package/dist/briefing.js +10 -7
- package/dist/buffer.d.ts +2 -1
- package/dist/buffer.js +4 -4
- package/dist/causal-consolidation.js +11 -8
- package/dist/causal-consolidation.js.map +1 -1
- package/dist/{chunk-T6JRMVOV.js → chunk-2EJNXE53.js} +2 -2
- package/dist/{chunk-SIP5DBDN.js → chunk-2P3GQVM2.js} +152 -130
- package/dist/chunk-2P3GQVM2.js.map +1 -0
- package/dist/{chunk-64BHFFKZ.js → chunk-2RPJ4NEE.js} +2 -2
- package/dist/{chunk-JFO5QZ7N.js → chunk-3JUYWPFJ.js} +2 -2
- package/dist/{chunk-L7G72WY4.js → chunk-3NDN5P55.js} +2 -2
- package/dist/{chunk-IXH2U2QT.js → chunk-45OTV7FZ.js} +19 -19
- package/dist/chunk-4O4TKIXQ.js +171 -0
- package/dist/chunk-4O4TKIXQ.js.map +1 -0
- package/dist/{chunk-BDNL34CT.js → chunk-4WBGZYZZ.js} +33 -8
- package/dist/chunk-4WBGZYZZ.js.map +1 -0
- package/dist/{chunk-U6JGQ37L.js → chunk-52I3YZZ6.js} +3 -3
- package/dist/{chunk-IZQ5TDFT.js → chunk-53AJILXF.js} +3 -3
- package/dist/chunk-5AUY5Z5E.js +34 -0
- package/dist/chunk-5AUY5Z5E.js.map +1 -0
- package/dist/{chunk-YWCOJOUI.js → chunk-5L7PTX2Q.js} +2 -2
- package/dist/{chunk-ELIL2A37.js → chunk-7FGL6CGA.js} +5 -5
- package/dist/{chunk-TVVEYCNW.js → chunk-7K5Q6COX.js} +4 -4
- package/dist/{chunk-HXXTXLKA.js → chunk-A2TFK5PG.js} +2 -2
- package/dist/{chunk-LI7OE7G6.js → chunk-ANCC3JJH.js} +2 -2
- package/dist/{chunk-ILBLZ5Y5.js → chunk-D5NGH2EW.js} +2 -2
- package/dist/{chunk-ST5IEK3H.js → chunk-DUEEQCTT.js} +2 -2
- package/dist/{chunk-QISYTKG4.js → chunk-EZNXW27Q.js} +6 -6
- package/dist/{chunk-47J4EDGR.js → chunk-FJ2ND6CZ.js} +2 -2
- package/dist/{chunk-FQU3HTQM.js → chunk-FMCHFSZF.js} +6 -6
- package/dist/{chunk-FULKGK7P.js → chunk-G2RBWVFQ.js} +2 -2
- package/dist/{chunk-W2ZSNIBP.js → chunk-GD7UPIBG.js} +2 -2
- package/dist/{chunk-Z54EM5YZ.js → chunk-GF22HWS2.js} +4 -4
- package/dist/{chunk-ZDAYWJV6.js → chunk-GVX3NAUU.js} +2 -2
- package/dist/{chunk-M2INOSTA.js → chunk-GWHPQOUC.js} +2 -2
- package/dist/{chunk-N5XULCU3.js → chunk-HIDEBEAN.js} +9 -9
- package/dist/{chunk-74HN5QUH.js → chunk-HRMEYQRO.js} +2 -2
- package/dist/{chunk-VFZS2HAA.js → chunk-IP3DHDU2.js} +2 -2
- package/dist/{chunk-RKNJBZ55.js → chunk-JBPKEARU.js} +4 -4
- package/dist/{chunk-PFJXPOD4.js → chunk-JYPO7L3G.js} +2 -2
- package/dist/{chunk-G2L5IZV4.js → chunk-KSGEGHYA.js} +44 -44
- package/dist/{chunk-PKCLQ7KM.js → chunk-LZGCJSQR.js} +5 -5
- package/dist/{chunk-WOC6DXP5.js → chunk-M4DMO67I.js} +4 -4
- package/dist/{chunk-JCNJIXZN.js → chunk-NXUJRXSF.js} +2 -2
- package/dist/{chunk-5VQPZ4GV.js → chunk-PELT6ADC.js} +3 -3
- package/dist/{chunk-IDE5AR5W.js → chunk-PLCZD3LG.js} +28 -66
- package/dist/chunk-PLCZD3LG.js.map +1 -0
- package/dist/{chunk-KMWYZTIE.js → chunk-PRVJH7CY.js} +2 -2
- package/dist/{chunk-RVO6TCTU.js → chunk-QCECHYZL.js} +6 -6
- package/dist/{chunk-KA47C7BF.js → chunk-RSPSHRZA.js} +2 -2
- package/dist/{chunk-E4FOAENZ.js → chunk-S55OAS5P.js} +5 -5
- package/dist/{chunk-BIPBQQFI.js → chunk-UMOO7IY7.js} +2 -2
- package/dist/{chunk-5U7ID3EZ.js → chunk-UTVZKHJK.js} +2 -2
- package/dist/{chunk-V5FE3UAI.js → chunk-WOEVJKKQ.js} +2 -2
- package/dist/{chunk-N3LL5NEU.js → chunk-WQIHRA5F.js} +2 -2
- package/dist/chunk-Z7L72ZHL.js +75 -0
- package/dist/chunk-Z7L72ZHL.js.map +1 -0
- package/dist/{cli-BWXSPxB9.d.ts → cli-p_WmVo2h.d.ts} +3 -3
- package/dist/cli.d.ts +7 -6
- package/dist/cli.js +55 -53
- package/dist/compounding/engine.d.ts +2 -1
- package/dist/compounding/engine.js +10 -7
- package/dist/connectors/codex-materialize-runner.js +10 -7
- package/dist/connectors/index.js +10 -7
- package/dist/consolidation-provenance-check.d.ts +2 -1
- package/dist/consolidation-undo.d.ts +2 -1
- package/dist/contradiction/index.d.ts +7 -6
- package/dist/convergence-refresh.d.ts +2 -1
- package/dist/convergence-refresh.js +12 -9
- package/dist/corpus-watermark.js +15 -13
- package/dist/entity-recall-cancellation.d.ts +36 -0
- package/dist/entity-recall-cancellation.js +19 -0
- package/dist/entity-recall-cancellation.js.map +1 -0
- package/dist/entity-retrieval.d.ts +14 -6
- package/dist/entity-retrieval.js +11 -7
- package/dist/explicit-capture.d.ts +5 -4
- package/dist/external-wiki-access.d.ts +6 -5
- package/dist/external-wiki-access.js +28 -26
- package/dist/external-wiki-mcp-tools.d.ts +6 -5
- package/dist/extraction.js +15 -13
- package/dist/index.d.ts +11 -10
- package/dist/index.js +114 -112
- package/dist/lcm/engine.js +2 -2
- package/dist/lcm/index.js +7 -7
- package/dist/maintenance/memory-governance.js +10 -7
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.d.ts +7 -6
- package/dist/maintenance/rebuild-memory-lifecycle-ledger.js +10 -7
- package/dist/maintenance/rebuild-memory-projection.d.ts +2 -1
- package/dist/maintenance/rebuild-memory-projection.js +11 -8
- package/dist/{maintenance-DQrL93Uu.d.ts → maintenance-BfcuwfJS.d.ts} +1 -1
- package/dist/mcp-memory-inspector-app.d.ts +6 -5
- package/dist/memory-worth-outcomes.d.ts +2 -1
- package/dist/namespaces/migrate.d.ts +2 -1
- package/dist/namespaces/migrate.js +14 -12
- package/dist/namespaces/search.js +4 -4
- package/dist/namespaces/storage.d.ts +7 -6
- package/dist/namespaces/storage.js +10 -7
- package/dist/offline-sync-impression-drain.js +3 -3
- package/dist/operator-doctor-corpus.js +16 -14
- package/dist/operator-toolkit.d.ts +2 -1
- package/dist/operator-toolkit.js +20 -18
- package/dist/orchestration/compression-guideline-coordinator.d.ts +2 -1
- package/dist/orchestration/maintenance.d.ts +3 -2
- package/dist/orchestration/maintenance.js +14 -12
- package/dist/{orchestrator-CRR-6sVB.d.ts → orchestrator-B7nLo4z1.d.ts} +3 -3
- package/dist/orchestrator.d.ts +5 -4
- package/dist/orchestrator.js +89 -87
- package/dist/qmd.js +4 -4
- package/dist/recall-qos.d.ts +109 -2
- package/dist/recall-qos.js +12 -3
- package/dist/search/factory.js +3 -3
- package/dist/search/index.js +3 -3
- package/dist/semantic-consolidation.js +11 -8
- package/dist/semantic-rule-promotion.js +10 -7
- package/dist/semantic-rule-verifier.js +10 -7
- package/dist/{service-DJ0LDyuN.d.ts → service-B5W5IYCx.d.ts} +1 -1
- package/dist/source-agent-qualifier.js +13 -11
- package/dist/{storage-y340tYKb.d.ts → storage-COL907QF.d.ts} +2 -1
- package/dist/storage.d.ts +2 -1
- package/dist/storage.js +9 -6
- package/dist/temporal-supersession.d.ts +2 -1
- package/dist/tier-migration.d.ts +2 -1
- package/dist/verified-recall.js +10 -7
- package/package.json +3 -3
- package/src/artifact-search.test.ts +110 -0
- package/src/artifact-search.ts +115 -0
- package/src/entity-recall-cancellation.ts +58 -0
- package/src/entity-retrieval.ts +58 -4
- package/src/orchestration/namespace-read-fanout.ts +4 -1
- package/src/orchestration/recall-internal-deps.ts +2 -0
- package/src/orchestration/recall-internal.ts +76 -55
- package/src/orchestration/recall-search-pipeline.ts +9 -1
- package/src/orchestration/recall-search-prefilter.ts +18 -1
- package/src/orchestrator.ts +5 -6
- package/src/recall-qos.test.ts +289 -0
- package/src/recall-qos.ts +238 -1
- package/src/storage.ts +12 -56
- package/dist/chunk-BDNL34CT.js.map +0 -1
- package/dist/chunk-C3AKM55W.js +0 -59
- package/dist/chunk-C3AKM55W.js.map +0 -1
- package/dist/chunk-IDE5AR5W.js.map +0 -1
- package/dist/chunk-SIP5DBDN.js.map +0 -1
- /package/dist/{chunk-T6JRMVOV.js.map → chunk-2EJNXE53.js.map} +0 -0
- /package/dist/{chunk-64BHFFKZ.js.map → chunk-2RPJ4NEE.js.map} +0 -0
- /package/dist/{chunk-JFO5QZ7N.js.map → chunk-3JUYWPFJ.js.map} +0 -0
- /package/dist/{chunk-L7G72WY4.js.map → chunk-3NDN5P55.js.map} +0 -0
- /package/dist/{chunk-IXH2U2QT.js.map → chunk-45OTV7FZ.js.map} +0 -0
- /package/dist/{chunk-U6JGQ37L.js.map → chunk-52I3YZZ6.js.map} +0 -0
- /package/dist/{chunk-IZQ5TDFT.js.map → chunk-53AJILXF.js.map} +0 -0
- /package/dist/{chunk-YWCOJOUI.js.map → chunk-5L7PTX2Q.js.map} +0 -0
- /package/dist/{chunk-ELIL2A37.js.map → chunk-7FGL6CGA.js.map} +0 -0
- /package/dist/{chunk-TVVEYCNW.js.map → chunk-7K5Q6COX.js.map} +0 -0
- /package/dist/{chunk-HXXTXLKA.js.map → chunk-A2TFK5PG.js.map} +0 -0
- /package/dist/{chunk-LI7OE7G6.js.map → chunk-ANCC3JJH.js.map} +0 -0
- /package/dist/{chunk-ILBLZ5Y5.js.map → chunk-D5NGH2EW.js.map} +0 -0
- /package/dist/{chunk-ST5IEK3H.js.map → chunk-DUEEQCTT.js.map} +0 -0
- /package/dist/{chunk-QISYTKG4.js.map → chunk-EZNXW27Q.js.map} +0 -0
- /package/dist/{chunk-47J4EDGR.js.map → chunk-FJ2ND6CZ.js.map} +0 -0
- /package/dist/{chunk-FQU3HTQM.js.map → chunk-FMCHFSZF.js.map} +0 -0
- /package/dist/{chunk-FULKGK7P.js.map → chunk-G2RBWVFQ.js.map} +0 -0
- /package/dist/{chunk-W2ZSNIBP.js.map → chunk-GD7UPIBG.js.map} +0 -0
- /package/dist/{chunk-Z54EM5YZ.js.map → chunk-GF22HWS2.js.map} +0 -0
- /package/dist/{chunk-ZDAYWJV6.js.map → chunk-GVX3NAUU.js.map} +0 -0
- /package/dist/{chunk-M2INOSTA.js.map → chunk-GWHPQOUC.js.map} +0 -0
- /package/dist/{chunk-N5XULCU3.js.map → chunk-HIDEBEAN.js.map} +0 -0
- /package/dist/{chunk-74HN5QUH.js.map → chunk-HRMEYQRO.js.map} +0 -0
- /package/dist/{chunk-VFZS2HAA.js.map → chunk-IP3DHDU2.js.map} +0 -0
- /package/dist/{chunk-RKNJBZ55.js.map → chunk-JBPKEARU.js.map} +0 -0
- /package/dist/{chunk-PFJXPOD4.js.map → chunk-JYPO7L3G.js.map} +0 -0
- /package/dist/{chunk-G2L5IZV4.js.map → chunk-KSGEGHYA.js.map} +0 -0
- /package/dist/{chunk-PKCLQ7KM.js.map → chunk-LZGCJSQR.js.map} +0 -0
- /package/dist/{chunk-WOC6DXP5.js.map → chunk-M4DMO67I.js.map} +0 -0
- /package/dist/{chunk-JCNJIXZN.js.map → chunk-NXUJRXSF.js.map} +0 -0
- /package/dist/{chunk-5VQPZ4GV.js.map → chunk-PELT6ADC.js.map} +0 -0
- /package/dist/{chunk-KMWYZTIE.js.map → chunk-PRVJH7CY.js.map} +0 -0
- /package/dist/{chunk-RVO6TCTU.js.map → chunk-QCECHYZL.js.map} +0 -0
- /package/dist/{chunk-KA47C7BF.js.map → chunk-RSPSHRZA.js.map} +0 -0
- /package/dist/{chunk-E4FOAENZ.js.map → chunk-S55OAS5P.js.map} +0 -0
- /package/dist/{chunk-BIPBQQFI.js.map → chunk-UMOO7IY7.js.map} +0 -0
- /package/dist/{chunk-5U7ID3EZ.js.map → chunk-UTVZKHJK.js.map} +0 -0
- /package/dist/{chunk-V5FE3UAI.js.map → chunk-WOEVJKKQ.js.map} +0 -0
- /package/dist/{chunk-N3LL5NEU.js.map → chunk-WQIHRA5F.js.map} +0 -0
|
@@ -46,7 +46,10 @@ import {
|
|
|
46
46
|
boundRecallContextComposition, composeRecallContext, contextBudgetForFooter,
|
|
47
47
|
formatCuriosityFooter, selectCuriosityQuestion,
|
|
48
48
|
} from "../recall-context-composition.js";
|
|
49
|
-
import {
|
|
49
|
+
import {
|
|
50
|
+
createBoundedCoreSectionRunner,
|
|
51
|
+
createRecallSectionMetricRecorder,
|
|
52
|
+
} from "../recall-qos.js";
|
|
50
53
|
import { buildRecallQueryPolicy } from "../recall-query-policy.js";
|
|
51
54
|
import { type GraphRecallExpandedEntry, type LastRecallSnapshot } from "../recall-state.js";
|
|
52
55
|
import {
|
|
@@ -74,7 +77,7 @@ import { searchTrustZoneRecords } from "../trust-zones.js";
|
|
|
74
77
|
import type { IdentityInjectionMode, MemoryFile, QmdSearchResult, RecallPlanMode } from "../types.js";
|
|
75
78
|
import { type VerifiedEpisodeResult, compareVerifiedEpisodeResults, searchVerifiedEpisodes } from "../verified-recall.js";
|
|
76
79
|
import { type WorkProductLedgerSearchResult, searchWorkProductLedgerEntries } from "../work-product-ledger.js";
|
|
77
|
-
import { abortError } from "../abort-error.js";
|
|
80
|
+
import { abortError, isAbortError, raceAbort } from "../abort-error.js";
|
|
78
81
|
import {
|
|
79
82
|
applyQueryAwareCandidateFilter,
|
|
80
83
|
filterRecallCandidates,
|
|
@@ -147,7 +150,13 @@ export class RecallInternalCoordinator {
|
|
|
147
150
|
profileTraceClosed = true;
|
|
148
151
|
this.deps.profiler.endTrace(profileTraceId); // persists to JSONL file
|
|
149
152
|
};
|
|
153
|
+
// The CONFIGURED core budget, which is what the sections that run outside the
|
|
154
|
+
// bounded runner report in their metrics: nothing enforces a deadline for them,
|
|
155
|
+
// so reporting a derived figure would claim a bound that does not exist. The
|
|
156
|
+
// bounded runner below derives and reports its own effective budget per
|
|
157
|
+
// section, against the request budget still left when that section starts.
|
|
150
158
|
const recallSectionDeadlineMs = this.deps.config.recallCoreDeadlineMs ?? 75_000;
|
|
159
|
+
const recallOuterTimeoutMs = this.deps.config.recallOuterTimeoutMs ?? 75_000;
|
|
151
160
|
const enrichmentSectionDeadlineMs =
|
|
152
161
|
this.deps.config.recallEnrichmentDeadlineMs ?? 25_000;
|
|
153
162
|
// Wrap entire recall body in try/finally so profiling trace is always closed,
|
|
@@ -206,6 +215,14 @@ export class RecallInternalCoordinator {
|
|
|
206
215
|
timings,
|
|
207
216
|
logger: log,
|
|
208
217
|
});
|
|
218
|
+
const runBoundedCoreSection = createBoundedCoreSectionRunner({
|
|
219
|
+
configuredDeadlineMs: recallSectionDeadlineMs,
|
|
220
|
+
outerDeadlineAtMs:
|
|
221
|
+
recallOuterTimeoutMs > 0 ? recallStart + recallOuterTimeoutMs : null,
|
|
222
|
+
parentSignal: options.abortSignal,
|
|
223
|
+
record: recordRecallSectionMetric,
|
|
224
|
+
logger: log,
|
|
225
|
+
});
|
|
209
226
|
const promptHash = createHash("sha256").update(prompt).digest("hex");
|
|
210
227
|
const traceId = createHash("sha256")
|
|
211
228
|
.update(`${sessionKey ?? "default"}:${recallStart}:${promptHash}`)
|
|
@@ -985,41 +1002,48 @@ export class RecallInternalCoordinator {
|
|
|
985
1002
|
});
|
|
986
1003
|
return null;
|
|
987
1004
|
}
|
|
988
|
-
|
|
989
|
-
|
|
990
|
-
|
|
991
|
-
|
|
992
|
-
|
|
993
|
-
|
|
994
|
-
|
|
1005
|
+
return await runBoundedCoreSection(
|
|
1006
|
+
"entityRetrieval",
|
|
1007
|
+
null as string | null,
|
|
1008
|
+
async (sectionSignal) => {
|
|
1009
|
+
// The transcript store is shared and takes no signal of its own, so the
|
|
1010
|
+
// section stops WAITING on a stalled read rather than pretending to
|
|
1011
|
+
// interrupt it — otherwise a cancelled section sits in this await.
|
|
1012
|
+
const transcriptEntries = sessionKey
|
|
1013
|
+
? await raceAbort(
|
|
1014
|
+
readRecentEntityTranscriptEntries(
|
|
1015
|
+
this.deps.transcript.readRecent(
|
|
1016
|
+
entityRecentTranscriptLookbackHours,
|
|
1017
|
+
sessionKey,
|
|
1018
|
+
),
|
|
1019
|
+
recentTurns,
|
|
1020
|
+
),
|
|
1021
|
+
sectionSignal,
|
|
1022
|
+
"entity recall aborted",
|
|
1023
|
+
)
|
|
1024
|
+
: [];
|
|
1025
|
+
return await buildEntityRecallSection({
|
|
1026
|
+
config: this.deps.config,
|
|
1027
|
+
storage: profileStorage,
|
|
1028
|
+
namespaceStorage: (namespace) => this.deps.getStorage(namespace),
|
|
1029
|
+
query: retrievalQuery,
|
|
1030
|
+
recallNamespaces,
|
|
995
1031
|
recentTurns,
|
|
996
|
-
|
|
997
|
-
|
|
998
|
-
|
|
999
|
-
|
|
1000
|
-
|
|
1001
|
-
|
|
1002
|
-
|
|
1003
|
-
|
|
1004
|
-
|
|
1005
|
-
|
|
1006
|
-
|
|
1007
|
-
|
|
1008
|
-
|
|
1009
|
-
|
|
1010
|
-
|
|
1011
|
-
log.warn(`entity retrieval build failed: ${err}`);
|
|
1012
|
-
return null;
|
|
1013
|
-
});
|
|
1014
|
-
recordRecallSectionMetric({
|
|
1015
|
-
section: "entityRetrieval",
|
|
1016
|
-
priority: "core",
|
|
1017
|
-
durationMs: Date.now() - t0,
|
|
1018
|
-
deadlineMs: recallSectionDeadlineMs,
|
|
1019
|
-
source: "fresh",
|
|
1020
|
-
success: true,
|
|
1021
|
-
});
|
|
1022
|
-
return section;
|
|
1032
|
+
maxHints,
|
|
1033
|
+
maxSupportingFacts,
|
|
1034
|
+
maxRelatedEntities,
|
|
1035
|
+
maxChars,
|
|
1036
|
+
transcriptEntries,
|
|
1037
|
+
abortSignal: sectionSignal,
|
|
1038
|
+
}).catch((err) => {
|
|
1039
|
+
// Cancellation is the deadline/abort contract working; let the bounded
|
|
1040
|
+
// runner record it as cancelled rather than as an empty section.
|
|
1041
|
+
if (isAbortError(err)) throw err;
|
|
1042
|
+
log.warn(`entity retrieval build failed: ${err}`);
|
|
1043
|
+
return null;
|
|
1044
|
+
});
|
|
1045
|
+
},
|
|
1046
|
+
);
|
|
1023
1047
|
})();
|
|
1024
1048
|
|
|
1025
1049
|
// 1b. Knowledge Index (v7.0)
|
|
@@ -1130,7 +1154,6 @@ export class RecallInternalCoordinator {
|
|
|
1130
1154
|
)
|
|
1131
1155
|
return [];
|
|
1132
1156
|
if (!resolvePresentationCapabilities(this.deps.config).verbatimArtifacts) return [];
|
|
1133
|
-
const t0 = Date.now();
|
|
1134
1157
|
const targetCount = computeArtifactRecallLimit(
|
|
1135
1158
|
recallMode,
|
|
1136
1159
|
recallResultLimit,
|
|
@@ -1148,25 +1171,23 @@ export class RecallInternalCoordinator {
|
|
|
1148
1171
|
});
|
|
1149
1172
|
return [];
|
|
1150
1173
|
}
|
|
1151
|
-
|
|
1152
|
-
|
|
1153
|
-
|
|
1154
|
-
|
|
1174
|
+
return await runBoundedCoreSection(
|
|
1175
|
+
"artifacts",
|
|
1176
|
+
[] as MemoryFile[],
|
|
1177
|
+
async (sectionSignal) => {
|
|
1178
|
+
const results = await this.deps.recallArtifactsAcrossNamespaces(
|
|
1179
|
+
retrievalQuery,
|
|
1180
|
+
recallNamespaces,
|
|
1181
|
+
targetCount,
|
|
1182
|
+
{ abortSignal: sectionSignal },
|
|
1183
|
+
);
|
|
1184
|
+
return lifecycleCaps.extractionScopeClassification
|
|
1185
|
+
? results.filter((artifact) =>
|
|
1186
|
+
canRecallToolScopedMemory(artifact.frontmatter, options.sourceConnector),
|
|
1187
|
+
)
|
|
1188
|
+
: results;
|
|
1189
|
+
},
|
|
1155
1190
|
);
|
|
1156
|
-
|
|
1157
|
-
recordRecallSectionMetric({
|
|
1158
|
-
section: "artifacts",
|
|
1159
|
-
priority: "core",
|
|
1160
|
-
durationMs: Date.now() - t0,
|
|
1161
|
-
deadlineMs: recallSectionDeadlineMs,
|
|
1162
|
-
source: "fresh",
|
|
1163
|
-
success: true,
|
|
1164
|
-
});
|
|
1165
|
-
return lifecycleCaps.extractionScopeClassification
|
|
1166
|
-
? results.filter((artifact) =>
|
|
1167
|
-
canRecallToolScopedMemory(artifact.frontmatter, options.sourceConnector),
|
|
1168
|
-
)
|
|
1169
|
-
: results;
|
|
1170
1191
|
})();
|
|
1171
1192
|
|
|
1172
1193
|
const objectiveStatePromise = (async (): Promise<string | null> => {
|
|
@@ -40,6 +40,7 @@ import {
|
|
|
40
40
|
fetchActiveArtifactsForNamespace as fetchActiveArtifactsForNamespaceHelper,
|
|
41
41
|
searchEmbeddingFallback as searchEmbeddingFallbackHelper,
|
|
42
42
|
searchQueryAwareFallback as searchQueryAwareFallbackHelper,
|
|
43
|
+
type ArtifactRecallOptions,
|
|
43
44
|
} from "./recall-search-prefilter.js";
|
|
44
45
|
import { shouldFilterSupersededFromRecall } from "../temporal-supersession.js";
|
|
45
46
|
import { isValidAsOf, isValidityExpiredNow } from "../temporal-validity.js";
|
|
@@ -251,8 +252,15 @@ export class RecallSearchPipelineCoordinator {
|
|
|
251
252
|
namespace: string,
|
|
252
253
|
prompt: string,
|
|
253
254
|
targetCount: number,
|
|
255
|
+
options: ArtifactRecallOptions = {},
|
|
254
256
|
): Promise<MemoryFile[]> {
|
|
255
|
-
return fetchActiveArtifactsForNamespaceHelper(
|
|
257
|
+
return fetchActiveArtifactsForNamespaceHelper(
|
|
258
|
+
this.deps,
|
|
259
|
+
namespace,
|
|
260
|
+
prompt,
|
|
261
|
+
targetCount,
|
|
262
|
+
options,
|
|
263
|
+
);
|
|
256
264
|
}
|
|
257
265
|
|
|
258
266
|
async buildQueryAwarePrefilter(
|
|
@@ -42,11 +42,22 @@ export interface PrefilterAndArtifactDeps {
|
|
|
42
42
|
): Promise<QmdSearchResult[]>;
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
/**
|
|
46
|
+
* Cancellation for the artifact recall path (issue #2291). Artifact recall
|
|
47
|
+
* re-scans and re-tokenizes every stored artifact, up to `MAX_ATTEMPTS` times
|
|
48
|
+
* with a growing fetch limit; on a large or slow memory tree that is minutes of
|
|
49
|
+
* work that used to keep running after the caller had already given up.
|
|
50
|
+
*/
|
|
51
|
+
export interface ArtifactRecallOptions {
|
|
52
|
+
abortSignal?: AbortSignal;
|
|
53
|
+
}
|
|
54
|
+
|
|
45
55
|
export async function fetchActiveArtifactsForNamespace(
|
|
46
56
|
deps: Pick<PrefilterAndArtifactDeps, "storageRouter" | "resolveArtifactSourceStatuses">,
|
|
47
57
|
namespace: string,
|
|
48
58
|
prompt: string,
|
|
49
59
|
targetCount: number,
|
|
60
|
+
options: ArtifactRecallOptions = {},
|
|
50
61
|
): Promise<MemoryFile[]> {
|
|
51
62
|
const storage = await deps.storageRouter.storageFor(namespace);
|
|
52
63
|
let fetchLimit = computeArtifactCandidateFetchLimit(targetCount);
|
|
@@ -55,7 +66,10 @@ export async function fetchActiveArtifactsForNamespace(
|
|
|
55
66
|
let bestFiltered: MemoryFile[] = [];
|
|
56
67
|
|
|
57
68
|
for (let attempt = 0; attempt < MAX_ATTEMPTS; attempt += 1) {
|
|
58
|
-
|
|
69
|
+
throwIfRecallAborted(options.abortSignal);
|
|
70
|
+
const rawResults = await storage.searchArtifacts(prompt, fetchLimit, {
|
|
71
|
+
abortSignal: options.abortSignal,
|
|
72
|
+
});
|
|
59
73
|
const sourceIds = Array.from(
|
|
60
74
|
new Set(
|
|
61
75
|
rawResults
|
|
@@ -65,6 +79,9 @@ export async function fetchActiveArtifactsForNamespace(
|
|
|
65
79
|
),
|
|
66
80
|
),
|
|
67
81
|
);
|
|
82
|
+
// The status snapshot rebuild reads every memory in the namespace; do not
|
|
83
|
+
// start one for a recall that has already been abandoned.
|
|
84
|
+
throwIfRecallAborted(options.abortSignal);
|
|
68
85
|
const sourceStatus =
|
|
69
86
|
sourceIds.length > 0
|
|
70
87
|
? await deps.resolveArtifactSourceStatuses(storage, sourceIds)
|
package/src/orchestrator.ts
CHANGED
|
@@ -120,6 +120,7 @@ import { ConsolidationRunCoordinator } from "./orchestration/consolidation-run.j
|
|
|
120
120
|
import { ExtractionPersistCoordinator } from "./orchestration/extraction-persist.js";
|
|
121
121
|
import { RecallInternalCoordinator } from "./orchestration/recall-internal.js";
|
|
122
122
|
import { RecallSearchPipelineCoordinator } from "./orchestration/recall-search-pipeline.js";
|
|
123
|
+
import type { ArtifactRecallOptions } from "./orchestration/recall-search-prefilter.js";
|
|
123
124
|
import { TurnIngestionCoordinator, type TurnIngestionOptions } from "./orchestration/turn-ingestion.js";
|
|
124
125
|
import { RecallIntrospectionCoordinator } from "./orchestration/recall-introspection.js";
|
|
125
126
|
import { OrchestratorInitCoordinator } from "./orchestration/orchestrator-init.js";
|
|
@@ -2460,11 +2461,10 @@ export class Orchestrator {
|
|
|
2460
2461
|
namespace: string,
|
|
2461
2462
|
prompt: string,
|
|
2462
2463
|
targetCount: number,
|
|
2464
|
+
options: ArtifactRecallOptions = {},
|
|
2463
2465
|
): Promise<MemoryFile[]> {
|
|
2464
2466
|
return this.recallSearchPipelineCoordinator.fetchActiveArtifactsForNamespace(
|
|
2465
|
-
namespace,
|
|
2466
|
-
prompt,
|
|
2467
|
-
targetCount,
|
|
2467
|
+
namespace, prompt, targetCount, options,
|
|
2468
2468
|
);
|
|
2469
2469
|
}
|
|
2470
2470
|
|
|
@@ -2472,11 +2472,10 @@ export class Orchestrator {
|
|
|
2472
2472
|
prompt: string,
|
|
2473
2473
|
recallNamespaces: string[],
|
|
2474
2474
|
targetCount: number,
|
|
2475
|
+
options: ArtifactRecallOptions = {},
|
|
2475
2476
|
): Promise<MemoryFile[]> {
|
|
2476
2477
|
return this.namespaceReadFanoutCoordinator.recallArtifactsAcrossNamespaces(
|
|
2477
|
-
prompt,
|
|
2478
|
-
recallNamespaces,
|
|
2479
|
-
targetCount,
|
|
2478
|
+
prompt, recallNamespaces, targetCount, options,
|
|
2480
2479
|
);
|
|
2481
2480
|
}
|
|
2482
2481
|
|
package/src/recall-qos.test.ts
CHANGED
|
@@ -1,7 +1,12 @@
|
|
|
1
1
|
import test from "node:test";
|
|
2
2
|
import assert from "node:assert/strict";
|
|
3
|
+
import { throwIfAborted } from "./abort-error.js";
|
|
3
4
|
import {
|
|
5
|
+
createBoundedCoreSectionRunner,
|
|
4
6
|
createRecallSectionMetricRecorder,
|
|
7
|
+
resolveRecallCoreSectionDeadlineMs,
|
|
8
|
+
runRecallSectionWithinDeadline,
|
|
9
|
+
yieldToEventLoop,
|
|
5
10
|
type RecallSectionMetric,
|
|
6
11
|
} from "./recall-qos.js";
|
|
7
12
|
|
|
@@ -83,3 +88,287 @@ test("recall section metric recorder respects timing overrides and logs enrichme
|
|
|
83
88
|
success: true,
|
|
84
89
|
});
|
|
85
90
|
});
|
|
91
|
+
|
|
92
|
+
test("timed-out section metric reports the breach instead of a plain duration", () => {
|
|
93
|
+
const timings: Record<string, string> = {};
|
|
94
|
+
const calls: Array<{ level: "info" | "debug"; payload: unknown[] }> = [];
|
|
95
|
+
const recorder = createRecallSectionMetricRecorder({
|
|
96
|
+
timings,
|
|
97
|
+
logger: {
|
|
98
|
+
info: (_message: string, ...payload: unknown[]) => calls.push({ level: "info", payload }),
|
|
99
|
+
debug: (_message: string, ...payload: unknown[]) => calls.push({ level: "debug", payload }),
|
|
100
|
+
},
|
|
101
|
+
});
|
|
102
|
+
|
|
103
|
+
recorder({
|
|
104
|
+
section: "entityRetrieval",
|
|
105
|
+
priority: "core",
|
|
106
|
+
durationMs: 75_004,
|
|
107
|
+
deadlineMs: 75_000,
|
|
108
|
+
source: "timeout",
|
|
109
|
+
success: false,
|
|
110
|
+
});
|
|
111
|
+
|
|
112
|
+
assert.equal(timings.entityRetrieval, "timeout(75004ms)");
|
|
113
|
+
// A breach is not a success, so it must not be logged as one.
|
|
114
|
+
assert.equal(calls[0]?.level, "debug");
|
|
115
|
+
});
|
|
116
|
+
|
|
117
|
+
test("recall section deadline degrades to the fallback and cancels the section", async () => {
|
|
118
|
+
let observedSignal: AbortSignal | undefined;
|
|
119
|
+
let cancelledDuringWork = false;
|
|
120
|
+
|
|
121
|
+
// A real timer is the subject here, not a guessed wait: the section below
|
|
122
|
+
// never settles on its own, so the deadline always wins regardless of load.
|
|
123
|
+
const outcome = await runRecallSectionWithinDeadline({
|
|
124
|
+
deadlineMs: 15,
|
|
125
|
+
fallback: "degraded",
|
|
126
|
+
run: async (sectionSignal) => {
|
|
127
|
+
observedSignal = sectionSignal;
|
|
128
|
+
const cancelled = Promise.withResolvers<void>();
|
|
129
|
+
sectionSignal.addEventListener("abort", () => {
|
|
130
|
+
cancelledDuringWork = true;
|
|
131
|
+
cancelled.resolve();
|
|
132
|
+
});
|
|
133
|
+
await cancelled.promise;
|
|
134
|
+
return "late value";
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
|
|
138
|
+
assert.equal(outcome.timedOut, true);
|
|
139
|
+
assert.equal(outcome.value, "degraded");
|
|
140
|
+
assert.equal(observedSignal?.aborted, true);
|
|
141
|
+
assert.equal(cancelledDuringWork, true);
|
|
142
|
+
});
|
|
143
|
+
|
|
144
|
+
test("recall section deadline returns the real value when the section finishes in time", async () => {
|
|
145
|
+
const outcome = await runRecallSectionWithinDeadline({
|
|
146
|
+
deadlineMs: 5_000,
|
|
147
|
+
fallback: null as string | null,
|
|
148
|
+
run: async (sectionSignal) => {
|
|
149
|
+
assert.equal(sectionSignal.aborted, false);
|
|
150
|
+
return "section body";
|
|
151
|
+
},
|
|
152
|
+
});
|
|
153
|
+
|
|
154
|
+
assert.equal(outcome.timedOut, false);
|
|
155
|
+
assert.equal(outcome.value, "section body");
|
|
156
|
+
});
|
|
157
|
+
|
|
158
|
+
test("a zero deadline disables the bound rather than failing every section", async () => {
|
|
159
|
+
const released = Promise.withResolvers<void>();
|
|
160
|
+
const pending = runRecallSectionWithinDeadline({
|
|
161
|
+
deadlineMs: 0,
|
|
162
|
+
fallback: "degraded",
|
|
163
|
+
run: async () => {
|
|
164
|
+
await released.promise;
|
|
165
|
+
return "unbounded value";
|
|
166
|
+
},
|
|
167
|
+
});
|
|
168
|
+
|
|
169
|
+
// Two full event-loop turns: any 0ms timer would already have fired, so a
|
|
170
|
+
// still-pending section proves the bound was disabled rather than instant.
|
|
171
|
+
for (let turn = 0; turn < 2; turn += 1) {
|
|
172
|
+
const turned = Promise.withResolvers<void>();
|
|
173
|
+
setImmediate(turned.resolve);
|
|
174
|
+
await turned.promise;
|
|
175
|
+
}
|
|
176
|
+
released.resolve();
|
|
177
|
+
|
|
178
|
+
const outcome = await pending;
|
|
179
|
+
assert.equal(outcome.timedOut, false);
|
|
180
|
+
assert.equal(outcome.value, "unbounded value");
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
test("a caller abort propagates into the section signal", async () => {
|
|
184
|
+
const caller = new AbortController();
|
|
185
|
+
let cancelledDuringWork = false;
|
|
186
|
+
|
|
187
|
+
const pending = runRecallSectionWithinDeadline({
|
|
188
|
+
deadlineMs: 5_000,
|
|
189
|
+
fallback: "degraded",
|
|
190
|
+
parentSignal: caller.signal,
|
|
191
|
+
run: async (sectionSignal) => {
|
|
192
|
+
const cancelled = Promise.withResolvers<void>();
|
|
193
|
+
sectionSignal.addEventListener("abort", () => {
|
|
194
|
+
cancelledDuringWork = true;
|
|
195
|
+
cancelled.resolve();
|
|
196
|
+
});
|
|
197
|
+
await cancelled.promise;
|
|
198
|
+
return "cancelled";
|
|
199
|
+
},
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
caller.abort();
|
|
203
|
+
const outcome = await pending;
|
|
204
|
+
|
|
205
|
+
assert.equal(cancelledDuringWork, true);
|
|
206
|
+
assert.equal(outcome.timedOut, false);
|
|
207
|
+
assert.equal(outcome.value, "cancelled");
|
|
208
|
+
});
|
|
209
|
+
|
|
210
|
+
test("an already-aborted caller signal cancels the section before it does work", async () => {
|
|
211
|
+
const caller = new AbortController();
|
|
212
|
+
caller.abort();
|
|
213
|
+
|
|
214
|
+
const outcome = await runRecallSectionWithinDeadline({
|
|
215
|
+
deadlineMs: 5_000,
|
|
216
|
+
fallback: "degraded",
|
|
217
|
+
parentSignal: caller.signal,
|
|
218
|
+
run: async (sectionSignal) => {
|
|
219
|
+
assert.equal(sectionSignal.aborted, true);
|
|
220
|
+
return "observed";
|
|
221
|
+
},
|
|
222
|
+
});
|
|
223
|
+
|
|
224
|
+
assert.equal(outcome.value, "observed");
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
test("a section rejection surfaces to the caller instead of being swallowed", async () => {
|
|
228
|
+
await assert.rejects(
|
|
229
|
+
runRecallSectionWithinDeadline({
|
|
230
|
+
deadlineMs: 5_000,
|
|
231
|
+
fallback: "degraded",
|
|
232
|
+
run: async () => {
|
|
233
|
+
throw new Error("section exploded");
|
|
234
|
+
},
|
|
235
|
+
}),
|
|
236
|
+
/section exploded/,
|
|
237
|
+
);
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
test("the default core section budget is capped below the remaining request budget", () => {
|
|
241
|
+
// Both settings default to 75s. Taken at face value the section deadline could
|
|
242
|
+
// never fire first, so degradation would be unreachable on default config.
|
|
243
|
+
assert.equal(
|
|
244
|
+
resolveRecallCoreSectionDeadlineMs({
|
|
245
|
+
configuredCoreDeadlineMs: 75_000,
|
|
246
|
+
remainingOuterMs: 75_000,
|
|
247
|
+
}),
|
|
248
|
+
60_000,
|
|
249
|
+
);
|
|
250
|
+
});
|
|
251
|
+
|
|
252
|
+
test("the core section budget shrinks with the request budget already spent", () => {
|
|
253
|
+
// A section that starts 20s into a 75s request must finish inside the 55s left,
|
|
254
|
+
// not inside the original ceiling — otherwise the request is aborted wholesale.
|
|
255
|
+
assert.equal(
|
|
256
|
+
resolveRecallCoreSectionDeadlineMs({
|
|
257
|
+
configuredCoreDeadlineMs: 75_000,
|
|
258
|
+
remainingOuterMs: 55_000,
|
|
259
|
+
}),
|
|
260
|
+
44_000,
|
|
261
|
+
);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
test("a request already over budget degrades its sections immediately", () => {
|
|
265
|
+
assert.equal(
|
|
266
|
+
resolveRecallCoreSectionDeadlineMs({
|
|
267
|
+
configuredCoreDeadlineMs: 75_000,
|
|
268
|
+
remainingOuterMs: 0,
|
|
269
|
+
}),
|
|
270
|
+
1,
|
|
271
|
+
);
|
|
272
|
+
assert.equal(
|
|
273
|
+
resolveRecallCoreSectionDeadlineMs({
|
|
274
|
+
configuredCoreDeadlineMs: 75_000,
|
|
275
|
+
remainingOuterMs: -5_000,
|
|
276
|
+
}),
|
|
277
|
+
1,
|
|
278
|
+
);
|
|
279
|
+
});
|
|
280
|
+
|
|
281
|
+
test("an explicitly lowered core section budget is honored exactly", () => {
|
|
282
|
+
assert.equal(
|
|
283
|
+
resolveRecallCoreSectionDeadlineMs({
|
|
284
|
+
configuredCoreDeadlineMs: 5_000,
|
|
285
|
+
remainingOuterMs: 75_000,
|
|
286
|
+
}),
|
|
287
|
+
5_000,
|
|
288
|
+
);
|
|
289
|
+
});
|
|
290
|
+
|
|
291
|
+
test("a zero budget on either side keeps the configured value", () => {
|
|
292
|
+
assert.equal(
|
|
293
|
+
resolveRecallCoreSectionDeadlineMs({ configuredCoreDeadlineMs: 0, remainingOuterMs: 75_000 }),
|
|
294
|
+
0,
|
|
295
|
+
);
|
|
296
|
+
// No request ceiling means nothing to reserve headroom against.
|
|
297
|
+
assert.equal(
|
|
298
|
+
resolveRecallCoreSectionDeadlineMs({ configuredCoreDeadlineMs: 75_000, remainingOuterMs: null }),
|
|
299
|
+
75_000,
|
|
300
|
+
);
|
|
301
|
+
});
|
|
302
|
+
|
|
303
|
+
test("a bounded core section resolves its budget from the budget left when it starts", async () => {
|
|
304
|
+
const metrics: RecallSectionMetric[] = [];
|
|
305
|
+
const clockMs = 1_000_000;
|
|
306
|
+
const runSection = createBoundedCoreSectionRunner({
|
|
307
|
+
configuredDeadlineMs: 75_000,
|
|
308
|
+
// 75s request that started 20s ago: 55s left, so the section gets 80% of 55s.
|
|
309
|
+
outerDeadlineAtMs: clockMs + 55_000,
|
|
310
|
+
record: (metric) => metrics.push(metric),
|
|
311
|
+
logger: { warn: () => {} },
|
|
312
|
+
now: () => clockMs,
|
|
313
|
+
});
|
|
314
|
+
|
|
315
|
+
await runSection("entityRetrieval", null as string | null, async () => "section body");
|
|
316
|
+
|
|
317
|
+
assert.equal(metrics[0]?.deadlineMs, 44_000);
|
|
318
|
+
});
|
|
319
|
+
|
|
320
|
+
test("a bounded core section degrades on cancellation rather than rejecting", async () => {
|
|
321
|
+
// The phase this section belongs to is awaited through a race the caller's
|
|
322
|
+
// abort also wins, so a rejection here would be an unhandled rejection.
|
|
323
|
+
const metrics: RecallSectionMetric[] = [];
|
|
324
|
+
const caller = new AbortController();
|
|
325
|
+
const runSection = createBoundedCoreSectionRunner({
|
|
326
|
+
configuredDeadlineMs: 5_000,
|
|
327
|
+
outerDeadlineAtMs: null,
|
|
328
|
+
parentSignal: caller.signal,
|
|
329
|
+
record: (metric) => metrics.push(metric),
|
|
330
|
+
logger: { warn: () => {} },
|
|
331
|
+
});
|
|
332
|
+
|
|
333
|
+
caller.abort();
|
|
334
|
+
const value = await runSection("artifacts", [] as string[], async (sectionSignal) => {
|
|
335
|
+
throwIfAborted(sectionSignal, "artifact search aborted");
|
|
336
|
+
return ["should not be reached"];
|
|
337
|
+
});
|
|
338
|
+
|
|
339
|
+
assert.deepEqual(value, []);
|
|
340
|
+
assert.equal(metrics[0]?.section, "artifacts");
|
|
341
|
+
assert.equal(metrics[0]?.success, false);
|
|
342
|
+
assert.match(metrics[0]?.timing ?? "", /^cancelled\(\d+ms\)$/);
|
|
343
|
+
});
|
|
344
|
+
|
|
345
|
+
test("a bounded core section still surfaces a non-abort failure", async () => {
|
|
346
|
+
const runSection = createBoundedCoreSectionRunner({
|
|
347
|
+
configuredDeadlineMs: 5_000,
|
|
348
|
+
outerDeadlineAtMs: null,
|
|
349
|
+
record: () => {},
|
|
350
|
+
logger: { warn: () => {} },
|
|
351
|
+
});
|
|
352
|
+
|
|
353
|
+
await assert.rejects(
|
|
354
|
+
runSection("artifacts", [] as string[], async () => {
|
|
355
|
+
throw new Error("artifact tier unreadable");
|
|
356
|
+
}),
|
|
357
|
+
/artifact tier unreadable/,
|
|
358
|
+
);
|
|
359
|
+
});
|
|
360
|
+
|
|
361
|
+
test("yieldToEventLoop lets a pending timer run", async () => {
|
|
362
|
+
// The guarantee corpus scans depend on: without a yield the timer that carries
|
|
363
|
+
// a section deadline cannot fire while the scan holds the loop.
|
|
364
|
+
let timerFired = false;
|
|
365
|
+
const timer = setTimeout(() => {
|
|
366
|
+
timerFired = true;
|
|
367
|
+
}, 0);
|
|
368
|
+
|
|
369
|
+
await yieldToEventLoop();
|
|
370
|
+
await yieldToEventLoop();
|
|
371
|
+
clearTimeout(timer);
|
|
372
|
+
|
|
373
|
+
assert.equal(timerFired, true);
|
|
374
|
+
});
|