@indigoai-us/hq-cli 5.126.0 → 5.128.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +218 -0
- package/assets/lanes-hooks/hq-lanes-drain.json +15 -0
- package/assets/lanes-hooks/hq-lanes-drain.mjs +254 -0
- package/assets/lanes-hooks/hq-lanes-drain.sh +21 -0
- package/assets/lanes-reconcile/hq-lanes-reconcile.service +11 -0
- package/assets/lanes-reconcile/hq-lanes-reconcile.timer +12 -0
- package/dist/command-catalog.generated.d.ts +245 -11
- package/dist/command-catalog.generated.js +323 -11
- package/dist/command-registration-plan.d.ts +1 -0
- package/dist/command-registration-plan.js +11 -2
- package/dist/commands/agents.d.ts +12 -0
- package/dist/commands/agents.js +46 -2
- package/dist/commands/lanes.d.ts +55 -16
- package/dist/commands/lanes.js +1187 -250
- package/dist/commands/mesh.js +254 -19
- package/dist/lib/core-utils/sentry-report.d.ts +2 -0
- package/dist/lib/core-utils/sentry-report.js +47 -7
- package/dist/lib/lanes/accounts.d.ts +67 -7
- package/dist/lib/lanes/accounts.js +233 -43
- package/dist/lib/lanes/compaction.d.ts +96 -0
- package/dist/lib/lanes/compaction.js +197 -0
- package/dist/lib/lanes/config.d.ts +15 -0
- package/dist/lib/lanes/config.js +65 -8
- package/dist/lib/lanes/delivery.d.ts +151 -0
- package/dist/lib/lanes/delivery.js +354 -0
- package/dist/lib/lanes/dropbox.d.ts +96 -0
- package/dist/lib/lanes/dropbox.js +408 -0
- package/dist/lib/lanes/effort.d.ts +50 -0
- package/dist/lib/lanes/effort.js +109 -0
- package/dist/lib/lanes/errors.d.ts +2 -2
- package/dist/lib/lanes/hooks-install.d.ts +81 -0
- package/dist/lib/lanes/hooks-install.js +326 -0
- package/dist/lib/lanes/hq-root.d.ts +19 -0
- package/dist/lib/lanes/hq-root.js +28 -0
- package/dist/lib/lanes/index.d.ts +35 -5
- package/dist/lib/lanes/index.js +19 -4
- package/dist/lib/lanes/liveness.d.ts +7 -1
- package/dist/lib/lanes/liveness.js +64 -5
- package/dist/lib/lanes/machine-identity.d.ts +34 -0
- package/dist/lib/lanes/machine-identity.js +92 -0
- package/dist/lib/lanes/mesh-events.d.ts +109 -0
- package/dist/lib/lanes/mesh-events.js +275 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +65 -0
- package/dist/lib/lanes/outpost-dispatch.js +90 -0
- package/dist/lib/lanes/questions.d.ts +75 -0
- package/dist/lib/lanes/questions.js +243 -0
- package/dist/lib/lanes/quota.d.ts +95 -0
- package/dist/lib/lanes/quota.js +320 -0
- package/dist/lib/lanes/reconcile-install.d.ts +69 -0
- package/dist/lib/lanes/reconcile-install.js +221 -0
- package/dist/lib/lanes/reconcile.d.ts +102 -0
- package/dist/lib/lanes/reconcile.js +985 -0
- package/dist/lib/lanes/registry.d.ts +18 -5
- package/dist/lib/lanes/registry.js +293 -19
- package/dist/lib/lanes/spawn.d.ts +274 -0
- package/dist/lib/lanes/spawn.js +1363 -0
- package/dist/lib/lanes/store.d.ts +7 -3
- package/dist/lib/lanes/store.js +150 -32
- package/dist/lib/lanes/types.d.ts +346 -4
- package/dist/lib/lanes/types.js +138 -5
- package/dist/lib/lanes/watcher-main.d.ts +9 -0
- package/dist/lib/lanes/watcher-main.js +22 -0
- package/dist/lib/lanes/watcher.d.ts +216 -0
- package/dist/lib/lanes/watcher.js +859 -0
- package/dist/lib/mesh/api.d.ts +71 -7
- package/dist/lib/mesh/api.js +167 -28
- package/dist/lib/mesh/live/backfill-held.d.ts +18 -7
- package/dist/lib/mesh/live/backfill-held.js +66 -21
- package/dist/lib/mesh/live/daemon/run.d.ts +2 -0
- package/dist/lib/mesh/live/daemon/run.js +111 -54
- package/dist/lib/mesh/live/daemon/state.d.ts +2 -0
- package/dist/lib/mesh/live/daemon/transcript-watch.d.ts +3 -0
- package/dist/lib/mesh/live/daemon/transcript-watch.js +31 -8
- package/dist/lib/scan-packages/index.d.ts +2 -0
- package/dist/lib/scan-packages/index.js +1 -1
- package/dist/lib/work-context/state.d.ts +12 -0
- package/dist/lib/work-context/state.js +131 -3
- package/package.json +3 -1
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* compare-and-set on `version`. Histories are capped at 50 at the append site.
|
|
8
8
|
*/
|
|
9
9
|
import * as fs from "node:fs";
|
|
10
|
-
import { type AttributionHistoryEntry, type LaneRecord, type QuestionRecord, type RoutingHistoryEntry, type SeniorHistoryEntry } from "./types.js";
|
|
10
|
+
import { type AttributionHistoryEntry, type LaneRecord, type LaneWriter, type QuestionRecord, type RoutingHistoryEntry, type SeniorHistoryEntry } from "./types.js";
|
|
11
11
|
export declare const LANES_STORE_REL: string;
|
|
12
12
|
export declare const LANE_RECORDS_DIR = "lanes";
|
|
13
13
|
export declare const QUESTION_RECORDS_DIR = "questions";
|
|
@@ -27,7 +27,7 @@ export interface StoreDeps {
|
|
|
27
27
|
fsyncSync: typeof fs.fsyncSync;
|
|
28
28
|
chmodSync: typeof fs.chmodSync;
|
|
29
29
|
}
|
|
30
|
-
export declare function resolveStoreHqRoot(hqRoot?: string): string;
|
|
30
|
+
export declare function resolveStoreHqRoot(hqRoot?: string, env?: NodeJS.ProcessEnv, cwd?: string): string;
|
|
31
31
|
export declare function lanesStoreRoot(hqRoot?: string): string;
|
|
32
32
|
export declare function laneRecordPath(hqRoot: string, laneId: string): string;
|
|
33
33
|
export declare function questionRecordPath(hqRoot: string, questionId: string): string;
|
|
@@ -53,7 +53,11 @@ export declare function readLane(hqRoot: string, laneId: string, deps?: StoreDep
|
|
|
53
53
|
export declare function tryReadQuestion(hqRoot: string, questionId: string, deps?: StoreDeps): QuestionRecord | null;
|
|
54
54
|
export declare function readQuestion(hqRoot: string, questionId: string, deps?: StoreDeps): QuestionRecord;
|
|
55
55
|
export declare function createLane(hqRoot: string, init: Omit<LaneRecord, "schema_version" | "lane_id" | "version" | "created_at" | "updated_at"> & Partial<Pick<LaneRecord, "schema_version" | "lane_id" | "version" | "created_at" | "updated_at">>, deps?: StoreDeps): LaneRecord;
|
|
56
|
-
export declare function updateLane(hqRoot: string, laneId: string, expectedVersion: number, updater: (current: LaneRecord) => LaneRecord, deps?: StoreDeps): LaneRecord;
|
|
56
|
+
export declare function updateLane(hqRoot: string, laneId: string, expectedVersion: number, writer: LaneWriter, updater: (current: LaneRecord) => LaneRecord, deps?: StoreDeps): LaneRecord;
|
|
57
|
+
/** Compare-and-set with conflict retry. Callers that race a concurrent writer re-read. */
|
|
58
|
+
export declare function retryUpdateLane(hqRoot: string, laneId: string, writer: LaneWriter, updater: (current: LaneRecord) => LaneRecord, deps?: StoreDeps, attempts?: number): LaneRecord;
|
|
59
|
+
/** Compare-and-set with conflict retry for question records (spec §6.2). */
|
|
60
|
+
export declare function retryUpdateQuestion(hqRoot: string, questionId: string, updater: (current: QuestionRecord) => QuestionRecord, deps?: StoreDeps, attempts?: number): QuestionRecord;
|
|
57
61
|
export declare function appendLaneSeniorHistory(hqRoot: string, laneId: string, expectedVersion: number, entry: SeniorHistoryEntry, deps?: StoreDeps): LaneRecord;
|
|
58
62
|
export declare function appendLaneAttribution(hqRoot: string, laneId: string, expectedVersion: number, entry: AttributionHistoryEntry, deps?: StoreDeps): LaneRecord;
|
|
59
63
|
export declare function createQuestion(hqRoot: string, init: Omit<QuestionRecord, "schema_version" | "question_id" | "version"> & Partial<Pick<QuestionRecord, "schema_version" | "question_id" | "version">>, deps?: StoreDeps): QuestionRecord;
|
package/dist/lib/lanes/store.js
CHANGED
|
@@ -11,9 +11,9 @@ import * as fs from "node:fs";
|
|
|
11
11
|
import * as os from "node:os";
|
|
12
12
|
import * as path from "node:path";
|
|
13
13
|
import { generateUlid } from "../mesh/live/ulid.js";
|
|
14
|
-
import {
|
|
15
|
-
import { LaneNotFoundError, LaneSchemaError, LaneVersionConflict, LanesError, } from "./errors.js";
|
|
16
|
-
import { HISTORY_CAP, HOST_KINDS, LANE_SCHEMA_VERSION, QUESTION_SCHEMA_VERSION, SENIOR_KINDS, isHostKind, isLaneProvider, isLaneState, isQuestionStatus, isSeniorKind, parseMajorSchema, } from "./types.js";
|
|
14
|
+
import { resolveLanesHqRoot } from "./hq-root.js";
|
|
15
|
+
import { LaneNotFoundError, LaneSchemaError, LaneVersionConflict, LanesError, isLaneVersionConflict, } from "./errors.js";
|
|
16
|
+
import { HISTORY_CAP, HOST_KINDS, LANE_SCHEMA_VERSION, QUESTION_SCHEMA_VERSION, SENIOR_KINDS, isHostKind, isLaneProvider, isLaneState, isQuestionStatus, isSeniorKind, parseMajorSchema, REQUIRED_LANE_RECORD_FIELDS, LANE_FIELD_WRITERS, LANE_WRITERS, } from "./types.js";
|
|
17
17
|
export const LANES_STORE_REL = path.join("workspace", "lanes");
|
|
18
18
|
export const LANE_RECORDS_DIR = "lanes";
|
|
19
19
|
export const QUESTION_RECORDS_DIR = "questions";
|
|
@@ -38,8 +38,8 @@ function defaultStoreDeps() {
|
|
|
38
38
|
chmodSync: fs.chmodSync.bind(fs),
|
|
39
39
|
};
|
|
40
40
|
}
|
|
41
|
-
export function resolveStoreHqRoot(hqRoot) {
|
|
42
|
-
return hqRoot ??
|
|
41
|
+
export function resolveStoreHqRoot(hqRoot, env = process.env, cwd) {
|
|
42
|
+
return hqRoot ?? resolveLanesHqRoot({ env, cwd });
|
|
43
43
|
}
|
|
44
44
|
export function lanesStoreRoot(hqRoot) {
|
|
45
45
|
return path.join(resolveStoreHqRoot(hqRoot), LANES_STORE_REL);
|
|
@@ -250,12 +250,35 @@ function requireInt(value, field) {
|
|
|
250
250
|
}
|
|
251
251
|
return value;
|
|
252
252
|
}
|
|
253
|
+
/**
|
|
254
|
+
* Company on a lane/question record is either a slug+uid pair or JSON `null`.
|
|
255
|
+
* Absence (omitted key) is normalised to `null`. Empty strings stay a schema
|
|
256
|
+
* error so a no-company lane cannot be confused with a broken company object.
|
|
257
|
+
*/
|
|
258
|
+
function parseOptionalCompanyField(raw, filePath) {
|
|
259
|
+
if (raw.company === undefined) {
|
|
260
|
+
raw.company = null;
|
|
261
|
+
return;
|
|
262
|
+
}
|
|
263
|
+
if (raw.company === null)
|
|
264
|
+
return;
|
|
265
|
+
if (!isPlainObject(raw.company)) {
|
|
266
|
+
throw new LaneSchemaError("company must be an object", { path: filePath });
|
|
267
|
+
}
|
|
268
|
+
requireString(raw.company.slug, "company.slug");
|
|
269
|
+
requireString(raw.company.uid, "company.uid");
|
|
270
|
+
}
|
|
253
271
|
export function parseLaneRecord(raw, filePath) {
|
|
254
272
|
if (!isPlainObject(raw)) {
|
|
255
273
|
throw new LaneSchemaError("Lane record must be a JSON object", {
|
|
256
274
|
path: filePath,
|
|
257
275
|
});
|
|
258
276
|
}
|
|
277
|
+
for (const field of REQUIRED_LANE_RECORD_FIELDS) {
|
|
278
|
+
if (!(field in raw)) {
|
|
279
|
+
throw new LaneSchemaError(`Missing required lane record field ${field}`, { path: filePath, field });
|
|
280
|
+
}
|
|
281
|
+
}
|
|
259
282
|
const schema_version = requireString(raw.schema_version, "schema_version");
|
|
260
283
|
parseMajorSchema(schema_version, "hq-lane", 1);
|
|
261
284
|
requireString(raw.lane_id, "lane_id");
|
|
@@ -270,11 +293,7 @@ export function parseLaneRecord(raw, filePath) {
|
|
|
270
293
|
if (!isLaneProvider(raw.provider)) {
|
|
271
294
|
throw new LaneSchemaError(`Unknown provider ${JSON.stringify(raw.provider)}`, { path: filePath, value: raw.provider });
|
|
272
295
|
}
|
|
273
|
-
|
|
274
|
-
throw new LaneSchemaError("company must be an object", { path: filePath });
|
|
275
|
-
}
|
|
276
|
-
requireString(raw.company.slug, "company.slug");
|
|
277
|
-
requireString(raw.company.uid, "company.uid");
|
|
296
|
+
parseOptionalCompanyField(raw, filePath);
|
|
278
297
|
if (!isPlainObject(raw.senior)) {
|
|
279
298
|
throw new LaneSchemaError("senior must be an object", { path: filePath });
|
|
280
299
|
}
|
|
@@ -330,6 +349,7 @@ export function parseQuestionRecord(raw, filePath) {
|
|
|
330
349
|
requireString(raw.lane_id, "lane_id");
|
|
331
350
|
requireInt(raw.version, "version");
|
|
332
351
|
requireInt(raw.hop, "hop");
|
|
352
|
+
parseOptionalCompanyField(raw, filePath);
|
|
333
353
|
if (!isPlainObject(raw.body)) {
|
|
334
354
|
throw new LaneSchemaError("body must be an object", { path: filePath });
|
|
335
355
|
}
|
|
@@ -338,6 +358,13 @@ export function parseQuestionRecord(raw, filePath) {
|
|
|
338
358
|
path: filePath,
|
|
339
359
|
});
|
|
340
360
|
}
|
|
361
|
+
// Optional on the record, but merge-on-read spreads it; a non-array from a
|
|
362
|
+
// synced sidecar must fail here, inside the per-sidecar skip, not later.
|
|
363
|
+
if (raw.answer_history !== undefined && !Array.isArray(raw.answer_history)) {
|
|
364
|
+
throw new LaneSchemaError("answer_history must be an array when present", {
|
|
365
|
+
path: filePath,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
341
368
|
return raw;
|
|
342
369
|
}
|
|
343
370
|
/**
|
|
@@ -378,9 +405,13 @@ function canonicalJson(value) {
|
|
|
378
405
|
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(obj[key])}`).join(",")}}`;
|
|
379
406
|
}
|
|
380
407
|
function historyTimestamp(entry) {
|
|
381
|
-
if (!isPlainObject(entry)
|
|
408
|
+
if (!isPlainObject(entry))
|
|
382
409
|
return "";
|
|
383
|
-
|
|
410
|
+
if (typeof entry.at === "string")
|
|
411
|
+
return entry.at;
|
|
412
|
+
if (typeof entry.answered_at === "string")
|
|
413
|
+
return entry.answered_at;
|
|
414
|
+
return "";
|
|
384
415
|
}
|
|
385
416
|
/**
|
|
386
417
|
* Attribution identity is the message that caused the re-point (spec §2).
|
|
@@ -395,6 +426,12 @@ function attributionIdentity(entry) {
|
|
|
395
426
|
function entryIdentity(entry) {
|
|
396
427
|
return canonicalJson(entry);
|
|
397
428
|
}
|
|
429
|
+
function questionAnswerHistory(record) {
|
|
430
|
+
return [
|
|
431
|
+
...(record.answer_history ?? []),
|
|
432
|
+
...(record.answer ? [record.answer] : []),
|
|
433
|
+
];
|
|
434
|
+
}
|
|
398
435
|
function unionHistory(lists, identity, preferred) {
|
|
399
436
|
const map = new Map();
|
|
400
437
|
for (const entry of preferred) {
|
|
@@ -438,6 +475,7 @@ export function mergeQuestionRecords(live, sidecars) {
|
|
|
438
475
|
const base = higherVersionRecord(live, sidecars);
|
|
439
476
|
return {
|
|
440
477
|
...base,
|
|
478
|
+
answer_history: unionHistory(all.map(questionAnswerHistory), entryIdentity, questionAnswerHistory(base)),
|
|
441
479
|
routing_history: unionHistory(all.map((record) => record.routing_history), entryIdentity, base.routing_history),
|
|
442
480
|
};
|
|
443
481
|
}
|
|
@@ -487,6 +525,17 @@ export function readQuestion(hqRoot, questionId, deps = defaultStoreDeps()) {
|
|
|
487
525
|
throw new LaneNotFoundError("question", questionId);
|
|
488
526
|
return record;
|
|
489
527
|
}
|
|
528
|
+
function recordsEquivalentAsideFromClock(current, updated) {
|
|
529
|
+
if (updated === current)
|
|
530
|
+
return true;
|
|
531
|
+
const strip = (record) => {
|
|
532
|
+
const copy = { ...record };
|
|
533
|
+
delete copy.version;
|
|
534
|
+
delete copy.updated_at;
|
|
535
|
+
return copy;
|
|
536
|
+
};
|
|
537
|
+
return JSON.stringify(strip(current)) === JSON.stringify(strip(updated));
|
|
538
|
+
}
|
|
490
539
|
function casWrite(filePath, expectedVersion, parse, merge, updater, deps) {
|
|
491
540
|
return withRecordLock(filePath, deps, () => {
|
|
492
541
|
const current = readMergedRecord(filePath, parse, merge, deps);
|
|
@@ -503,6 +552,9 @@ function casWrite(filePath, expectedVersion, parse, merge, updater, deps) {
|
|
|
503
552
|
});
|
|
504
553
|
}
|
|
505
554
|
const updated = updater(current);
|
|
555
|
+
if (recordsEquivalentAsideFromClock(current, updated)) {
|
|
556
|
+
return current;
|
|
557
|
+
}
|
|
506
558
|
const next = {
|
|
507
559
|
...current,
|
|
508
560
|
...updated,
|
|
@@ -536,17 +588,76 @@ export function createLane(hqRoot, init, deps = defaultStoreDeps()) {
|
|
|
536
588
|
return record;
|
|
537
589
|
});
|
|
538
590
|
}
|
|
539
|
-
|
|
540
|
-
|
|
591
|
+
/** Value equality over JSON-shaped record fields; the clone breaks reference identity. */
|
|
592
|
+
function fieldUnchanged(before, after) {
|
|
593
|
+
if (Object.is(before, after))
|
|
594
|
+
return true;
|
|
595
|
+
if (before === undefined || after === undefined)
|
|
596
|
+
return false;
|
|
597
|
+
return JSON.stringify(before) === JSON.stringify(after);
|
|
598
|
+
}
|
|
599
|
+
export function updateLane(hqRoot, laneId, expectedVersion, writer, updater, deps = defaultStoreDeps()) {
|
|
600
|
+
if (!LANE_WRITERS.includes(writer)) {
|
|
601
|
+
throw new LanesError("lane_writer_unknown", `Unknown lane writer ${String(writer)}`, { writer });
|
|
602
|
+
}
|
|
603
|
+
return casWrite(laneRecordPath(hqRoot, laneId), expectedVersion, parseLaneRecord, mergeLaneRecords, (current) => {
|
|
604
|
+
// `current` is the pre-update snapshot and stays untouched: the updater
|
|
605
|
+
// receives a deep clone, so mutating its argument in place cannot reach
|
|
606
|
+
// the record the ownership check compares against.
|
|
607
|
+
const updated = updater(structuredClone(current));
|
|
608
|
+
for (const key of Object.keys(updated)) {
|
|
609
|
+
if (key === "version" || key === "updated_at" || fieldUnchanged(current[key], updated[key]))
|
|
610
|
+
continue;
|
|
611
|
+
const allowedWriters = LANE_FIELD_WRITERS[key];
|
|
612
|
+
// Unknown forward-version fields are preserved, never assigned an
|
|
613
|
+
// ownership rule by a v1 reader.
|
|
614
|
+
if (allowedWriters && !allowedWriters.includes(writer)) {
|
|
615
|
+
throw new LanesError("lane_writer_unauthorized", `Writer ${writer} cannot write lane field ${key}`, { writer, field: key });
|
|
616
|
+
}
|
|
617
|
+
}
|
|
618
|
+
return updated;
|
|
619
|
+
}, deps);
|
|
620
|
+
}
|
|
621
|
+
/** Compare-and-set with conflict retry. Callers that race a concurrent writer re-read. */
|
|
622
|
+
export function retryUpdateLane(hqRoot, laneId, writer, updater, deps = defaultStoreDeps(), attempts = 5) {
|
|
623
|
+
let last;
|
|
624
|
+
for (let i = 0; i < attempts; i += 1) {
|
|
625
|
+
try {
|
|
626
|
+
const current = readLane(hqRoot, laneId, deps);
|
|
627
|
+
return updateLane(hqRoot, laneId, current.version, writer, updater, deps);
|
|
628
|
+
}
|
|
629
|
+
catch (err) {
|
|
630
|
+
last = err;
|
|
631
|
+
if (!isLaneVersionConflict(err) || i === attempts - 1)
|
|
632
|
+
throw err;
|
|
633
|
+
}
|
|
634
|
+
}
|
|
635
|
+
throw last;
|
|
636
|
+
}
|
|
637
|
+
/** Compare-and-set with conflict retry for question records (spec §6.2). */
|
|
638
|
+
export function retryUpdateQuestion(hqRoot, questionId, updater, deps = defaultStoreDeps(), attempts = 5) {
|
|
639
|
+
let last;
|
|
640
|
+
for (let i = 0; i < attempts; i += 1) {
|
|
641
|
+
try {
|
|
642
|
+
const current = readQuestion(hqRoot, questionId, deps);
|
|
643
|
+
return updateQuestion(hqRoot, questionId, current.version, updater, deps);
|
|
644
|
+
}
|
|
645
|
+
catch (err) {
|
|
646
|
+
last = err;
|
|
647
|
+
if (!isLaneVersionConflict(err) || i === attempts - 1)
|
|
648
|
+
throw err;
|
|
649
|
+
}
|
|
650
|
+
}
|
|
651
|
+
throw last;
|
|
541
652
|
}
|
|
542
653
|
export function appendLaneSeniorHistory(hqRoot, laneId, expectedVersion, entry, deps = defaultStoreDeps()) {
|
|
543
|
-
return updateLane(hqRoot, laneId, expectedVersion, (current) => ({
|
|
654
|
+
return updateLane(hqRoot, laneId, expectedVersion, "command", (current) => ({
|
|
544
655
|
...current,
|
|
545
656
|
senior_history: appendCappedHistory(current.senior_history, entry),
|
|
546
657
|
}), deps);
|
|
547
658
|
}
|
|
548
659
|
export function appendLaneAttribution(hqRoot, laneId, expectedVersion, entry, deps = defaultStoreDeps()) {
|
|
549
|
-
return updateLane(hqRoot, laneId, expectedVersion, (current) => ({
|
|
660
|
+
return updateLane(hqRoot, laneId, expectedVersion, "command", (current) => ({
|
|
550
661
|
...current,
|
|
551
662
|
attribution_history: appendCappedHistory(current.attribution_history, entry),
|
|
552
663
|
}), deps);
|
|
@@ -600,28 +711,35 @@ export function appendQuestionRouting(hqRoot, questionId, expectedVersion, entry
|
|
|
600
711
|
routing_history: appendCappedHistory(current.routing_history, entry),
|
|
601
712
|
}), deps);
|
|
602
713
|
}
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
if (!deps.existsSync(dir))
|
|
606
|
-
return [];
|
|
607
|
-
return deps
|
|
608
|
-
.readdirSync(dir)
|
|
714
|
+
function jsonRecordIds(names) {
|
|
715
|
+
return names
|
|
609
716
|
.filter((name) => typeof name === "string" &&
|
|
610
717
|
name.endsWith(".json") &&
|
|
611
718
|
!name.startsWith(".") &&
|
|
612
719
|
!name.includes(".conflict-"))
|
|
613
720
|
.map((name) => name.slice(0, -".json".length));
|
|
614
721
|
}
|
|
722
|
+
/**
|
|
723
|
+
* Absent directory → empty list. A path that exists but cannot be listed
|
|
724
|
+
* (ENOTDIR, EACCES, …) is unknown, not empty — throw `store_unreadable`.
|
|
725
|
+
*/
|
|
726
|
+
function listRecordIds(dir, label, deps) {
|
|
727
|
+
let names;
|
|
728
|
+
try {
|
|
729
|
+
names = deps.readdirSync(dir);
|
|
730
|
+
}
|
|
731
|
+
catch (err) {
|
|
732
|
+
const code = err.code;
|
|
733
|
+
if (code === "ENOENT")
|
|
734
|
+
return [];
|
|
735
|
+
throw new LanesError("store_unreadable", `Failed to read ${label} directory ${dir}: ${err instanceof Error ? err.message : String(err)}`, { path: dir });
|
|
736
|
+
}
|
|
737
|
+
return jsonRecordIds(names);
|
|
738
|
+
}
|
|
739
|
+
export function listLaneIds(hqRoot, deps = defaultStoreDeps()) {
|
|
740
|
+
return listRecordIds(path.join(lanesStoreRoot(hqRoot), LANE_RECORDS_DIR), "lane", deps);
|
|
741
|
+
}
|
|
615
742
|
export function listQuestionIds(hqRoot, deps = defaultStoreDeps()) {
|
|
616
|
-
|
|
617
|
-
if (!deps.existsSync(dir))
|
|
618
|
-
return [];
|
|
619
|
-
return deps
|
|
620
|
-
.readdirSync(dir)
|
|
621
|
-
.filter((name) => typeof name === "string" &&
|
|
622
|
-
name.endsWith(".json") &&
|
|
623
|
-
!name.startsWith(".") &&
|
|
624
|
-
!name.includes(".conflict-"))
|
|
625
|
-
.map((name) => name.slice(0, -".json".length));
|
|
743
|
+
return listRecordIds(path.join(lanesStoreRoot(hqRoot), QUESTION_RECORDS_DIR), "question", deps);
|
|
626
744
|
}
|
|
627
745
|
//# sourceMappingURL=store.js.map
|