@indigoai-us/hq-cli 5.204.0 → 5.206.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 +25 -0
- package/dist/command-catalog.generated.d.ts +6 -0
- package/dist/command-catalog.generated.js +8 -0
- package/dist/commands/lanes.js +53 -25
- package/dist/lib/lanes/admission.js +6 -0
- package/dist/lib/lanes/hire-grants.d.ts +4 -0
- package/dist/lib/lanes/hire-grants.js +62 -0
- package/dist/lib/lanes/index.d.ts +1 -1
- package/dist/lib/lanes/index.js +1 -1
- package/dist/lib/lanes/keep-alive.d.ts +8 -0
- package/dist/lib/lanes/keep-alive.js +14 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +1 -0
- package/dist/lib/lanes/outpost-dispatch.js +2 -0
- package/dist/lib/lanes/profile.d.ts +1 -0
- package/dist/lib/lanes/profile.js +8 -0
- package/dist/lib/lanes/provider-rejection.d.ts +1 -1
- package/dist/lib/lanes/provider-rejection.js +15 -5
- package/dist/lib/lanes/reconcile.d.ts +3 -0
- package/dist/lib/lanes/reconcile.js +273 -4
- package/dist/lib/lanes/store.js +25 -0
- package/dist/lib/lanes/types.d.ts +10 -0
- package/dist/lib/lanes/types.js +1 -0
- package/dist/startup-registration.js +2 -0
- package/dist/utils/install-tree-torn.d.ts +22 -7
- package/dist/utils/install-tree-torn.js +108 -20
- package/dist/utils/sentry-fingerprint.js +9 -0
- package/package.json +1 -1
|
@@ -48,15 +48,33 @@ const ESM_PACKAGE_RE = /^Cannot find package '([^']+)' imported from (.+)$/s;
|
|
|
48
48
|
const CJS_MODULE_RE = /^Cannot find module '([^']+)'/s;
|
|
49
49
|
const ESM_MODULE_IMPORTED_RE = /^Cannot find module '([^']+)' imported from (.+)$/s;
|
|
50
50
|
/**
|
|
51
|
-
* The Node ESM loader's SOURCE-READ
|
|
52
|
-
* `node:internal/modules/esm/
|
|
53
|
-
*
|
|
54
|
-
*
|
|
55
|
-
*
|
|
56
|
-
* never `esm/load` — is NOT misclassified as a torn install and does not trigger
|
|
57
|
-
* the settle wait. `load\b` also excludes the sibling `esm/loader` module.
|
|
51
|
+
* The Node ESM loader's SOURCE-READ helper in `node:internal/modules/esm/load`
|
|
52
|
+
* (Node 22) or `node:internal/modules/esm/loader` (Node 24's sync loader).
|
|
53
|
+
* `getSourceSync` must be the first non-`node:fs` caller after the source read;
|
|
54
|
+
* an ordinary fs ENOENT that escapes module evaluation therefore stays outside
|
|
55
|
+
* recovery even when an outer ModuleLoader frame exists.
|
|
58
56
|
*/
|
|
59
|
-
const
|
|
57
|
+
const ESM_SOURCE_READ_FRAME = /^at getSourceSync \(node:internal[\\/]modules[\\/]esm[\\/](?:load|loader)\b/;
|
|
58
|
+
const NODE_FS_FRAME = /^at .* \(node:fs\b/;
|
|
59
|
+
/** True only when Node's ESM source-read helper called the failing fs read. */
|
|
60
|
+
function hasEsmLoaderSourceReadFrame(stack) {
|
|
61
|
+
const frames = stack
|
|
62
|
+
.split("\n")
|
|
63
|
+
.map((line) => line.trim())
|
|
64
|
+
.filter((line) => line.startsWith("at "));
|
|
65
|
+
let sawFsRead = false;
|
|
66
|
+
for (const frame of frames) {
|
|
67
|
+
if (!sawFsRead) {
|
|
68
|
+
if (NODE_FS_READ_FRAME.test(frame))
|
|
69
|
+
sawFsRead = true;
|
|
70
|
+
continue;
|
|
71
|
+
}
|
|
72
|
+
if (NODE_FS_FRAME.test(frame))
|
|
73
|
+
continue;
|
|
74
|
+
return ESM_SOURCE_READ_FRAME.test(frame);
|
|
75
|
+
}
|
|
76
|
+
return false;
|
|
77
|
+
}
|
|
60
78
|
/**
|
|
61
79
|
* The Node module RESOLVER frame — `finalizeResolution` in
|
|
62
80
|
* `node:internal/modules/esm/resolve` (the observed HQ-CLI-1S route, where
|
|
@@ -85,12 +103,12 @@ function hasModuleEvalFrame(stack) {
|
|
|
85
103
|
}
|
|
86
104
|
/**
|
|
87
105
|
* The Node CJS loader's SOURCE-READ position (HQ-CLI-20) — the CJS twin of
|
|
88
|
-
* {@link
|
|
106
|
+
* {@link hasEsmLoaderSourceReadFrame}. A CJS require whose target was present at RESOLVE (its
|
|
89
107
|
* filename cached in Module._pathCache) and gone at READ fails inside the loader's
|
|
90
108
|
* source read: `readFileSync (node:fs)` called by `defaultLoadImpl`/`loadSource`
|
|
91
109
|
* (Node ≥22) or, on older Node, the `Module._extensions..js` extension handler —
|
|
92
110
|
* all frames in `node:internal/modules/cjs/loader`, and (unlike HQ-CLI-1M) with NO
|
|
93
|
-
*
|
|
111
|
+
* ESM source-read frame.
|
|
94
112
|
*
|
|
95
113
|
* The discriminator is POSITIONAL: the innermost `node:fs` read frame's
|
|
96
114
|
* IMMEDIATELY-following (caller) frame must be a cjs/loader frame that is NOT a
|
|
@@ -251,7 +269,7 @@ export function classifyModuleNotFound(err) {
|
|
|
251
269
|
if (record.syscall === "open" &&
|
|
252
270
|
typeof record.path === "string" &&
|
|
253
271
|
typeof record.stack === "string" &&
|
|
254
|
-
|
|
272
|
+
hasEsmLoaderSourceReadFrame(record.stack)) {
|
|
255
273
|
return {
|
|
256
274
|
code,
|
|
257
275
|
dialect: "esm-enoent",
|
|
@@ -263,7 +281,7 @@ export function classifyModuleNotFound(err) {
|
|
|
263
281
|
// cjs-loader source-read ENOENT (HQ-CLI-20): the CJS twin of esm-enoent — a
|
|
264
282
|
// require() whose target was present at RESOLVE (its filename held in
|
|
265
283
|
// Module._pathCache) and gone at READ, so the CJS loader's source read raises
|
|
266
|
-
// ENOENT with syscall 'open' and a cjs/loader frame but NO
|
|
284
|
+
// ENOENT with syscall 'open' and a cjs/loader frame but NO ESM source-read frame
|
|
267
285
|
// (which is why arm (0) missed it and classifyModuleNotFound returned null,
|
|
268
286
|
// filing the bare HQ-CLI-20 capture). Gated on the FULL conjunction — code
|
|
269
287
|
// ENOENT AND syscall 'open' AND a string path AND the POSITIONAL CJS
|
|
@@ -449,6 +467,7 @@ export function classifyEvalThrow(err, packageRoot) {
|
|
|
449
467
|
const nodeInstallTreeFs = {
|
|
450
468
|
existsSync: (p) => nodeFs.existsSync(p),
|
|
451
469
|
statSync: (p) => nodeFs.statSync(p),
|
|
470
|
+
lstatSync: (p) => nodeFs.lstatSync(p),
|
|
452
471
|
readFileSync: (p, encoding) => nodeFs.readFileSync(p, encoding),
|
|
453
472
|
readdirSync: (p) => nodeFs.readdirSync(p),
|
|
454
473
|
};
|
|
@@ -633,6 +652,56 @@ function retiredSiblingPresent(packageRoot, fs) {
|
|
|
633
652
|
const entries = fs.readdirSync(parent); // caller treats a throw as "not ready"
|
|
634
653
|
return entries.some((entry) => entry.startsWith(stagingPrefix));
|
|
635
654
|
}
|
|
655
|
+
/** Snapshot retired sibling trees so changes below their root stay observable. */
|
|
656
|
+
function retiredSiblingActivitySignature(packageRoot, fs) {
|
|
657
|
+
const { leaf } = cliNameParts();
|
|
658
|
+
const stagingPrefix = `.${leaf}-`;
|
|
659
|
+
const parent = path.dirname(packageRoot);
|
|
660
|
+
const siblings = fs.readdirSync(parent).filter((entry) => entry.startsWith(stagingPrefix)).sort();
|
|
661
|
+
const snapshots = [["names", siblings]];
|
|
662
|
+
let observed = true;
|
|
663
|
+
let visited = 0;
|
|
664
|
+
const maxEntries = 20_000;
|
|
665
|
+
const maxDepth = 64;
|
|
666
|
+
const visit = (absolutePath, relativePath, depth) => {
|
|
667
|
+
visited += 1;
|
|
668
|
+
if (visited > maxEntries) {
|
|
669
|
+
observed = false;
|
|
670
|
+
return;
|
|
671
|
+
}
|
|
672
|
+
try {
|
|
673
|
+
const stat = fs.lstatSync ? fs.lstatSync(absolutePath) : fs.statSync(absolutePath);
|
|
674
|
+
const symbolicLink = stat.isSymbolicLink?.() ?? false;
|
|
675
|
+
const directory = !symbolicLink && stat.isDirectory();
|
|
676
|
+
const mtimeMs = stat.mtimeMs;
|
|
677
|
+
if (typeof mtimeMs !== "number" || !Number.isFinite(mtimeMs))
|
|
678
|
+
observed = false;
|
|
679
|
+
snapshots.push([
|
|
680
|
+
relativePath,
|
|
681
|
+
symbolicLink ? "symlink" : directory ? "directory" : "file",
|
|
682
|
+
stat.size ?? null,
|
|
683
|
+
typeof mtimeMs === "number" && Number.isFinite(mtimeMs) ? mtimeMs : null,
|
|
684
|
+
]);
|
|
685
|
+
if (!directory)
|
|
686
|
+
return;
|
|
687
|
+
if (depth >= maxDepth) {
|
|
688
|
+
observed = false;
|
|
689
|
+
return;
|
|
690
|
+
}
|
|
691
|
+
for (const child of fs.readdirSync(absolutePath).sort()) {
|
|
692
|
+
visit(path.join(absolutePath, child), path.join(relativePath, child), depth + 1);
|
|
693
|
+
}
|
|
694
|
+
}
|
|
695
|
+
catch {
|
|
696
|
+
observed = false;
|
|
697
|
+
snapshots.push([relativePath, "unreadable"]);
|
|
698
|
+
}
|
|
699
|
+
};
|
|
700
|
+
for (const sibling of siblings) {
|
|
701
|
+
visit(path.join(parent, sibling), sibling, 0);
|
|
702
|
+
}
|
|
703
|
+
return { value: JSON.stringify(snapshots), observed };
|
|
704
|
+
}
|
|
636
705
|
/** Whether packageRoot's own manifest is healthy (`name === CLI_NAME`). */
|
|
637
706
|
function packageRootHealthy(packageRoot, fs) {
|
|
638
707
|
const manifest = JSON.parse(fs.readFileSync(path.join(packageRoot, "package.json"), "utf-8"));
|
|
@@ -754,6 +823,18 @@ function installTreeActivitySignature(target, packageRoot, fs) {
|
|
|
754
823
|
snapshots.push([directory, `entries-unreadable:${errorCode(err)}`]);
|
|
755
824
|
}
|
|
756
825
|
}
|
|
826
|
+
if (packageRoot) {
|
|
827
|
+
try {
|
|
828
|
+
const retired = retiredSiblingActivitySignature(packageRoot, fs);
|
|
829
|
+
snapshots.push(["retired-siblings", retired.value]);
|
|
830
|
+
if (!retired.observed)
|
|
831
|
+
observed = false;
|
|
832
|
+
}
|
|
833
|
+
catch (err) {
|
|
834
|
+
observed = false;
|
|
835
|
+
snapshots.push(["retired-siblings-unreadable", errorCode(err)]);
|
|
836
|
+
}
|
|
837
|
+
}
|
|
757
838
|
return { value: JSON.stringify(snapshots), observed };
|
|
758
839
|
}
|
|
759
840
|
export const DEFAULT_INSTALL_TREE_STALE_PROBE_MS = 1_500;
|
|
@@ -769,10 +850,11 @@ export const DEFAULT_INSTALL_TREE_STALE_PROBE_MS = 1_500;
|
|
|
769
850
|
* app, the box's update timer, another `hq`) cannot make a legitimately-in-progress
|
|
770
851
|
* reinstall look stale:
|
|
771
852
|
* - UNLOCKED time — wall time observed with no fresh foreign lock — is bounded
|
|
772
|
-
* by `deadlineMs`. If the target is still missing
|
|
773
|
-
*
|
|
774
|
-
* wait returns `staleTree` early. Any observed tree
|
|
775
|
-
* settle deadline in force. `deadlineMs === 0`
|
|
853
|
+
* by `deadlineMs`. If the target is still missing and the observed tree,
|
|
854
|
+
* including retired sibling names and mtimes, stays unchanged for
|
|
855
|
+
* `staleProbeMs`, the wait returns `staleTree` early. Any observed tree
|
|
856
|
+
* change keeps the ordinary settle deadline in force. `deadlineMs === 0`
|
|
857
|
+
* evaluates readiness once.
|
|
776
858
|
* - LOCKED time — wall time observed under a fresh foreign lock held by a live
|
|
777
859
|
* pid — is bounded SEPARATELY by `lockWaitMs` (default the lock contract's own
|
|
778
860
|
* 10-minute staleness window) and does NOT consume the settle budget. When
|
|
@@ -839,7 +921,7 @@ export async function waitForInstallTreeSettled(args) {
|
|
|
839
921
|
}
|
|
840
922
|
lastActivitySnapshot = activitySnapshot;
|
|
841
923
|
}
|
|
842
|
-
let retiredPresent =
|
|
924
|
+
let retiredPresent = null;
|
|
843
925
|
let retiredCheckFailed = false;
|
|
844
926
|
if (args.packageRoot) {
|
|
845
927
|
try {
|
|
@@ -848,6 +930,7 @@ export async function waitForInstallTreeSettled(args) {
|
|
|
848
930
|
sawRetired = true;
|
|
849
931
|
}
|
|
850
932
|
catch {
|
|
933
|
+
retiredPresent = null;
|
|
851
934
|
retiredCheckFailed = true;
|
|
852
935
|
}
|
|
853
936
|
}
|
|
@@ -883,14 +966,13 @@ export async function waitForInstallTreeSettled(args) {
|
|
|
883
966
|
lockTool,
|
|
884
967
|
sawTreeChange,
|
|
885
968
|
staleTree: false,
|
|
969
|
+
retiredSiblingPresent: retiredPresent,
|
|
886
970
|
};
|
|
887
971
|
}
|
|
888
972
|
if (!ready &&
|
|
889
973
|
!foreignLockHeld &&
|
|
890
|
-
!retiredPresent &&
|
|
891
974
|
!retiredCheckFailed &&
|
|
892
975
|
!sawLock &&
|
|
893
|
-
!sawRetired &&
|
|
894
976
|
!sawTreeChange &&
|
|
895
977
|
staleObservationStartedAt !== null &&
|
|
896
978
|
t - staleObservationStartedAt >= staleProbeMs) {
|
|
@@ -904,6 +986,7 @@ export async function waitForInstallTreeSettled(args) {
|
|
|
904
986
|
lockTool,
|
|
905
987
|
sawTreeChange,
|
|
906
988
|
staleTree: true,
|
|
989
|
+
retiredSiblingPresent: retiredPresent,
|
|
907
990
|
};
|
|
908
991
|
}
|
|
909
992
|
// Give up when the TREE has had a full unlocked budget with no writer visible,
|
|
@@ -925,6 +1008,7 @@ export async function waitForInstallTreeSettled(args) {
|
|
|
925
1008
|
lockTool,
|
|
926
1009
|
sawTreeChange,
|
|
927
1010
|
staleTree: false,
|
|
1011
|
+
retiredSiblingPresent: retiredPresent,
|
|
928
1012
|
};
|
|
929
1013
|
}
|
|
930
1014
|
// The first poll at which the wait has reached `deadlineMs` while a lock is
|
|
@@ -1007,7 +1091,7 @@ export class InstallTreeTornError extends Error {
|
|
|
1007
1091
|
this.cause = init.cause;
|
|
1008
1092
|
const sawTreeChange = init.sawTreeChange ?? false;
|
|
1009
1093
|
const treeState = init.treeState ?? (init.outcome === "stale_tree" ? "stale" : "in_flight");
|
|
1010
|
-
|
|
1094
|
+
const diagnostics = {
|
|
1011
1095
|
dialect: init.classified.dialect,
|
|
1012
1096
|
code: init.classified.code,
|
|
1013
1097
|
specifier: boundedDiagnosticValue(init.classified.specifier, SPECIFIER_BYTES),
|
|
@@ -1026,6 +1110,10 @@ export class InstallTreeTornError extends Error {
|
|
|
1026
1110
|
node: process.version,
|
|
1027
1111
|
errorName: boundedDiagnosticValue(init.classified.errorName ?? "", ERRORNAME_BYTES),
|
|
1028
1112
|
};
|
|
1113
|
+
if (typeof init.retiredSiblingPresent === "boolean") {
|
|
1114
|
+
diagnostics.retiredSiblingPresent = init.retiredSiblingPresent;
|
|
1115
|
+
}
|
|
1116
|
+
this.diagnostics = diagnostics;
|
|
1029
1117
|
Object.setPrototypeOf(this, new.target.prototype);
|
|
1030
1118
|
}
|
|
1031
1119
|
}
|
|
@@ -240,6 +240,15 @@ export function installTreeTornSentryTagsFor(err) {
|
|
|
240
240
|
tree_state: installTreeStateTag(diagnostics.treeState),
|
|
241
241
|
os: installTreeOsTag(process.platform),
|
|
242
242
|
};
|
|
243
|
+
if (diagnostics.code === "ERR_MODULE_NOT_FOUND" || diagnostics.code === "MODULE_NOT_FOUND") {
|
|
244
|
+
tags.error_shape = "module_not_found";
|
|
245
|
+
}
|
|
246
|
+
else if (diagnostics.code === "ENOENT") {
|
|
247
|
+
tags.error_shape = "loader_enoent";
|
|
248
|
+
}
|
|
249
|
+
if (typeof diagnostics.retiredSiblingPresent === "boolean") {
|
|
250
|
+
tags.retired_sibling = diagnostics.retiredSiblingPresent ? "present" : "absent";
|
|
251
|
+
}
|
|
243
252
|
if (recoveryOutcome)
|
|
244
253
|
tags.recovery_outcome = recoveryOutcome;
|
|
245
254
|
const lockTool = diagnostics.lockTool;
|