@remnic/core 10.0.54 → 10.0.56
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 +8 -8
- package/dist/access-http-query.js +2 -2
- package/dist/access-http.js +3 -3
- package/dist/{chunk-PVG7TCUG.js → chunk-5C44AMRV.js} +2 -2
- package/dist/{chunk-2GU6XS4L.js → chunk-CC2ESKHP.js} +7 -7
- package/dist/{chunk-R3WZ74B2.js → chunk-CZS7UTXL.js} +5 -3
- package/dist/chunk-CZS7UTXL.js.map +1 -0
- package/dist/{chunk-4BUJICMI.js → chunk-FCAXGYDT.js} +2 -2
- package/dist/{chunk-GGFQAGOT.js → chunk-GMRP3HN3.js} +2 -2
- package/dist/{chunk-Y56D5FSD.js → chunk-OGR52N7A.js} +3 -3
- package/dist/{chunk-XVTB6BZS.js → chunk-S3OI5DP5.js} +22 -10
- package/dist/chunk-S3OI5DP5.js.map +1 -0
- package/dist/{chunk-WGFEGBFB.js → chunk-VZ7VJGPL.js} +2 -2
- package/dist/cli.js +6 -6
- package/dist/index.js +8 -8
- package/dist/maintenance/namespace-planner.d.ts.map +1 -1
- package/dist/namespaces/catalog.d.ts +9 -1
- package/dist/namespaces/catalog.d.ts.map +1 -1
- package/dist/operator-toolkit.js +3 -3
- package/dist/orchestration/maintenance.js +3 -3
- package/dist/orchestrator.js +8 -8
- package/package.json +2 -2
- package/src/maintenance/namespace-planner.test.ts +41 -1
- package/src/maintenance/namespace-planner.ts +8 -1
- package/src/namespaces/catalog.test.ts +83 -0
- package/src/namespaces/catalog.ts +46 -10
- package/src/orchestration/maintenance.test.ts +33 -1
- package/src/profiling.test.ts +20 -5
- package/dist/chunk-R3WZ74B2.js.map +0 -1
- package/dist/chunk-XVTB6BZS.js.map +0 -1
- /package/dist/{chunk-PVG7TCUG.js.map → chunk-5C44AMRV.js.map} +0 -0
- /package/dist/{chunk-2GU6XS4L.js.map → chunk-CC2ESKHP.js.map} +0 -0
- /package/dist/{chunk-4BUJICMI.js.map → chunk-FCAXGYDT.js.map} +0 -0
- /package/dist/{chunk-GGFQAGOT.js.map → chunk-GMRP3HN3.js.map} +0 -0
- /package/dist/{chunk-Y56D5FSD.js.map → chunk-OGR52N7A.js.map} +0 -0
- /package/dist/{chunk-WGFEGBFB.js.map → chunk-VZ7VJGPL.js.map} +0 -0
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-CC2ESKHP.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-5C44AMRV.js";
|
|
14
14
|
import "./chunk-I74SUMNI.js";
|
|
15
15
|
import "./chunk-ILNPU5W5.js";
|
|
16
16
|
import "./chunk-VZAKYDMJ.js";
|
|
@@ -121,7 +121,7 @@ import "./chunk-PYIR4SSB.js";
|
|
|
121
121
|
import "./chunk-7G5NTS7F.js";
|
|
122
122
|
import "./chunk-FFQNNFHX.js";
|
|
123
123
|
import "./chunk-3I4FUAVJ.js";
|
|
124
|
-
import "./chunk-
|
|
124
|
+
import "./chunk-OGR52N7A.js";
|
|
125
125
|
import "./chunk-MMACM5G2.js";
|
|
126
126
|
import "./chunk-54BGIBCR.js";
|
|
127
127
|
import "./chunk-H4CKS5B2.js";
|
|
@@ -164,8 +164,8 @@ import "./chunk-HDN7IVIO.js";
|
|
|
164
164
|
import "./chunk-LR7RW4T6.js";
|
|
165
165
|
import "./chunk-3T74IZB3.js";
|
|
166
166
|
import "./chunk-2LA3H54D.js";
|
|
167
|
-
import "./chunk-
|
|
168
|
-
import "./chunk-
|
|
167
|
+
import "./chunk-GMRP3HN3.js";
|
|
168
|
+
import "./chunk-CZS7UTXL.js";
|
|
169
169
|
import "./chunk-KFY3SGN7.js";
|
|
170
170
|
import "./chunk-CMTINOFS.js";
|
|
171
171
|
import "./chunk-M4U25HLR.js";
|
|
@@ -238,7 +238,7 @@ import "./chunk-27NO2KVB.js";
|
|
|
238
238
|
import "./chunk-KFZY2OFH.js";
|
|
239
239
|
import "./chunk-B32WYRXX.js";
|
|
240
240
|
import "./chunk-QW27DOHG.js";
|
|
241
|
-
import "./chunk-
|
|
241
|
+
import "./chunk-FCAXGYDT.js";
|
|
242
242
|
import "./chunk-V7IZKJI3.js";
|
|
243
243
|
import "./chunk-TB6CBZ5V.js";
|
|
244
244
|
import "./chunk-XW2T4QAW.js";
|
|
@@ -283,8 +283,8 @@ import "./chunk-2FLFJTCI.js";
|
|
|
283
283
|
import "./chunk-JATHBCAV.js";
|
|
284
284
|
import "./chunk-ZQUVBZI4.js";
|
|
285
285
|
import "./chunk-5R627MYY.js";
|
|
286
|
-
import "./chunk-
|
|
287
|
-
import "./chunk-
|
|
286
|
+
import "./chunk-VZ7VJGPL.js";
|
|
287
|
+
import "./chunk-S3OI5DP5.js";
|
|
288
288
|
import "./chunk-AB3A7EFH.js";
|
|
289
289
|
import "./chunk-KIUVEMBI.js";
|
|
290
290
|
import "./chunk-HJ4X4BGS.js";
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@remnic/core",
|
|
3
|
-
"version": "10.0.
|
|
3
|
+
"version": "10.0.56",
|
|
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.56"
|
|
3350
3350
|
},
|
|
3351
3351
|
"peerDependenciesMeta": {
|
|
3352
3352
|
"@remnic/coding-graph": {
|
|
@@ -4,7 +4,7 @@ import os from "node:os";
|
|
|
4
4
|
import path from "node:path";
|
|
5
5
|
import test from "node:test";
|
|
6
6
|
|
|
7
|
-
import type { NamespaceCatalog, NamespaceRecord } from "../namespaces/catalog.js";
|
|
7
|
+
import type { NamespaceCatalog, NamespaceCatalogFilter, NamespaceCatalogReadOptions, NamespaceRecord } from "../namespaces/catalog.js";
|
|
8
8
|
import { namespaceIdentityToken } from "../namespaces/identity.js";
|
|
9
9
|
import type { PluginConfig } from "../types.js";
|
|
10
10
|
import {
|
|
@@ -145,6 +145,46 @@ test("planner fans out to live cataloged project and team namespaces while skipp
|
|
|
145
145
|
}
|
|
146
146
|
});
|
|
147
147
|
|
|
148
|
+
test("planner records catalog_read_failed for an incomplete catalog read and still plans what it found (#3293)", async () => {
|
|
149
|
+
const memoryDir = await mkMemoryDir();
|
|
150
|
+
try {
|
|
151
|
+
await createNamespaceData(memoryDir, "project-alpha");
|
|
152
|
+
// Object.create(null) yields an untyped value, so no assertion is needed: the planner only
|
|
153
|
+
// reads enabled and calls listNamespaces.
|
|
154
|
+
const catalog: NamespaceCatalog = Object.assign(Object.create(null), {
|
|
155
|
+
enabled: true,
|
|
156
|
+
async listNamespaces(_filter?: NamespaceCatalogFilter, options?: NamespaceCatalogReadOptions) {
|
|
157
|
+
options?.onIncomplete?.("1 malformed catalog line skipped");
|
|
158
|
+
return [record(memoryDir, "project-alpha", "project", "2026-06-30T10:00:00.000Z")];
|
|
159
|
+
},
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
const plan = await planNamespaceMaintenance(makeConfig(memoryDir), { jobName: "qmd", catalog });
|
|
163
|
+
|
|
164
|
+
assert.ok(
|
|
165
|
+
plan.namespaces.some((candidate) => candidate.namespace === "project-alpha"),
|
|
166
|
+
"the namespaces that were read are still maintained"
|
|
167
|
+
);
|
|
168
|
+
const failed = plan.skipped.filter((skipped) => skipped.reason === "catalog_read_failed");
|
|
169
|
+
assert.equal(failed.length, 1);
|
|
170
|
+
assert.equal(failed[0].namespace, "*");
|
|
171
|
+
assert.match(failed[0].detail ?? "", /malformed/);
|
|
172
|
+
} finally {
|
|
173
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
|
|
177
|
+
test("planner without a catalog plans the configured namespaces and reports nothing (#3293)", async () => {
|
|
178
|
+
const memoryDir = await mkMemoryDir();
|
|
179
|
+
try {
|
|
180
|
+
const plan = await planNamespaceMaintenance(makeConfig(memoryDir), { jobName: "qmd" });
|
|
181
|
+
assert.deepEqual(plan.namespaces.map((candidate) => candidate.namespace), ["default", "shared"]);
|
|
182
|
+
assert.equal(plan.skipped.some((skipped) => skipped.reason === "catalog_read_failed"), false);
|
|
183
|
+
} finally {
|
|
184
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
|
|
148
188
|
test("planner includes branch namespaces only when branch maintenance is enabled", async () => {
|
|
149
189
|
const memoryDir = await mkMemoryDir();
|
|
150
190
|
try {
|
|
@@ -229,7 +229,14 @@ export async function planNamespaceMaintenance(
|
|
|
229
229
|
if (resolveNamespaceCapabilities(config).namespaces && resolveConversationContextCapabilities(config).maintenanceNamespaceFanout !== false) {
|
|
230
230
|
const configuredSet = new Set(configured);
|
|
231
231
|
try {
|
|
232
|
-
|
|
232
|
+
// An incomplete read (unreadable file, malformed rows) still returns the readable records,
|
|
233
|
+
// so they are planned as before; the callback records that the catalog was not whole.
|
|
234
|
+
const records = options.catalog?.enabled
|
|
235
|
+
? await options.catalog.listNamespaces(undefined, {
|
|
236
|
+
onIncomplete: (reason) =>
|
|
237
|
+
skipped.push({ namespace: "*", reason: "catalog_read_failed", detail: reason }),
|
|
238
|
+
})
|
|
239
|
+
: [];
|
|
233
240
|
for (const record of records) {
|
|
234
241
|
const namespace = record.namespace.trim();
|
|
235
242
|
if (!namespace) continue;
|
|
@@ -3997,3 +3997,86 @@ test("#2469 persisted self/legacy kinds coerce to explicit and drop unused hints
|
|
|
3997
3997
|
}
|
|
3998
3998
|
});
|
|
3999
3999
|
|
|
4000
|
+
|
|
4001
|
+
test("listNamespaces reports an incomplete read for a malformed record but still returns the valid ones (#3293)", async () => {
|
|
4002
|
+
const memoryDir = await mkMemoryDir();
|
|
4003
|
+
try {
|
|
4004
|
+
const catalog = new NamespaceCatalog(makeConfig(memoryDir));
|
|
4005
|
+
await catalog.markWrite("project-origin-abc123", { discoveredBy: "write" });
|
|
4006
|
+
const catalogPath = path.join(memoryDir, "state", "namespaces.jsonl");
|
|
4007
|
+
await appendFile(catalogPath, "{this is not json\n", "utf8");
|
|
4008
|
+
|
|
4009
|
+
const reasons: string[] = [];
|
|
4010
|
+
const records = await catalog.listNamespaces(undefined, { onIncomplete: (reason) => reasons.push(reason) });
|
|
4011
|
+
assert.deepEqual(records.map((r) => r.namespace), ["project-origin-abc123"], "valid records are still returned");
|
|
4012
|
+
assert.equal(reasons.length, 1);
|
|
4013
|
+
assert.match(reasons[0], /malformed/);
|
|
4014
|
+
|
|
4015
|
+
// A second read served from the warm cache must still say the catalog was incomplete.
|
|
4016
|
+
const again: string[] = [];
|
|
4017
|
+
await catalog.listNamespaces(undefined, { onIncomplete: (reason) => again.push(reason) });
|
|
4018
|
+
assert.equal(again.length, 1, "the cached result keeps reporting the malformed line");
|
|
4019
|
+
} finally {
|
|
4020
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
4021
|
+
}
|
|
4022
|
+
});
|
|
4023
|
+
|
|
4024
|
+
test("listNamespaces reports an incomplete read for an unreadable catalog (#3293)", async () => {
|
|
4025
|
+
const memoryDir = await mkMemoryDir();
|
|
4026
|
+
try {
|
|
4027
|
+
// A directory where the catalog file should be: open succeeds, reading it fails.
|
|
4028
|
+
await mkdir(path.join(memoryDir, "state", "namespaces.jsonl"), { recursive: true });
|
|
4029
|
+
const catalog = new NamespaceCatalog(makeConfig(memoryDir));
|
|
4030
|
+
const reasons: string[] = [];
|
|
4031
|
+
const records = await catalog.listNamespaces(undefined, { onIncomplete: (reason) => reasons.push(reason) });
|
|
4032
|
+
assert.deepEqual(records, []);
|
|
4033
|
+
assert.equal(reasons.length, 1);
|
|
4034
|
+
assert.match(reasons[0], /unreadable/);
|
|
4035
|
+
} finally {
|
|
4036
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
4037
|
+
}
|
|
4038
|
+
});
|
|
4039
|
+
|
|
4040
|
+
test("listNamespaces does not report a clean or missing catalog as incomplete (#3293)", async () => {
|
|
4041
|
+
const memoryDir = await mkMemoryDir();
|
|
4042
|
+
try {
|
|
4043
|
+
const catalog = new NamespaceCatalog(makeConfig(memoryDir));
|
|
4044
|
+
const reasons: string[] = [];
|
|
4045
|
+
const onIncomplete = (reason: string) => reasons.push(reason);
|
|
4046
|
+
await catalog.listNamespaces(undefined, { onIncomplete });
|
|
4047
|
+
assert.deepEqual(reasons, [], "a catalog that was never written is normal on a fresh install");
|
|
4048
|
+
await catalog.markWrite("project-origin-abc123", { discoveredBy: "write" });
|
|
4049
|
+
await catalog.listNamespaces(undefined, { onIncomplete });
|
|
4050
|
+
assert.deepEqual(reasons, []);
|
|
4051
|
+
} finally {
|
|
4052
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
4053
|
+
}
|
|
4054
|
+
});
|
|
4055
|
+
|
|
4056
|
+
test("a catalog with malformed rows stays tolerant for callers that do not ask about completeness (#3293)", async () => {
|
|
4057
|
+
const memoryDir = await mkMemoryDir();
|
|
4058
|
+
try {
|
|
4059
|
+
const catalog = new NamespaceCatalog(makeConfig(memoryDir));
|
|
4060
|
+
await catalog.markWrite("project-origin-abc123", { discoveredBy: "write" });
|
|
4061
|
+
const catalogPath = path.join(memoryDir, "state", "namespaces.jsonl");
|
|
4062
|
+
// Valid JSON that is not a catalog record counts as malformed too.
|
|
4063
|
+
await appendFile(catalogPath, "{\"not\":\"a record\"}\n", "utf8");
|
|
4064
|
+
|
|
4065
|
+
// No options, or options without a callback: nothing to call, nothing may throw.
|
|
4066
|
+
assert.deepEqual((await catalog.listNamespaces()).map((r) => r.namespace), ["project-origin-abc123"]);
|
|
4067
|
+
assert.deepEqual((await catalog.listNamespaces(undefined, {})).map((r) => r.namespace), ["project-origin-abc123"]);
|
|
4068
|
+
|
|
4069
|
+
const reasons: string[] = [];
|
|
4070
|
+
await catalog.listNamespaces(undefined, { onIncomplete: (reason) => reasons.push(reason) });
|
|
4071
|
+
assert.equal(reasons.length, 1, "the non-record row is reported");
|
|
4072
|
+
|
|
4073
|
+
// The warm cache now remembers the problem; a reader without a reporter must still work.
|
|
4074
|
+
assert.equal((await catalog.getNamespaceRecord("project-origin-abc123"))?.namespace, "project-origin-abc123");
|
|
4075
|
+
|
|
4076
|
+
// A cold read of a malformed catalog by a reader without a reporter must work too.
|
|
4077
|
+
const cold = new NamespaceCatalog(makeConfig(memoryDir));
|
|
4078
|
+
assert.equal((await cold.getNamespaceRecord("project-origin-abc123"))?.namespace, "project-origin-abc123");
|
|
4079
|
+
} finally {
|
|
4080
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
4081
|
+
}
|
|
4082
|
+
});
|
|
@@ -87,6 +87,20 @@ export interface NamespaceCatalogFilter {
|
|
|
87
87
|
writtenSince?: Date;
|
|
88
88
|
}
|
|
89
89
|
|
|
90
|
+
/** Out-parameter a catalog read fills in when it could not see the whole catalog. */
|
|
91
|
+
interface CatalogReadHealth {
|
|
92
|
+
incomplete?: string;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
export interface NamespaceCatalogReadOptions {
|
|
96
|
+
/**
|
|
97
|
+
* Called when the read could not see the whole catalog (an unreadable file, or malformed rows
|
|
98
|
+
* that were skipped). The records that were readable are still returned. A catalog that was
|
|
99
|
+
* never written is normal and does not call this.
|
|
100
|
+
*/
|
|
101
|
+
onIncomplete?: (reason: string) => void;
|
|
102
|
+
}
|
|
103
|
+
|
|
90
104
|
export interface NamespaceTouchMetadata {
|
|
91
105
|
discoveredBy?: NamespaceDiscoverySource;
|
|
92
106
|
kind?: NamespaceKind;
|
|
@@ -412,6 +426,8 @@ interface CatalogFileIdentity {
|
|
|
412
426
|
interface CompactedCatalogCache {
|
|
413
427
|
identity: CatalogFileIdentity;
|
|
414
428
|
records: Map<string, NamespaceRecord>;
|
|
429
|
+
/** Why the read was incomplete (malformed rows), kept so a cache hit reports it too. */
|
|
430
|
+
incomplete?: string;
|
|
415
431
|
}
|
|
416
432
|
|
|
417
433
|
export class NamespaceCatalog {
|
|
@@ -626,9 +642,9 @@ export class NamespaceCatalog {
|
|
|
626
642
|
}
|
|
627
643
|
return [...byStorageDir.values()];
|
|
628
644
|
}
|
|
629
|
-
private loadSanitizedRecords(): Promise<NamespaceRecord[]> {
|
|
645
|
+
private loadSanitizedRecords(health?: CatalogReadHealth): Promise<NamespaceRecord[]> {
|
|
630
646
|
return this.queueCritical(async () => {
|
|
631
|
-
const records = await this.loadCompacted();
|
|
647
|
+
const records = await this.loadCompacted(false, health);
|
|
632
648
|
const sanitized = await Promise.all(
|
|
633
649
|
[...records.values()].map((r) => this.sanitizeRecordForRead(r)),
|
|
634
650
|
);
|
|
@@ -642,9 +658,14 @@ export class NamespaceCatalog {
|
|
|
642
658
|
});
|
|
643
659
|
}
|
|
644
660
|
|
|
645
|
-
async listNamespaces(
|
|
661
|
+
async listNamespaces(
|
|
662
|
+
filter?: NamespaceCatalogFilter,
|
|
663
|
+
options?: NamespaceCatalogReadOptions,
|
|
664
|
+
): Promise<NamespaceRecord[]> {
|
|
646
665
|
if (!this.enabled) return [];
|
|
647
|
-
|
|
666
|
+
const health: CatalogReadHealth = {};
|
|
667
|
+
let out = await this.loadSanitizedRecords(health);
|
|
668
|
+
if (health.incomplete !== undefined) options?.onIncomplete?.(health.incomplete);
|
|
648
669
|
if (filter?.kind) out = out.filter((r) => r.kind === filter.kind);
|
|
649
670
|
if (filter?.discoveredBy) out = out.filter((r) => r.discoveredBy === filter.discoveredBy);
|
|
650
671
|
if (filter?.writtenSince) {
|
|
@@ -2078,13 +2099,18 @@ export class NamespaceCatalog {
|
|
|
2078
2099
|
// ── Persistence ──────────────────────────────────────────────────────────
|
|
2079
2100
|
|
|
2080
2101
|
/** Load the JSONL log and fold it into current state (last-record-wins). */
|
|
2081
|
-
private async loadCompacted(
|
|
2102
|
+
private async loadCompacted(
|
|
2103
|
+
forceFresh = false,
|
|
2104
|
+
health?: CatalogReadHealth,
|
|
2105
|
+
): Promise<Map<string, NamespaceRecord>> {
|
|
2082
2106
|
const records = new Map<string, NamespaceRecord>();
|
|
2083
2107
|
let handle;
|
|
2084
2108
|
try {
|
|
2085
2109
|
handle = await open(this.catalogPath, "r");
|
|
2086
|
-
} catch {
|
|
2110
|
+
} catch (error) {
|
|
2087
2111
|
this.compactedCache = undefined;
|
|
2112
|
+
// A catalog that was never written is normal; any other open failure hid the whole catalog.
|
|
2113
|
+
if (health && !isNotFoundError(error)) health.incomplete = `catalog unreadable: ${String(error)}`;
|
|
2088
2114
|
return records;
|
|
2089
2115
|
}
|
|
2090
2116
|
|
|
@@ -2106,6 +2132,7 @@ export class NamespaceCatalog {
|
|
|
2106
2132
|
this.compactedCache.identity.ino === identity.ino
|
|
2107
2133
|
) {
|
|
2108
2134
|
await handle.close().catch(() => undefined);
|
|
2135
|
+
if (health && this.compactedCache.incomplete) health.incomplete = this.compactedCache.incomplete;
|
|
2109
2136
|
return this.cloneCompactedRecords(this.compactedCache.records);
|
|
2110
2137
|
}
|
|
2111
2138
|
} catch {
|
|
@@ -2113,6 +2140,7 @@ export class NamespaceCatalog {
|
|
|
2113
2140
|
// do not cache the result without a trustworthy identity.
|
|
2114
2141
|
}
|
|
2115
2142
|
|
|
2143
|
+
let malformed = 0;
|
|
2116
2144
|
try {
|
|
2117
2145
|
this.onCatalogReadForTest?.();
|
|
2118
2146
|
// Streaming chunked parse (issue #1903): the log can reach 100+ MB in
|
|
@@ -2136,11 +2164,16 @@ export class NamespaceCatalog {
|
|
|
2136
2164
|
try {
|
|
2137
2165
|
parsed = JSON.parse(trimmed);
|
|
2138
2166
|
} catch {
|
|
2139
|
-
// Skip corrupt lines (CLAUDE.md rule #18 robustness)
|
|
2167
|
+
// Skip corrupt lines (CLAUDE.md rule #18 robustness), but count them so a caller that
|
|
2168
|
+
// needs the whole catalog can tell the read was incomplete (issue #3293).
|
|
2169
|
+
malformed += 1;
|
|
2140
2170
|
return;
|
|
2141
2171
|
}
|
|
2142
2172
|
const record = coerceRecord(parsed);
|
|
2143
|
-
if (!record)
|
|
2173
|
+
if (!record) {
|
|
2174
|
+
malformed += 1;
|
|
2175
|
+
return;
|
|
2176
|
+
}
|
|
2144
2177
|
const prior = records.get(record.namespace);
|
|
2145
2178
|
records.set(record.namespace, prior ? mergeNewerTouchFields(record, prior) : record);
|
|
2146
2179
|
};
|
|
@@ -2161,14 +2194,17 @@ export class NamespaceCatalog {
|
|
|
2161
2194
|
}
|
|
2162
2195
|
leftover += decoder.end();
|
|
2163
2196
|
foldLine(leftover);
|
|
2164
|
-
} catch {
|
|
2197
|
+
} catch (error) {
|
|
2165
2198
|
this.compactedCache = undefined;
|
|
2199
|
+
if (health) health.incomplete = `catalog unreadable: ${String(error)}`;
|
|
2166
2200
|
return new Map();
|
|
2167
2201
|
} finally {
|
|
2168
2202
|
await handle.close().catch(() => undefined);
|
|
2169
2203
|
}
|
|
2170
2204
|
|
|
2171
|
-
|
|
2205
|
+
const incomplete = malformed > 0 ? `${malformed} malformed catalog line(s) skipped` : undefined;
|
|
2206
|
+
if (health && incomplete) health.incomplete = incomplete;
|
|
2207
|
+
this.compactedCache = identity ? { identity, records, incomplete } : undefined;
|
|
2172
2208
|
return this.cloneCompactedRecords(records);
|
|
2173
2209
|
}
|
|
2174
2210
|
|
|
@@ -43,7 +43,7 @@ import type {
|
|
|
43
43
|
NamespaceSearchRouter,
|
|
44
44
|
NamespaceUpdateResult,
|
|
45
45
|
} from "../namespaces/search.js";
|
|
46
|
-
import
|
|
46
|
+
import { NamespaceCatalog, type NamespaceRecord } from "../namespaces/catalog.js";
|
|
47
47
|
import { namespaceIdentityFromToken, namespaceIdentityToken } from "../namespaces/identity.js";
|
|
48
48
|
import { isSafeRouteNamespace } from "../routing/engine.js";
|
|
49
49
|
import { readNamespaceMaintenanceStatuses } from "../maintenance/namespace-planner.js";
|
|
@@ -3397,6 +3397,38 @@ test("requestQmdMaintenanceCompletion over namespaces: a failed catalog read lea
|
|
|
3397
3397
|
}
|
|
3398
3398
|
});
|
|
3399
3399
|
|
|
3400
|
+
test("requestQmdMaintenanceCompletion over namespaces: a malformed catalog record leaves the request owed (#3293)", async () => {
|
|
3401
|
+
// A real catalog: the malformed row is skipped (the rest is still maintained), but the pass did
|
|
3402
|
+
// not see the whole catalog, so the migration must keep its marker.
|
|
3403
|
+
const memoryDir = await mkdtemp(path.join(os.tmpdir(), "remnic-qmd-completion-malformed-"));
|
|
3404
|
+
const config = fixtureConfig({
|
|
3405
|
+
memoryDir,
|
|
3406
|
+
namespacesEnabled: true,
|
|
3407
|
+
defaultNamespace: "default",
|
|
3408
|
+
sharedNamespace: "shared",
|
|
3409
|
+
namespacePolicies: [],
|
|
3410
|
+
maintenanceNamespaceLockStaleMs: 100,
|
|
3411
|
+
qmdAutoEmbedEnabled: false,
|
|
3412
|
+
});
|
|
3413
|
+
await mkdir(path.join(memoryDir, "state"), { recursive: true });
|
|
3414
|
+
await writeFile(path.join(memoryDir, "state", "namespaces.jsonl"), "{this is not json\n", "utf8");
|
|
3415
|
+
const router: NamespaceSearchRouter = Object.assign(Object.create(null), {
|
|
3416
|
+
async updateNamespacesDetailed(ns: string[]): Promise<NamespaceUpdateResult> {
|
|
3417
|
+
return { backendCount: ns.length, eligibleNamespaces: ns };
|
|
3418
|
+
},
|
|
3419
|
+
async embedNamespaces(): Promise<void> {},
|
|
3420
|
+
});
|
|
3421
|
+
const scheduler = buildScheduler({ config, catalog: new NamespaceCatalog(config), router });
|
|
3422
|
+
try {
|
|
3423
|
+
const served = scheduler.requestQmdMaintenanceCompletion();
|
|
3424
|
+
await scheduler.runQmdMaintenance();
|
|
3425
|
+
assert.equal(await served, false);
|
|
3426
|
+
} finally {
|
|
3427
|
+
await scheduler.dispose();
|
|
3428
|
+
await rm(memoryDir, { recursive: true, force: true });
|
|
3429
|
+
}
|
|
3430
|
+
});
|
|
3431
|
+
|
|
3400
3432
|
test("requestQmdMaintenanceCompletion over namespaces: a permanent skip (branch_disabled) does not block serving", async () => {
|
|
3401
3433
|
// A cataloged branch namespace is never maintained by default; that is a standing configuration,
|
|
3402
3434
|
// not an unfinished pass, so the ran namespace must still serve the request.
|
package/src/profiling.test.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import assert from "node:assert/strict";
|
|
2
|
+
import { setImmediate as nextTurn } from "node:timers/promises";
|
|
2
3
|
import { existsSync, mkdtempSync, readdirSync, rmSync } from "node:fs";
|
|
3
4
|
import { join } from "node:path";
|
|
4
5
|
import { tmpdir } from "node:os";
|
|
@@ -75,7 +76,7 @@ describe("ProfilingCollector", () => {
|
|
|
75
76
|
}
|
|
76
77
|
});
|
|
77
78
|
|
|
78
|
-
test("records parallel groups with correct wall time and member durations", async () => {
|
|
79
|
+
test("records parallel groups with correct wall time and member durations", async (t) => {
|
|
79
80
|
const dir = tempDir();
|
|
80
81
|
try {
|
|
81
82
|
const collector = new ProfilingCollector({
|
|
@@ -84,16 +85,26 @@ describe("ProfilingCollector", () => {
|
|
|
84
85
|
maxTraces: 10,
|
|
85
86
|
});
|
|
86
87
|
|
|
88
|
+
// A fake clock: the collector reads Date.now(), so real timers and a real wall clock would
|
|
89
|
+
// make the measured wall time depend on the machine (issue #3294).
|
|
90
|
+
t.mock.timers.enable({ apis: ["setTimeout", "Date"], now: 1_000 });
|
|
91
|
+
|
|
87
92
|
collector.startTrace("extraction");
|
|
88
93
|
|
|
89
94
|
// Start a parallel group with a 5ms and 30ms promise.
|
|
90
95
|
const handle = collector.startParallelGroup("embed+classify");
|
|
91
96
|
const p1 = new Promise<void>((resolve) => setTimeout(resolve, 5));
|
|
92
97
|
const p2 = new Promise<void>((resolve) => setTimeout(resolve, 30));
|
|
93
|
-
|
|
98
|
+
const ended = collector.endParallelGroup(handle, [
|
|
94
99
|
{ name: "embed", promise: p1 },
|
|
95
100
|
{ name: "classify", promise: p2 },
|
|
96
101
|
]);
|
|
102
|
+
// Two steps with a real event-loop turn between them, so the 5ms member's continuation
|
|
103
|
+
// reads the clock at 5ms instead of after the whole 30ms has been ticked.
|
|
104
|
+
t.mock.timers.tick(5);
|
|
105
|
+
await nextTurn();
|
|
106
|
+
t.mock.timers.tick(25);
|
|
107
|
+
await ended;
|
|
97
108
|
|
|
98
109
|
const trace = collector.endTrace();
|
|
99
110
|
|
|
@@ -103,12 +114,16 @@ describe("ProfilingCollector", () => {
|
|
|
103
114
|
|
|
104
115
|
const group = trace!.parallelGroups![0];
|
|
105
116
|
assert.equal(group.name, "embed+classify");
|
|
106
|
-
// Wall time
|
|
107
|
-
assert.
|
|
108
|
-
assert.
|
|
117
|
+
// Wall time is the longest member (30ms); each member's duration is its own timer.
|
|
118
|
+
assert.equal(group.wallMs, 30);
|
|
119
|
+
assert.equal(group.members[0].durationMs, 5);
|
|
120
|
+
assert.equal(group.members[1].durationMs, 30);
|
|
109
121
|
assert.equal(group.members.length, 2);
|
|
110
122
|
assert.equal(group.members[0].name, "embed");
|
|
111
123
|
assert.equal(group.members[1].name, "classify");
|
|
124
|
+
// The shorter member resolved first.
|
|
125
|
+
assert.equal(group.members[0].resolvedIndex, 0);
|
|
126
|
+
assert.equal(group.members[1].resolvedIndex, 1);
|
|
112
127
|
} finally {
|
|
113
128
|
cleanup(dir);
|
|
114
129
|
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/maintenance/namespace-planner.ts"],"sourcesContent":["import { resolveNamespaceCapabilities } from \"../capabilities.js\";\nimport { createHash, randomUUID } from \"node:crypto\";\nimport type { Dirent } from \"node:fs\";\nimport { lstat, mkdir, open, readFile, readdir, rename, rm, rmdir, stat, utimes, writeFile } from \"node:fs/promises\";\nimport path from \"node:path\";\n\nimport type { NamespaceCatalog, NamespaceKind, NamespaceRecord } from \"../namespaces/catalog.js\";\nimport { hasMemoryData } from \"../namespaces/catalog.js\";\nimport { namespaceIdentityToken } from \"../namespaces/identity.js\";\nimport { resolveNamespaceStorageRoot } from \"../namespaces/storage.js\";\nimport { displayErrorDetail } from \"../runtime/better-sqlite.js\";\nimport type { PluginConfig } from \"../types.js\";\nimport { getConfiguredNamespaces } from \"../scopes/scope-plan.js\";\nimport { resolveConversationContextCapabilities } from \"../capabilities.js\";\n\nexport type NamespaceMaintenanceJobName = string;\n\nexport type NamespaceMaintenanceSkipReason =\n | \"fanout_disabled\"\n | \"empty_root\"\n | \"branch_disabled\"\n | \"project_disabled\"\n | \"team_project_disabled\"\n | \"catalog_read_failed\"\n | \"unsafe_or_stale_root\"\n | \"budget_exhausted\"\n | \"lock_held\"\n | \"batch_lock_incomplete\"\n | \"throttled\"\n | \"job_failed\";\n\nexport interface NamespaceMaintenancePlannerOptions {\n jobName: NamespaceMaintenanceJobName;\n catalog?: NamespaceCatalog;\n now?: Date;\n budgetMode?: \"cycle\" | \"unbounded\";\n}\n\nexport interface NamespaceMaintenanceCandidate {\n namespace: string;\n kind: NamespaceKind;\n storageDir?: string;\n source: \"configured\" | \"catalog\";\n lastWriteAt?: string;\n lastMaintenanceAt?: string;\n}\n\nexport interface NamespaceMaintenancePlan {\n jobName: NamespaceMaintenanceJobName;\n generatedAt: string;\n namespaces: NamespaceMaintenanceCandidate[];\n skipped: NamespaceMaintenanceSkippedNamespace[];\n budget: {\n maxNamespacesPerCycle: number;\n selected: number;\n };\n}\n\nexport interface NamespaceMaintenanceSkippedNamespace {\n namespace: string;\n kind?: NamespaceKind;\n reason: NamespaceMaintenanceSkipReason;\n detail?: string;\n}\n\nexport interface NamespaceMaintenanceRunStatus {\n namespace: string;\n jobName: NamespaceMaintenanceJobName;\n state: \"ran\" | \"skipped\" | \"failed\";\n reason?: NamespaceMaintenanceSkipReason;\n startedAt: string;\n completedAt: string;\n itemCount?: number;\n error?: string;\n}\n\nexport interface NamespaceMaintenanceSummary {\n jobName: NamespaceMaintenanceJobName;\n generatedAt: string;\n ran: number;\n skipped: number;\n failed: number;\n statuses: NamespaceMaintenanceRunStatus[];\n}\n\nexport interface NamespaceMaintenanceBatchRunResult {\n itemCount?: number;\n itemCounts?: Record<string, number> | Map<string, number>;\n}\n\nexport interface NamespaceMaintenanceBatchRunOptions {\n requireAllLocks?: boolean;\n skipReasonForError?: (error: unknown) => NamespaceMaintenanceSkipReason | null | undefined;\n}\n\ninterface LockHandle {\n path: string;\n touch(): Promise<void>;\n release(): Promise<void>;\n}\n\nconst DEFAULT_MAX_NAMESPACES_PER_CYCLE = 20;\nconst DEFAULT_LOCK_STALE_MS = 10 * 60_000;\nconst LOCK_BASE = \"maintenance-locks\";\nconst STATUS_BASE = \"namespace-maintenance-status\";\nconst namespaceMaintenanceFs = { open, rm };\n\nexport function __setNamespaceMaintenanceFsForTest(overrides: Partial<typeof namespaceMaintenanceFs>): () => void {\n const previous = { ...namespaceMaintenanceFs };\n Object.assign(namespaceMaintenanceFs, overrides);\n return () => {\n Object.assign(namespaceMaintenanceFs, previous);\n };\n}\n\n\n\nfunction inferConfiguredKind(config: PluginConfig, namespace: string): NamespaceKind {\n if (namespace === config.defaultNamespace.trim()) return \"default\";\n if (namespace === config.sharedNamespace.trim()) return \"shared\";\n return \"explicit\";\n}\n\nfunction maxNamespacesPerCycle(config: PluginConfig): number {\n return Math.max(\n 1,\n Math.floor(\n typeof config.maintenanceMaxNamespacesPerCycle === \"number\" &&\n Number.isFinite(config.maintenanceMaxNamespacesPerCycle)\n ? config.maintenanceMaxNamespacesPerCycle\n : DEFAULT_MAX_NAMESPACES_PER_CYCLE\n )\n );\n}\n\nfunction namespaceKindAllowed(config: PluginConfig, kind: NamespaceKind): boolean {\n switch (kind) {\n case \"branch\":\n return config.maintenanceIncludeBranchNamespaces === true;\n case \"project\":\n return config.maintenanceIncludeProjectNamespaces !== false;\n case \"team-project\":\n return config.maintenanceIncludeTeamProjectNamespaces !== false;\n default:\n return true;\n }\n}\n\nfunction disabledReasonForKind(kind: NamespaceKind): NamespaceMaintenanceSkipReason {\n if (kind === \"branch\") return \"branch_disabled\";\n if (kind === \"project\") return \"project_disabled\";\n if (kind === \"team-project\") return \"team_project_disabled\";\n return \"fanout_disabled\";\n}\n\nasync function catalogRootIsLive(config: PluginConfig, record: NamespaceRecord): Promise<boolean> {\n if (typeof record.storageDir !== \"string\" || record.storageDir.length === 0) {\n return false;\n }\n try {\n const liveRoot = await resolveNamespaceStorageRoot(config, record.namespace);\n if (path.resolve(liveRoot) !== path.resolve(record.storageDir)) return false;\n return hasMemoryData(liveRoot);\n } catch {\n return false;\n }\n}\n\nfunction candidateSortKey(candidate: NamespaceMaintenanceCandidate): string {\n const write = candidate.lastWriteAt ?? \"\";\n return `${write}\\u0000${candidate.namespace}`;\n}\n\nfunction candidatePriority(candidate: NamespaceMaintenanceCandidate): number {\n if (candidate.kind === \"default\") return 0;\n if (candidate.kind === \"shared\") return 1;\n if (candidate.source === \"configured\") return 2;\n if (candidate.kind === \"team-project\") return 3;\n if (candidate.kind === \"project\") return 4;\n if (candidate.kind === \"branch\") return 8;\n return 7;\n}\n\nfunction sortCandidates(a: NamespaceMaintenanceCandidate, b: NamespaceMaintenanceCandidate): number {\n const priority = candidatePriority(a) - candidatePriority(b);\n if (priority !== 0) return priority;\n const am = Date.parse(a.lastMaintenanceAt ?? \"\");\n const bm = Date.parse(b.lastMaintenanceAt ?? \"\");\n const aMaintained = Number.isFinite(am);\n const bMaintained = Number.isFinite(bm);\n if (aMaintained && bMaintained && am !== bm) return am - bm;\n if (aMaintained !== bMaintained) return aMaintained ? 1 : -1;\n const aw = Date.parse(a.lastWriteAt ?? \"\");\n const bw = Date.parse(b.lastWriteAt ?? \"\");\n const aValid = Number.isFinite(aw);\n const bValid = Number.isFinite(bw);\n if (aValid && bValid && aw !== bw) return bw - aw;\n if (aValid !== bValid) return aValid ? -1 : 1;\n const byKey = candidateSortKey(a).localeCompare(candidateSortKey(b));\n if (byKey !== 0) return byKey;\n return a.namespace.localeCompare(b.namespace);\n}\n\nexport async function planNamespaceMaintenance(\n config: PluginConfig,\n options: NamespaceMaintenancePlannerOptions\n): Promise<NamespaceMaintenancePlan> {\n const generatedAt = (options.now ?? new Date()).toISOString();\n // When namespaces are disabled, storageFor() collapses every namespace name\n // to config.memoryDir. Seeding all configured namespaces (default + shared +\n // policies) would make a mutating maintenance job process the SAME corpus\n // once per configured name. The #1500 contract is \"namespaces disabled:\n // maintain the current default storage only,\" so collapse to the default.\n const configured = resolveNamespaceCapabilities(config).namespaces\n ? getConfiguredNamespaces(config)\n : [config.defaultNamespace.trim()].filter(Boolean);\n const byNamespace = new Map<string, NamespaceMaintenanceCandidate>();\n const skipped: NamespaceMaintenanceSkippedNamespace[] = [];\n\n for (const namespace of configured) {\n const kind = inferConfiguredKind(config, namespace);\n byNamespace.set(namespace, {\n namespace,\n kind,\n source: \"configured\",\n });\n }\n\n if (resolveNamespaceCapabilities(config).namespaces && resolveConversationContextCapabilities(config).maintenanceNamespaceFanout !== false) {\n const configuredSet = new Set(configured);\n try {\n const records = options.catalog?.enabled ? await options.catalog.listNamespaces() : [];\n for (const record of records) {\n const namespace = record.namespace.trim();\n if (!namespace) continue;\n const isConfigured = configuredSet.has(namespace);\n const kind = isConfigured ? inferConfiguredKind(config, namespace) : record.kind;\n if (!namespaceKindAllowed(config, kind)) {\n skipped.push({\n namespace,\n kind,\n reason: disabledReasonForKind(kind),\n });\n continue;\n }\n if (!isConfigured && !(await catalogRootIsLive(config, record))) {\n skipped.push({\n namespace,\n kind,\n reason: \"unsafe_or_stale_root\",\n });\n continue;\n }\n byNamespace.set(namespace, {\n namespace,\n kind,\n storageDir: record.storageDir,\n source: isConfigured ? \"configured\" : \"catalog\",\n lastWriteAt: record.lastWriteAt,\n lastMaintenanceAt: record.lastMaintenanceAt?.[options.jobName],\n });\n }\n } catch (error) {\n skipped.push({\n namespace: \"*\",\n reason: \"catalog_read_failed\",\n detail: error instanceof Error ? error.message : String(error),\n });\n }\n } else if (resolveNamespaceCapabilities(config).namespaces) {\n skipped.push({\n namespace: \"*\",\n reason: \"fanout_disabled\",\n });\n }\n\n if (options.budgetMode !== \"unbounded\") {\n const latestStatusAtByNamespace = await readLatestStatusAtByNamespace(config, options.jobName);\n for (const candidate of byNamespace.values()) {\n if (!candidate.lastMaintenanceAt) {\n candidate.lastMaintenanceAt = latestStatusAtByNamespace.get(candidate.namespace);\n }\n }\n }\n\n const candidates = [...byNamespace.values()]\n .filter((candidate) => namespaceKindAllowed(config, candidate.kind))\n .sort(sortCandidates);\n\n const max = maxNamespacesPerCycle(config);\n const applyCycleBudget = options.budgetMode !== \"unbounded\";\n const selected = applyCycleBudget ? candidates.slice(0, max) : candidates;\n if (applyCycleBudget) {\n for (const candidate of candidates.slice(max)) {\n skipped.push({\n namespace: candidate.namespace,\n kind: candidate.kind,\n reason: \"budget_exhausted\",\n });\n }\n }\n\n return {\n jobName: options.jobName,\n generatedAt,\n namespaces: selected,\n skipped,\n budget: {\n maxNamespacesPerCycle: max,\n selected: selected.length,\n },\n };\n}\n\nfunction stablePathSegment(value: string): string {\n const sanitized = value.replace(/[^a-zA-Z0-9._-]/g, \"_\").slice(0, 128) || \"unnamed\";\n if (sanitized.length <= 128 && sanitized === value) return sanitized;\n return `${sanitized.slice(0, 80)}-${createHash(\"sha256\").update(value).digest(\"hex\").slice(0, 16)}`;\n}\n\nfunction namespacePathSegment(namespace: string): string {\n const token = namespaceIdentityToken(namespace);\n if (token.length <= 160) return token;\n return `ns-${createHash(\"sha256\").update(namespace).digest(\"hex\")}`;\n}\n\nfunction lockPath(config: PluginConfig, jobName: string, namespace: string): string {\n return path.join(\n config.memoryDir,\n \"state\",\n LOCK_BASE,\n stablePathSegment(jobName),\n `${namespacePathSegment(namespace)}.lock`\n );\n}\n\nfunction namespaceMaintenanceLockStaleMs(config: PluginConfig): number {\n if (\n typeof config.maintenanceNamespaceLockStaleMs === \"number\" &&\n Number.isFinite(config.maintenanceNamespaceLockStaleMs) &&\n config.maintenanceNamespaceLockStaleMs > 0\n ) {\n return Math.floor(config.maintenanceNamespaceLockStaleMs);\n }\n return DEFAULT_LOCK_STALE_MS;\n}\n\nfunction namespaceMaintenanceLockHeartbeatMs(config: PluginConfig): number {\n const staleMs = namespaceMaintenanceLockStaleMs(config);\n return Math.max(1, Math.min(30_000, Math.floor(staleMs / 3) || 1));\n}\n\nfunction errorCode(error: unknown): string | undefined {\n return typeof error === \"object\" && error !== null && \"code\" in error\n ? (error as { code?: string }).code\n : undefined;\n}\n\nasync function withNamespaceMaintenanceLockHeartbeat<T>(\n config: PluginConfig,\n locks: LockHandle | LockHandle[],\n task: () => Promise<T>,\n): Promise<T> {\n const activeLocks = Array.isArray(locks) ? locks : [locks];\n const interval = setInterval(() => {\n for (const lock of activeLocks) {\n void lock.touch().catch(() => undefined);\n }\n }, namespaceMaintenanceLockHeartbeatMs(config));\n interval.unref?.();\n try {\n return await task();\n } finally {\n clearInterval(interval);\n }\n}\n\nasync function removeStaleLockDirectory(filePath: string): Promise<void> {\n let entries: Dirent[];\n try {\n entries = await readdir(filePath, { withFileTypes: true });\n } catch (error) {\n if (errorCode(error) === \"ENOENT\") return;\n throw error;\n }\n for (const entry of entries) {\n if (!entry.isFile()) continue;\n await namespaceMaintenanceFs.rm(path.join(filePath, entry.name), { force: true });\n }\n try {\n await rmdir(filePath);\n } catch (error) {\n if (errorCode(error) === \"ENOENT\") return;\n throw error;\n }\n}\n\nasync function tryAcquireNamespaceMaintenanceLock(\n config: PluginConfig,\n jobName: string,\n namespace: string\n): Promise<LockHandle | null> {\n const filePath = lockPath(config, jobName, namespace);\n await mkdir(path.dirname(filePath), { recursive: true });\n try {\n const lockId = randomUUID();\n await mkdir(filePath);\n const ownerPath = path.join(filePath, `${lockId}.json`);\n let handle: Awaited<ReturnType<typeof open>> | undefined;\n try {\n handle = await namespaceMaintenanceFs.open(ownerPath, \"wx\");\n await handle.writeFile(\n `${JSON.stringify({\n lockId,\n pid: process.pid,\n jobName,\n namespace,\n acquiredAt: new Date().toISOString(),\n })}\\n`,\n \"utf8\"\n );\n await handle.close();\n } catch (setupError) {\n await handle?.close().catch(() => undefined);\n await namespaceMaintenanceFs.rm(ownerPath, { force: true }).catch(() => undefined);\n await rmdir(filePath).catch(() => undefined);\n throw setupError;\n }\n return {\n path: filePath,\n async touch() {\n try {\n const parsed = JSON.parse(await readFile(ownerPath, \"utf8\")) as { lockId?: unknown };\n if (parsed.lockId === lockId) {\n const now = new Date();\n await utimes(ownerPath, now, now);\n await utimes(filePath, now, now);\n }\n } catch {}\n },\n async release() {\n try {\n const parsed = JSON.parse(await readFile(ownerPath, \"utf8\")) as { lockId?: unknown };\n if (parsed.lockId === lockId) {\n await namespaceMaintenanceFs.rm(ownerPath, { force: true });\n await rmdir(filePath).catch(() => undefined);\n }\n } catch {}\n },\n };\n } catch (error) {\n if (errorCode(error) === \"EEXIST\") {\n const staleMs =\n namespaceMaintenanceLockStaleMs(config);\n try {\n const s = await lstat(filePath);\n if (s.isSymbolicLink()) {\n return null;\n }\n if ((s.isFile() || s.isDirectory()) && Date.now() - s.mtimeMs > staleMs) {\n try {\n if (s.isDirectory()) {\n await removeStaleLockDirectory(filePath);\n } else {\n await namespaceMaintenanceFs.rm(filePath, { force: true });\n }\n } catch (removeError) {\n if (errorCode(removeError) === \"ENOENT\") {\n return tryAcquireNamespaceMaintenanceLock(config, jobName, namespace);\n }\n if (errorCode(removeError) === \"ENOTEMPTY\") {\n return null;\n }\n throw removeError;\n }\n return tryAcquireNamespaceMaintenanceLock(config, jobName, namespace);\n }\n } catch (statError) {\n if (errorCode(statError) === \"ENOENT\") {\n return tryAcquireNamespaceMaintenanceLock(config, jobName, namespace);\n }\n throw statError;\n }\n return null;\n }\n throw error;\n }\n}\n\nfunction statusBasePath(config: PluginConfig): string {\n return path.join(config.memoryDir, \"state\", STATUS_BASE);\n}\n\nfunction statusPath(config: PluginConfig, jobName: string, namespace: string): string {\n return path.join(statusBasePath(config), stablePathSegment(jobName), `${namespacePathSegment(namespace)}.json`);\n}\n\nfunction lastRanStatusPath(config: PluginConfig, jobName: string, namespace: string): string {\n return path.join(statusBasePath(config), stablePathSegment(jobName), `${namespacePathSegment(namespace)}.last-ran.json`);\n}\n\nfunction parseStatus(value: unknown): NamespaceMaintenanceRunStatus | null {\n if (!value || typeof value !== \"object\" || Array.isArray(value)) return null;\n const v = value as Partial<NamespaceMaintenanceRunStatus>;\n if (\n typeof v.namespace === \"string\" &&\n typeof v.jobName === \"string\" &&\n (v.state === \"ran\" || v.state === \"skipped\" || v.state === \"failed\") &&\n typeof v.startedAt === \"string\" &&\n typeof v.completedAt === \"string\"\n ) {\n return v as NamespaceMaintenanceRunStatus;\n }\n return null;\n}\n\nasync function readLatestStatusAtByNamespace(config: PluginConfig, jobName: string): Promise<Map<string, string>> {\n const latest = new Map<string, string>();\n const latestMs = new Map<string, number>();\n for (const status of [...(await readStatusFiles(config)), ...(await readLastRanStatusFiles(config))]) {\n if (status.state !== \"ran\") continue;\n if (status.jobName !== jobName) continue;\n const completedAtMs = Date.parse(status.completedAt);\n if (!Number.isFinite(completedAtMs)) continue;\n const previousMs = latestMs.get(status.namespace);\n if (previousMs !== undefined && previousMs >= completedAtMs) continue;\n latestMs.set(status.namespace, completedAtMs);\n latest.set(status.namespace, status.completedAt);\n }\n return latest;\n}\n\nasync function readStatusFile(filePath: string): Promise<NamespaceMaintenanceRunStatus | null> {\n try {\n const parsed = JSON.parse(await readFile(filePath, \"utf8\")) as unknown;\n return parseStatus(parsed);\n } catch {\n return null;\n }\n}\n\nasync function readStatusFiles(config: PluginConfig): Promise<NamespaceMaintenanceRunStatus[]> {\n if (typeof config.memoryDir !== \"string\" || config.memoryDir.length === 0) {\n return [];\n }\n const root = statusBasePath(config);\n const statuses: NamespaceMaintenanceRunStatus[] = [];\n let jobDirs: Dirent[];\n try {\n jobDirs = await readdir(root, { withFileTypes: true });\n } catch {\n return statuses;\n }\n for (const jobDir of jobDirs) {\n if (!jobDir.isDirectory()) continue;\n let files: Dirent[];\n try {\n files = await readdir(path.join(root, jobDir.name), { withFileTypes: true });\n } catch {\n continue;\n }\n for (const file of files) {\n if (!file.isFile() || !file.name.endsWith(\".json\")) continue;\n if (file.name.endsWith(\".last-ran.json\")) continue;\n const status = await readStatusFile(path.join(root, jobDir.name, file.name));\n if (status) statuses.push(status);\n }\n }\n return statuses;\n}\n\nasync function readLastRanStatusFiles(config: PluginConfig): Promise<NamespaceMaintenanceRunStatus[]> {\n if (typeof config.memoryDir !== \"string\" || config.memoryDir.length === 0) {\n return [];\n }\n const root = statusBasePath(config);\n const statuses: NamespaceMaintenanceRunStatus[] = [];\n let jobDirs: Dirent[];\n try {\n jobDirs = await readdir(root, { withFileTypes: true });\n } catch {\n return statuses;\n }\n for (const jobDir of jobDirs) {\n if (!jobDir.isDirectory()) continue;\n let files: Dirent[];\n try {\n files = await readdir(path.join(root, jobDir.name), { withFileTypes: true });\n } catch {\n continue;\n }\n for (const file of files) {\n if (!file.isFile() || !file.name.endsWith(\".last-ran.json\")) continue;\n const status = await readStatusFile(path.join(root, jobDir.name, file.name));\n if (status) statuses.push(status);\n }\n }\n return statuses;\n}\n\nasync function writeStatusPayload(target: string, status: NamespaceMaintenanceRunStatus): Promise<void> {\n const dir = path.dirname(target);\n await mkdir(dir, { recursive: true });\n const temp = `${target}.${process.pid}.${Date.now()}.${Math.random().toString(36).slice(2)}.tmp`;\n const payload = {\n version: 1,\n ...status,\n };\n await writeFile(temp, `${JSON.stringify(payload, null, 2)}\\n`, \"utf8\");\n await rename(temp, target);\n}\n\nasync function writeStatusFile(config: PluginConfig, status: NamespaceMaintenanceRunStatus): Promise<void> {\n await writeStatusPayload(statusPath(config, status.jobName, status.namespace), status);\n if (status.state === \"ran\") {\n await writeStatusPayload(lastRanStatusPath(config, status.jobName, status.namespace), status);\n }\n}\n\nasync function recordNamespaceMaintenanceStatusSafely(\n config: PluginConfig,\n status: NamespaceMaintenanceRunStatus\n): Promise<void> {\n try {\n await writeStatusFile(config, status);\n } catch {\n // Observability must not fail the maintenance operation.\n }\n}\n\nfunction maintenanceErrorDetail(error: unknown): string {\n return displayErrorDetail(error) || \"Error\";\n}\n\nexport async function readNamespaceMaintenanceStatuses(config: PluginConfig): Promise<NamespaceMaintenanceRunStatus[]> {\n return (await readStatusFiles(config)).sort((a, b) => {\n const byJob = a.jobName.localeCompare(b.jobName);\n if (byJob !== 0) return byJob;\n return a.namespace.localeCompare(b.namespace);\n });\n}\n\n/**\n * Read the last SUCCESSFUL run status per job+namespace (the\n * `<namespace>.last-ran.json` files written only when state === \"ran\").\n *\n * The latest status file (`<namespace>.json`) is overwritten on every run,\n * so after a successful run followed by a skip (budget/lock/cadence) the\n * latest file shows \"skipped\" and the prior success is invisible. Merging\n * these last-ran records into the health summary lets `lastRunAt` and run\n * history reflect the most recent successful maintenance (review #1622).\n */\nexport async function readNamespaceMaintenanceLastRanStatuses(\n config: PluginConfig,\n): Promise<NamespaceMaintenanceRunStatus[]> {\n return (await readLastRanStatusFiles(config)).sort((a, b) => {\n const byJob = a.jobName.localeCompare(b.jobName);\n if (byJob !== 0) return byJob;\n return a.namespace.localeCompare(b.namespace);\n });\n}\n\nexport type NamespaceMaintenancePlanRunnerResult = {\n itemCount?: number;\n /**\n * When `true`, the runner performed NO work for this namespace (e.g. the\n * job's own cadence gate throttled it). The planner records the namespace\n * as `state: \"skipped\"` with the given reason and does NOT touch the\n * catalog's `lastMaintenanceAt`, so a throttled namespace is not falsely\n * reported as maintained. Without this signal a runner that resolves\n * without throwing is always recorded as `state: \"ran\"`.\n */\n skipped?: boolean;\n skipReason?: string;\n} | undefined;\n\nexport async function runNamespaceMaintenancePlan(\n config: PluginConfig,\n plan: NamespaceMaintenancePlan,\n runner: (candidate: NamespaceMaintenanceCandidate) => Promise<NamespaceMaintenancePlanRunnerResult>,\n catalog?: NamespaceCatalog\n): Promise<NamespaceMaintenanceSummary> {\n const statuses: NamespaceMaintenanceRunStatus[] = [];\n\n for (const skipped of plan.skipped) {\n if (skipped.namespace === \"*\") continue;\n const now = new Date().toISOString();\n const status: NamespaceMaintenanceRunStatus = {\n namespace: skipped.namespace,\n jobName: plan.jobName,\n state: \"skipped\",\n reason: skipped.reason,\n startedAt: now,\n completedAt: now,\n };\n statuses.push(status);\n await recordNamespaceMaintenanceStatusSafely(config, status);\n }\n\n for (const candidate of plan.namespaces) {\n const startedAt = new Date().toISOString();\n const lock = await tryAcquireNamespaceMaintenanceLock(config, plan.jobName, candidate.namespace);\n if (!lock) {\n const completedAt = new Date().toISOString();\n const status: NamespaceMaintenanceRunStatus = {\n namespace: candidate.namespace,\n jobName: plan.jobName,\n state: \"skipped\",\n reason: \"lock_held\",\n startedAt,\n completedAt,\n };\n statuses.push(status);\n await recordNamespaceMaintenanceStatusSafely(config, status);\n continue;\n }\n try {\n const result = await withNamespaceMaintenanceLockHeartbeat(config, lock, () => runner(candidate));\n const completedAt = new Date().toISOString();\n if (result?.skipped) {\n // The runner performed no work (e.g. the job's own cadence gate\n // throttled this namespace). Record skipped WITHOUT touching the\n // catalog's lastMaintenanceAt so a throttled namespace is not\n // falsely reported as maintained.\n const status: NamespaceMaintenanceRunStatus = {\n namespace: candidate.namespace,\n jobName: plan.jobName,\n state: \"skipped\",\n reason: (result.skipReason ?? \"throttled\") as NamespaceMaintenanceSkipReason,\n startedAt,\n completedAt,\n };\n statuses.push(status);\n await recordNamespaceMaintenanceStatusSafely(config, status);\n } else {\n const status: NamespaceMaintenanceRunStatus = {\n namespace: candidate.namespace,\n jobName: plan.jobName,\n state: \"ran\",\n startedAt,\n completedAt,\n itemCount: result?.itemCount,\n };\n statuses.push(status);\n await recordNamespaceMaintenanceStatusSafely(config, status);\n try {\n await catalog?.markMaintenance(candidate.namespace, plan.jobName, new Date(completedAt));\n } catch {\n // Catalog maintenance touches are best-effort status metadata.\n }\n }\n } catch (error) {\n const completedAt = new Date().toISOString();\n const status: NamespaceMaintenanceRunStatus = {\n namespace: candidate.namespace,\n jobName: plan.jobName,\n state: \"failed\",\n reason: \"job_failed\",\n startedAt,\n completedAt,\n error: maintenanceErrorDetail(error),\n };\n statuses.push(status);\n await recordNamespaceMaintenanceStatusSafely(config, status);\n } finally {\n await lock.release().catch(() => undefined);\n }\n }\n\n return {\n jobName: plan.jobName,\n generatedAt: new Date().toISOString(),\n ran: statuses.filter((s) => s.state === \"ran\").length,\n skipped: statuses.filter((s) => s.state === \"skipped\").length,\n failed: statuses.filter((s) => s.state === \"failed\").length,\n statuses,\n };\n}\n\nexport async function runNamespaceMaintenanceBatchPlan(\n config: PluginConfig,\n plan: NamespaceMaintenancePlan,\n runner: (candidates: NamespaceMaintenanceCandidate[]) => Promise<NamespaceMaintenanceBatchRunResult | undefined>,\n catalog?: NamespaceCatalog,\n options: NamespaceMaintenanceBatchRunOptions = {}\n): Promise<NamespaceMaintenanceSummary> {\n const statuses: NamespaceMaintenanceRunStatus[] = [];\n\n for (const skipped of plan.skipped) {\n if (skipped.namespace === \"*\") continue;\n const now = new Date().toISOString();\n const status: NamespaceMaintenanceRunStatus = {\n namespace: skipped.namespace,\n jobName: plan.jobName,\n state: \"skipped\",\n reason: skipped.reason,\n startedAt: now,\n completedAt: now,\n };\n statuses.push(status);\n await recordNamespaceMaintenanceStatusSafely(config, status);\n }\n\n const acquired: Array<{\n candidate: NamespaceMaintenanceCandidate;\n lock: LockHandle;\n startedAt: string;\n }> = [];\n\n try {\n for (const candidate of plan.namespaces) {\n const startedAt = new Date().toISOString();\n const lock = await tryAcquireNamespaceMaintenanceLock(config, plan.jobName, candidate.namespace);\n if (!lock) {\n const completedAt = new Date().toISOString();\n const status: NamespaceMaintenanceRunStatus = {\n namespace: candidate.namespace,\n jobName: plan.jobName,\n state: \"skipped\",\n reason: \"lock_held\",\n startedAt,\n completedAt,\n };\n statuses.push(status);\n await recordNamespaceMaintenanceStatusSafely(config, status);\n continue;\n }\n acquired.push({ candidate, lock, startedAt });\n }\n } catch (error) {\n await Promise.all(acquired.map(({ lock }) => lock.release().catch(() => undefined)));\n throw error;\n }\n\n if (options.requireAllLocks && acquired.length > 0 && acquired.length < plan.namespaces.length) {\n for (const { candidate, startedAt } of acquired) {\n const completedAt = new Date().toISOString();\n const status: NamespaceMaintenanceRunStatus = {\n namespace: candidate.namespace,\n jobName: plan.jobName,\n state: \"skipped\",\n reason: \"batch_lock_incomplete\",\n startedAt,\n completedAt,\n };\n statuses.push(status);\n await recordNamespaceMaintenanceStatusSafely(config, status);\n }\n await Promise.all(acquired.map(({ lock }) => lock.release().catch(() => undefined)));\n return {\n jobName: plan.jobName,\n generatedAt: new Date().toISOString(),\n ran: statuses.filter((s) => s.state === \"ran\").length,\n skipped: statuses.filter((s) => s.state === \"skipped\").length,\n failed: statuses.filter((s) => s.state === \"failed\").length,\n statuses,\n };\n }\n\n if (acquired.length === 0) {\n return {\n jobName: plan.jobName,\n generatedAt: new Date().toISOString(),\n ran: statuses.filter((s) => s.state === \"ran\").length,\n skipped: statuses.filter((s) => s.state === \"skipped\").length,\n failed: statuses.filter((s) => s.state === \"failed\").length,\n statuses,\n };\n }\n\n try {\n const result = await withNamespaceMaintenanceLockHeartbeat(\n config,\n acquired.map(({ lock }) => lock),\n () => runner(acquired.map(({ candidate }) => candidate)),\n );\n for (const { candidate, startedAt } of acquired) {\n const completedAt = new Date().toISOString();\n const status: NamespaceMaintenanceRunStatus = {\n namespace: candidate.namespace,\n jobName: plan.jobName,\n state: \"ran\",\n startedAt,\n completedAt,\n itemCount: itemCountForNamespace(result, candidate.namespace),\n };\n statuses.push(status);\n await recordNamespaceMaintenanceStatusSafely(config, status);\n try {\n await catalog?.markMaintenance(candidate.namespace, plan.jobName, new Date(completedAt));\n } catch {\n // Catalog maintenance touches are best-effort status metadata.\n }\n }\n } catch (error) {\n const skipReason = options.skipReasonForError?.(error);\n for (const { candidate, startedAt } of acquired) {\n const completedAt = new Date().toISOString();\n const status: NamespaceMaintenanceRunStatus = skipReason\n ? {\n namespace: candidate.namespace,\n jobName: plan.jobName,\n state: \"skipped\",\n reason: skipReason,\n startedAt,\n completedAt,\n }\n : {\n namespace: candidate.namespace,\n jobName: plan.jobName,\n state: \"failed\",\n reason: \"job_failed\",\n startedAt,\n completedAt,\n error: maintenanceErrorDetail(error),\n };\n statuses.push(status);\n await recordNamespaceMaintenanceStatusSafely(config, status);\n }\n } finally {\n await Promise.all(acquired.map(({ lock }) => lock.release().catch(() => undefined)));\n }\n\n return {\n jobName: plan.jobName,\n generatedAt: new Date().toISOString(),\n ran: statuses.filter((s) => s.state === \"ran\").length,\n skipped: statuses.filter((s) => s.state === \"skipped\").length,\n failed: statuses.filter((s) => s.state === \"failed\").length,\n statuses,\n };\n}\n\nfunction itemCountForNamespace(\n result: NamespaceMaintenanceBatchRunResult | undefined,\n namespace: string,\n): number | undefined {\n const itemCounts = result?.itemCounts;\n if (itemCounts instanceof Map) return itemCounts.get(namespace);\n if (itemCounts && Object.prototype.hasOwnProperty.call(itemCounts, namespace)) {\n return itemCounts[namespace];\n }\n return result?.itemCount;\n}\n"],"mappings":"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","names":[]}
|