@remnic/core 10.0.53 → 10.0.54
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 +2 -2
- package/dist/{chunk-CA6X37OB.js → chunk-2GU6XS4L.js} +3 -3
- package/dist/chunk-2GU6XS4L.js.map +1 -0
- package/dist/{chunk-H4DWSOBQ.js → chunk-PVG7TCUG.js} +31 -14
- package/dist/chunk-PVG7TCUG.js.map +1 -0
- package/dist/index.js +2 -2
- package/dist/orchestration/maintenance.d.ts +1 -1
- package/dist/orchestration/maintenance.d.ts.map +1 -1
- package/dist/orchestration/maintenance.js +1 -1
- package/dist/orchestration/qmd-maintenance-completion.d.ts +9 -4
- package/dist/orchestration/qmd-maintenance-completion.d.ts.map +1 -1
- package/dist/orchestrator.js +2 -2
- package/package.json +2 -2
- package/src/orchestration/maintenance.test.ts +139 -0
- package/src/orchestration/maintenance.ts +17 -8
- package/src/orchestration/qmd-maintenance-completion.ts +21 -9
- package/src/orchestrator.ts +1 -1
- package/dist/chunk-CA6X37OB.js.map +0 -1
- package/dist/chunk-H4DWSOBQ.js.map +0 -1
package/dist/index.js
CHANGED
|
@@ -181,7 +181,7 @@ import {
|
|
|
181
181
|
writeManifest,
|
|
182
182
|
writeMeetingEpisodeMemory,
|
|
183
183
|
writeTimelineState
|
|
184
|
-
} from "./chunk-
|
|
184
|
+
} from "./chunk-2GU6XS4L.js";
|
|
185
185
|
import {
|
|
186
186
|
WEARABLE_SOURCE_PREFIX,
|
|
187
187
|
buildExtractionTurns,
|
|
@@ -247,7 +247,7 @@ import {
|
|
|
247
247
|
timelineAnalysisClientsFromConfig,
|
|
248
248
|
timelineCorrectionDatabasePath,
|
|
249
249
|
timelineDayPath
|
|
250
|
-
} from "./chunk-
|
|
250
|
+
} from "./chunk-PVG7TCUG.js";
|
|
251
251
|
import "./chunk-I74SUMNI.js";
|
|
252
252
|
import {
|
|
253
253
|
PEERS_DIR_NAME,
|
|
@@ -130,7 +130,7 @@ export declare class MaintenanceScheduler {
|
|
|
130
130
|
* unavailable or maintenance off), the pass failed, or dispose() abandoned it,
|
|
131
131
|
* so a caller that must not lose the re-index keeps owing it.
|
|
132
132
|
*/
|
|
133
|
-
requestQmdMaintenanceCompletion(): Promise<boolean>;
|
|
133
|
+
requestQmdMaintenanceCompletion(namespace?: string): Promise<boolean>;
|
|
134
134
|
/**
|
|
135
135
|
* Public entrypoint for tool-driven QMD maintenance requests.
|
|
136
136
|
* Routes through existing debounced/singleflight maintenance controls.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"maintenance.d.ts","sourceRoot":"","sources":["../../src/orchestration/maintenance.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;GAmBG;AAQH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AAChD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,mBAAmB,CAAC;AACvD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAClD,OAAO,EACL,qBAAqB,EAEtB,MAAM,yBAAyB,CAAC;AACjC,OAAO,KAAK,EAAE,gBAAgB,EAAmB,MAAM,0BAA0B,CAAC;AA+BlF,OAAO,KAAK,EAAE,sBAAsB,EAAE,MAAM,uBAAuB,CAAC;AAoBpE,wEAAwE;AACxE,MAAM,WAAW,wBAAwB;IACvC,MAAM,EAAE,YAAY,CAAC;IACrB;;+EAE2E;IAC3E,MAAM,EAAE,MAAM,aAAa,CAAC;IAC5B,qBAAqB,EAAE,qBAAqB,CAAC;IAC7C,gBAAgB,EAAE,gBAAgB,CAAC;IACnC;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,cAAc,CAAC;IAClC;;;;;OAKG;IACH,mBAAmB,CAAC,EAAE,CAAC,SAAS,EAAE,MAAM,KAAK,OAAO,CAAC,cAAc,CAAC,CAAC;IACrE;;;;OAIG;IACH,gBAAgB,CAAC,EAAE,CAAC,OAAO,EAAE,sBAAsB,KAAK,IAAI,CAAC;CAC9D;AAED;;;;;;;GAOG;AACH,qBAAa,oBAAoB;IAuCnB,OAAO,CAAC,QAAQ,CAAC,IAAI;IArCjC,OAAO,CAAC,oCAAoC,CAAK;IACjD,OAAO,CAAC,wBAAwB,CAAK;IACrC,OAAO,CAAC,uBAAuB,CAAiC;IAChE,OAAO,CAAC,QAAQ,CAAS;IAGzB,OAAO,CAAC,mBAAmB,CAA+B;IAC1D,OAAO,CAAC,qBAAqB,CAAS;IACtC,OAAO,CAAC,sBAAsB,CAAS;IACvC,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAkC;IAChE,oFAAoF;IACpF,OAAO,CAAC,QAAQ,CAAC,oBAAoB,CAAqB;IAC1D,OAAO,CAAC,gBAAgB,CAAK;IAC7B,OAAO,CAAC,2BAA2B,CAA6B;IAGhE,OAAO,CAAC,2BAA2B,CAAS;IAC5C,OAAO,CAAC,2BAA2B,CAAK;IAMxC,OAAO,CAAC,uBAAuB,CAAgC;IAG/D,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAA0C;IAI5E,OAAO,CAAC,QAAQ,CAAC,iBAAiB,CAAwB;IAG1D,OAAO,CAAC,gBAAgB,CAAuC;IAC/D,OAAO,CAAC,iBAAiB,CAAiD;gBAG7C,IAAI,EAAE,wBAAwB;IAiB3D;;;;;;;;OAQG;IACG,iBAAiB,CAAC,MAAM,EAAE,WAAW,GAAG,OAAO,CAAC,IAAI,CAAC;IA8D3D,wBAAwB,IAAI,OAAO;IAE7B,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC;IAqC3C,iCAAiC,IAAI,OAAO,CAAC,IAAI,CAAC;IAyBlD,gCAAgC,IAAI,OAAO,CAAC,IAAI,CAAC;IAqBjD,iCAAiC,IAAI,OAAO,CAAC,IAAI,CAAC;IAqBlD,oCAAoC,IAAI,OAAO,CAAC,IAAI,CAAC;IAqBrD,8BAA8B,IAAI,OAAO,CAAC,IAAI,CAAC;IA6BrD;;;;;;OAMG;IACH,0BAA0B,CACxB,iBAAiB,EAAE,OAAO,EAC1B,gBAAgB,EAAE,MAAM,OAAO,CAAC,OAAO,CAAC,GACvC,IAAI;IAoCP;;sDAEkD;IAClD,uBAAuB,IAAI,OAAO;IAIlC;;;gDAG4C;IAC5C,uBAAuB,IAAI,OAAO;IAIlC;gFAC4E;IAC5E,wBAAwB,IAAI,OAAO;IAOnC,2FAA2F;IAC3F,OAAO,CAAC,qBAAqB;IAgB7B;;;;;OAKG;IACH,+BAA+B,
|
|
1
|
+
{"version":3,"file":"maintenance.d.ts","sourceRoot":"","sources":["../../src/orchestration/maintenance.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,13 +7,18 @@
|
|
|
7
7
|
*/
|
|
8
8
|
export declare class QmdMaintenanceCompletion {
|
|
9
9
|
private waiters;
|
|
10
|
-
/**
|
|
11
|
-
|
|
10
|
+
/**
|
|
11
|
+
* Resolves true when a pass that started after this call completed successfully. With a
|
|
12
|
+
* `namespace` the request is served as soon as a pass ran that namespace, whatever else it skipped.
|
|
13
|
+
*/
|
|
14
|
+
wait(namespace?: string): Promise<boolean>;
|
|
12
15
|
/**
|
|
13
16
|
* A pass is starting: take every waiter it will serve and return their settle function.
|
|
14
|
-
* `
|
|
17
|
+
* `ran` lists the namespaces a namespace-aware pass ran (absent when namespaces are disabled:
|
|
18
|
+
* one corpus is maintained and the pass outcome applies to every waiter); `defer` keeps the
|
|
19
|
+
* waiters it did not serve waiting for a later pass (a pass that covered only part of the selection).
|
|
15
20
|
*/
|
|
16
|
-
claim(): (served: boolean, defer?: boolean) => void;
|
|
21
|
+
claim(): (served: boolean, defer?: boolean, ran?: ReadonlySet<string>) => void;
|
|
17
22
|
/** The scheduler stopped: nothing will serve the waiters. */
|
|
18
23
|
abandon(): void;
|
|
19
24
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"qmd-maintenance-completion.d.ts","sourceRoot":"","sources":["../../src/orchestration/qmd-maintenance-completion.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,qBAAa,wBAAwB;IACnC,OAAO,CAAC,OAAO,
|
|
1
|
+
{"version":3,"file":"qmd-maintenance-completion.d.ts","sourceRoot":"","sources":["../../src/orchestration/qmd-maintenance-completion.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AACH,qBAAa,wBAAwB;IACnC,OAAO,CAAC,OAAO,CAAyE;IAExF;;;OAGG;IACH,IAAI,CAAC,SAAS,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,OAAO,CAAC;IAI1C;;;;;OAKG;IACH,KAAK,IAAI,CAAC,MAAM,EAAE,OAAO,EAAE,KAAK,CAAC,EAAE,OAAO,EAAE,GAAG,CAAC,EAAE,WAAW,CAAC,MAAM,CAAC,KAAK,IAAI;IAe9E,6DAA6D;IAC7D,OAAO,IAAI,IAAI;CAGhB"}
|
package/dist/orchestrator.js
CHANGED
|
@@ -4,13 +4,13 @@ import {
|
|
|
4
4
|
graphPathRelativeToStorage,
|
|
5
5
|
mergeGraphExpandedResults,
|
|
6
6
|
qmdStartupCollectionChecksWithTimeout
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-2GU6XS4L.js";
|
|
8
8
|
import "./chunk-YDIOMDBW.js";
|
|
9
9
|
import "./chunk-HM3ZTDAX.js";
|
|
10
10
|
import {
|
|
11
11
|
formatCompressionGuidelinesForRecall
|
|
12
12
|
} from "./chunk-5PHOU5QI.js";
|
|
13
|
-
import "./chunk-
|
|
13
|
+
import "./chunk-PVG7TCUG.js";
|
|
14
14
|
import "./chunk-I74SUMNI.js";
|
|
15
15
|
import "./chunk-ILNPU5W5.js";
|
|
16
16
|
import "./chunk-VZAKYDMJ.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/core",
|
|
3
|
-
"version": "10.0.
|
|
3
|
+
"version": "10.0.54",
|
|
4
4
|
"description": "Framework-agnostic Remnic memory engine — orchestrator, storage, extraction, search, trust zones",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
@@ -3346,7 +3346,7 @@
|
|
|
3346
3346
|
"core"
|
|
3347
3347
|
],
|
|
3348
3348
|
"peerDependencies": {
|
|
3349
|
-
"@remnic/coding-graph": "^10.0.
|
|
3349
|
+
"@remnic/coding-graph": "^10.0.54"
|
|
3350
3350
|
},
|
|
3351
3351
|
"peerDependenciesMeta": {
|
|
3352
3352
|
"@remnic/coding-graph": {
|
|
@@ -3077,6 +3077,66 @@ test("requestQmdMaintenanceCompletion resolves false when dispose() clears the a
|
|
|
3077
3077
|
assert.equal(await served, false, "a destroy inside the debounce window must leave the re-index owed");
|
|
3078
3078
|
});
|
|
3079
3079
|
|
|
3080
|
+
test("requestQmdMaintenanceCompletion over namespaces: a scoped request is served only when its own namespace ran, an unscoped one by the pass outcome", async () => {
|
|
3081
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-completion-permanent-"));
|
|
3082
|
+
const config = fixtureConfig({
|
|
3083
|
+
memoryDir,
|
|
3084
|
+
namespacesEnabled: true,
|
|
3085
|
+
defaultNamespace: "default",
|
|
3086
|
+
sharedNamespace: "shared",
|
|
3087
|
+
namespacePolicies: [],
|
|
3088
|
+
maintenanceNamespaceLockStaleMs: 100,
|
|
3089
|
+
qmdAutoEmbedEnabled: false,
|
|
3090
|
+
});
|
|
3091
|
+
const catalog: NamespaceCatalog = Object.assign(Object.create(null), {
|
|
3092
|
+
enabled: true,
|
|
3093
|
+
async listNamespaces() {
|
|
3094
|
+
return [{ namespace: "feature-x", kind: "branch" }];
|
|
3095
|
+
},
|
|
3096
|
+
async markMaintenance() {},
|
|
3097
|
+
});
|
|
3098
|
+
const router: NamespaceSearchRouter = Object.assign(Object.create(null), {
|
|
3099
|
+
async updateNamespacesDetailed(ns: string[]): Promise<NamespaceUpdateResult> {
|
|
3100
|
+
return { backendCount: ns.length, eligibleNamespaces: ns };
|
|
3101
|
+
},
|
|
3102
|
+
async embedNamespaces(): Promise<void> {},
|
|
3103
|
+
});
|
|
3104
|
+
const scheduler = buildScheduler({ config, catalog, router });
|
|
3105
|
+
try {
|
|
3106
|
+
// feature-x is skipped as branch_disabled (never maintained): the pass is served for the
|
|
3107
|
+
// namespaces that ran, but nothing re-indexed feature-x.
|
|
3108
|
+
const unscoped = scheduler.requestQmdMaintenanceCompletion();
|
|
3109
|
+
const forBranch = scheduler.requestQmdMaintenanceCompletion("feature-x");
|
|
3110
|
+
const forDefault = scheduler.requestQmdMaintenanceCompletion("default");
|
|
3111
|
+
await scheduler.runQmdMaintenance();
|
|
3112
|
+
assert.equal(await unscoped, true);
|
|
3113
|
+
assert.equal(await forDefault, true, "default ran");
|
|
3114
|
+
assert.equal(await forBranch, false, "feature-x never ran, so its re-index is still owed");
|
|
3115
|
+
} finally {
|
|
3116
|
+
await scheduler.dispose();
|
|
3117
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
3118
|
+
}
|
|
3119
|
+
});
|
|
3120
|
+
|
|
3121
|
+
test("requestQmdMaintenanceCompletion serves a scoped request when namespaces are disabled and the update ran", async () => {
|
|
3122
|
+
// Without namespaces there is one corpus; the pass has no per-namespace statuses to match.
|
|
3123
|
+
const scheduler = completionScheduler({});
|
|
3124
|
+
try {
|
|
3125
|
+
const served = scheduler.requestQmdMaintenanceCompletion("default");
|
|
3126
|
+
await scheduler.runQmdMaintenance();
|
|
3127
|
+
assert.equal(await served, true);
|
|
3128
|
+
} finally {
|
|
3129
|
+
await scheduler.dispose();
|
|
3130
|
+
}
|
|
3131
|
+
});
|
|
3132
|
+
|
|
3133
|
+
test("requestQmdMaintenanceCompletion resolves false for a namespace-scoped request when dispose() abandons it", async () => {
|
|
3134
|
+
const scheduler = completionScheduler({});
|
|
3135
|
+
const served = scheduler.requestQmdMaintenanceCompletion("default");
|
|
3136
|
+
await scheduler.dispose();
|
|
3137
|
+
assert.equal(await served, false, "no pass ran the namespace, so the re-index stays owed");
|
|
3138
|
+
});
|
|
3139
|
+
|
|
3080
3140
|
test("requestQmdMaintenanceCompletion resolves false without arming anything when QMD is unavailable or disposed", async () => {
|
|
3081
3141
|
const unavailable = completionScheduler({ available: false });
|
|
3082
3142
|
try {
|
|
@@ -3300,6 +3360,43 @@ test("requestQmdMaintenanceCompletion over namespaces: true only when a namespac
|
|
|
3300
3360
|
}
|
|
3301
3361
|
});
|
|
3302
3362
|
|
|
3363
|
+
test("requestQmdMaintenanceCompletion over namespaces: a failed catalog read leaves the request owed (#3289)", async () => {
|
|
3364
|
+
// The configured namespaces still run, but cataloged (dynamic) namespaces were never discovered,
|
|
3365
|
+
// so the re-index is incomplete and the migration must keep its marker.
|
|
3366
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-completion-catalog-"));
|
|
3367
|
+
const config = fixtureConfig({
|
|
3368
|
+
memoryDir,
|
|
3369
|
+
namespacesEnabled: true,
|
|
3370
|
+
defaultNamespace: "default",
|
|
3371
|
+
sharedNamespace: "shared",
|
|
3372
|
+
namespacePolicies: [],
|
|
3373
|
+
maintenanceNamespaceLockStaleMs: 100,
|
|
3374
|
+
qmdAutoEmbedEnabled: false,
|
|
3375
|
+
});
|
|
3376
|
+
const catalog: NamespaceCatalog = Object.assign(Object.create(null), {
|
|
3377
|
+
enabled: true,
|
|
3378
|
+
async listNamespaces(): Promise<never> {
|
|
3379
|
+
throw new Error("catalog unavailable");
|
|
3380
|
+
},
|
|
3381
|
+
async markMaintenance() {},
|
|
3382
|
+
});
|
|
3383
|
+
const router: NamespaceSearchRouter = Object.assign(Object.create(null), {
|
|
3384
|
+
async updateNamespacesDetailed(ns: string[]): Promise<NamespaceUpdateResult> {
|
|
3385
|
+
return { backendCount: ns.length, eligibleNamespaces: ns };
|
|
3386
|
+
},
|
|
3387
|
+
async embedNamespaces(): Promise<void> {},
|
|
3388
|
+
});
|
|
3389
|
+
const scheduler = buildScheduler({ config, catalog, router });
|
|
3390
|
+
try {
|
|
3391
|
+
const served = scheduler.requestQmdMaintenanceCompletion();
|
|
3392
|
+
await scheduler.runQmdMaintenance();
|
|
3393
|
+
assert.equal(await served, false);
|
|
3394
|
+
} finally {
|
|
3395
|
+
await scheduler.dispose();
|
|
3396
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
3397
|
+
}
|
|
3398
|
+
});
|
|
3399
|
+
|
|
3303
3400
|
test("requestQmdMaintenanceCompletion over namespaces: a permanent skip (branch_disabled) does not block serving", async () => {
|
|
3304
3401
|
// A cataloged branch namespace is never maintained by default; that is a standing configuration,
|
|
3305
3402
|
// not an unfinished pass, so the ran namespace must still serve the request.
|
|
@@ -3424,3 +3521,45 @@ test("requestQmdMaintenanceCompletion over namespaces: a pass that adds no cover
|
|
|
3424
3521
|
await rm(memoryDir, { recursive: true, force: true });
|
|
3425
3522
|
}
|
|
3426
3523
|
});
|
|
3524
|
+
|
|
3525
|
+
test("requestQmdMaintenanceCompletion over namespaces: a request scoped to a namespace is served once that namespace ran, however the budget ranks the rest", async () => {
|
|
3526
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-completion-budget-"));
|
|
3527
|
+
const config = fixtureConfig({
|
|
3528
|
+
memoryDir,
|
|
3529
|
+
namespacesEnabled: true,
|
|
3530
|
+
defaultNamespace: "default",
|
|
3531
|
+
sharedNamespace: "shared",
|
|
3532
|
+
namespacePolicies: [],
|
|
3533
|
+
maintenanceNamespaceLockStaleMs: 100,
|
|
3534
|
+
qmdAutoEmbedEnabled: false,
|
|
3535
|
+
maintenanceMaxNamespacesPerCycle: 1,
|
|
3536
|
+
});
|
|
3537
|
+
const catalog: NamespaceCatalog = Object.assign(Object.create(null), {
|
|
3538
|
+
enabled: true,
|
|
3539
|
+
async listNamespaces() {
|
|
3540
|
+
return [{ namespace: "default" }];
|
|
3541
|
+
},
|
|
3542
|
+
async markMaintenance() {},
|
|
3543
|
+
});
|
|
3544
|
+
const router: NamespaceSearchRouter = Object.assign(Object.create(null), {
|
|
3545
|
+
async updateNamespacesDetailed(ns: string[]): Promise<NamespaceUpdateResult> {
|
|
3546
|
+
return { backendCount: ns.length, eligibleNamespaces: ns };
|
|
3547
|
+
},
|
|
3548
|
+
async embedNamespaces(): Promise<void> {},
|
|
3549
|
+
});
|
|
3550
|
+
const scheduler = buildScheduler({ config, catalog, router });
|
|
3551
|
+
try {
|
|
3552
|
+
// Budget 1 with default and shared: default always ranks first and shared is never selected.
|
|
3553
|
+
const forDefault = scheduler.requestQmdMaintenanceCompletion("default");
|
|
3554
|
+
const forShared = scheduler.requestQmdMaintenanceCompletion("shared");
|
|
3555
|
+
await scheduler.runQmdMaintenance();
|
|
3556
|
+
assert.equal(await forDefault, true, "the namespace the migration rewrote ran this pass");
|
|
3557
|
+
// shared starved: the pass added coverage once, then a pass adds none and the request ends unserved.
|
|
3558
|
+
await scheduler.runQmdMaintenance();
|
|
3559
|
+
assert.equal(await forShared, false);
|
|
3560
|
+
assert.equal(scheduler.isQmdMaintenancePending(), false);
|
|
3561
|
+
} finally {
|
|
3562
|
+
await scheduler.dispose();
|
|
3563
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
3564
|
+
}
|
|
3565
|
+
});
|
|
@@ -499,9 +499,9 @@ export class MaintenanceScheduler {
|
|
|
499
499
|
* unavailable or maintenance off), the pass failed, or dispose() abandoned it,
|
|
500
500
|
* so a caller that must not lose the re-index keeps owing it.
|
|
501
501
|
*/
|
|
502
|
-
requestQmdMaintenanceCompletion(): Promise<boolean> {
|
|
502
|
+
requestQmdMaintenanceCompletion(namespace?: string): Promise<boolean> {
|
|
503
503
|
if (this.disposed || !this.requestQmdMaintenance()) return Promise.resolve(false);
|
|
504
|
-
return this.qmdCompletion.wait();
|
|
504
|
+
return this.qmdCompletion.wait(namespace);
|
|
505
505
|
}
|
|
506
506
|
|
|
507
507
|
/**
|
|
@@ -546,6 +546,8 @@ export class MaintenanceScheduler {
|
|
|
546
546
|
const settle = this.qmdCompletion.claim();
|
|
547
547
|
let served = false;
|
|
548
548
|
let deferred = false;
|
|
549
|
+
// Set only by the namespace-aware branch; absent means the pass outcome applies to every waiter.
|
|
550
|
+
let ranNamespaces: Set<string> | undefined;
|
|
549
551
|
|
|
550
552
|
try {
|
|
551
553
|
if (resolveNamespaceCapabilities(this.deps.config).namespaces) {
|
|
@@ -626,9 +628,13 @@ export class MaintenanceScheduler {
|
|
|
626
628
|
// request, so a deployment with more namespaces than the cycle budget still converges.
|
|
627
629
|
// Permanent skip reasons describe namespaces this deployment never re-indexes.
|
|
628
630
|
const covered = this.qmdCoveredNamespaces;
|
|
631
|
+
const ran = new Set<string>();
|
|
632
|
+
ranNamespaces = ran;
|
|
629
633
|
const before = covered.size;
|
|
630
634
|
for (const status of summary.statuses) {
|
|
631
|
-
if (status.state
|
|
635
|
+
if (status.state !== "ran") continue;
|
|
636
|
+
covered.add(status.namespace);
|
|
637
|
+
ran.add(status.namespace);
|
|
632
638
|
}
|
|
633
639
|
const uncovered = summary.statuses.some(
|
|
634
640
|
(status) =>
|
|
@@ -636,10 +642,13 @@ export class MaintenanceScheduler {
|
|
|
636
642
|
(status.reason === "budget_exhausted" || status.reason === "lock_held") &&
|
|
637
643
|
!covered.has(status.namespace),
|
|
638
644
|
);
|
|
639
|
-
|
|
640
|
-
//
|
|
641
|
-
|
|
642
|
-
|
|
645
|
+
// The batch runner drops namespace "*" skips from the statuses, so a failed catalog read
|
|
646
|
+
// is only visible on the plan: cataloged namespaces were never discovered this pass.
|
|
647
|
+
const catalogReadFailed = plan.skipped.some((skip) => skip.reason === "catalog_read_failed");
|
|
648
|
+
served = summary.ran > 0 && summary.failed === 0 && !uncovered && !catalogReadFailed;
|
|
649
|
+
// Partial progress keeps the waiters for another pass; no progress, a failure or a failed
|
|
650
|
+
// catalog read ends the request unserved so the next start asks again.
|
|
651
|
+
deferred = !served && summary.failed === 0 && !catalogReadFailed && covered.size > before && !this.disposed;
|
|
643
652
|
} else {
|
|
644
653
|
const qmd = this.deps.getQmd();
|
|
645
654
|
// Plain update() is fail-open on every backend: it resolves when the min-interval gate,
|
|
@@ -678,7 +687,7 @@ export class MaintenanceScheduler {
|
|
|
678
687
|
// backoff) does not advance lastUpdateRanAtMs, so it leaves caches warm
|
|
679
688
|
// (#1904, Codex/Cursor).
|
|
680
689
|
} finally {
|
|
681
|
-
settle(served, deferred);
|
|
690
|
+
settle(served, deferred, ranNamespaces);
|
|
682
691
|
if (!deferred) this.qmdCoveredNamespaces.clear();
|
|
683
692
|
this.qmdMaintenanceInFlight = false;
|
|
684
693
|
if (deferred) this.qmdMaintenancePending = true;
|
|
@@ -6,22 +6,34 @@
|
|
|
6
6
|
* settles them with the pass outcome, and abandons them on dispose().
|
|
7
7
|
*/
|
|
8
8
|
export class QmdMaintenanceCompletion {
|
|
9
|
-
private waiters: Array<(served: boolean) => void> = [];
|
|
9
|
+
private waiters: Array<{ resolve: (served: boolean) => void; namespace?: string }> = [];
|
|
10
10
|
|
|
11
|
-
/**
|
|
12
|
-
|
|
13
|
-
|
|
11
|
+
/**
|
|
12
|
+
* Resolves true when a pass that started after this call completed successfully. With a
|
|
13
|
+
* `namespace` the request is served as soon as a pass ran that namespace, whatever else it skipped.
|
|
14
|
+
*/
|
|
15
|
+
wait(namespace?: string): Promise<boolean> {
|
|
16
|
+
return new Promise((resolve) => this.waiters.push({ resolve, namespace }));
|
|
14
17
|
}
|
|
15
18
|
|
|
16
19
|
/**
|
|
17
20
|
* A pass is starting: take every waiter it will serve and return their settle function.
|
|
18
|
-
* `
|
|
21
|
+
* `ran` lists the namespaces a namespace-aware pass ran (absent when namespaces are disabled:
|
|
22
|
+
* one corpus is maintained and the pass outcome applies to every waiter); `defer` keeps the
|
|
23
|
+
* waiters it did not serve waiting for a later pass (a pass that covered only part of the selection).
|
|
19
24
|
*/
|
|
20
|
-
claim(): (served: boolean, defer?: boolean) => void {
|
|
25
|
+
claim(): (served: boolean, defer?: boolean, ran?: ReadonlySet<string>) => void {
|
|
21
26
|
const claimed = this.waiters.splice(0);
|
|
22
|
-
return (served, defer = false) => {
|
|
23
|
-
|
|
24
|
-
|
|
27
|
+
return (served, defer = false, ran) => {
|
|
28
|
+
const requeue: typeof claimed = [];
|
|
29
|
+
for (const waiter of claimed) {
|
|
30
|
+
// A scoped waiter needs its own namespace to have run; the pass outcome alone is not enough.
|
|
31
|
+
const own = waiter.namespace !== undefined && ran !== undefined ? ran.has(waiter.namespace) : served;
|
|
32
|
+
if (own) waiter.resolve(true);
|
|
33
|
+
else if (defer) requeue.push(waiter);
|
|
34
|
+
else waiter.resolve(false);
|
|
35
|
+
}
|
|
36
|
+
this.waiters.unshift(...requeue);
|
|
25
37
|
};
|
|
26
38
|
}
|
|
27
39
|
|
package/src/orchestrator.ts
CHANGED
|
@@ -1515,7 +1515,7 @@ export class Orchestrator {
|
|
|
1515
1515
|
storage.onEntityMigrationComplete = () => {
|
|
1516
1516
|
if (!this.acceptingEntityMigrationMaintenance) return false;
|
|
1517
1517
|
// Settles once a QMD pass covering this request ran; the migration keeps its pending marker until then.
|
|
1518
|
-
const served = this.maintenanceScheduler.requestQmdMaintenanceCompletion();
|
|
1518
|
+
const served = this.maintenanceScheduler.requestQmdMaintenanceCompletion(namespace);
|
|
1519
1519
|
// Same side effects as before: projection rebuild and ledger-compaction checks, independent of QMD.
|
|
1520
1520
|
this.requestQmdMaintenance();
|
|
1521
1521
|
return served;
|