@indigoai-us/hq-cli 5.198.1 → 5.200.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 +38 -7
- package/dist/command-catalog.generated.d.ts +6 -0
- package/dist/command-catalog.generated.js +8 -0
- package/dist/commands/lanes.d.ts +3 -0
- package/dist/commands/lanes.js +685 -32
- package/dist/lib/core-utils/hook-timeout-debug-context-flag.js +5 -4
- package/dist/lib/doctor/client-health-reason-codes-flag.js +3 -2
- package/dist/lib/flag-registry-endpoint.d.ts +5 -0
- package/dist/lib/flag-registry-endpoint.js +9 -0
- package/dist/lib/flag-registry.d.ts +7 -3
- package/dist/lib/flag-registry.js +19 -7
- package/dist/lib/lanes/outpost-dispatch.d.ts +11 -2
- package/dist/lib/lanes/outpost-dispatch.js +27 -3
- package/dist/lib/lanes/outpost-hire-cache.d.ts +20 -0
- package/dist/lib/lanes/outpost-hire-cache.js +146 -0
- package/dist/lib/lanes/outpost-pruner-install.js +4 -3
- package/dist/lib/lanes/outpost-worktree-pruner-flag.js +3 -3
- package/dist/lib/lanes/registry-flag.js +3 -2
- package/dist/node-network-compat-flag.js +3 -3
- package/package.json +1 -1
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { loadCachedTokens } from "@indigoai-us/hq-cloud";
|
|
2
2
|
import { createFlagClient, } from "@indigoai-us/hq-flags-client";
|
|
3
|
+
import { resolveProcessFlagCompanyContext } from "../flag-registry.js";
|
|
4
|
+
import { resolveFlagRegistryEndpoint } from "../flag-registry-endpoint.js";
|
|
3
5
|
export const HOOK_TIMEOUT_DEBUG_CONTEXT_FLAG = "hook-timeout-debug-context";
|
|
4
6
|
export const HOOK_TIMEOUT_DEBUG_CONTEXT_FLAG_FALLBACK = false;
|
|
5
7
|
export const HOOK_TIMEOUT_DEBUG_CONTEXT_FLAG_TIMEOUT_MS = 150;
|
|
@@ -20,12 +22,11 @@ function combineSignals(first, second) {
|
|
|
20
22
|
*/
|
|
21
23
|
export async function hookTimeoutDebugContextEnabled(dependencies = {}, callerSignal) {
|
|
22
24
|
const env = dependencies.env ?? process.env;
|
|
23
|
-
const endpoint = env
|
|
24
|
-
const companyUid = env
|
|
25
|
-
if (!
|
|
25
|
+
const endpoint = resolveFlagRegistryEndpoint(undefined, env);
|
|
26
|
+
const { companyUid, companySlug } = resolveProcessFlagCompanyContext(env);
|
|
27
|
+
if (!companyUid) {
|
|
26
28
|
return HOOK_TIMEOUT_DEBUG_CONTEXT_FLAG_FALLBACK;
|
|
27
29
|
}
|
|
28
|
-
const companySlug = env.HQ_COMPANY_SLUG?.trim() || "";
|
|
29
30
|
const deadline = AbortSignal.timeout(HOOK_TIMEOUT_DEBUG_CONTEXT_FLAG_TIMEOUT_MS);
|
|
30
31
|
const signal = callerSignal ? AbortSignal.any([callerSignal, deadline]) : deadline;
|
|
31
32
|
const doFetch = dependencies.fetch ?? globalThis.fetch;
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { loadCachedTokens } from "@indigoai-us/hq-cloud";
|
|
2
2
|
import { createFlagClient, } from "@indigoai-us/hq-flags-client";
|
|
3
|
+
import { resolveFlagRegistryEndpoint } from "../flag-registry-endpoint.js";
|
|
3
4
|
export const CLIENT_HEALTH_REASON_CODES_FLAG = "client-health-sync-reason-codes";
|
|
4
5
|
export const CLIENT_HEALTH_REASON_CODES_FLAG_FALLBACK = false;
|
|
5
6
|
export const CLIENT_HEALTH_REASON_CODES_FLAG_TIMEOUT_MS = 150;
|
|
@@ -32,9 +33,9 @@ export async function clientHealthReasonCodesEnabled(dependencies = {}, callerSi
|
|
|
32
33
|
/** Resolve the same default-off gate for one explicitly identified company. */
|
|
33
34
|
export async function clientHealthReasonCodesEnabledForCompany(company, dependencies = {}, callerSignal) {
|
|
34
35
|
const env = dependencies.env ?? process.env;
|
|
35
|
-
const endpoint = env
|
|
36
|
+
const endpoint = resolveFlagRegistryEndpoint(undefined, env);
|
|
36
37
|
const companyUid = company.companyUid.trim();
|
|
37
|
-
if (
|
|
38
|
+
if (!/^cmp_[A-Za-z0-9]{3,128}$/.test(companyUid)) {
|
|
38
39
|
return CLIENT_HEALTH_REASON_CODES_FLAG_FALLBACK;
|
|
39
40
|
}
|
|
40
41
|
const companySlug = company.companySlug?.trim() || "";
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare const FLAG_REGISTRY_ENDPOINT_ENV = "HQ_FLAGS_API_URL";
|
|
2
|
+
export declare const FLAG_REGISTRY_DEFAULT_ENDPOINT = "https://hqapi.hq.computer";
|
|
3
|
+
/** Resolve one shared endpoint from an explicit option, environment, or service default. */
|
|
4
|
+
export declare function resolveFlagRegistryEndpoint(explicitEndpoint?: string, env?: NodeJS.ProcessEnv): string;
|
|
5
|
+
//# sourceMappingURL=flag-registry-endpoint.d.ts.map
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
export const FLAG_REGISTRY_ENDPOINT_ENV = "HQ_FLAGS_API_URL";
|
|
2
|
+
export const FLAG_REGISTRY_DEFAULT_ENDPOINT = "https://hqapi.hq.computer";
|
|
3
|
+
/** Resolve one shared endpoint from an explicit option, environment, or service default. */
|
|
4
|
+
export function resolveFlagRegistryEndpoint(explicitEndpoint, env = process.env) {
|
|
5
|
+
return explicitEndpoint?.trim()
|
|
6
|
+
|| env[FLAG_REGISTRY_ENDPOINT_ENV]?.trim()
|
|
7
|
+
|| FLAG_REGISTRY_DEFAULT_ENDPOINT;
|
|
8
|
+
}
|
|
9
|
+
//# sourceMappingURL=flag-registry-endpoint.js.map
|
|
@@ -7,8 +7,7 @@
|
|
|
7
7
|
*/
|
|
8
8
|
import { type WorkerOptions } from "node:worker_threads";
|
|
9
9
|
import { type FlagClient, type FlagLookupOptions, type FlagSnapshot } from "@indigoai-us/hq-flags-client";
|
|
10
|
-
|
|
11
|
-
export declare const FLAG_REGISTRY_ENDPOINT_ENV = "HQ_FLAGS_API_URL";
|
|
10
|
+
export { FLAG_REGISTRY_ENDPOINT_ENV } from "./flag-registry-endpoint.js";
|
|
12
11
|
/** The synchronous capability that command gate call sites need. */
|
|
13
12
|
export type FlagReader = Pick<FlagClient, "isEnabled">;
|
|
14
13
|
interface WorkerLike {
|
|
@@ -20,6 +19,12 @@ export interface FlagRegistryDependencies {
|
|
|
20
19
|
endpoint?: string;
|
|
21
20
|
createWorker?: (filename: URL, options: WorkerOptions) => WorkerLike;
|
|
22
21
|
}
|
|
22
|
+
export interface ProcessFlagCompanyContext {
|
|
23
|
+
companyUid?: string;
|
|
24
|
+
companySlug?: string;
|
|
25
|
+
}
|
|
26
|
+
/** Resolve the same company identity used by the process-wide flag reader. */
|
|
27
|
+
export declare function resolveProcessFlagCompanyContext(env?: NodeJS.ProcessEnv): ProcessFlagCompanyContext;
|
|
23
28
|
/**
|
|
24
29
|
* Begin one registry refresh without awaiting it or retaining the CLI process.
|
|
25
30
|
*
|
|
@@ -46,5 +51,4 @@ export declare function resolveProcessFlagGate(flagKey: string, lookup: FlagLook
|
|
|
46
51
|
export declare function currentFlagSnapshot(): FlagSnapshot | null;
|
|
47
52
|
/** Test-only reset for process-global readiness state. */
|
|
48
53
|
export declare function _resetFlagRegistryForTests(): void;
|
|
49
|
-
export {};
|
|
50
54
|
//# sourceMappingURL=flag-registry.d.ts.map
|
|
@@ -12,8 +12,8 @@ import { readSessionState } from "./work-context/state.js";
|
|
|
12
12
|
import { workContextRoot } from "./work-context/paths.js";
|
|
13
13
|
import { readCompaniesManifestMap } from "./work-context/repo-remote.js";
|
|
14
14
|
import { resolveDefaultHqRoot } from "../utils/cognito-session.js";
|
|
15
|
-
|
|
16
|
-
export
|
|
15
|
+
import { resolveFlagRegistryEndpoint, } from "./flag-registry-endpoint.js";
|
|
16
|
+
export { FLAG_REGISTRY_ENDPOINT_ENV } from "./flag-registry-endpoint.js";
|
|
17
17
|
const COMPANY_UID_RE = /^cmp_[A-Za-z0-9]{3,128}$/;
|
|
18
18
|
const COMPANY_SLUG_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,127}$/;
|
|
19
19
|
let heldSnapshot = null;
|
|
@@ -76,6 +76,15 @@ function companyUidForContext(context, env = process.env) {
|
|
|
76
76
|
const manifestUid = readCompaniesManifestMap(hqRoot)?.[context.companySlug]?.cloud_uid;
|
|
77
77
|
return typeof manifestUid === "string" ? normalizedCompanyUid(manifestUid) : undefined;
|
|
78
78
|
}
|
|
79
|
+
/** Resolve the same company identity used by the process-wide flag reader. */
|
|
80
|
+
export function resolveProcessFlagCompanyContext(env = process.env) {
|
|
81
|
+
const context = processCompanyContext(env);
|
|
82
|
+
const companyUid = companyUidForContext(context, env);
|
|
83
|
+
return {
|
|
84
|
+
...(companyUid ? { companyUid } : {}),
|
|
85
|
+
...(context.companySlug ? { companySlug: context.companySlug } : {}),
|
|
86
|
+
};
|
|
87
|
+
}
|
|
79
88
|
function processSnapshotReader(companyIdentifiers) {
|
|
80
89
|
return {
|
|
81
90
|
isEnabled(flagKey, lookup = {}) {
|
|
@@ -101,11 +110,9 @@ function processSnapshotReader(companyIdentifiers) {
|
|
|
101
110
|
export function kickFlagRegistryReadiness(dependencies = {}) {
|
|
102
111
|
if (processReader)
|
|
103
112
|
return processReader;
|
|
104
|
-
const endpoint = (dependencies.endpoint
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
const company = processCompanyContext();
|
|
108
|
-
const companyUid = companyUidForContext(company);
|
|
113
|
+
const endpoint = resolveFlagRegistryEndpoint(dependencies.endpoint, process.env);
|
|
114
|
+
const company = resolveProcessFlagCompanyContext();
|
|
115
|
+
const companyUid = company.companyUid;
|
|
109
116
|
const companyIdentifiers = [companyUid, company.companySlug].filter((value) => Boolean(value));
|
|
110
117
|
processReader = processSnapshotReader(companyIdentifiers);
|
|
111
118
|
try {
|
|
@@ -149,6 +156,11 @@ export function resolveFlagGate(reader, flagKey, lookup, legacyFallback) {
|
|
|
149
156
|
}
|
|
150
157
|
/** Resolve a process gate against the shared snapshot reader, when configured. */
|
|
151
158
|
export function resolveProcessFlagGate(flagKey, lookup, legacyFallback) {
|
|
159
|
+
// The endpoint refresh is deliberately asynchronous. Until a validated
|
|
160
|
+
// snapshot arrives, preserve each caller's exact legacy fallback.
|
|
161
|
+
if (!processReader || !heldSnapshot) {
|
|
162
|
+
return localOverrideOrFallback(flagKey, legacyFallback);
|
|
163
|
+
}
|
|
152
164
|
return resolveFlagGate(processReader, flagKey, lookup, legacyFallback);
|
|
153
165
|
}
|
|
154
166
|
/**
|
|
@@ -46,6 +46,7 @@ export declare function buildOutpostQuestionAnswerCommand(input: {
|
|
|
46
46
|
choice?: string;
|
|
47
47
|
freeform?: string;
|
|
48
48
|
notes?: string;
|
|
49
|
+
company?: string;
|
|
49
50
|
hqBinary?: string;
|
|
50
51
|
}): string;
|
|
51
52
|
export interface OutpostExecPort {
|
|
@@ -71,8 +72,16 @@ export declare function parseOutpostPickupPayload(raw: unknown): OutpostPickupPa
|
|
|
71
72
|
export declare function buildOutpostPickupCommand(payloadUrl: string): string;
|
|
72
73
|
/** Bounded, read-only capacity query executed by the target Outpost CLI. */
|
|
73
74
|
export declare function buildOutpostCapacityCommand(): string;
|
|
74
|
-
/** Read
|
|
75
|
-
export declare function buildOutpostLaneListCommand(
|
|
75
|
+
/** Read every lane on an Outpost, optionally using its configured HQ binary. */
|
|
76
|
+
export declare function buildOutpostLaneListCommand(input?: {
|
|
77
|
+
hqBinary?: string;
|
|
78
|
+
}): string;
|
|
79
|
+
/** Read questions on one Outpost, scoped to a company when supplied. */
|
|
80
|
+
export declare function buildOutpostQuestionListCommand(input?: {
|
|
81
|
+
company?: string;
|
|
82
|
+
}): string;
|
|
83
|
+
/** Read one question's body and owner-computed audience from its Outpost. */
|
|
84
|
+
export declare function buildOutpostQuestionShowCommand(questionId: string): string;
|
|
76
85
|
/** Commit a queued lane move on the host that currently owns its state. */
|
|
77
86
|
export declare function buildOutpostMoveTargetCommand(input: {
|
|
78
87
|
laneId: string;
|
|
@@ -22,6 +22,7 @@ export function buildOutpostQuestionAnswerCommand(input) {
|
|
|
22
22
|
["--choice", input.choice],
|
|
23
23
|
["--freeform", input.freeform],
|
|
24
24
|
["--notes", input.notes],
|
|
25
|
+
["--company", input.company],
|
|
25
26
|
]) {
|
|
26
27
|
if (value !== undefined)
|
|
27
28
|
args.push(flag, value);
|
|
@@ -64,9 +65,32 @@ export function buildOutpostPickupCommand(payloadUrl) {
|
|
|
64
65
|
export function buildOutpostCapacityCommand() {
|
|
65
66
|
return withRemoteHqDir(joinCommandParts(["hq", "lanes", "capacity", "--json"]));
|
|
66
67
|
}
|
|
67
|
-
/** Read
|
|
68
|
-
export function buildOutpostLaneListCommand() {
|
|
69
|
-
return withRemoteHqDir(joinCommandParts([
|
|
68
|
+
/** Read every lane on an Outpost, optionally using its configured HQ binary. */
|
|
69
|
+
export function buildOutpostLaneListCommand(input = {}) {
|
|
70
|
+
return withRemoteHqDir(joinCommandParts([
|
|
71
|
+
input.hqBinary?.trim() || "hq",
|
|
72
|
+
"lanes",
|
|
73
|
+
"list",
|
|
74
|
+
"--json",
|
|
75
|
+
]));
|
|
76
|
+
}
|
|
77
|
+
/** Read questions on one Outpost, scoped to a company when supplied. */
|
|
78
|
+
export function buildOutpostQuestionListCommand(input = {}) {
|
|
79
|
+
const args = ["hq", "lanes", "questions", "list"];
|
|
80
|
+
const company = input.company?.trim();
|
|
81
|
+
if (company)
|
|
82
|
+
args.push("--company", company);
|
|
83
|
+
return withRemoteHqDir(joinCommandParts(args));
|
|
84
|
+
}
|
|
85
|
+
/** Read one question's body and owner-computed audience from its Outpost. */
|
|
86
|
+
export function buildOutpostQuestionShowCommand(questionId) {
|
|
87
|
+
return withRemoteHqDir(joinCommandParts([
|
|
88
|
+
"hq",
|
|
89
|
+
"lanes",
|
|
90
|
+
"questions",
|
|
91
|
+
"show",
|
|
92
|
+
questionId,
|
|
93
|
+
]));
|
|
70
94
|
}
|
|
71
95
|
/** Commit a queued lane move on the host that currently owns its state. */
|
|
72
96
|
export function buildOutpostMoveTargetCommand(input) {
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
export declare const OUTPOST_HIRE_SUMMARY_CACHE_TTL_MS = 60000;
|
|
2
|
+
export interface OutpostHireSummary {
|
|
3
|
+
hire_grant: number;
|
|
4
|
+
hire_alive_children: number;
|
|
5
|
+
hire: string;
|
|
6
|
+
hire_summary: string;
|
|
7
|
+
}
|
|
8
|
+
export interface OutpostHireSummaryCacheEntry extends OutpostHireSummary {
|
|
9
|
+
outpost_id: string;
|
|
10
|
+
lane_id: string;
|
|
11
|
+
cached_at: string;
|
|
12
|
+
}
|
|
13
|
+
export declare function outpostHireSummaryCachePath(hqRoot: string): string;
|
|
14
|
+
export declare function outpostHireSummaryCacheKey(outpostId: string, laneId: string): string;
|
|
15
|
+
export declare function readOutpostHireSummaryCache(hqRoot: string, nowMs: number): Map<string, OutpostHireSummaryCacheEntry>;
|
|
16
|
+
export declare function writeOutpostHireSummaryCache(hqRoot: string, updates: readonly (OutpostHireSummary & {
|
|
17
|
+
outpost_id: string;
|
|
18
|
+
lane_id: string;
|
|
19
|
+
})[], nowMs: number, existingEntries?: ReadonlyMap<string, OutpostHireSummaryCacheEntry>): void;
|
|
20
|
+
//# sourceMappingURL=outpost-hire-cache.d.ts.map
|
|
@@ -0,0 +1,146 @@
|
|
|
1
|
+
import { randomBytes } from "node:crypto";
|
|
2
|
+
import * as fs from "node:fs";
|
|
3
|
+
import * as path from "node:path";
|
|
4
|
+
export const OUTPOST_HIRE_SUMMARY_CACHE_TTL_MS = 60_000;
|
|
5
|
+
const CACHE_RELATIVE_PATH = path.join("workspace", "lanes-runs", ".cache", "outpost-hire-summaries.json");
|
|
6
|
+
export function outpostHireSummaryCachePath(hqRoot) {
|
|
7
|
+
return path.join(hqRoot, CACHE_RELATIVE_PATH);
|
|
8
|
+
}
|
|
9
|
+
export function outpostHireSummaryCacheKey(outpostId, laneId) {
|
|
10
|
+
return JSON.stringify([outpostId, laneId]);
|
|
11
|
+
}
|
|
12
|
+
export function readOutpostHireSummaryCache(hqRoot, nowMs) {
|
|
13
|
+
const filePath = outpostHireSummaryCachePath(hqRoot);
|
|
14
|
+
let contents;
|
|
15
|
+
try {
|
|
16
|
+
contents = fs.readFileSync(filePath, "utf8");
|
|
17
|
+
}
|
|
18
|
+
catch (err) {
|
|
19
|
+
if (isMissingFile(err))
|
|
20
|
+
return new Map();
|
|
21
|
+
console.warn(`Ignoring unreadable Outpost hire summary cache at ${filePath}: ${errorText(err)}`);
|
|
22
|
+
return new Map();
|
|
23
|
+
}
|
|
24
|
+
let parsed;
|
|
25
|
+
try {
|
|
26
|
+
parsed = JSON.parse(contents);
|
|
27
|
+
}
|
|
28
|
+
catch (err) {
|
|
29
|
+
console.warn(`Ignoring malformed Outpost hire summary cache at ${filePath}: ${errorText(err)}`);
|
|
30
|
+
return new Map();
|
|
31
|
+
}
|
|
32
|
+
if (!Array.isArray(parsed)) {
|
|
33
|
+
console.warn(`Ignoring malformed Outpost hire summary cache at ${filePath}: expected an array`);
|
|
34
|
+
return new Map();
|
|
35
|
+
}
|
|
36
|
+
const entries = new Map();
|
|
37
|
+
const duplicates = new Set();
|
|
38
|
+
for (const value of parsed) {
|
|
39
|
+
const entry = parseCacheEntry(value, nowMs);
|
|
40
|
+
if (!entry)
|
|
41
|
+
continue;
|
|
42
|
+
const key = outpostHireSummaryCacheKey(entry.outpost_id, entry.lane_id);
|
|
43
|
+
if (entries.has(key)) {
|
|
44
|
+
entries.delete(key);
|
|
45
|
+
duplicates.add(key);
|
|
46
|
+
continue;
|
|
47
|
+
}
|
|
48
|
+
if (!duplicates.has(key))
|
|
49
|
+
entries.set(key, entry);
|
|
50
|
+
}
|
|
51
|
+
return entries;
|
|
52
|
+
}
|
|
53
|
+
export function writeOutpostHireSummaryCache(hqRoot, updates, nowMs, existingEntries) {
|
|
54
|
+
if (updates.length === 0)
|
|
55
|
+
return;
|
|
56
|
+
const filePath = outpostHireSummaryCachePath(hqRoot);
|
|
57
|
+
const cached = new Map(existingEntries ?? readOutpostHireSummaryCache(hqRoot, nowMs));
|
|
58
|
+
for (const update of updates) {
|
|
59
|
+
const entry = {
|
|
60
|
+
outpost_id: update.outpost_id,
|
|
61
|
+
lane_id: update.lane_id,
|
|
62
|
+
hire_grant: update.hire_grant,
|
|
63
|
+
hire_alive_children: update.hire_alive_children,
|
|
64
|
+
hire: update.hire,
|
|
65
|
+
hire_summary: update.hire_summary,
|
|
66
|
+
cached_at: new Date(nowMs).toISOString(),
|
|
67
|
+
};
|
|
68
|
+
if (!parseCacheEntry(entry, nowMs))
|
|
69
|
+
continue;
|
|
70
|
+
cached.set(outpostHireSummaryCacheKey(entry.outpost_id, entry.lane_id), entry);
|
|
71
|
+
}
|
|
72
|
+
const directory = path.dirname(filePath);
|
|
73
|
+
let temporaryPath;
|
|
74
|
+
try {
|
|
75
|
+
fs.mkdirSync(directory, { recursive: true });
|
|
76
|
+
temporaryPath = path.join(directory, `.outpost-hire-summaries.${process.pid}.${randomBytes(6).toString("hex")}.tmp`);
|
|
77
|
+
fs.writeFileSync(temporaryPath, `${JSON.stringify([...cached.values()])}\n`, {
|
|
78
|
+
encoding: "utf8",
|
|
79
|
+
mode: 0o600,
|
|
80
|
+
});
|
|
81
|
+
fs.renameSync(temporaryPath, filePath);
|
|
82
|
+
}
|
|
83
|
+
catch (err) {
|
|
84
|
+
if (temporaryPath) {
|
|
85
|
+
try {
|
|
86
|
+
fs.unlinkSync(temporaryPath);
|
|
87
|
+
}
|
|
88
|
+
catch (cleanupErr) {
|
|
89
|
+
if (!isMissingFile(cleanupErr)) {
|
|
90
|
+
console.warn(`Could not remove temporary Outpost hire summary cache ${temporaryPath}: ${errorText(cleanupErr)}`);
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
console.warn(`Could not write Outpost hire summary cache at ${filePath}: ${errorText(err)}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
function parseCacheEntry(value, nowMs) {
|
|
98
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
99
|
+
return undefined;
|
|
100
|
+
const entry = value;
|
|
101
|
+
const allowedKeys = new Set([
|
|
102
|
+
"outpost_id",
|
|
103
|
+
"lane_id",
|
|
104
|
+
"hire_grant",
|
|
105
|
+
"hire_alive_children",
|
|
106
|
+
"hire",
|
|
107
|
+
"hire_summary",
|
|
108
|
+
"cached_at",
|
|
109
|
+
]);
|
|
110
|
+
if (Object.keys(entry).length !== allowedKeys.size || Object.keys(entry).some((key) => !allowedKeys.has(key))) {
|
|
111
|
+
return undefined;
|
|
112
|
+
}
|
|
113
|
+
if (typeof entry.outpost_id !== "string" || !entry.outpost_id.trim() ||
|
|
114
|
+
typeof entry.lane_id !== "string" || !entry.lane_id.trim() ||
|
|
115
|
+
!Number.isSafeInteger(entry.hire_grant) || entry.hire_grant < 0 ||
|
|
116
|
+
!Number.isSafeInteger(entry.hire_alive_children) || entry.hire_alive_children < 0 ||
|
|
117
|
+
typeof entry.cached_at !== "string") {
|
|
118
|
+
return undefined;
|
|
119
|
+
}
|
|
120
|
+
const cachedAt = Date.parse(entry.cached_at);
|
|
121
|
+
if (!Number.isFinite(cachedAt) ||
|
|
122
|
+
cachedAt > nowMs ||
|
|
123
|
+
nowMs - cachedAt > OUTPOST_HIRE_SUMMARY_CACHE_TTL_MS) {
|
|
124
|
+
return undefined;
|
|
125
|
+
}
|
|
126
|
+
const hire = `${entry.hire_alive_children}/${entry.hire_grant}`;
|
|
127
|
+
const hireSummary = `hire ${hire}`;
|
|
128
|
+
if (entry.hire !== hire || entry.hire_summary !== hireSummary)
|
|
129
|
+
return undefined;
|
|
130
|
+
return {
|
|
131
|
+
outpost_id: entry.outpost_id,
|
|
132
|
+
lane_id: entry.lane_id,
|
|
133
|
+
hire_grant: entry.hire_grant,
|
|
134
|
+
hire_alive_children: entry.hire_alive_children,
|
|
135
|
+
hire,
|
|
136
|
+
hire_summary: hireSummary,
|
|
137
|
+
cached_at: entry.cached_at,
|
|
138
|
+
};
|
|
139
|
+
}
|
|
140
|
+
function isMissingFile(err) {
|
|
141
|
+
return err !== null && typeof err === "object" && "code" in err && err.code === "ENOENT";
|
|
142
|
+
}
|
|
143
|
+
function errorText(err) {
|
|
144
|
+
return err instanceof Error ? err.message : String(err);
|
|
145
|
+
}
|
|
146
|
+
//# sourceMappingURL=outpost-hire-cache.js.map
|
|
@@ -3,6 +3,7 @@ import * as os from "node:os";
|
|
|
3
3
|
import * as path from "node:path";
|
|
4
4
|
import { resolveHqBinary, resolveNodeBinary } from "../mesh/live/daemon/install.js";
|
|
5
5
|
import { SYSTEMD_PATH_ENV } from "../service-manager/systemd.js";
|
|
6
|
+
import { resolveFlagRegistryEndpoint } from "../flag-registry-endpoint.js";
|
|
6
7
|
export const OUTPOST_PRUNER_PRESSURE_SERVICE = "hq-outpost-worktree-pruner-pressure.service";
|
|
7
8
|
export const OUTPOST_PRUNER_PRESSURE_TIMER = "hq-outpost-worktree-pruner-pressure.timer";
|
|
8
9
|
export const OUTPOST_PRUNER_DAILY_SERVICE = "hq-outpost-worktree-pruner-daily.service";
|
|
@@ -160,10 +161,10 @@ export function installOutpostPrunerTimers(options) {
|
|
|
160
161
|
const nodeBinary = options.nodeBinary ?? resolveNodeBinary();
|
|
161
162
|
const hqBinary = options.hqBinary ?? resolveHqBinary();
|
|
162
163
|
const home = options.home ?? os.homedir();
|
|
163
|
-
const flagApiUrl = (options.flagApiUrl
|
|
164
|
+
const flagApiUrl = resolveFlagRegistryEndpoint(options.flagApiUrl);
|
|
164
165
|
const companyUid = (options.companyUid ?? process.env.HQ_COMPANY_UID ?? "").trim();
|
|
165
|
-
if (!
|
|
166
|
-
throw new TypeError("install requires
|
|
166
|
+
if (!companyUid) {
|
|
167
|
+
throw new TypeError("install requires HQ_COMPANY_UID for the timer services");
|
|
167
168
|
}
|
|
168
169
|
const flagEnvironment = [
|
|
169
170
|
systemdEnvironment("HQ_FLAGS_API_URL", flagApiUrl),
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { loadCachedTokens } from "@indigoai-us/hq-cloud";
|
|
2
2
|
import { createFlagClient } from "@indigoai-us/hq-flags-client";
|
|
3
|
+
import { resolveFlagRegistryEndpoint } from "../flag-registry-endpoint.js";
|
|
3
4
|
export const OUTPOST_WORKTREE_PRUNER_FLAG = "outpost.worktree-pruner";
|
|
4
5
|
export const OUTPOST_WORKTREE_PRUNER_FLAG_FALLBACK = false;
|
|
5
6
|
export const OUTPOST_WORKTREE_PRUNER_FLAG_TIMEOUT_MS = 5_000;
|
|
@@ -21,14 +22,13 @@ function reportFlagFailure(error) {
|
|
|
21
22
|
}
|
|
22
23
|
export async function outpostWorktreePrunerEnabled(dependencies = {}) {
|
|
23
24
|
const env = dependencies.env ?? process.env;
|
|
24
|
-
const endpoint = env
|
|
25
|
+
const endpoint = resolveFlagRegistryEndpoint(undefined, env);
|
|
25
26
|
const companyUid = env.HQ_COMPANY_UID?.trim() || "";
|
|
26
|
-
if (!
|
|
27
|
+
if (!companyUid) {
|
|
27
28
|
process.stderr.write(`${JSON.stringify({
|
|
28
29
|
event: "outpost.worktree_pruner.flag_config_missing",
|
|
29
30
|
flag: OUTPOST_WORKTREE_PRUNER_FLAG,
|
|
30
31
|
missing: [
|
|
31
|
-
...(!endpoint ? ["HQ_FLAGS_API_URL"] : []),
|
|
32
32
|
...(!companyUid ? ["HQ_COMPANY_UID"] : []),
|
|
33
33
|
],
|
|
34
34
|
fallback: OUTPOST_WORKTREE_PRUNER_FLAG_FALLBACK,
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { createFlagClient, readLocalOverride, } from "@indigoai-us/hq-flags-client";
|
|
2
2
|
import { ensureCognitoIdToken } from "../../utils/cognito-session.js";
|
|
3
|
-
|
|
3
|
+
import { FLAG_REGISTRY_DEFAULT_ENDPOINT, resolveFlagRegistryEndpoint, } from "../flag-registry-endpoint.js";
|
|
4
|
+
export const LANES_FLAG_API_DEFAULT_ENDPOINT = FLAG_REGISTRY_DEFAULT_ENDPOINT;
|
|
4
5
|
export const LANES_FLAG_READ_TIMEOUT_MS = 5_000;
|
|
5
6
|
const registryReads = new Map();
|
|
6
7
|
class MissingFlagTokenError extends Error {
|
|
@@ -45,7 +46,7 @@ export async function readLanesRegistryFlag(flagKey, options = {}) {
|
|
|
45
46
|
const localOverride = readLocalOverride(flagKey, env);
|
|
46
47
|
if (localOverride !== null)
|
|
47
48
|
return localOverride;
|
|
48
|
-
const endpoint = env
|
|
49
|
+
const endpoint = resolveFlagRegistryEndpoint(undefined, env);
|
|
49
50
|
const key = cacheKey(flagKey, options.company, endpoint);
|
|
50
51
|
const cached = registryReads.get(key);
|
|
51
52
|
if (cached)
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { createFlagClient, readLocalOverride } from "@indigoai-us/hq-flags-client";
|
|
2
|
+
import { resolveFlagRegistryEndpoint } from "./lib/flag-registry-endpoint.js";
|
|
2
3
|
export const NODE_NETWORK_FORCE_IPV4_FLAG = "cli.network.force-ipv4";
|
|
3
4
|
export const NODE_NETWORK_FORCE_IPV4_FLAG_FALLBACK = false;
|
|
4
5
|
// Node waits up to 1s before trying the next address family. Keep this request
|
|
@@ -29,10 +30,9 @@ export async function nodeNetworkForceIpv4Enabled(dependencies = {}) {
|
|
|
29
30
|
const localOverride = readLocalOverride(NODE_NETWORK_FORCE_IPV4_FLAG, env);
|
|
30
31
|
if (localOverride !== null)
|
|
31
32
|
return localOverride;
|
|
32
|
-
const endpoint = env
|
|
33
|
+
const endpoint = resolveFlagRegistryEndpoint(undefined, env);
|
|
33
34
|
const companyUid = env.HQ_COMPANY_UID?.trim() || "";
|
|
34
|
-
if (
|
|
35
|
-
!/^cmp_[A-Za-z0-9]{3,128}$/.test(companyUid)) {
|
|
35
|
+
if (!/^cmp_[A-Za-z0-9]{3,128}$/.test(companyUid)) {
|
|
36
36
|
return NODE_NETWORK_FORCE_IPV4_FLAG_FALLBACK;
|
|
37
37
|
}
|
|
38
38
|
const companySlug = env.HQ_COMPANY_SLUG?.trim() || "";
|