@indigoai-us/hq-cli 5.273.0-alpha.0 → 5.274.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 +65 -2
- package/README.md +32 -1
- package/dist/command-catalog.generated.d.ts +83 -536
- package/dist/command-catalog.generated.js +129 -714
- package/dist/command-registration-plan.d.ts +12 -0
- package/dist/command-registration-plan.js +2 -0
- package/dist/commands/cloud.js +40 -0
- package/dist/commands/core-checkpoint.js +3 -1
- package/dist/commands/core-command-catalog.d.ts +32 -0
- package/dist/commands/core-command-catalog.js +58 -0
- package/dist/commands/core-ontology.js +9 -4
- package/dist/commands/core.d.ts +4 -0
- package/dist/commands/core.js +79 -75
- package/dist/commands/daemon.d.ts +0 -3
- package/dist/commands/daemon.js +1 -173
- package/dist/commands/deploy-comments.d.ts +3 -0
- package/dist/commands/deploy-comments.js +159 -0
- package/dist/commands/lanes-checks.d.ts +0 -1
- package/dist/commands/lanes-checks.js +217 -54
- package/dist/commands/lanes-hold.js +40 -2
- package/dist/commands/lanes-message.js +98 -14
- package/dist/commands/lanes-move.d.ts +12 -0
- package/dist/commands/lanes-move.js +342 -0
- package/dist/commands/lanes-resume.js +3 -1
- package/dist/commands/lanes-shared.d.ts +39 -3
- package/dist/commands/lanes-shared.js +59 -0
- package/dist/commands/lanes.d.ts +3 -5
- package/dist/commands/lanes.js +206 -478
- package/dist/commands/report.js +2 -14
- package/dist/commands/sync-mode.d.ts +0 -11
- package/dist/commands/sync-mode.js +0 -30
- package/dist/fast-core.js +1 -1
- package/dist/{commands/scaffold-fast.d.ts → fast-scaffold.d.ts} +1 -1
- package/dist/{commands/scaffold-fast.js → fast-scaffold.js} +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/lib/checkpoint-ontology/apply.d.ts +8 -0
- package/dist/lib/checkpoint-ontology/apply.js +33 -0
- package/dist/lib/checkpoint-ontology/plan.d.ts +15 -0
- package/dist/lib/checkpoint-ontology/plan.js +15 -0
- package/dist/lib/daemon/catalog.d.ts +2 -18
- package/dist/lib/daemon/catalog.js +2 -18
- package/dist/lib/daemon/config.d.ts +0 -6
- package/dist/lib/daemon/config.js +2 -15
- package/dist/lib/daemon/requests.d.ts +0 -4
- package/dist/lib/daemon/requests.js +0 -2
- package/dist/lib/daemon/run.js +19 -78
- package/dist/lib/daemon/supervisor.d.ts +0 -8
- package/dist/lib/daemon/supervisor.js +1 -101
- package/dist/lib/deploy-api.d.ts +6 -0
- package/dist/lib/deploy-api.js +21 -0
- package/dist/lib/doctor/checks/scaffold-cli-floor.d.ts +7 -0
- package/dist/lib/doctor/checks/scaffold-cli-floor.js +73 -0
- package/dist/lib/doctor/fix/apply.d.ts +17 -2
- package/dist/lib/doctor/fix/apply.js +111 -57
- package/dist/lib/doctor/fix/json-output.d.ts +9 -1
- package/dist/lib/doctor/fix/json-output.js +11 -0
- package/dist/lib/doctor/registry.js +2 -0
- package/dist/lib/first-sync-next-step.d.ts +37 -0
- package/dist/lib/first-sync-next-step.js +189 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/lanes/hire-grant-dispatch.d.ts +6 -1
- package/dist/lib/lanes/hire-grant-dispatch.js +78 -3
- package/dist/lib/lanes/keep-alive.d.ts +3 -2
- package/dist/lib/lanes/keep-alive.js +2 -2
- package/dist/lib/lanes/merge-command.d.ts +1 -0
- package/dist/lib/lanes/merge-command.js +299 -33
- package/dist/lib/lanes/orphans.d.ts +7 -0
- package/dist/lib/lanes/orphans.js +39 -4
- package/dist/lib/lanes/tracker-store.d.ts +2 -18
- package/dist/lib/lanes/tracker-store.js +1 -172
- package/dist/lib/scaffold-cli-floor.d.ts +9 -0
- package/dist/lib/scaffold-cli-floor.js +50 -0
- package/dist/main.js +4 -1
- package/dist/utils/cli-telemetry.d.ts +7 -1
- package/dist/utils/cli-telemetry.js +84 -8
- package/dist/utils/contribution-table.d.ts +1 -1
- package/dist/utils/scaffold-fast-import-guard.d.ts +3 -0
- package/dist/utils/scaffold-fast-import-guard.js +28 -0
- package/dist/utils/scaffold-packlist-guard.d.ts +7 -0
- package/dist/utils/scaffold-packlist-guard.js +23 -0
- package/dist/utils/scaffold-sibling-references.d.ts +21 -0
- package/dist/utils/scaffold-sibling-references.js +156 -0
- package/package.json +2 -2
- package/dist/commands/lanes-import.d.ts +0 -14
- package/dist/commands/lanes-import.js +0 -1210
- package/dist/commands/lanes-tracker.d.ts +0 -18
- package/dist/commands/lanes-tracker.js +0 -452
- package/dist/lib/daemon/sync-runner.d.ts +0 -58
- package/dist/lib/daemon/sync-runner.js +0 -277
package/dist/commands/lanes.js
CHANGED
|
@@ -13,29 +13,29 @@ import * as path from "node:path";
|
|
|
13
13
|
import { Option } from "commander";
|
|
14
14
|
import { createLaneAccountsHandlers } from "./lanes-accounts.js";
|
|
15
15
|
import { createLaneMessageHandlers } from "./lanes-message.js";
|
|
16
|
+
import { createLaneMoveHandlers } from "./lanes-move.js";
|
|
16
17
|
import { createLaneQuestionsHandlers } from "./lanes-questions.js";
|
|
17
18
|
import { createLaneResumeHandlers } from "./lanes-resume.js";
|
|
18
19
|
import { createLaneWatchHandlers } from "./lanes-watch.js";
|
|
20
|
+
import { assertPlacementEnabled, defaultLanePlacementEnabled, parsePlacementPolicy, processLanePlacementCompany, readLanePlacementEnabled, } from "./lanes-shared.js";
|
|
21
|
+
export { LANE_OUTPOST_LOAD_BALANCING_FLAG } from "./lanes-shared.js";
|
|
19
22
|
export { resolveAnswerChoice } from "./lanes-questions.js";
|
|
20
23
|
import { registerOutpostPrunerCommand } from "./outpost-pruner.js";
|
|
21
24
|
import { registerLaneKpisCommand } from "./lanes-kpis.js";
|
|
22
25
|
import { registerLaneChecksCommand } from "./lanes-checks.js";
|
|
23
26
|
import { registerLaneReportCommands } from "./lanes-report.js";
|
|
24
|
-
import { registerLaneTrackerImportCommand } from "./lanes-import.js";
|
|
25
27
|
import { registerLaneRepoMergeHoldCommand } from "./lanes-hold.js";
|
|
26
28
|
import { registerLaneReviewCommand } from "./lanes-review.js";
|
|
27
29
|
import { ensureCognitoToken } from "../utils/cognito-session.js";
|
|
28
30
|
import { resolveLanesHqRoot } from "../lib/lanes/hq-root.js";
|
|
29
31
|
import { isSsmRunCommandProcess } from "../lib/lanes/ssm-command-context.js";
|
|
30
32
|
import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
|
|
31
|
-
import { resolveProcessFlagCompanyContext } from "../lib/flag-registry.js";
|
|
32
|
-
import { defaultTrackerActor, LANES_TRACKER_FLAG, registerLaneTrackerCommands, } from "./lanes-tracker.js";
|
|
33
33
|
import { createOutpostHireGrantContext, parseOutpostHireGrantContext, sameHireGrantSenior, } from "../lib/lanes/hire-grant-dispatch.js";
|
|
34
34
|
import { createOutpostProfileRepinContext, parseOutpostProfileRepinContext, } from "../lib/lanes/outpost-profile-repin.js";
|
|
35
35
|
import { createOutpostRegistryWorkerApprovalContext, parseOutpostRegistryWorkerApprovalContext, } from "../lib/lanes/outpost-registry-worker-approval.js";
|
|
36
36
|
import { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity, resolveAdmissionHost, } from "../lib/lanes/admission-host.js";
|
|
37
37
|
export { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity } from "../lib/lanes/admission-host.js";
|
|
38
|
-
import { buildOutpostCapacityCommand, buildOutpostIdentityBackfillCommand, buildOutpostLaneCommand,
|
|
38
|
+
import { buildOutpostCapacityCommand, buildOutpostIdentityBackfillCommand, buildOutpostLaneCommand, buildOutpostQuestionListCommand, buildOutpostPickupCommand, createOutpostsClient, dispatchErrorMessage, dispatchOutpostPickup, buildOutpostLaneListCommand, outpostRejectedEffortOption, parseOutpostPickupPayload, stageOutpostPayload, } from "../lib/lanes/outpost-dispatch.js";
|
|
39
39
|
import { outpostHireSummaryCacheKey, readOutpostHireSummaryCache, writeOutpostHireSummaryCache, } from "../lib/lanes/outpost-hire-cache.js";
|
|
40
40
|
import { buildHostCapacitySnapshot, chooseHostForLane, parseHostCapacitySnapshot, } from "../lib/lanes/host-capacity.js";
|
|
41
41
|
import { BALANCE_MOVABLE_STATES, selectClaudeAccount, } from "../lib/lanes/claude-balance.js";
|
|
@@ -75,49 +75,6 @@ export { MAX_LANE_DEPTH };
|
|
|
75
75
|
export const LANES_RUNS_REL = path.join("workspace", "lanes-runs");
|
|
76
76
|
/** The lane → mesh seam. Defined in lib/lanes/mesh-events.ts; the watcher shares it. */
|
|
77
77
|
export { emitLaneDecisionEvents };
|
|
78
|
-
export const LANE_OUTPOST_LOAD_BALANCING_FLAG = "lanes.outpost-load-balancing";
|
|
79
|
-
function lanePlacementFlagErrorCode(error) {
|
|
80
|
-
if (error && typeof error === "object") {
|
|
81
|
-
for (const key of ["code", "error", "name"]) {
|
|
82
|
-
const value = error[key];
|
|
83
|
-
if (typeof value === "string" && value.trim())
|
|
84
|
-
return value;
|
|
85
|
-
}
|
|
86
|
-
}
|
|
87
|
-
return "UnknownError";
|
|
88
|
-
}
|
|
89
|
-
function reportLanePlacementFlagReadFailure(error) {
|
|
90
|
-
process.stderr.write(`${JSON.stringify({
|
|
91
|
-
event: "lanes.outpost_load_balancing.flag_read_failed",
|
|
92
|
-
flag: LANE_OUTPOST_LOAD_BALANCING_FLAG,
|
|
93
|
-
error: lanePlacementFlagErrorCode(error),
|
|
94
|
-
fallback: false,
|
|
95
|
-
})}\n`);
|
|
96
|
-
}
|
|
97
|
-
function defaultLanePlacementEnabled(company) {
|
|
98
|
-
return readLanesRegistryFlag(LANE_OUTPOST_LOAD_BALANCING_FLAG, {
|
|
99
|
-
company,
|
|
100
|
-
reportFailure: reportLanePlacementFlagReadFailure,
|
|
101
|
-
});
|
|
102
|
-
}
|
|
103
|
-
async function readLanePlacementEnabled(ctx, company) {
|
|
104
|
-
try {
|
|
105
|
-
return await ctx.lanePlacementEnabled(company);
|
|
106
|
-
}
|
|
107
|
-
catch (error) {
|
|
108
|
-
reportLanePlacementFlagReadFailure(error);
|
|
109
|
-
return false;
|
|
110
|
-
}
|
|
111
|
-
}
|
|
112
|
-
function defaultLaneTrackerEnabled(company) {
|
|
113
|
-
return readLanesRegistryFlag(LANES_TRACKER_FLAG, { company });
|
|
114
|
-
}
|
|
115
|
-
function processLanePlacementCompany(ctx) {
|
|
116
|
-
const context = resolveProcessFlagCompanyContext(ctx.env);
|
|
117
|
-
if (!context.companySlug)
|
|
118
|
-
return undefined;
|
|
119
|
-
return { slug: context.companySlug, uid: context.companyUid ?? "" };
|
|
120
|
-
}
|
|
121
78
|
function withoutProviderHome(value) {
|
|
122
79
|
if (Array.isArray(value))
|
|
123
80
|
return value.map(withoutProviderHome);
|
|
@@ -328,9 +285,8 @@ function resolveDeps(deps = {}) {
|
|
|
328
285
|
storyLedgerEnabled: deps.storyLedgerEnabled ?? laneStoryLedgerEnabled,
|
|
329
286
|
isSsmOwnerDispatch: deps.isSsmOwnerDispatch ?? (() => isSsmRunCommandProcess()),
|
|
330
287
|
withGrantHireLocks: deps.withGrantHireLocks ?? withLaneHireLocks,
|
|
331
|
-
laneTrackerEnabled: deps.laneTrackerEnabled ?? defaultLaneTrackerEnabled,
|
|
332
288
|
mergeReviewEnabled: deps.mergeReviewEnabled,
|
|
333
|
-
|
|
289
|
+
mergeHoldEnabled: deps.mergeHoldEnabled,
|
|
334
290
|
};
|
|
335
291
|
}
|
|
336
292
|
function livenessDepsFromCtx(ctx) {
|
|
@@ -513,6 +469,12 @@ function mergeOwnerLaneHierarchy(supplied, ownerLanes) {
|
|
|
513
469
|
byId.set(lane.lane_id, lane);
|
|
514
470
|
return [...byId.values()];
|
|
515
471
|
}
|
|
472
|
+
function mergeOwnerLaneRecords(supplied, ownerLanes) {
|
|
473
|
+
const byId = new Map(supplied.map((lane) => [lane.lane_id, lane]));
|
|
474
|
+
for (const lane of ownerLanes)
|
|
475
|
+
byId.set(lane.lane_id, lane);
|
|
476
|
+
return [...byId.values()];
|
|
477
|
+
}
|
|
516
478
|
function outpostCliOverride(ctx, outpostId) {
|
|
517
479
|
const envKey = `HQ_LANES_OUTPOST_CLI_${outpostId.toUpperCase().replace(/[^A-Z0-9_]/g, "_")}`;
|
|
518
480
|
const configured = ctx.env[envKey];
|
|
@@ -863,8 +825,7 @@ async function mapWithConcurrency(values, concurrency, map) {
|
|
|
863
825
|
async function withResumeHireAdmission(ctx, lane, fn, grantContext) {
|
|
864
826
|
const hqRoot = ctx.hqRoot();
|
|
865
827
|
if (grantContext !== undefined && (!ctx.isSsmOwnerDispatch() ||
|
|
866
|
-
lane
|
|
867
|
-
!laneIsHostedHere(ctx, lane) ||
|
|
828
|
+
!laneGrantTargetHostedHere(ctx, lane) ||
|
|
868
829
|
lane.senior.kind !== "lane" ||
|
|
869
830
|
!sameHireGrantSenior(lane.senior, grantContext.expected_senior))) {
|
|
870
831
|
throw new LanesError("hire_grant_senior_changed", `Lane ${lane.lane_id}'s senior changed after the primary authorized its resume`, { lane_id: lane.lane_id, expected_senior: grantContext.expected_senior, current_senior: lane.senior });
|
|
@@ -1124,9 +1085,27 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
|
|
|
1124
1085
|
new Set(outpostIds).size !== outpostIds.length) {
|
|
1125
1086
|
throw new LanesError("outpost_list_invalid", "Registered Outpost list is malformed or exceeds the lane lookup limit", { lane_id: laneId, count: outpostIds.length, maximum: MAX_PLACEMENT_OUTPOSTS });
|
|
1126
1087
|
}
|
|
1088
|
+
const requestedOutpostId = options.outpostId?.trim();
|
|
1089
|
+
if (options.outpostId !== undefined && !requestedOutpostId) {
|
|
1090
|
+
throw new LanesError("outpost_id_invalid", "--outpost must be a registered Outpost id", { lane_id: laneId });
|
|
1091
|
+
}
|
|
1092
|
+
if (requestedOutpostId !== undefined && !outpostIds.includes(requestedOutpostId)) {
|
|
1093
|
+
throw new LanesError("outpost_not_registered", `Outpost ${requestedOutpostId} is not registered`, { lane_id: laneId, outpost_id: requestedOutpostId });
|
|
1094
|
+
}
|
|
1127
1095
|
const matches = [];
|
|
1128
|
-
|
|
1129
|
-
|
|
1096
|
+
const failedOutpostIds = [];
|
|
1097
|
+
const lookupOutpostIds = requestedOutpostId === undefined ? outpostIds : [requestedOutpostId];
|
|
1098
|
+
for (const outpostId of lookupOutpostIds) {
|
|
1099
|
+
let ownerLanes;
|
|
1100
|
+
try {
|
|
1101
|
+
ownerLanes = await readOutpostLaneRecords(ctx, outpostId);
|
|
1102
|
+
}
|
|
1103
|
+
catch (err) {
|
|
1104
|
+
if (requestedOutpostId !== undefined)
|
|
1105
|
+
throw err;
|
|
1106
|
+
failedOutpostIds.push(outpostId);
|
|
1107
|
+
continue;
|
|
1108
|
+
}
|
|
1130
1109
|
for (const lane of ownerLanes) {
|
|
1131
1110
|
if (lane.lane_id === laneId &&
|
|
1132
1111
|
lane.host.kind === "local" &&
|
|
@@ -1136,7 +1115,7 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
|
|
|
1136
1115
|
}
|
|
1137
1116
|
}
|
|
1138
1117
|
if (matches.length === 0) {
|
|
1139
|
-
throw commandLaneNotFound(laneId, options.outpostHint === true);
|
|
1118
|
+
throw commandLaneNotFound(laneId, options.outpostHint === true, failedOutpostIds.length > 0 ? { failed_outpost_ids: failedOutpostIds } : undefined);
|
|
1140
1119
|
}
|
|
1141
1120
|
if (matches.length !== 1) {
|
|
1142
1121
|
throw new LanesError("lane_outpost_ambiguous", `More than one registered Outpost reported lane ${laneId} with the same recorded host identity`, { lane_id: laneId, outpost_ids: matches.map((match) => match.outpostId) });
|
|
@@ -1149,11 +1128,11 @@ async function resolveCommandLaneTarget(ctx, laneId, options = {}) {
|
|
|
1149
1128
|
ownerHost: match.lane.host,
|
|
1150
1129
|
};
|
|
1151
1130
|
}
|
|
1152
|
-
function commandLaneNotFound(laneId, outpostHint) {
|
|
1131
|
+
function commandLaneNotFound(laneId, outpostHint, details = {}) {
|
|
1153
1132
|
const hint = outpostHint
|
|
1154
1133
|
? "; pass --outpost <id> to confirm a registered Outpost"
|
|
1155
1134
|
: "";
|
|
1156
|
-
return new LanesError("lane_not_found", `Lane ${laneId} was not found locally or on a registered Outpost with a matching host suffix${hint}`, { lane_id: laneId });
|
|
1135
|
+
return new LanesError("lane_not_found", `Lane ${laneId} was not found locally or on a registered Outpost with a matching host suffix${hint}`, { lane_id: laneId, ...details });
|
|
1157
1136
|
}
|
|
1158
1137
|
async function loadClaudeBalanceHostSnapshots(ctx, hqRoot, config) {
|
|
1159
1138
|
const localHost = laneHostFromCtx(ctx);
|
|
@@ -1280,29 +1259,18 @@ async function loadClaudeBalanceHostSnapshots(ctx, hqRoot, config) {
|
|
|
1280
1259
|
}
|
|
1281
1260
|
return { snapshots, unavailableHosts };
|
|
1282
1261
|
}
|
|
1283
|
-
function parsePlacementPolicy(raw) {
|
|
1284
|
-
const value = raw.trim();
|
|
1285
|
-
if (value === "local")
|
|
1286
|
-
return { kind: "local" };
|
|
1287
|
-
if (value === "auto")
|
|
1288
|
-
return { kind: "auto" };
|
|
1289
|
-
const match = /^outpost:(.+)$/.exec(value);
|
|
1290
|
-
const id = match?.[1]?.trim();
|
|
1291
|
-
if (id)
|
|
1292
|
-
return { kind: "outpost", id };
|
|
1293
|
-
throw new LanesError("lane_placement_invalid", `Invalid placement ${JSON.stringify(raw)}. Accepted: local, auto, outpost:<id>`, { value: raw, accepted: ["local", "auto", "outpost:<id>"] });
|
|
1294
|
-
}
|
|
1295
|
-
async function assertPlacementEnabled(ctx, company, readPlacementEnabled = () => readLanePlacementEnabled(ctx, company)) {
|
|
1296
|
-
if (await readPlacementEnabled())
|
|
1297
|
-
return;
|
|
1298
|
-
throw new LanesError("lane_placement_disabled", `Lane placement options require hq-flags gate ${LANE_OUTPOST_LOAD_BALANCING_FLAG}`, { flag: LANE_OUTPOST_LOAD_BALANCING_FLAG });
|
|
1299
|
-
}
|
|
1300
1262
|
function loadAllQuestions(hqRoot) {
|
|
1301
1263
|
return listQuestionIds(hqRoot).map((id) => readQuestion(hqRoot, id));
|
|
1302
1264
|
}
|
|
1303
1265
|
function sessionMetaPath(hqRoot, sessionId) {
|
|
1304
1266
|
return path.join(hqRoot, "workspace", "sessions", sessionId, "meta.yaml");
|
|
1305
1267
|
}
|
|
1268
|
+
function isSessionMetaDocument(value) {
|
|
1269
|
+
if (value === null || typeof value !== "object" || Array.isArray(value))
|
|
1270
|
+
return false;
|
|
1271
|
+
const prototype = Object.getPrototypeOf(value);
|
|
1272
|
+
return prototype === Object.prototype || prototype === null;
|
|
1273
|
+
}
|
|
1306
1274
|
function loadSessionMeta(hqRoot, sessionId, ctx) {
|
|
1307
1275
|
const filePath = sessionMetaPath(hqRoot, sessionId);
|
|
1308
1276
|
if (!ctx.existsSync(filePath))
|
|
@@ -1323,7 +1291,7 @@ function loadSessionMeta(hqRoot, sessionId, ctx) {
|
|
|
1323
1291
|
process.stderr.write(`hq lanes: failed to parse session meta ${filePath}: ${err instanceof Error ? err.message : String(err)}\n`);
|
|
1324
1292
|
return { status: "malformed" };
|
|
1325
1293
|
}
|
|
1326
|
-
if (
|
|
1294
|
+
if (!isSessionMetaDocument(doc)) {
|
|
1327
1295
|
return { status: "malformed" };
|
|
1328
1296
|
}
|
|
1329
1297
|
return { status: "ok", doc: doc };
|
|
@@ -1334,11 +1302,21 @@ function readSessionMeta(hqRoot, sessionId, ctx) {
|
|
|
1334
1302
|
return null;
|
|
1335
1303
|
return loaded.doc;
|
|
1336
1304
|
}
|
|
1305
|
+
/**
|
|
1306
|
+
* Apply the documented session senior default only when the key was never
|
|
1307
|
+
* written. `core/scripts/hq-session.sh` section 3.1 defaults new sessions to
|
|
1308
|
+
* `user` and says authority walkers apply that default at the walk site.
|
|
1309
|
+
*/
|
|
1310
|
+
function sessionSeniorValue(meta) {
|
|
1311
|
+
return Object.prototype.hasOwnProperty.call(meta, "senior")
|
|
1312
|
+
? meta.senior
|
|
1313
|
+
: "user";
|
|
1314
|
+
}
|
|
1337
1315
|
function seniorOfSession(hqRoot, sessionId, ctx) {
|
|
1338
1316
|
const meta = readSessionMeta(hqRoot, sessionId, ctx);
|
|
1339
1317
|
if (!meta)
|
|
1340
1318
|
return { kind: "user" };
|
|
1341
|
-
const senior = meta
|
|
1319
|
+
const senior = sessionSeniorValue(meta);
|
|
1342
1320
|
if (typeof senior === "string" && senior.trim()) {
|
|
1343
1321
|
return parseSeniorRef(senior);
|
|
1344
1322
|
}
|
|
@@ -2325,13 +2303,16 @@ async function runCreate(ctx, opts) {
|
|
|
2325
2303
|
const company = companyFlag
|
|
2326
2304
|
? await ctx.resolveCompany(companyFlag)
|
|
2327
2305
|
: null;
|
|
2306
|
+
const callerAuthorization = requestedHireGrant !== undefined || ctx.env.HQ_LANE_ID?.trim()
|
|
2307
|
+
? callerSeniorAuthorization(ctx, senior)
|
|
2308
|
+
: { authorized: true };
|
|
2328
2309
|
if (requestedHireGrant !== undefined &&
|
|
2329
|
-
!
|
|
2330
|
-
throw new LanesError("hire_grant_unauthorized", `Only senior ${seniorKey(senior)} may set a lane's hire grant`, { senior, hire_grant: requestedHireGrant });
|
|
2310
|
+
!callerAuthorization.authorized) {
|
|
2311
|
+
throw new LanesError("hire_grant_unauthorized", appendCallerSeniorReason(`Only senior ${seniorKey(senior)} may set a lane's hire grant`, callerAuthorization), { senior, hire_grant: requestedHireGrant, reason: callerAuthorization.reason });
|
|
2331
2312
|
}
|
|
2332
2313
|
if (ctx.env.HQ_LANE_ID?.trim() &&
|
|
2333
|
-
!
|
|
2334
|
-
throw new LanesError("hire_grant_unauthorized", `Only senior lane ${senior.id} may create a direct child lane`, { senior_lane_id: senior.id });
|
|
2314
|
+
!callerAuthorization.authorized) {
|
|
2315
|
+
throw new LanesError("hire_grant_unauthorized", appendCallerSeniorReason(`Only senior lane ${senior.id} may create a direct child lane`, callerAuthorization), { senior_lane_id: senior.id, reason: callerAuthorization.reason });
|
|
2335
2316
|
}
|
|
2336
2317
|
const hireGrant = requestedHireGrant ?? 0;
|
|
2337
2318
|
const laneDepth = derivedLaneDepth(hqRoot, senior, ctx);
|
|
@@ -3355,7 +3336,7 @@ function authorizeFailedResume(ctx, lane, encodedContext) {
|
|
|
3355
3336
|
throw new LanesError("failed_resume_authorization_invalid", "Failed-resume authorization context is only valid on an Outpost owner dispatch", { lane_id: lane.lane_id });
|
|
3356
3337
|
}
|
|
3357
3338
|
const context = parseOutpostFailedResumeContext(encodedContext, lane.lane_id);
|
|
3358
|
-
if (lane
|
|
3339
|
+
if (!laneGrantTargetHostedHere(ctx, lane) ||
|
|
3359
3340
|
!sameHireGrantSenior(lane.senior, context.expectedSenior)) {
|
|
3360
3341
|
throw new LanesError("failed_resume_senior_changed", `Lane ${lane.lane_id}'s senior changed after its failed-resume request was authorized`, {
|
|
3361
3342
|
lane_id: lane.lane_id,
|
|
@@ -3365,18 +3346,25 @@ function authorizeFailedResume(ctx, lane, encodedContext) {
|
|
|
3365
3346
|
}
|
|
3366
3347
|
return context.actor;
|
|
3367
3348
|
}
|
|
3368
|
-
|
|
3369
|
-
|
|
3349
|
+
const callerAuthorization = callerSeniorAuthorization(ctx, lane.senior);
|
|
3350
|
+
if (!callerAuthorization.authorized) {
|
|
3351
|
+
throw new LanesError("failed_resume_unauthorized", appendCallerSeniorReason("Only the lane's senior may resume a failed lane with --from-failed", callerAuthorization), { lane_id: lane.lane_id, reason: callerAuthorization.reason });
|
|
3370
3352
|
}
|
|
3371
3353
|
return resumeProfileRepinActor(ctx);
|
|
3372
3354
|
}
|
|
3373
|
-
function workerProfileHashMismatch(lane, profile) {
|
|
3374
|
-
|
|
3355
|
+
function workerProfileHashMismatch(lane, profile, callerAuthorization) {
|
|
3356
|
+
const message = `Worker profile ${JSON.stringify(profile.id)} on Outpost ${lane.host.id} does not match the hash recorded by the lane`;
|
|
3357
|
+
return new LanesError("worker_profile_hash_mismatch", callerAuthorization && !callerAuthorization.authorized
|
|
3358
|
+
? appendCallerSeniorReason(message, callerAuthorization)
|
|
3359
|
+
: message, {
|
|
3375
3360
|
lane_id: lane.lane_id,
|
|
3376
3361
|
worker: profile.id,
|
|
3377
3362
|
outpost: lane.host.kind === "outpost" ? lane.host.id : null,
|
|
3378
3363
|
expected_hash: lane.worker_profile_hash ?? null,
|
|
3379
3364
|
actual_hash: profile.profile.contentHash,
|
|
3365
|
+
...(callerAuthorization && !callerAuthorization.authorized
|
|
3366
|
+
? { reason: callerAuthorization.reason }
|
|
3367
|
+
: {}),
|
|
3380
3368
|
});
|
|
3381
3369
|
}
|
|
3382
3370
|
function authorizeResumeProfileRepin(ctx, lane, profile, encodedContext) {
|
|
@@ -3396,7 +3384,7 @@ function authorizeResumeProfileRepin(ctx, lane, profile, encodedContext) {
|
|
|
3396
3384
|
? new LanesError("worker_profile_hash_mismatch", "Profile re-pin context is invalid", { lane_id: lane.lane_id })
|
|
3397
3385
|
: workerProfileHashMismatch(lane, profile);
|
|
3398
3386
|
}
|
|
3399
|
-
if (lane
|
|
3387
|
+
if (!laneGrantTargetHostedHere(ctx, lane) ||
|
|
3400
3388
|
!sameHireGrantSenior(lane.senior, context.expected_senior)) {
|
|
3401
3389
|
throw profile === undefined
|
|
3402
3390
|
? new LanesError("worker_profile_hash_mismatch", "Lane senior changed after profile re-pin authorization", { lane_id: lane.lane_id })
|
|
@@ -3404,7 +3392,7 @@ function authorizeResumeProfileRepin(ctx, lane, profile, encodedContext) {
|
|
|
3404
3392
|
}
|
|
3405
3393
|
}
|
|
3406
3394
|
if (profile === undefined ||
|
|
3407
|
-
lane.host.kind !== "outpost" ||
|
|
3395
|
+
(lane.host.kind !== "outpost" && !(ssmOwnerDispatch && laneGrantTargetHostedHere(ctx, lane))) ||
|
|
3408
3396
|
lane.worker_profile_hash === profile.profile.contentHash) {
|
|
3409
3397
|
return undefined;
|
|
3410
3398
|
}
|
|
@@ -3419,8 +3407,9 @@ function authorizeResumeProfileRepin(ctx, lane, profile, encodedContext) {
|
|
|
3419
3407
|
newHash: profile.profile.contentHash,
|
|
3420
3408
|
};
|
|
3421
3409
|
}
|
|
3422
|
-
|
|
3423
|
-
|
|
3410
|
+
const callerAuthorization = callerSeniorAuthorization(ctx, lane.senior);
|
|
3411
|
+
if (context !== undefined || !callerAuthorization.authorized) {
|
|
3412
|
+
throw workerProfileHashMismatch(lane, profile, context === undefined ? callerAuthorization : undefined);
|
|
3424
3413
|
}
|
|
3425
3414
|
return {
|
|
3426
3415
|
kind: "senior",
|
|
@@ -3431,18 +3420,26 @@ function authorizeResumeProfileRepin(ctx, lane, profile, encodedContext) {
|
|
|
3431
3420
|
};
|
|
3432
3421
|
}
|
|
3433
3422
|
function assertResumeProfileRepinStillAuthorized(ctx, current, authorization) {
|
|
3434
|
-
const
|
|
3435
|
-
|
|
3423
|
+
const callerAuthorization = authorization.kind === "senior"
|
|
3424
|
+
? callerSeniorAuthorization(ctx, current.senior)
|
|
3425
|
+
: undefined;
|
|
3426
|
+
const authorized = sameHireGrantSenior(current.senior, authorization.expectedSenior) &&
|
|
3436
3427
|
(authorization.kind === "ssm-owner-dispatch"
|
|
3437
|
-
? ctx.isSsmOwnerDispatch()
|
|
3438
|
-
:
|
|
3428
|
+
? ctx.isSsmOwnerDispatch() && laneGrantTargetHostedHere(ctx, current)
|
|
3429
|
+
: current.host.kind === "outpost" && callerAuthorization?.authorized === true);
|
|
3439
3430
|
if (!authorized) {
|
|
3440
|
-
|
|
3431
|
+
const message = `Lane ${current.lane_id}'s senior changed before its Outpost profile could be re-pinned`;
|
|
3432
|
+
throw new LanesError("worker_profile_hash_mismatch", callerAuthorization && !callerAuthorization.authorized
|
|
3433
|
+
? appendCallerSeniorReason(message, callerAuthorization)
|
|
3434
|
+
: message, {
|
|
3441
3435
|
lane_id: current.lane_id,
|
|
3442
3436
|
expected_senior: authorization.expectedSenior,
|
|
3443
3437
|
current_senior: current.senior,
|
|
3444
3438
|
expected_hash: current.worker_profile_hash ?? null,
|
|
3445
3439
|
actual_hash: authorization.newHash,
|
|
3440
|
+
...(callerAuthorization && !callerAuthorization.authorized
|
|
3441
|
+
? { reason: callerAuthorization.reason }
|
|
3442
|
+
: {}),
|
|
3446
3443
|
});
|
|
3447
3444
|
}
|
|
3448
3445
|
}
|
|
@@ -3485,19 +3482,14 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
|
|
|
3485
3482
|
let grantContext;
|
|
3486
3483
|
let grantHierarchy;
|
|
3487
3484
|
if (action === "grant") {
|
|
3488
|
-
|
|
3489
|
-
const ownerLanes = await readOutpostLaneRecords(ctx, outpostId);
|
|
3490
|
-
const byId = new Map(hierarchy.map((record) => [record.lane_id, record]));
|
|
3491
|
-
for (const ownerLane of ownerLanes) {
|
|
3492
|
-
byId.set(ownerLane.lane_id, ownerLane);
|
|
3493
|
-
}
|
|
3494
|
-
hierarchy = [...byId.values()];
|
|
3485
|
+
const hierarchy = mergeOwnerLaneRecords(loadAllLanes(ctx.hqRoot()), await readOutpostLaneRecords(ctx, outpostId));
|
|
3495
3486
|
grantHierarchy = hierarchy;
|
|
3496
3487
|
grantContext = createOutpostHireGrantContext(lane, hierarchy);
|
|
3497
3488
|
}
|
|
3498
3489
|
else if ((action === "resume" || action === "message") &&
|
|
3499
3490
|
lane.senior.kind === "lane") {
|
|
3500
|
-
|
|
3491
|
+
const hierarchy = mergeOwnerLaneRecords(loadAllLanes(ctx.hqRoot()), await readOutpostLaneRecords(ctx, outpostId));
|
|
3492
|
+
grantContext = createOutpostHireGrantContext(lane, hierarchy, action === "message" ? options.supersededMessageCallerId : undefined);
|
|
3501
3493
|
}
|
|
3502
3494
|
let dispatched;
|
|
3503
3495
|
let grantContextRejected = false;
|
|
@@ -4304,336 +4296,6 @@ async function runOutpostIdentityBackfillTarget(ctx, opts) {
|
|
|
4304
4296
|
marker_path: OUTPOST_ID_PATH,
|
|
4305
4297
|
});
|
|
4306
4298
|
}
|
|
4307
|
-
function laneCanMoveBeforeStart(lane) {
|
|
4308
|
-
return lane.state === "queued" &&
|
|
4309
|
-
lane.pickup_validation_pending !== true &&
|
|
4310
|
-
lane.admission_reserved_at === undefined &&
|
|
4311
|
-
lane.provider_session_id === undefined &&
|
|
4312
|
-
lane.provider_session_id_absent === undefined &&
|
|
4313
|
-
lane.provider_home_at_spawn === undefined &&
|
|
4314
|
-
lane.last_resume === undefined &&
|
|
4315
|
-
lane.last_run_usage === undefined &&
|
|
4316
|
-
lane.last_host_report_at === undefined &&
|
|
4317
|
-
lane.pending_resume === undefined;
|
|
4318
|
-
}
|
|
4319
|
-
function readLaneTimeout(hqRoot, laneId) {
|
|
4320
|
-
const raw = readJsonFile(timeoutPath(hqRoot, laneId));
|
|
4321
|
-
if (raw === null)
|
|
4322
|
-
return undefined;
|
|
4323
|
-
if (typeof raw !== "object" || Array.isArray(raw) ||
|
|
4324
|
-
typeof raw.timeout_secs !== "number" ||
|
|
4325
|
-
typeof raw.deadline_at !== "string" ||
|
|
4326
|
-
!Number.isSafeInteger(raw.timeout_secs) ||
|
|
4327
|
-
raw.timeout_secs <= 0 ||
|
|
4328
|
-
!Number.isFinite(Date.parse(raw.deadline_at))) {
|
|
4329
|
-
throw new LanesError("lane_timeout_invalid", `Lane ${laneId} has a malformed timeout record`, {
|
|
4330
|
-
lane_id: laneId,
|
|
4331
|
-
});
|
|
4332
|
-
}
|
|
4333
|
-
const record = raw;
|
|
4334
|
-
return { timeout_secs: record.timeout_secs, deadline_at: record.deadline_at };
|
|
4335
|
-
}
|
|
4336
|
-
async function runMove(ctx, laneId, opts) {
|
|
4337
|
-
const hqRoot = ctx.hqRoot();
|
|
4338
|
-
const policy = parsePlacementPolicy(opts.placement ?? "");
|
|
4339
|
-
const source = readLane(hqRoot, laneId);
|
|
4340
|
-
const placementCompany = resolveLaneCompany(hqRoot, source).company ?? undefined;
|
|
4341
|
-
let placementGate;
|
|
4342
|
-
const placementEnabled = () => {
|
|
4343
|
-
placementGate ??= readLanePlacementEnabled(ctx, placementCompany);
|
|
4344
|
-
return placementGate;
|
|
4345
|
-
};
|
|
4346
|
-
await assertPlacementEnabled(ctx, placementCompany, placementEnabled);
|
|
4347
|
-
if (!laneCanMoveBeforeStart(source)) {
|
|
4348
|
-
throw new LanesError(source.state === "queued" ? "lane_move_started" : "lane_not_queued", `Lane ${laneId} cannot move after it has started; current state is ${source.state}`, { lane_id: laneId, state: source.state });
|
|
4349
|
-
}
|
|
4350
|
-
const sourceHost = source.host;
|
|
4351
|
-
const config = loadLanesConfig(hqRoot);
|
|
4352
|
-
const capacity = await loadHostCapacitySnapshots(ctx, hqRoot, config);
|
|
4353
|
-
let targetSnapshots = capacity.snapshots.filter((snapshot) => snapshot.host.kind !== sourceHost.kind || snapshot.host.id !== sourceHost.id);
|
|
4354
|
-
if (policy.kind === "outpost") {
|
|
4355
|
-
targetSnapshots = targetSnapshots.filter((snapshot) => snapshot.host.kind === "outpost" && snapshot.host.id === policy.id);
|
|
4356
|
-
}
|
|
4357
|
-
else if (policy.kind === "local") {
|
|
4358
|
-
targetSnapshots = targetSnapshots.filter((snapshot) => snapshot.host.kind === "local");
|
|
4359
|
-
}
|
|
4360
|
-
const profileAccounts = source.worker_profile === undefined
|
|
4361
|
-
? undefined
|
|
4362
|
-
: loadWorkerProfile(hqRoot, source.worker_profile).meta.accounts;
|
|
4363
|
-
const allowedAccounts = source.admission_accounts ?? profileAccounts;
|
|
4364
|
-
const choice = chooseHostForLane(targetSnapshots, {
|
|
4365
|
-
provider: source.provider,
|
|
4366
|
-
...(source.account !== undefined ? { account: source.account } : {}),
|
|
4367
|
-
...(allowedAccounts !== undefined ? { allowedAccounts } : {}),
|
|
4368
|
-
});
|
|
4369
|
-
let moved;
|
|
4370
|
-
if (laneRunsHere(source, reconcileHostFromCtx(ctx))) {
|
|
4371
|
-
moved = await commitLaneMove(ctx, laneId, choice.host, choice.account, placementEnabled);
|
|
4372
|
-
}
|
|
4373
|
-
else if (source.host.kind === "outpost") {
|
|
4374
|
-
moved = await dispatchRemoteLaneMove(ctx, source, choice.host, choice.account);
|
|
4375
|
-
}
|
|
4376
|
-
else {
|
|
4377
|
-
throw new LanesError("lane_remote", `Cannot move lane ${laneId} from this host; its state is owned by ${source.host.kind}:${source.host.id}`, { lane_id: laneId, host: source.host, action: "move" });
|
|
4378
|
-
}
|
|
4379
|
-
printJson({
|
|
4380
|
-
ok: true,
|
|
4381
|
-
action: "move",
|
|
4382
|
-
lane_id: laneId,
|
|
4383
|
-
state: moved.state,
|
|
4384
|
-
host: moved.host,
|
|
4385
|
-
account: moved.account ?? null,
|
|
4386
|
-
lane: moved,
|
|
4387
|
-
});
|
|
4388
|
-
}
|
|
4389
|
-
async function commitLaneMove(ctx, laneId, target, account, readPlacementEnabled) {
|
|
4390
|
-
const hqRoot = ctx.hqRoot();
|
|
4391
|
-
const before = readLane(hqRoot, laneId);
|
|
4392
|
-
assertLaneRunsHereForOperatorMutation(ctx, before, "move-target");
|
|
4393
|
-
await assertPlacementEnabled(ctx, resolveLaneCompany(hqRoot, before).company ?? undefined, readPlacementEnabled);
|
|
4394
|
-
if (before.host.kind === target.kind && before.host.id === target.id) {
|
|
4395
|
-
throw new LanesError("lane_move_same_host", `Lane ${laneId} is already on ${target.kind}:${target.id}`, {
|
|
4396
|
-
lane_id: laneId,
|
|
4397
|
-
host: target,
|
|
4398
|
-
});
|
|
4399
|
-
}
|
|
4400
|
-
if (!laneCanMoveBeforeStart(before)) {
|
|
4401
|
-
throw new LanesError(before.state === "queued" ? "lane_move_started" : "lane_not_queued", `Lane ${laneId} cannot move after it has started; current state is ${before.state}`, { lane_id: laneId, state: before.state });
|
|
4402
|
-
}
|
|
4403
|
-
if (target.kind === "outpost") {
|
|
4404
|
-
let known;
|
|
4405
|
-
try {
|
|
4406
|
-
known = await ctx.listOutpostIds();
|
|
4407
|
-
}
|
|
4408
|
-
catch (err) {
|
|
4409
|
-
throw new LanesError("outpost_list_unavailable", `Failed to list Outpost ids: ${dispatchErrorMessage(err)}`, {});
|
|
4410
|
-
}
|
|
4411
|
-
assertKnownOutpost(target.id, known);
|
|
4412
|
-
}
|
|
4413
|
-
const accountConfig = loadLanesConfig(hqRoot).providers[before.provider].accounts[account];
|
|
4414
|
-
if (accountConfig === undefined || !accountConfig.enabled) {
|
|
4415
|
-
throw new LanesError("lane_move_account_invalid", `Account ${account} is unavailable for ${before.provider}`, {
|
|
4416
|
-
lane_id: laneId,
|
|
4417
|
-
provider: before.provider,
|
|
4418
|
-
account,
|
|
4419
|
-
});
|
|
4420
|
-
}
|
|
4421
|
-
const profileAccounts = before.worker_profile === undefined
|
|
4422
|
-
? undefined
|
|
4423
|
-
: loadWorkerProfile(hqRoot, before.worker_profile).meta.accounts;
|
|
4424
|
-
const allowedAccounts = before.admission_accounts ?? profileAccounts;
|
|
4425
|
-
if (allowedAccounts !== undefined &&
|
|
4426
|
-
!allowedAccounts.includes(account)) {
|
|
4427
|
-
throw new LanesError("lane_move_account_invalid", `Account ${account} is not allowed by lane ${laneId}`, {
|
|
4428
|
-
lane_id: laneId,
|
|
4429
|
-
account,
|
|
4430
|
-
allowed_accounts: allowedAccounts,
|
|
4431
|
-
});
|
|
4432
|
-
}
|
|
4433
|
-
const briefText = target.kind === "outpost"
|
|
4434
|
-
? (() => {
|
|
4435
|
-
const briefPath = path.join(runDir(hqRoot, laneId), "brief.md");
|
|
4436
|
-
if (!ctx.existsSync(briefPath)) {
|
|
4437
|
-
throw new LanesError("lane_move_brief_missing", `Lane ${laneId} has no stored brief to dispatch`, {
|
|
4438
|
-
lane_id: laneId,
|
|
4439
|
-
});
|
|
4440
|
-
}
|
|
4441
|
-
return ctx.readFileSync(briefPath, "utf8");
|
|
4442
|
-
})()
|
|
4443
|
-
: undefined;
|
|
4444
|
-
const timeout = target.kind === "outpost" ? readLaneTimeout(hqRoot, laneId) : undefined;
|
|
4445
|
-
const moved = await withLaneAdmissionLock(hqRoot, before.provider, async () => {
|
|
4446
|
-
const current = readLane(hqRoot, laneId);
|
|
4447
|
-
assertLaneRunsHereForOperatorMutation(ctx, current, "move-target");
|
|
4448
|
-
if (!laneCanMoveBeforeStart(current)) {
|
|
4449
|
-
throw new LanesError(current.state === "queued" ? "lane_move_started" : "lane_not_queued", `Lane ${laneId} started before the move could commit`, { lane_id: laneId, state: current.state });
|
|
4450
|
-
}
|
|
4451
|
-
return updateLane(hqRoot, laneId, current.version, "command", (live) => ({
|
|
4452
|
-
...live,
|
|
4453
|
-
host: target,
|
|
4454
|
-
account,
|
|
4455
|
-
queued_reason: undefined,
|
|
4456
|
-
admission_reserved_at: undefined,
|
|
4457
|
-
}));
|
|
4458
|
-
}, ctx.sleep);
|
|
4459
|
-
if (target.kind !== "outpost")
|
|
4460
|
-
return moved;
|
|
4461
|
-
try {
|
|
4462
|
-
return await dispatchCreatedOutpostLane(ctx, {
|
|
4463
|
-
hqRoot,
|
|
4464
|
-
lane: moved,
|
|
4465
|
-
outpostId: target.id,
|
|
4466
|
-
briefText: briefText ?? "",
|
|
4467
|
-
timeout,
|
|
4468
|
-
preserveQueuedOnFailure: true,
|
|
4469
|
-
});
|
|
4470
|
-
}
|
|
4471
|
-
catch (err) {
|
|
4472
|
-
const dispatchDetails = isLanesError(err) ? err.details : {};
|
|
4473
|
-
const commandId = typeof dispatchDetails.command_id === "string" &&
|
|
4474
|
-
dispatchDetails.command_id.trim() !== ""
|
|
4475
|
-
? dispatchDetails.command_id
|
|
4476
|
-
: undefined;
|
|
4477
|
-
const definitelyNotSubmitted = commandId === undefined &&
|
|
4478
|
-
dispatchDetails.outcome_unknown !== true &&
|
|
4479
|
-
dispatchDetails.done !== false;
|
|
4480
|
-
const knownNoPersistence = (isLanesError(err) && err.error === "outpost_self_target") ||
|
|
4481
|
-
(dispatchDetails.done === true && dispatchDetails.pickup_persisted === false);
|
|
4482
|
-
if (!definitelyNotSubmitted && !knownNoPersistence) {
|
|
4483
|
-
const checkCommand = `hq lanes show ${laneId}`;
|
|
4484
|
-
const message = `Failed to dispatch lane ${laneId} to Outpost ${target.id}; outcome is unknown and the lane remains assigned to Outpost ${target.id}. ` +
|
|
4485
|
-
`Check the target copy by running \`${checkCommand}\` on Outpost ${target.id}. ` +
|
|
4486
|
-
dispatchErrorMessage(err);
|
|
4487
|
-
throw new LanesError("lane_move_dispatch_failed", message, {
|
|
4488
|
-
lane_id: laneId,
|
|
4489
|
-
message,
|
|
4490
|
-
source_host: before.host,
|
|
4491
|
-
target_host: target,
|
|
4492
|
-
source_restored: false,
|
|
4493
|
-
outcome: "unknown",
|
|
4494
|
-
...(commandId !== undefined ? { command_id: commandId } : {}),
|
|
4495
|
-
check_command: checkCommand,
|
|
4496
|
-
...(dispatchDetails.owner_error !== undefined
|
|
4497
|
-
? { owner_error: dispatchDetails.owner_error }
|
|
4498
|
-
: {}),
|
|
4499
|
-
});
|
|
4500
|
-
}
|
|
4501
|
-
let restored = false;
|
|
4502
|
-
let rollbackError;
|
|
4503
|
-
try {
|
|
4504
|
-
restored = await withLaneAdmissionLock(hqRoot, before.provider, async () => {
|
|
4505
|
-
const current = readLane(hqRoot, laneId);
|
|
4506
|
-
if (current.version !== moved.version ||
|
|
4507
|
-
current.state !== "queued" ||
|
|
4508
|
-
current.host.kind !== target.kind ||
|
|
4509
|
-
current.host.id !== target.id ||
|
|
4510
|
-
current.account !== account) {
|
|
4511
|
-
return false;
|
|
4512
|
-
}
|
|
4513
|
-
updateLane(hqRoot, laneId, current.version, "command", (live) => ({
|
|
4514
|
-
...live,
|
|
4515
|
-
host: before.host,
|
|
4516
|
-
account: before.account,
|
|
4517
|
-
queued_reason: before.queued_reason,
|
|
4518
|
-
admission_reserved_at: before.admission_reserved_at,
|
|
4519
|
-
}));
|
|
4520
|
-
return true;
|
|
4521
|
-
}, ctx.sleep);
|
|
4522
|
-
}
|
|
4523
|
-
catch (restoreErr) {
|
|
4524
|
-
rollbackError = restoreErr;
|
|
4525
|
-
}
|
|
4526
|
-
const dispatchMessage = dispatchErrorMessage(err);
|
|
4527
|
-
const outcome = restored
|
|
4528
|
-
? "the lane stayed queued on its source"
|
|
4529
|
-
: rollbackError === undefined
|
|
4530
|
-
? "the lane changed after the move and was not overwritten"
|
|
4531
|
-
: `source restoration failed: ${dispatchErrorMessage(rollbackError)}`;
|
|
4532
|
-
const message = `Failed to dispatch lane ${laneId} to Outpost ${target.id}; ${outcome}: ${dispatchMessage}`;
|
|
4533
|
-
throw new LanesError("lane_move_dispatch_failed", message, {
|
|
4534
|
-
lane_id: laneId,
|
|
4535
|
-
message,
|
|
4536
|
-
source_host: before.host,
|
|
4537
|
-
target_host: target,
|
|
4538
|
-
source_restored: restored,
|
|
4539
|
-
...(dispatchDetails.owner_error !== undefined
|
|
4540
|
-
? { owner_error: dispatchDetails.owner_error }
|
|
4541
|
-
: {}),
|
|
4542
|
-
...(rollbackError !== undefined
|
|
4543
|
-
? { rollback_error: dispatchErrorMessage(rollbackError) }
|
|
4544
|
-
: {}),
|
|
4545
|
-
});
|
|
4546
|
-
}
|
|
4547
|
-
}
|
|
4548
|
-
async function dispatchRemoteLaneMove(ctx, source, target, account) {
|
|
4549
|
-
if (source.host.kind !== "outpost") {
|
|
4550
|
-
throw new LanesError("lane_move_wrong_host", `Lane ${source.lane_id} is not Outpost-hosted`, {
|
|
4551
|
-
lane_id: source.lane_id,
|
|
4552
|
-
host: source.host,
|
|
4553
|
-
});
|
|
4554
|
-
}
|
|
4555
|
-
let dispatched;
|
|
4556
|
-
try {
|
|
4557
|
-
dispatched = await ctx.dispatchOutpostExec({
|
|
4558
|
-
outpostId: source.host.id,
|
|
4559
|
-
command: buildOutpostMoveTargetCommand({
|
|
4560
|
-
laneId: source.lane_id,
|
|
4561
|
-
host: target,
|
|
4562
|
-
account,
|
|
4563
|
-
}),
|
|
4564
|
-
timeoutSeconds: 120,
|
|
4565
|
-
waitMs: 120_000,
|
|
4566
|
-
});
|
|
4567
|
-
}
|
|
4568
|
-
catch (err) {
|
|
4569
|
-
throw new LanesError("lane_move_dispatch_failed", `Could not ask Outpost ${source.host.id} to move lane ${source.lane_id}: ${dispatchErrorMessage(err)}`, { lane_id: source.lane_id, source_outpost: source.host.id });
|
|
4570
|
-
}
|
|
4571
|
-
const ownerError = ownerErrorFromOutput(dispatched.stdout);
|
|
4572
|
-
if (ownerError) {
|
|
4573
|
-
throw new LanesError("outpost_dispatch_failed", `Outpost ${source.host.id} rejected the lane move: ${ownerError.message}`, {
|
|
4574
|
-
lane_id: source.lane_id,
|
|
4575
|
-
source_outpost: source.host.id,
|
|
4576
|
-
command_id: dispatched.commandId,
|
|
4577
|
-
status: dispatched.status,
|
|
4578
|
-
done: dispatched.done ?? false,
|
|
4579
|
-
exit_code: dispatched.exitCode,
|
|
4580
|
-
owner_error: ownerError,
|
|
4581
|
-
});
|
|
4582
|
-
}
|
|
4583
|
-
if (dispatched.done !== true || dispatched.status !== "Success" || dispatched.exitCode !== 0) {
|
|
4584
|
-
throw new LanesError("lane_move_dispatch_failed", `Outpost ${source.host.id} did not confirm the lane move`, {
|
|
4585
|
-
lane_id: source.lane_id,
|
|
4586
|
-
source_outpost: source.host.id,
|
|
4587
|
-
command_id: dispatched.commandId,
|
|
4588
|
-
status: dispatched.status,
|
|
4589
|
-
done: dispatched.done ?? false,
|
|
4590
|
-
exit_code: dispatched.exitCode,
|
|
4591
|
-
});
|
|
4592
|
-
}
|
|
4593
|
-
const remote = parseRemoteJson(dispatched.stdout, "Outpost lane move");
|
|
4594
|
-
const remoteHost = remote?.host;
|
|
4595
|
-
if (remote?.ok !== true ||
|
|
4596
|
-
remote.action !== "move-target" ||
|
|
4597
|
-
remote.lane_id !== source.lane_id ||
|
|
4598
|
-
remote.state !== "queued" ||
|
|
4599
|
-
remote.account !== account ||
|
|
4600
|
-
remoteHost === null || typeof remoteHost !== "object" || Array.isArray(remoteHost) ||
|
|
4601
|
-
remoteHost.kind !== target.kind ||
|
|
4602
|
-
remoteHost.id !== target.id) {
|
|
4603
|
-
throw new LanesError("lane_move_unconfirmed", `Outpost ${source.host.id} returned an invalid lane move result`, {
|
|
4604
|
-
lane_id: source.lane_id,
|
|
4605
|
-
source_outpost: source.host.id,
|
|
4606
|
-
target,
|
|
4607
|
-
});
|
|
4608
|
-
}
|
|
4609
|
-
return {
|
|
4610
|
-
...source,
|
|
4611
|
-
state: "queued",
|
|
4612
|
-
host: target,
|
|
4613
|
-
account,
|
|
4614
|
-
};
|
|
4615
|
-
}
|
|
4616
|
-
async function runMoveTarget(ctx, laneId, opts) {
|
|
4617
|
-
const kind = opts.hostKind?.trim();
|
|
4618
|
-
const id = opts.hostId?.trim();
|
|
4619
|
-
const account = opts.account?.trim();
|
|
4620
|
-
if ((kind !== "local" && kind !== "outpost") || !id || !account) {
|
|
4621
|
-
throw new LanesError("lane_move_target_invalid", "Move target requires a valid host kind, host id, and account", {
|
|
4622
|
-
host_kind: kind ?? null,
|
|
4623
|
-
host_id: id ?? null,
|
|
4624
|
-
account: account ?? null,
|
|
4625
|
-
});
|
|
4626
|
-
}
|
|
4627
|
-
const moved = await commitLaneMove(ctx, laneId, { kind, id }, account);
|
|
4628
|
-
printJson({
|
|
4629
|
-
ok: true,
|
|
4630
|
-
action: "move-target",
|
|
4631
|
-
lane_id: moved.lane_id,
|
|
4632
|
-
state: moved.state,
|
|
4633
|
-
host: moved.host,
|
|
4634
|
-
account: moved.account ?? null,
|
|
4635
|
-
});
|
|
4636
|
-
}
|
|
4637
4299
|
async function refreshOutpostLaneCopiesForShow(ctx, lane, localLanes) {
|
|
4638
4300
|
if (lane.host.kind !== "outpost" || laneRunsHere(lane, reconcileHostFromCtx(ctx))) {
|
|
4639
4301
|
return lane;
|
|
@@ -4779,25 +4441,78 @@ async function runShow(ctx, laneId) {
|
|
|
4779
4441
|
: null,
|
|
4780
4442
|
});
|
|
4781
4443
|
}
|
|
4782
|
-
function
|
|
4444
|
+
function formatSessionSeniorValue(value) {
|
|
4445
|
+
if (value === undefined)
|
|
4446
|
+
return "<undefined>";
|
|
4447
|
+
try {
|
|
4448
|
+
return JSON.stringify(value) ?? String(value);
|
|
4449
|
+
}
|
|
4450
|
+
catch {
|
|
4451
|
+
return String(value);
|
|
4452
|
+
}
|
|
4453
|
+
}
|
|
4454
|
+
function callerSeniorAuthorization(ctx, seniorRef) {
|
|
4783
4455
|
const sessionId = sessionIdFromEnv(ctx.env);
|
|
4784
4456
|
const callerLaneId = ctx.env.HQ_LANE_ID?.trim() || undefined;
|
|
4785
4457
|
if (seniorRef.kind === "lane") {
|
|
4786
|
-
|
|
4458
|
+
if (callerLaneId === seniorRef.id)
|
|
4459
|
+
return { authorized: true };
|
|
4460
|
+
if (callerLaneId !== undefined)
|
|
4461
|
+
return { authorized: false, reason: "caller is a different lane" };
|
|
4462
|
+
if (!sessionId)
|
|
4463
|
+
return { authorized: false, reason: "no session id" };
|
|
4464
|
+
return { authorized: false, reason: "caller is not a lane" };
|
|
4465
|
+
}
|
|
4466
|
+
if (callerLaneId !== undefined)
|
|
4467
|
+
return { authorized: false, reason: "caller is a lane" };
|
|
4468
|
+
if (!sessionId)
|
|
4469
|
+
return { authorized: false, reason: "no session id" };
|
|
4470
|
+
if (seniorRef.kind === "session") {
|
|
4471
|
+
return sessionId === seniorRef.id
|
|
4472
|
+
? { authorized: true }
|
|
4473
|
+
: { authorized: false, reason: `session id ${sessionId} is not senior session ${seniorRef.id}` };
|
|
4787
4474
|
}
|
|
4788
|
-
if (!sessionId || callerLaneId !== undefined)
|
|
4789
|
-
return false;
|
|
4790
|
-
if (seniorRef.kind === "session")
|
|
4791
|
-
return sessionId === seniorRef.id;
|
|
4792
4475
|
if (seniorRef.kind !== "user")
|
|
4793
|
-
return false;
|
|
4476
|
+
return { authorized: false, reason: "senior is not a user" };
|
|
4794
4477
|
const loaded = loadSessionMeta(ctx.hqRoot(), sessionId, ctx);
|
|
4795
|
-
if (loaded.status
|
|
4796
|
-
return
|
|
4797
|
-
|
|
4798
|
-
|
|
4478
|
+
if (loaded.status === "missing") {
|
|
4479
|
+
return {
|
|
4480
|
+
authorized: false,
|
|
4481
|
+
reason: "session meta missing",
|
|
4482
|
+
repair: "if this is the owner's session, run bash core/scripts/hq-session.sh set senior user",
|
|
4483
|
+
};
|
|
4484
|
+
}
|
|
4485
|
+
if (loaded.status === "unreadable") {
|
|
4486
|
+
return { authorized: false, reason: "session meta unreadable" };
|
|
4487
|
+
}
|
|
4488
|
+
if (loaded.status === "malformed") {
|
|
4489
|
+
return { authorized: false, reason: "session meta invalid" };
|
|
4490
|
+
}
|
|
4491
|
+
const senior = sessionSeniorValue(loaded.doc);
|
|
4492
|
+
if (senior === "user" ||
|
|
4799
4493
|
(senior !== null && typeof senior === "object" && !Array.isArray(senior) &&
|
|
4800
|
-
senior.kind === "user")
|
|
4494
|
+
senior.kind === "user")) {
|
|
4495
|
+
return { authorized: true };
|
|
4496
|
+
}
|
|
4497
|
+
return {
|
|
4498
|
+
authorized: false,
|
|
4499
|
+
reason: `session senior is ${formatSessionSeniorValue(senior)}`,
|
|
4500
|
+
};
|
|
4501
|
+
}
|
|
4502
|
+
function callerSeniorReason(authorization) {
|
|
4503
|
+
if (authorization.authorized)
|
|
4504
|
+
return "";
|
|
4505
|
+
return authorization.repair
|
|
4506
|
+
? `${authorization.reason}; ${authorization.repair}`
|
|
4507
|
+
: authorization.reason;
|
|
4508
|
+
}
|
|
4509
|
+
function callerMatchesSenior(ctx, seniorRef) {
|
|
4510
|
+
return callerSeniorAuthorization(ctx, seniorRef).authorized;
|
|
4511
|
+
}
|
|
4512
|
+
function appendCallerSeniorReason(message, authorization) {
|
|
4513
|
+
return authorization.authorized
|
|
4514
|
+
? message
|
|
4515
|
+
: `${message}; authorization refused: ${callerSeniorReason(authorization)}`;
|
|
4801
4516
|
}
|
|
4802
4517
|
function laneGrantTargetHostedHere(ctx, lane) {
|
|
4803
4518
|
if (lane.host.kind === "local") {
|
|
@@ -4805,14 +4520,17 @@ function laneGrantTargetHostedHere(ctx, lane) {
|
|
|
4805
4520
|
}
|
|
4806
4521
|
return lane.host.kind === "outpost" && laneIsHostedHere(ctx, lane);
|
|
4807
4522
|
}
|
|
4808
|
-
function
|
|
4809
|
-
return
|
|
4523
|
+
function forceAdmissionAuthorization(ctx, lane) {
|
|
4524
|
+
return callerSeniorAuthorization(ctx, lane.senior);
|
|
4810
4525
|
}
|
|
4811
4526
|
async function applyHireGrant(ctx, laneId, hireGrant, authorization, grantContext) {
|
|
4812
4527
|
const hqRoot = ctx.hqRoot();
|
|
4813
4528
|
const initial = readLane(hqRoot, laneId);
|
|
4814
|
-
|
|
4815
|
-
|
|
4529
|
+
const initialCallerAuthorization = authorization === "senior"
|
|
4530
|
+
? callerSeniorAuthorization(ctx, initial.senior)
|
|
4531
|
+
: { authorized: true };
|
|
4532
|
+
if (!initialCallerAuthorization.authorized) {
|
|
4533
|
+
throw new LanesError("hire_grant_unauthorized", appendCallerSeniorReason(`Only senior ${seniorKey(initial.senior)} may change lane ${laneId}'s hire grant`, initialCallerAuthorization), { lane_id: laneId, senior: initial.senior, reason: initialCallerAuthorization.reason });
|
|
4816
4534
|
}
|
|
4817
4535
|
if (authorization === "ssm-owner-dispatch" &&
|
|
4818
4536
|
!laneGrantTargetHostedHere(ctx, initial)) {
|
|
@@ -4830,8 +4548,11 @@ async function applyHireGrant(ctx, laneId, hireGrant, authorization, grantContex
|
|
|
4830
4548
|
];
|
|
4831
4549
|
const updated = ctx.withGrantHireLocks(hqRoot, lockedLaneIds, () => {
|
|
4832
4550
|
const current = readLane(hqRoot, laneId);
|
|
4833
|
-
|
|
4834
|
-
|
|
4551
|
+
const currentCallerAuthorization = authorization === "senior"
|
|
4552
|
+
? callerSeniorAuthorization(ctx, current.senior)
|
|
4553
|
+
: { authorized: true };
|
|
4554
|
+
if (!currentCallerAuthorization.authorized) {
|
|
4555
|
+
throw new LanesError("hire_grant_unauthorized", appendCallerSeniorReason(`Only senior ${seniorKey(current.senior)} may change lane ${laneId}'s hire grant`, currentCallerAuthorization), { lane_id: laneId, senior: current.senior, reason: currentCallerAuthorization.reason });
|
|
4835
4556
|
}
|
|
4836
4557
|
if (authorization === "ssm-owner-dispatch" &&
|
|
4837
4558
|
!laneGrantTargetHostedHere(ctx, current)) {
|
|
@@ -4944,8 +4665,9 @@ async function runKeepAlive(ctx, laneId, opts) {
|
|
|
4944
4665
|
});
|
|
4945
4666
|
}
|
|
4946
4667
|
function assertKeepAliveChangeAllowed(ctx, lane, laneId) {
|
|
4947
|
-
|
|
4948
|
-
|
|
4668
|
+
const callerAuthorization = callerSeniorAuthorization(ctx, lane.senior);
|
|
4669
|
+
if (!callerAuthorization.authorized) {
|
|
4670
|
+
throw new LanesError("keep_alive_unauthorized", appendCallerSeniorReason("Only senior " + seniorKey(lane.senior) + " may change lane " + laneId + "'s keep-alive setting", callerAuthorization), { lane_id: laneId, senior: lane.senior, reason: callerAuthorization.reason });
|
|
4949
4671
|
}
|
|
4950
4672
|
if (!laneRunsHere(lane, reconcileHostFromCtx(ctx))) {
|
|
4951
4673
|
throw new LanesError("keep_alive_owner_host_required", "Run hq lanes keep-alive " + laneId + " on its owner host " + lane.host.kind + ":" + lane.host.id, { lane_id: laneId, host: lane.host });
|
|
@@ -4960,10 +4682,14 @@ async function runGrant(ctx, laneId, opts) {
|
|
|
4960
4682
|
}
|
|
4961
4683
|
const hireGrant = parseHireGrant(opts.hire);
|
|
4962
4684
|
const hqRoot = ctx.hqRoot();
|
|
4963
|
-
const target = await resolveCommandLaneTarget(ctx, laneId, {
|
|
4685
|
+
const target = await resolveCommandLaneTarget(ctx, laneId, {
|
|
4686
|
+
outpostHint: true,
|
|
4687
|
+
outpostId: opts.outpost,
|
|
4688
|
+
});
|
|
4964
4689
|
const initial = target.lane;
|
|
4965
|
-
|
|
4966
|
-
|
|
4690
|
+
const callerAuthorization = callerSeniorAuthorization(ctx, initial.senior);
|
|
4691
|
+
if (!callerAuthorization.authorized) {
|
|
4692
|
+
throw new LanesError("hire_grant_unauthorized", appendCallerSeniorReason(`Only senior ${seniorKey(initial.senior)} may change lane ${laneId}'s hire grant`, callerAuthorization), { lane_id: laneId, senior: initial.senior, reason: callerAuthorization.reason });
|
|
4967
4693
|
}
|
|
4968
4694
|
if (opts.outpost !== undefined) {
|
|
4969
4695
|
const requestedOutpost = opts.outpost.trim();
|
|
@@ -5018,8 +4744,9 @@ async function runApprove(ctx, laneId, opts) {
|
|
|
5018
4744
|
if (!allowed.includes(checkpoint)) {
|
|
5019
4745
|
throw new LanesError("registry_worker_checkpoint_unknown", `Checkpoint ${JSON.stringify(checkpoint)} is not declared by the lane's registry worker`, { lane_id: laneId, checkpoint, accepted: allowed });
|
|
5020
4746
|
}
|
|
5021
|
-
|
|
5022
|
-
|
|
4747
|
+
const callerAuthorization = callerSeniorAuthorization(ctx, initial.senior);
|
|
4748
|
+
if (!callerAuthorization.authorized) {
|
|
4749
|
+
throw new LanesError("registry_worker_approval_unauthorized", appendCallerSeniorReason(`Only senior ${seniorKey(initial.senior)} may approve lane ${laneId}'s registry worker checkpoint`, callerAuthorization), { lane_id: laneId, senior: initial.senior, reason: callerAuthorization.reason });
|
|
5023
4750
|
}
|
|
5024
4751
|
if (remoteOutpost) {
|
|
5025
4752
|
const approvalContext = createOutpostRegistryWorkerApprovalContext({
|
|
@@ -5045,8 +4772,9 @@ async function runApprove(ctx, laneId, opts) {
|
|
|
5045
4772
|
const actor = resumeProfileRepinActor(ctx);
|
|
5046
4773
|
const updated = retryUpdateLane(hqRoot, laneId, "command", (current) => {
|
|
5047
4774
|
assertLaneRunsHereForOperatorMutation(ctx, current, "approve");
|
|
5048
|
-
|
|
5049
|
-
|
|
4775
|
+
const currentCallerAuthorization = callerSeniorAuthorization(ctx, current.senior);
|
|
4776
|
+
if (!currentCallerAuthorization.authorized) {
|
|
4777
|
+
throw new LanesError("registry_worker_approval_unauthorized", appendCallerSeniorReason(`Only senior ${seniorKey(current.senior)} may approve lane ${laneId}'s registry worker checkpoint`, currentCallerAuthorization), { lane_id: laneId, senior: current.senior, reason: currentCallerAuthorization.reason });
|
|
5050
4778
|
}
|
|
5051
4779
|
if (JSON.stringify(current.registry_worker) !== JSON.stringify(initial.registry_worker) ||
|
|
5052
4780
|
!current.registry_worker ||
|
|
@@ -5212,8 +4940,9 @@ async function runAdmit(ctx, laneId, opts) {
|
|
|
5212
4940
|
}
|
|
5213
4941
|
let blockedBy;
|
|
5214
4942
|
if (opts.force) {
|
|
5215
|
-
|
|
5216
|
-
|
|
4943
|
+
const callerAuthorization = forceAdmissionAuthorization(ctx, lane);
|
|
4944
|
+
if (!callerAuthorization.authorized) {
|
|
4945
|
+
throw new LanesError("force_not_authorized", appendCallerSeniorReason(`Only lane ${lane.senior.kind === "lane" ? lane.senior.id : lane.senior.kind === "session" ? lane.senior.id : "senior/operator"} may force admission of ${laneId}`, callerAuthorization), { lane_id: laneId, senior: lane.senior, reason: callerAuthorization.reason });
|
|
5217
4946
|
}
|
|
5218
4947
|
const config = loadLanesConfig(hqRoot);
|
|
5219
4948
|
const lanes = loadAllLanes(hqRoot).filter((row) => row.lane_id !== laneId && laneRunsHere(row, host));
|
|
@@ -5341,7 +5070,7 @@ function applyResumeProfileAndEffort(current, next, input) {
|
|
|
5341
5070
|
? registryWorkerRecord(profile.registryWorker)
|
|
5342
5071
|
: undefined;
|
|
5343
5072
|
if (input.profileRepin !== undefined &&
|
|
5344
|
-
current.host.kind === "outpost" &&
|
|
5073
|
+
(current.host.kind === "outpost" || input.profileRepin.kind === "ssm-owner-dispatch") &&
|
|
5345
5074
|
current.worker_profile_hash !== profile.contentHash) {
|
|
5346
5075
|
const entry = {
|
|
5347
5076
|
old_hash: current.worker_profile_hash ?? null,
|
|
@@ -7080,26 +6809,25 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
7080
6809
|
readOutpostCapacity,
|
|
7081
6810
|
};
|
|
7082
6811
|
const watchHandlers = createLaneWatchHandlers(watchHandlerDeps);
|
|
6812
|
+
const moveHandlers = createLaneMoveHandlers({
|
|
6813
|
+
assertKnownOutpost,
|
|
6814
|
+
assertLaneRunsHereForOperatorMutation,
|
|
6815
|
+
dispatchCreatedOutpostLane,
|
|
6816
|
+
loadHostCapacitySnapshots,
|
|
6817
|
+
ownerErrorFromOutput,
|
|
6818
|
+
parseRemoteJson,
|
|
6819
|
+
printJson,
|
|
6820
|
+
readJsonFile,
|
|
6821
|
+
reconcileHostFromCtx,
|
|
6822
|
+
runDir,
|
|
6823
|
+
timeoutPath,
|
|
6824
|
+
});
|
|
7083
6825
|
const lanes = parent
|
|
7084
6826
|
.command("lanes", { hidden: true })
|
|
7085
6827
|
.description("Create, supervise, and steer worker lanes; routed decision queue");
|
|
7086
|
-
registerLaneTrackerCommands(lanes, {
|
|
7087
|
-
hqRoot: ctx.hqRoot,
|
|
7088
|
-
resolveCompany: ctx.resolveCompany,
|
|
7089
|
-
trackerEnabled: ctx.laneTrackerEnabled,
|
|
7090
|
-
now: ctx.now,
|
|
7091
|
-
actor: ctx.trackerActor,
|
|
7092
|
-
}, wrap);
|
|
7093
|
-
registerLaneTrackerImportCommand(lanes, {
|
|
7094
|
-
hqRoot: ctx.hqRoot,
|
|
7095
|
-
resolveCompany: ctx.resolveCompany,
|
|
7096
|
-
trackerEnabled: ctx.laneTrackerEnabled,
|
|
7097
|
-
now: ctx.now,
|
|
7098
|
-
}, wrap);
|
|
7099
6828
|
registerLaneChecksCommand(lanes, {
|
|
7100
6829
|
hqRoot: ctx.hqRoot,
|
|
7101
6830
|
resolveCompany: ctx.resolveCompany,
|
|
7102
|
-
trackerEnabled: ctx.laneTrackerEnabled,
|
|
7103
6831
|
now: ctx.now,
|
|
7104
6832
|
stopLane: (laneId, reason) => runStop(ctx, laneId, reason, true),
|
|
7105
6833
|
sessionState: (root, sessionId, now) => sessionLiveness(root, sessionId, {
|
|
@@ -7111,7 +6839,6 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
7111
6839
|
registerLaneReportCommands(lanes, {
|
|
7112
6840
|
hqRoot: ctx.hqRoot,
|
|
7113
6841
|
resolveCompany: ctx.resolveCompany,
|
|
7114
|
-
trackerEnabled: ctx.laneTrackerEnabled,
|
|
7115
6842
|
now: ctx.now,
|
|
7116
6843
|
readQuestionBody: (root, question) => {
|
|
7117
6844
|
const body = questionHandlers.requireQuestionBody(root, question);
|
|
@@ -7124,6 +6851,7 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
7124
6851
|
resolveCompany: ctx.resolveCompany,
|
|
7125
6852
|
now: ctx.now,
|
|
7126
6853
|
env: ctx.env,
|
|
6854
|
+
mergeHoldEnabled: ctx.mergeHoldEnabled,
|
|
7127
6855
|
mergeReviewEnabled: ctx.mergeReviewEnabled,
|
|
7128
6856
|
}, wrap);
|
|
7129
6857
|
registerLaneReviewCommand(lanes, {
|
|
@@ -7262,7 +6990,7 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
7262
6990
|
.argument("<lane-id>", "Lane id")
|
|
7263
6991
|
.requiredOption("--placement <mode>", "local | auto | outpost:<id>")
|
|
7264
6992
|
.action(wrap(async (laneId, opts) => {
|
|
7265
|
-
await runMove(ctx, laneId, opts);
|
|
6993
|
+
await moveHandlers.runMove(ctx, laneId, opts);
|
|
7266
6994
|
}));
|
|
7267
6995
|
lanes
|
|
7268
6996
|
.command("move-target", { hidden: true })
|
|
@@ -7271,7 +6999,7 @@ export function registerLanesCommand(parent, deps = {}) {
|
|
|
7271
6999
|
.requiredOption("--host-id <id>", "Target host id")
|
|
7272
7000
|
.requiredOption("--account <id>", "Provider account id")
|
|
7273
7001
|
.action(wrap(async (laneId, opts) => {
|
|
7274
|
-
await runMoveTarget(ctx, laneId, opts);
|
|
7002
|
+
await moveHandlers.runMoveTarget(ctx, laneId, opts);
|
|
7275
7003
|
}));
|
|
7276
7004
|
lanes
|
|
7277
7005
|
.command("balance-move-target", { hidden: true })
|