@indigoai-us/hq-cli 5.227.1 → 5.228.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 CHANGED
@@ -2,6 +2,13 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.228.0] — 2026-09-27
6
+
7
+ ### Changed
8
+
9
+ - Lane hire grants and Claude account balancing are always active.
10
+ `backfill-grants --apply` refuses to write; dry-run output remains available.
11
+
5
12
  ## [5.227.1] — 2026-09-27
6
13
 
7
14
  ### Fixed
@@ -8285,7 +8285,7 @@ export declare const COMMAND_CATALOG: readonly [{
8285
8285
  readonly description: "claude";
8286
8286
  }, {
8287
8287
  readonly flags: "--apply";
8288
- readonly description: "Move eligible between-turn sessions (requires the Claude account balance gate)";
8288
+ readonly description: "Move eligible between-turn sessions to balance Claude accounts";
8289
8289
  }, {
8290
8290
  readonly flags: "--json";
8291
8291
  readonly description: "JSON on stdout (always)";
@@ -10717,7 +10717,7 @@ export const COMMAND_CATALOG = [
10717
10717
  },
10718
10718
  {
10719
10719
  "flags": "--apply",
10720
- "description": "Move eligible between-turn sessions (requires the Claude account balance gate)"
10720
+ "description": "Move eligible between-turn sessions to balance Claude accounts"
10721
10721
  },
10722
10722
  {
10723
10723
  "flags": "--json",
@@ -10,10 +10,9 @@ import { type TrackerMeshRequest } from "../lib/lanes/tracker-mesh.js";
10
10
  import type { OutpostIdentity } from "../lib/lanes/admission-host.js";
11
11
  export { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity } from "../lib/lanes/admission-host.js";
12
12
  export type { OutpostIdentity } from "../lib/lanes/admission-host.js";
13
- export { LANE_HIRE_GRANTS_FLAG };
14
13
  import { type OutpostDispatchResult } from "../lib/lanes/outpost-dispatch.js";
15
14
  import { emitLaneDecisionEvents, type LaneMesh } from "../lib/lanes/mesh-events.js";
16
- import { LANE_HIRE_GRANTS_FLAG, MAX_LANE_DEPTH, withLaneHireLocks, type LaneCompanyRef, type QuestionBody, type LaneRecord, type QuestionRecord, type ResolvedLaneWorker, type SeniorRef, type LivenessDeps, type MachineIdentity, type MonitorEvidenceReader, type RunCommand, type ThrottleDeps } from "../lib/lanes/index.js";
15
+ import { MAX_LANE_DEPTH, withLaneHireLocks, type LaneCompanyRef, type QuestionBody, type LaneRecord, type QuestionRecord, type ResolvedLaneWorker, type SeniorRef, type LivenessDeps, type MachineIdentity, type MonitorEvidenceReader, type RunCommand, type ThrottleDeps } from "../lib/lanes/index.js";
17
16
  import { questionBodyAbsPath } from "../lib/lanes/questions.js";
18
17
  import { type RunCompactionFn } from "../lib/lanes/compaction.js";
19
18
  import type { WatcherLoopDeps } from "../lib/lanes/watcher.js";
@@ -87,16 +86,12 @@ export interface LanesCommandDeps {
87
86
  monitorEvidence?: MonitorEvidenceReader;
88
87
  /** Test seam for the default-off outpost placement rollout flag. */
89
88
  lanePlacementEnabled?: () => boolean;
90
- /** Test seam for the default-off lane hire-grants rollout gate. */
91
- laneHireGrantsEnabled?: (company?: LaneCompanyRef | null) => boolean | Promise<boolean>;
92
89
  /** Test seam for the default-off story ledger flag. */
93
90
  storyLedgerEnabled?: () => boolean;
94
91
  /** Test seam for the SSM-only Outpost owner grant path. */
95
92
  isSsmOwnerDispatch?: () => boolean;
96
93
  /** Test seam for changing a lane between pre-lock and locked grant reads. */
97
94
  withGrantHireLocks?: typeof withLaneHireLocks;
98
- /** Test seam for the Claude account-balancing gate. */
99
- laneClaudeAccountBalanceEnabled?: (company?: LaneCompanyRef | null) => boolean | Promise<boolean>;
100
95
  /** Test seam for the default-off Outpost question visibility gate. */
101
96
  laneOutpostQuestionVisibilityEnabled?: (company?: LaneCompanyRef | null) => boolean | Promise<boolean>;
102
97
  /** Test seam for the hq-flags gated tracker commands. */
@@ -27,11 +27,10 @@ import { defaultTrackerActor, LANES_TRACKER_FLAG, LANES_TRACKER_MESH_FLAG, regis
27
27
  import { createOutpostHireGrantContext, parseOutpostHireGrantContext, sameHireGrantSenior, } from "../lib/lanes/hire-grant-dispatch.js";
28
28
  import { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity, resolveAdmissionHost, } from "../lib/lanes/admission-host.js";
29
29
  export { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity } from "../lib/lanes/admission-host.js";
30
- export { LANE_HIRE_GRANTS_FLAG };
31
30
  import { buildOutpostCapacityCommand, buildOutpostBalanceMoveCommand, buildOutpostIdentityBackfillCommand, buildOutpostLaneCommand, buildOutpostMoveTargetCommand, buildOutpostQuestionAnswerCommand, buildOutpostQuestionListCommand, buildOutpostQuestionShowCommand, buildOutpostPickupCommand, createOutpostsClient, dispatchErrorMessage, dispatchOutpostPickup, buildOutpostLaneListCommand, outpostRejectedEffortOption, parseOutpostPickupPayload, stageOutpostPayload, } from "../lib/lanes/outpost-dispatch.js";
32
31
  import { outpostHireSummaryCacheKey, readOutpostHireSummaryCache, writeOutpostHireSummaryCache, } from "../lib/lanes/outpost-hire-cache.js";
33
32
  import { buildHostCapacitySnapshot, chooseHostForLane, parseHostCapacitySnapshot, } from "../lib/lanes/host-capacity.js";
34
- import { BALANCE_MOVABLE_STATES, CLAUDE_ACCOUNT_BALANCE_FLAG, accountLoadsForReport, assertBalanceApplyEnabled, planClaudeBalanceMoves, selectClaudeAccount, } from "../lib/lanes/claude-account-balance.js";
33
+ import { BALANCE_MOVABLE_STATES, accountLoadsForReport, planClaudeBalanceMoves, selectClaudeAccount, } from "../lib/lanes/claude-balance.js";
35
34
  import { parseUserYaml } from "../utils/user-yaml-error.js";
36
35
  import { backfillOutpostIdentityMarker as backfillOutpostIdentityMarkerImpl, createOutpostIdentityMarkerIfMissing, validateOutpostIdentityId, } from "../lib/lanes/outpost-identity.js";
37
36
  import { vaultApiFetch } from "../utils/vault-api.js";
@@ -42,7 +41,7 @@ import { assertNoSecretText } from "../lib/lanes/text-scan.js";
42
41
  import { createLaneMesh, emitLaneDecisionEvents, emitLaneTerminalStateEvent, laneMeshEventIdentity, } from "../lib/lanes/mesh-events.js";
43
42
  import { sessionIdFromEnv } from "../lib/mesh/live/session-identity.js";
44
43
  import { effectiveAccountHome } from "../lib/lanes/account-home.js";
45
- import { AccountResolutionError, compareLaneQueueOrder, coveredLaneIds, DEFAULT_ACCOUNT_HOMES, LANE_PROVIDERS, LANE_STATES, PROVIDER_HOME_ENV, QUESTION_STATUSES, SENIOR_KINDS, accountStatus, appendCappedHistory, canonicalizeHome, createLane, createQuestion, decideAdmission, defaultLivenessDeps, defaultSystemdUserDir, defaultThrottleDeps, installDrainHook, installReconcileTimer, providerPathEnv, probeDrainHookRegistration, isLanesError, isLaneVersionConflict, isTerminalLaneState, aliveDirectChildren, assignedChildHireGrants, hireGrantCapacityError, LANE_HIRE_GRANTS_FLAG, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, laneStoryLedgerEnabled, validateProjectStories, buildLaneStatusSnapshot, formatLaneStatusHuman, listAccountStatuses, listLaneIds, listQuestionIds, listSessionLiveness, loadLanesConfig, laneRunsHere, MAX_LANE_DEPTH, MONITOR_ERROR_EXIT_CODE, MONITOR_ENGINES, MONITOR_MAX_TIMEOUT_MS, MONITOR_UNCOVERED_EXIT_CODE, MAX_WATCHED_LANES, monitorReminderText, readLiveMonitorEvidence, resolveMachineIdentity as lookupMachineIdentity, loadLivenessIndex, parseLaneProvider, parseLaneRecord, parseLaneState, parseLivenessState, parseQuestionRecord, parseQuestionStatus, parseSeniorKind, readDaemonPresenceConnected, readLane, retryUpdateLane, syncLaneRecordFromOwner, readQuestion, resolveAccount, resolveLaneWorker, ignoredWorkerProfileFields, loadWorkerProfile, runReconcilePass, wakeAnsweredLaneNow, runLaneWatch, sessionLiveness, tryReadLane, uninstallReconcileTimer, updateLane, updateQuestion, writeLanesConfig, appendDropboxMessage, answerDropboxMessage, deliveryRefusalReason, resumeCommandRefusal, dropboxDir, dropboxInboxPath, laneAcceptsDropboxDelivery, laneAcceptsOperatorMessage, laneDeliveryMechanism, hasActiveAdmissionReservation, hasActivePendingResume, hasRecordedSessionId, hasEarlierEligibleQueuedLane, laneAdmissionCapReason, providerSupportsSessionResume, providerSessionPortable, sessionNotPortableMessage, withLaneAdmissionLock, withAllLaneAdmissionLocks, withLaneHireLocks, laneIdForAnswerDelivery, operatorMessageRefusalReason, raiseLaneQuestion, sha256Text, writeQuestionBody, } from "../lib/lanes/index.js";
44
+ import { AccountResolutionError, compareLaneQueueOrder, coveredLaneIds, DEFAULT_ACCOUNT_HOMES, LANE_PROVIDERS, LANE_STATES, PROVIDER_HOME_ENV, QUESTION_STATUSES, SENIOR_KINDS, accountStatus, appendCappedHistory, canonicalizeHome, createLane, createQuestion, decideAdmission, defaultLivenessDeps, defaultSystemdUserDir, defaultThrottleDeps, installDrainHook, installReconcileTimer, providerPathEnv, probeDrainHookRegistration, isLanesError, isLaneVersionConflict, isTerminalLaneState, aliveDirectChildren, assignedChildHireGrants, hireGrantCapacityError, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, laneStoryLedgerEnabled, validateProjectStories, buildLaneStatusSnapshot, formatLaneStatusHuman, listAccountStatuses, listLaneIds, listQuestionIds, listSessionLiveness, loadLanesConfig, laneRunsHere, MAX_LANE_DEPTH, MONITOR_ERROR_EXIT_CODE, MONITOR_ENGINES, MONITOR_MAX_TIMEOUT_MS, MONITOR_UNCOVERED_EXIT_CODE, MAX_WATCHED_LANES, monitorReminderText, readLiveMonitorEvidence, resolveMachineIdentity as lookupMachineIdentity, loadLivenessIndex, parseLaneProvider, parseLaneRecord, parseLaneState, parseLivenessState, parseQuestionRecord, parseQuestionStatus, parseSeniorKind, readDaemonPresenceConnected, readLane, retryUpdateLane, syncLaneRecordFromOwner, readQuestion, resolveAccount, resolveLaneWorker, ignoredWorkerProfileFields, loadWorkerProfile, runReconcilePass, wakeAnsweredLaneNow, runLaneWatch, sessionLiveness, tryReadLane, uninstallReconcileTimer, updateLane, updateQuestion, writeLanesConfig, appendDropboxMessage, answerDropboxMessage, deliveryRefusalReason, resumeCommandRefusal, dropboxDir, dropboxInboxPath, laneAcceptsDropboxDelivery, laneAcceptsOperatorMessage, laneDeliveryMechanism, hasActiveAdmissionReservation, hasActivePendingResume, hasRecordedSessionId, hasEarlierEligibleQueuedLane, laneAdmissionCapReason, providerSupportsSessionResume, providerSessionPortable, sessionNotPortableMessage, withLaneAdmissionLock, withAllLaneAdmissionLocks, withLaneHireLocks, laneIdForAnswerDelivery, operatorMessageRefusalReason, raiseLaneQuestion, sha256Text, writeQuestionBody, } from "../lib/lanes/index.js";
46
45
  import { activeLaneStories, laneStories, laneStory, storyWarning, } from "../lib/lanes/stories.js";
47
46
  import { resolveLaneKeepAlive } from "../lib/lanes/profile.js";
48
47
  import { keepAliveExclusion, LEAD_LANE_KEEP_ALIVE_FLAG, loadRetiredLaneIds, } from "../lib/lanes/keep-alive.js";
@@ -77,20 +76,12 @@ export const LANE_HOST_SCOPED_ACCOUNT_HOMES_FLAG = "lanes.host-scoped-account-ho
77
76
  function defaultLanePlacementEnabled() {
78
77
  return resolveProcessFlagGate(LANE_OUTPOST_LOAD_BALANCING_FLAG, { fallback: false }, () => false);
79
78
  }
80
- function defaultLaneClaudeAccountBalanceEnabled(company) {
81
- // Keep the local HQ_FLAG override and the registry reader's fail-closed
82
- // behavior, matching the lane hire-grants gate.
83
- return readLanesRegistryFlag(CLAUDE_ACCOUNT_BALANCE_FLAG, { company });
84
- }
85
79
  function defaultLaneTrackerEnabled(company) {
86
80
  return readLanesRegistryFlag(LANES_TRACKER_FLAG, { company });
87
81
  }
88
82
  function defaultLaneTrackerMeshEnabled(company) {
89
83
  return readLanesRegistryFlag(LANES_TRACKER_MESH_FLAG, { company });
90
84
  }
91
- function defaultLaneHireGrantsEnabled(company) {
92
- return readLanesRegistryFlag(LANE_HIRE_GRANTS_FLAG, { company });
93
- }
94
85
  function defaultLaneOutpostQuestionVisibilityEnabled(company, env) {
95
86
  return readLanesRegistryFlag(LANE_OUTPOST_QUESTION_VISIBILITY_FLAG, {
96
87
  company,
@@ -320,11 +311,9 @@ function resolveDeps(deps = {}) {
320
311
  })),
321
312
  fetchText: deps.fetchText ?? defaultFetchText,
322
313
  lanePlacementEnabled: deps.lanePlacementEnabled ?? defaultLanePlacementEnabled,
323
- laneHireGrantsEnabled: deps.laneHireGrantsEnabled ?? defaultLaneHireGrantsEnabled,
324
314
  storyLedgerEnabled: deps.storyLedgerEnabled ?? laneStoryLedgerEnabled,
325
315
  isSsmOwnerDispatch: deps.isSsmOwnerDispatch ?? (() => isSsmRunCommandProcess()),
326
316
  withGrantHireLocks: deps.withGrantHireLocks ?? withLaneHireLocks,
327
- laneClaudeAccountBalanceEnabled: deps.laneClaudeAccountBalanceEnabled ?? defaultLaneClaudeAccountBalanceEnabled,
328
317
  laneOutpostQuestionVisibilityEnabled: deps.laneOutpostQuestionVisibilityEnabled ??
329
318
  ((company) => defaultLaneOutpostQuestionVisibilityEnabled(company, env)),
330
319
  laneTrackerEnabled: deps.laneTrackerEnabled ?? defaultLaneTrackerEnabled,
@@ -1022,9 +1011,6 @@ async function laneHirePresentation(ctx, lane, lanes) {
1022
1011
  laneRunsHere(lane, reconcileHostFromCtx(ctx))) {
1023
1012
  return { ...local, hire_source: "local" };
1024
1013
  }
1025
- if (!await ctx.laneHireGrantsEnabled(lane.company)) {
1026
- return { ...local, hire_source: "local" };
1027
- }
1028
1014
  try {
1029
1015
  return { ...(await ownerLaneHireFields(ctx, lane)), hire_source: "owner" };
1030
1016
  }
@@ -1059,8 +1045,6 @@ async function withResumeHireAdmission(ctx, lane, fn) {
1059
1045
  !lane.senior.id) {
1060
1046
  return fn();
1061
1047
  }
1062
- if (!await ctx.laneHireGrantsEnabled(lane.company))
1063
- return fn();
1064
1048
  return withHireGrantAdmission(hqRoot, lane.senior.id, (snapshot) => {
1065
1049
  const current = readLane(hqRoot, lane.lane_id);
1066
1050
  // A different resume already reactivated this lane; it occupies the slot
@@ -2664,18 +2648,14 @@ async function runCreate(ctx, opts) {
2664
2648
  }
2665
2649
  const senior = opts.senior ? parseSeniorRef(opts.senior) : defaultSenior(ctx);
2666
2650
  assertSeniorExists(hqRoot, senior, ctx);
2667
- // Resolve tenant context before the registry read so the flag evaluator
2668
- // applies the same company scope as this lane.
2669
2651
  const company = companyFlag
2670
2652
  ? await ctx.resolveCompany(companyFlag)
2671
2653
  : null;
2672
- const hireGrantsEnabled = await ctx.laneHireGrantsEnabled(company);
2673
2654
  if (requestedHireGrant !== undefined &&
2674
2655
  !callerMatchesSenior(ctx, senior)) {
2675
2656
  throw new LanesError("hire_grant_unauthorized", `Only senior ${seniorKey(senior)} may set a lane's hire grant`, { senior, hire_grant: requestedHireGrant });
2676
2657
  }
2677
- if (hireGrantsEnabled &&
2678
- ctx.env.HQ_LANE_ID?.trim() &&
2658
+ if (ctx.env.HQ_LANE_ID?.trim() &&
2679
2659
  !callerMatchesSenior(ctx, senior)) {
2680
2660
  throw new LanesError("hire_grant_unauthorized", `Only senior lane ${senior.id} may create a direct child lane`, { senior_lane_id: senior.id });
2681
2661
  }
@@ -3031,7 +3011,7 @@ async function runCreate(ctx, opts) {
3031
3011
  }
3032
3012
  const allowedAccounts = validatedAccounts.size > 0 ? [...validatedAccounts] : profileAccounts;
3033
3013
  let accountSelection;
3034
- if (provider === "claude" && await ctx.laneClaudeAccountBalanceEnabled(company)) {
3014
+ if (provider === "claude") {
3035
3015
  const targetHost = placementHostOverride ?? (outpostId !== undefined
3036
3016
  ? { kind: "outpost", id: outpostId }
3037
3017
  : laneHostFromCtx(ctx));
@@ -3223,13 +3203,13 @@ async function runCreate(ctx, opts) {
3223
3203
  };
3224
3204
  const lane = senior.kind === "lane"
3225
3205
  ? withHireGrantAdmission(hqRoot, senior.id, ({ parent, children, parentGrant, availableChildGrant }) => {
3226
- if (hireGrantsEnabled && isTerminalLaneState(parent.state)) {
3206
+ if (isTerminalLaneState(parent.state)) {
3227
3207
  throw new LanesError("hire_grant_senior_inactive", `Senior lane ${parent.lane_id} is ${parent.state} and cannot hire lanes`, { senior_lane_id: parent.lane_id, state: parent.state });
3228
3208
  }
3229
- if (hireGrantsEnabled && children.length >= parentGrant) {
3209
+ if (children.length >= parentGrant) {
3230
3210
  throw hireGrantCapacityError(parent, children);
3231
3211
  }
3232
- if (hireGrantsEnabled && hireGrant > availableChildGrant) {
3212
+ if (hireGrant > availableChildGrant) {
3233
3213
  throw new LanesError("hire_grant_carve_exceeded", `Lane ${parent.lane_id} has ${availableChildGrant} unassigned hire grant remaining after its other alive children. Ask its senior to raise its grant before assigning ${hireGrant}.`, {
3234
3214
  senior_lane_id: parent.lane_id,
3235
3215
  parent_grant: parentGrant,
@@ -3622,7 +3602,6 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
3622
3602
  let grantContext;
3623
3603
  let grantHierarchy;
3624
3604
  if (action === "grant") {
3625
- const enforcementEnabled = await ctx.laneHireGrantsEnabled(lane.company);
3626
3605
  let hierarchy = loadAllLanes(ctx.hqRoot());
3627
3606
  const ownerLanes = await readOutpostLaneRecords(ctx, outpostId);
3628
3607
  const byId = new Map(hierarchy.map((record) => [record.lane_id, record]));
@@ -3631,7 +3610,7 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
3631
3610
  }
3632
3611
  hierarchy = [...byId.values()];
3633
3612
  grantHierarchy = hierarchy;
3634
- grantContext = createOutpostHireGrantContext(lane, hierarchy, enforcementEnabled);
3613
+ grantContext = createOutpostHireGrantContext(lane, hierarchy);
3635
3614
  }
3636
3615
  let dispatched;
3637
3616
  try {
@@ -4177,25 +4156,15 @@ async function runList(ctx, opts) {
4177
4156
  })
4178
4157
  .sort(compareLaneQueueOrder);
4179
4158
  const ownerEligibleLaneIds = new Set();
4180
- const companyContexts = new Map();
4181
4159
  for (const lane of selectedLanes) {
4182
4160
  if (lane.host.kind !== "outpost" ||
4183
4161
  isTerminalLaneState(lane.state) ||
4184
4162
  laneRunsHere(lane, host)) {
4185
4163
  continue;
4186
4164
  }
4187
- const companyKey = JSON.stringify([
4188
- lane.company?.uid ?? null,
4189
- lane.company?.slug ?? null,
4190
- ]);
4191
4165
  ownerEligibleLaneIds.add(lane.lane_id);
4192
- companyContexts.set(companyKey, lane.company);
4193
4166
  }
4194
- const enabledByCompany = new Map(await Promise.all([...companyContexts.entries()].map(async ([companyKey, company]) => [
4195
- companyKey,
4196
- await ctx.laneHireGrantsEnabled(company),
4197
- ])));
4198
- const ownerLanes = [];
4167
+ const ownerLanes = selectedLanes.filter((lane) => ownerEligibleLaneIds.has(lane.lane_id));
4199
4168
  const hireByLane = new Map();
4200
4169
  for (const lane of selectedLanes) {
4201
4170
  if (!ownerEligibleLaneIds.has(lane.lane_id)) {
@@ -4205,18 +4174,6 @@ async function runList(ctx, opts) {
4205
4174
  });
4206
4175
  continue;
4207
4176
  }
4208
- const companyKey = JSON.stringify([
4209
- lane.company?.uid ?? null,
4210
- lane.company?.slug ?? null,
4211
- ]);
4212
- if (enabledByCompany.get(companyKey))
4213
- ownerLanes.push(lane);
4214
- else {
4215
- hireByLane.set(lane.lane_id, {
4216
- ...laneHireFields(lane, allLanes),
4217
- hire_source: "local",
4218
- });
4219
- }
4220
4177
  }
4221
4178
  if (ownerLanes.length > 0) {
4222
4179
  const ownerSummaries = await batchOwnerLaneHireFields(ctx, ownerLanes);
@@ -4852,8 +4809,6 @@ async function applyHireGrant(ctx, laneId, hireGrant, authorization, grantContex
4852
4809
  (!grantContext || !sameHireGrantSenior(initial.senior, grantContext.expected_senior))) {
4853
4810
  throw new LanesError("hire_grant_senior_changed", `Lane ${laneId}'s senior changed after the primary authorized its grant update`, { lane_id: laneId, current_senior: initial.senior });
4854
4811
  }
4855
- const enforce = grantContext?.enforcement_enabled ??
4856
- await ctx.laneHireGrantsEnabled(initial.company);
4857
4812
  const lockedLaneIds = [
4858
4813
  laneId,
4859
4814
  ...(initial.senior.kind === "lane" && initial.senior.id
@@ -4878,7 +4833,7 @@ async function applyHireGrant(ctx, laneId, hireGrant, authorization, grantContex
4878
4833
  ? mergeOwnerLaneHierarchy(grantContext.hierarchy, loadAllLanes(hqRoot))
4879
4834
  : grantContext.hierarchy
4880
4835
  : loadAllLanes(hqRoot);
4881
- if (enforce && current.senior.kind === "lane") {
4836
+ if (current.senior.kind === "lane") {
4882
4837
  if (!current.senior.id) {
4883
4838
  throw new LanesError("hire_grant_senior_invalid", `Lane ${laneId} has a lane senior without an id`, {
4884
4839
  lane_id: laneId,
@@ -4908,19 +4863,17 @@ async function applyHireGrant(ctx, laneId, hireGrant, authorization, grantContex
4908
4863
  });
4909
4864
  }
4910
4865
  }
4911
- if (enforce) {
4912
- const children = aliveDirectChildren(hierarchy, current.lane_id);
4913
- const allocated = assignedChildHireGrants(children);
4914
- const minimumGrant = Math.max(allocated, children.length);
4915
- if (hireGrant < minimumGrant) {
4916
- throw new LanesError(allocated > children.length ? "hire_grant_below_allocated" : "hire_grant_below_occupancy", `Lane ${laneId} cannot lower its grant to ${hireGrant}; it has ${children.length} alive children and ${allocated} grant allocated to them`, {
4917
- lane_id: laneId,
4918
- requested_hire_grant: hireGrant,
4919
- alive_children: children.map((child) => child.lane_id),
4920
- alive_child_count: children.length,
4921
- assigned_child_grants: allocated,
4922
- });
4923
- }
4866
+ const children = aliveDirectChildren(hierarchy, current.lane_id);
4867
+ const allocated = assignedChildHireGrants(children);
4868
+ const minimumGrant = Math.max(allocated, children.length);
4869
+ if (hireGrant < minimumGrant) {
4870
+ throw new LanesError(allocated > children.length ? "hire_grant_below_allocated" : "hire_grant_below_occupancy", `Lane ${laneId} cannot lower its grant to ${hireGrant}; it has ${children.length} alive children and ${allocated} grant allocated to them`, {
4871
+ lane_id: laneId,
4872
+ requested_hire_grant: hireGrant,
4873
+ alive_children: children.map((child) => child.lane_id),
4874
+ alive_child_count: children.length,
4875
+ assigned_child_grants: allocated,
4876
+ });
4924
4877
  }
4925
4878
  return retryUpdateLane(hqRoot, laneId, "command", (record) => ({
4926
4879
  ...record,
@@ -5011,11 +4964,8 @@ async function runBackfillGrants(ctx, opts) {
5011
4964
  }
5012
4965
  const apply = Boolean(opts.apply);
5013
4966
  const hqRoot = ctx.hqRoot();
5014
- if (apply && await ctx.laneHireGrantsEnabled(null)) {
5015
- throw new LanesError("hire_grant_backfill_gate_enabled", "Backfill must be applied and verified before lanes.hire-grants is enabled", { flag: LANE_HIRE_GRANTS_FLAG });
5016
- }
5017
- if (apply && ctx.env.HQ_LANE_ID?.trim()) {
5018
- throw new LanesError("hire_grant_backfill_unauthorized", "A lane cannot apply the hire-grant backfill; run it from an operator session", {});
4967
+ if (apply) {
4968
+ throw new LanesError("hire_grant_backfill_gate_enabled", "Grant backfill can only be applied before always-on enforcement is released", {});
5019
4969
  }
5020
4970
  const allLanes = loadAllLanes(hqRoot);
5021
4971
  const profileIds = new Set(allLanes
@@ -5040,76 +4990,11 @@ async function runBackfillGrants(ctx, opts) {
5040
4990
  if (maxConcurrent !== undefined)
5041
4991
  profileMaxConcurrent.set(profileId, maxConcurrent);
5042
4992
  }
5043
- let rows = planHireGrantBackfill(allLanes, profileMaxConcurrent);
5044
- if (apply) {
5045
- const lanesById = new Map(allLanes.map((lane) => [lane.lane_id, lane]));
5046
- const targetCompanies = new Map();
5047
- for (const row of rows) {
5048
- if (row.action === "unchanged")
5049
- continue;
5050
- const lane = lanesById.get(row.lane_id);
5051
- if (!lane) {
5052
- throw new LanesError("hire_grant_backfill_target_changed", `Backfill target lane ${row.lane_id} is unavailable`, { lane_id: row.lane_id });
5053
- }
5054
- const company = lane.company;
5055
- if (!company)
5056
- continue;
5057
- targetCompanies.set(JSON.stringify([company.uid ?? null, company.slug ?? null]), company);
5058
- }
5059
- const companyGates = await Promise.all([...targetCompanies.values()].map((company) => ctx.laneHireGrantsEnabled(company)));
5060
- if (companyGates.some(Boolean)) {
5061
- throw new LanesError("hire_grant_backfill_gate_enabled", "Backfill must be applied and verified before lanes.hire-grants is enabled", { flag: LANE_HIRE_GRANTS_FLAG });
5062
- }
5063
- const shortfalls = rows.filter((row) => row.grant_shortfall > 0);
5064
- if (shortfalls.length > 0) {
5065
- throw new LanesError("hire_grant_backfill_allocation_shortfall", "Existing parent grants do not cover the grants allocated to alive children; resolve these rows before applying the backfill", { lanes: shortfalls });
5066
- }
5067
- rows = rows.map((row) => {
5068
- if (row.action === "unchanged")
5069
- return row;
5070
- const lane = readLane(hqRoot, row.lane_id);
5071
- const lockIds = [
5072
- lane.lane_id,
5073
- ...(lane.senior.kind === "lane" && lane.senior.id ? [lane.senior.id] : []),
5074
- ];
5075
- return withLaneHireLocks(hqRoot, lockIds, () => {
5076
- const current = readLane(hqRoot, row.lane_id);
5077
- if (current.hire_grant !== undefined) {
5078
- return {
5079
- ...row,
5080
- existing_grant: current.hire_grant,
5081
- proposed_grant: current.hire_grant,
5082
- action: "unchanged",
5083
- };
5084
- }
5085
- const latestLanes = loadAllLanes(hqRoot);
5086
- const latestRow = planHireGrantBackfill(latestLanes, profileMaxConcurrent)
5087
- .find((candidate) => candidate.lane_id === current.lane_id);
5088
- if (!latestRow) {
5089
- throw new LanesError("hire_grant_backfill_target_changed", `Lane ${current.lane_id} no longer qualifies for the hire-grant backfill`, { lane_id: current.lane_id });
5090
- }
5091
- if (latestRow.grant_shortfall > 0) {
5092
- throw new LanesError("hire_grant_backfill_allocation_shortfall", `Lane ${current.lane_id}'s existing grant does not cover grants allocated to its alive children`, { lane: latestRow });
5093
- }
5094
- const proposed = latestRow.proposed_grant;
5095
- const written = retryUpdateLane(hqRoot, current.lane_id, "command", (record) => ({
5096
- ...record,
5097
- ...(record.hire_grant === undefined ? { hire_grant: proposed } : {}),
5098
- }));
5099
- const value = written.hire_grant ?? 0;
5100
- return {
5101
- ...row,
5102
- existing_grant: value,
5103
- proposed_grant: value,
5104
- action: value === proposed ? "set" : "unchanged",
5105
- };
5106
- });
5107
- });
5108
- }
4993
+ const rows = planHireGrantBackfill(allLanes, profileMaxConcurrent);
5109
4994
  printJson({
5110
4995
  ok: true,
5111
4996
  action: "backfill-grants",
5112
- mode: apply ? "apply" : "dry-run",
4997
+ mode: "dry-run",
5113
4998
  lanes: rows,
5114
4999
  });
5115
5000
  }
@@ -5584,7 +5469,6 @@ async function prepareAccountMove(ctx, lane, input) {
5584
5469
  async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile, options = {}) {
5585
5470
  if (lane.provider !== "claude")
5586
5471
  return undefined;
5587
- const balanceEnabled = await ctx.laneClaudeAccountBalanceEnabled(lane.company);
5588
5472
  const hqRoot = ctx.hqRoot();
5589
5473
  const config = loadLanesConfig(hqRoot);
5590
5474
  const currentConfig = lane.account === undefined
@@ -5601,19 +5485,6 @@ async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile, opti
5601
5485
  const accountNeedsRepair = lane.account !== undefined &&
5602
5486
  (currentConfig?.enabled !== true || currentLogin === false);
5603
5487
  const repairUnavailableAccount = options.repairUnavailableAccount === true && accountNeedsRepair;
5604
- if (!balanceEnabled && !(options.repairUnavailableAccount && accountNeedsRepair)) {
5605
- if (lane.account === undefined)
5606
- return undefined;
5607
- if (currentConfig === undefined || !currentConfig.enabled) {
5608
- throw new AccountResolutionError(currentConfig === undefined ? "unknown_account" : "account_disabled", currentConfig === undefined
5609
- ? `Lane ${lane.lane_id} records account ${JSON.stringify(lane.account)} which is not configured for claude`
5610
- : `Account ${JSON.stringify(lane.account)} is unavailable for claude`, { provider: "claude", account: lane.account });
5611
- }
5612
- if (currentLogin === false) {
5613
- throw new LanesError("account_not_logged_in", `Account ${lane.account} for claude is not logged in`, { provider: "claude", account: lane.account, lane_id: lane.lane_id });
5614
- }
5615
- return undefined;
5616
- }
5617
5488
  const profile = workerProfile?.profile ?? (lane.worker_profile !== undefined
5618
5489
  ? loadWorkerProfile(hqRoot, lane.worker_profile)
5619
5490
  : undefined);
@@ -5641,28 +5512,7 @@ async function selectAutomaticClaudeResumeAccount(ctx, lane, workerProfile, opti
5641
5512
  const mayRebalance = BALANCE_MOVABLE_STATES.includes(lane.state);
5642
5513
  if (!accountNeedsRepair && !mayRebalance)
5643
5514
  return undefined;
5644
- const balanceHosts = balanceEnabled
5645
- ? await loadClaudeBalanceHostSnapshots(ctx, hqRoot, config)
5646
- : (() => {
5647
- const lanes = loadAllLanes(hqRoot).filter((candidate) => candidate.host.kind === lane.host.kind && candidate.host.id === lane.host.id);
5648
- return {
5649
- snapshots: [{
5650
- capacity: buildHostCapacitySnapshot({
5651
- host: lane.host,
5652
- lanes,
5653
- config,
5654
- existsSync: ctx.existsSync,
5655
- probe: ctx.probeProvider,
5656
- env: ctx.env,
5657
- hostname: ctx.hostname(),
5658
- now: ctx.now(),
5659
- }),
5660
- lanes,
5661
- laneListAvailable: true,
5662
- }],
5663
- unavailableHosts: [],
5664
- };
5665
- })();
5515
+ const balanceHosts = await loadClaudeBalanceHostSnapshots(ctx, hqRoot, config);
5666
5516
  const targetHost = balanceHosts.snapshots.find((host) => host.capacity.host.kind === lane.host.kind && host.capacity.host.id === lane.host.id);
5667
5517
  const currentTargetAccount = lane.account === undefined
5668
5518
  ? undefined
@@ -6294,7 +6144,6 @@ async function applyClaudeBalanceMove(ctx, suggestion) {
6294
6144
  return { lane_id: suggestion.lane_id, host: suggestion.host, from: suggestion.from, to: suggestion.to, state: suggestion.state };
6295
6145
  }
6296
6146
  const lane = laneWithRecoveredProviderSession(ctx.hqRoot(), readLane(ctx.hqRoot(), suggestion.lane_id));
6297
- assertBalanceApplyEnabled(await ctx.laneClaudeAccountBalanceEnabled(lane.company));
6298
6147
  if (lane.state === "running") {
6299
6148
  throw new LanesError("lane_running", `Lane ${lane.lane_id} is running; balance never moves a running turn`, {
6300
6149
  lane_id: lane.lane_id,
@@ -6324,7 +6173,6 @@ async function applyClaudeBalanceMove(ctx, suggestion) {
6324
6173
  }
6325
6174
  async function runClaudeBalanceMoveTarget(ctx, laneId, opts) {
6326
6175
  const lane = laneWithRecoveredProviderSession(ctx.hqRoot(), readLane(ctx.hqRoot(), laneId));
6327
- assertBalanceApplyEnabled(await ctx.laneClaudeAccountBalanceEnabled(lane.company));
6328
6176
  assertLaneRunsHereForOperatorMutation(ctx, lane, "balance-move-target");
6329
6177
  if (lane.provider !== "claude") {
6330
6178
  throw new LanesError("claude_account_balance_provider_mismatch", `Lane ${laneId} does not use Claude`, {
@@ -7072,19 +6920,14 @@ async function runReparent(ctx, laneId, opts) {
7072
6920
  const nextSenior = parseSeniorRef(opts.senior);
7073
6921
  assertSeniorExists(hqRoot, nextSenior, ctx);
7074
6922
  const initial = readLane(hqRoot, laneId);
7075
- const needsHireGrantGate = nextSenior.kind === "lane" &&
7076
- seniorKey(initial.senior) !== seniorKey(nextSenior);
7077
- const hireGrantsEnabled = needsHireGrantGate
7078
- ? await ctx.laneHireGrantsEnabled(initial.company)
7079
- : false;
7080
6923
  const lockIds = [
7081
6924
  laneId,
7082
6925
  ...(initial.senior.kind === "lane" && initial.senior.id ? [initial.senior.id] : []),
7083
6926
  ...(nextSenior.kind === "lane" && nextSenior.id ? [nextSenior.id] : []),
7084
6927
  ];
7085
- withLaneHireLocks(hqRoot, lockIds, () => runReparentLocked(ctx, laneId, opts, hireGrantsEnabled));
6928
+ withLaneHireLocks(hqRoot, lockIds, () => runReparentLocked(ctx, laneId, opts));
7086
6929
  }
7087
- function runReparentLocked(ctx, laneId, opts, hireGrantsEnabled) {
6930
+ function runReparentLocked(ctx, laneId, opts) {
7088
6931
  const hqRoot = ctx.hqRoot();
7089
6932
  if (!opts.senior) {
7090
6933
  throw new LanesError("missing_senior", "--senior is required", {});
@@ -7098,8 +6941,7 @@ function runReparentLocked(ctx, laneId, opts, hireGrantsEnabled) {
7098
6941
  }
7099
6942
  const seniorCompany = companyOfSenior(hqRoot, nextSenior, ctx);
7100
6943
  assertSameTenant(lane.company, nextSenior, seniorCompany, { lane_id: laneId });
7101
- if (hireGrantsEnabled &&
7102
- nextSenior.kind === "lane" &&
6944
+ if (nextSenior.kind === "lane" &&
7103
6945
  seniorKey(lane.senior) !== seniorKey(nextSenior)) {
7104
6946
  const parent = readLane(hqRoot, nextSenior.id);
7105
6947
  if (isTerminalLaneState(parent.state)) {
@@ -8558,27 +8400,9 @@ async function runAccountsBalance(ctx, opts) {
8558
8400
  const hqRoot = ctx.hqRoot();
8559
8401
  const config = loadLanesConfig(hqRoot);
8560
8402
  const loaded = await loadClaudeBalanceHostSnapshots(ctx, hqRoot, config);
8561
- const laneBalanceGateEnabled = new Map();
8562
- const companyBalanceGateEnabled = new Map();
8563
- const lanes = loaded.snapshots.flatMap((host) => host.lanes);
8564
- for (const lane of lanes) {
8565
- const companyKey = JSON.stringify([lane.company?.uid ?? null, lane.company?.slug ?? null]);
8566
- let enabled = companyBalanceGateEnabled.get(companyKey);
8567
- if (enabled === undefined) {
8568
- enabled = await ctx.laneClaudeAccountBalanceEnabled(lane.company);
8569
- companyBalanceGateEnabled.set(companyKey, enabled);
8570
- }
8571
- laneBalanceGateEnabled.set(lane.lane_id, enabled);
8572
- }
8573
- // With no lanes there is no tenant scope to resolve, so report and enforce
8574
- // the unscoped/default gate. Otherwise the report reflects whether any
8575
- // represented lane company has the gate enabled.
8576
- if (lanes.length === 0) {
8577
- companyBalanceGateEnabled.set(JSON.stringify([null, null]), await ctx.laneClaudeAccountBalanceEnabled());
8578
- }
8579
- const balanceGateEnabled = [...companyBalanceGateEnabled.values()].some(Boolean);
8580
- if (opts.apply)
8581
- assertBalanceApplyEnabled(balanceGateEnabled);
8403
+ // Retain the established response field for clients; balancing is now
8404
+ // unconditional, so this compatibility value is always true.
8405
+ const balanceGateEnabled = true;
8582
8406
  const loads = accountLoadsForReport({ hosts: loaded.snapshots, config });
8583
8407
  const suggestions = planClaudeBalanceMoves({ hosts: loaded.snapshots, config });
8584
8408
  const selections = loaded.snapshots.map((host) => {
@@ -8640,16 +8464,6 @@ async function runAccountsBalance(ctx, opts) {
8640
8464
  });
8641
8465
  continue;
8642
8466
  }
8643
- if (laneBalanceGateEnabled.get(suggestion.lane_id) !== true) {
8644
- skipped.push({
8645
- lane_id: suggestion.lane_id,
8646
- host: suggestion.host,
8647
- from: suggestion.from,
8648
- to: suggestion.to,
8649
- reason: "claude_account_balance_disabled",
8650
- });
8651
- continue;
8652
- }
8653
8467
  try {
8654
8468
  moved.push(await applyClaudeBalanceMove(ctx, suggestion));
8655
8469
  }
@@ -9514,7 +9328,7 @@ export function registerLanesCommand(parent, deps = {}) {
9514
9328
  .command("balance")
9515
9329
  .description("Report Claude account load and suggest safe between-turn moves")
9516
9330
  .requiredOption("--provider <name>", "claude")
9517
- .option("--apply", "Move eligible between-turn sessions (requires the Claude account balance gate)")
9331
+ .option("--apply", "Move eligible between-turn sessions to balance Claude accounts")
9518
9332
  .option("--json", "JSON on stdout (always)")
9519
9333
  .action(wrap(async (opts) => {
9520
9334
  await runAccountsBalance(ctx, opts);
@@ -1,7 +1,6 @@
1
1
  import type { AuthState } from "./accounts.js";
2
2
  import type { HostCapacityAccount, HostCapacitySnapshot } from "./host-capacity.js";
3
3
  import type { LaneHost, LaneRecord, LanesConfig } from "./types.js";
4
- export declare const CLAUDE_ACCOUNT_BALANCE_FLAG = "lanes.claude-account-balance";
5
4
  export declare const BALANCE_LIVE_STATES: readonly ["running", "queued", "awaiting_input"];
6
5
  export declare const BALANCE_MOVABLE_STATES: readonly ["interrupted", "done", "awaiting_input"];
7
6
  export interface BalanceHostSnapshot {
@@ -82,5 +81,4 @@ export declare function planClaudeBalanceMoves(input: {
82
81
  hosts: readonly BalanceHostSnapshot[];
83
82
  config: LanesConfig;
84
83
  }): BalanceMoveSuggestion[];
85
- export declare function assertBalanceApplyEnabled(enabled: boolean): void;
86
- //# sourceMappingURL=claude-account-balance.d.ts.map
84
+ //# sourceMappingURL=claude-balance.d.ts.map
@@ -1,5 +1,4 @@
1
1
  import { LanesError } from "./errors.js";
2
- export const CLAUDE_ACCOUNT_BALANCE_FLAG = "lanes.claude-account-balance";
3
2
  export const BALANCE_LIVE_STATES = ["running", "queued", "awaiting_input"];
4
3
  export const BALANCE_MOVABLE_STATES = ["interrupted", "done", "awaiting_input"];
5
4
  function sameHost(a, b) {
@@ -263,9 +262,4 @@ export function planClaudeBalanceMoves(input) {
263
262
  }
264
263
  return suggestions;
265
264
  }
266
- export function assertBalanceApplyEnabled(enabled) {
267
- if (!enabled) {
268
- throw new LanesError("claude_account_balance_disabled", `Applying Claude account balance moves requires hq-flags gate ${CLAUDE_ACCOUNT_BALANCE_FLAG}`, { flag: CLAUDE_ACCOUNT_BALANCE_FLAG });
269
- }
270
- }
271
- //# sourceMappingURL=claude-account-balance.js.map
265
+ //# sourceMappingURL=claude-balance.js.map
@@ -1,6 +1,5 @@
1
1
  import { type LaneRecord } from "./types.js";
2
2
  import { LanesError } from "./errors.js";
3
- export declare const LANE_HIRE_GRANTS_FLAG = "lanes.hire-grants";
4
3
  export type LaneHireGrantRow = Pick<LaneRecord, "lane_id" | "state" | "senior" | "hire_grant">;
5
4
  export declare function aliveDirectChildren<T extends LaneHireGrantRow>(lanes: readonly T[], parentLaneId: string): T[];
6
5
  export declare function assignedChildHireGrants(children: readonly LaneHireGrantRow[], exceptLaneId?: string): number;
@@ -27,4 +26,4 @@ export interface HireGrantBackfillRow {
27
26
  }
28
27
  /** Select alive manager lanes and calculate the non-destructive one-time backfill. */
29
28
  export declare function planHireGrantBackfill(lanes: readonly LaneRecord[], profileMaxConcurrent: ReadonlyMap<string, number>): HireGrantBackfillRow[];
30
- //# sourceMappingURL=hire-grants.d.ts.map
29
+ //# sourceMappingURL=grants.d.ts.map
@@ -1,7 +1,6 @@
1
1
  import { isTerminalLaneState } from "./types.js";
2
2
  import { listLaneIds, readLane, withLaneHireLocks } from "./store.js";
3
3
  import { LanesError } from "./errors.js";
4
- export const LANE_HIRE_GRANTS_FLAG = "lanes.hire-grants";
5
4
  export function aliveDirectChildren(lanes, parentLaneId) {
6
5
  return lanes.filter((lane) => lane.senior.kind === "lane" &&
7
6
  lane.senior.id === parentLaneId &&
@@ -144,4 +143,4 @@ export function planHireGrantBackfill(lanes, profileMaxConcurrent) {
144
143
  })
145
144
  .sort((a, b) => a.lane_id.localeCompare(b.lane_id));
146
145
  }
147
- //# sourceMappingURL=hire-grants.js.map
146
+ //# sourceMappingURL=grants.js.map
@@ -13,7 +13,7 @@ export interface OutpostHireGrantContext {
13
13
  hierarchy: HireGrantLaneSnapshot[];
14
14
  }
15
15
  /** Encode only the owner-relevant hierarchy from the primary's complete store. */
16
- export declare function createOutpostHireGrantContext(target: LaneRecord, lanes: readonly LaneRecord[], enforcementEnabled: boolean): string;
16
+ export declare function createOutpostHireGrantContext(target: LaneRecord, lanes: readonly LaneRecord[]): string;
17
17
  /** Decode and validate an owner grant context, binding it to the target lane. */
18
18
  export declare function parseOutpostHireGrantContext(encoded: string, targetLaneId: string): OutpostHireGrantContext;
19
19
  export declare function sameHireGrantSenior(a: SeniorRef, b: SeniorRef): boolean;
@@ -1,4 +1,4 @@
1
- import { aliveDirectChildren } from "./hire-grants.js";
1
+ import { aliveDirectChildren } from "./grants.js";
2
2
  import { LanesError } from "./errors.js";
3
3
  import { isLaneState, isSeniorKind, } from "./types.js";
4
4
  const HIRE_GRANT_CONTEXT_SCHEMA = "hq-lane-hire-grant-context.v1";
@@ -24,29 +24,27 @@ function snapshot(lane) {
24
24
  };
25
25
  }
26
26
  /** Encode only the owner-relevant hierarchy from the primary's complete store. */
27
- export function createOutpostHireGrantContext(target, lanes, enforcementEnabled) {
27
+ export function createOutpostHireGrantContext(target, lanes) {
28
28
  const current = lanes.find((lane) => lane.lane_id === target.lane_id);
29
29
  if (!current || !sameSenior(current.senior, target.senior)) {
30
30
  throw new LanesError("hire_grant_hierarchy_unavailable", `Cannot build an authoritative hire-grant hierarchy for lane ${target.lane_id}`, { lane_id: target.lane_id });
31
31
  }
32
32
  const relevant = new Map();
33
33
  relevant.set(current.lane_id, current);
34
- if (enforcementEnabled) {
35
- if (current.senior.kind === "lane") {
36
- const parentId = current.senior.id;
37
- const parent = parentId ? lanes.find((lane) => lane.lane_id === parentId) : undefined;
38
- if (!parent) {
39
- throw new LanesError("hire_grant_senior_invalid", `Lane ${target.lane_id} has no senior lane in the primary hierarchy`, { lane_id: target.lane_id, senior_lane_id: parentId ?? null });
40
- }
41
- relevant.set(parent.lane_id, parent);
42
- for (const sibling of aliveDirectChildren(lanes, parent.lane_id)) {
43
- relevant.set(sibling.lane_id, sibling);
44
- }
34
+ if (current.senior.kind === "lane") {
35
+ const parentId = current.senior.id;
36
+ const parent = parentId ? lanes.find((lane) => lane.lane_id === parentId) : undefined;
37
+ if (!parent) {
38
+ throw new LanesError("hire_grant_senior_invalid", `Lane ${target.lane_id} has no senior lane in the primary hierarchy`, { lane_id: target.lane_id, senior_lane_id: parentId ?? null });
45
39
  }
46
- for (const child of aliveDirectChildren(lanes, current.lane_id)) {
47
- relevant.set(child.lane_id, child);
40
+ relevant.set(parent.lane_id, parent);
41
+ for (const sibling of aliveDirectChildren(lanes, parent.lane_id)) {
42
+ relevant.set(sibling.lane_id, sibling);
48
43
  }
49
44
  }
45
+ for (const child of aliveDirectChildren(lanes, current.lane_id)) {
46
+ relevant.set(child.lane_id, child);
47
+ }
50
48
  const hierarchy = [...relevant.values()]
51
49
  .map(snapshot)
52
50
  .sort((a, b) => a.lane_id.localeCompare(b.lane_id));
@@ -55,7 +53,7 @@ export function createOutpostHireGrantContext(target, lanes, enforcementEnabled)
55
53
  }
56
54
  const context = {
57
55
  schema_version: HIRE_GRANT_CONTEXT_SCHEMA,
58
- enforcement_enabled: enforcementEnabled,
56
+ enforcement_enabled: true,
59
57
  expected_senior: copySenior(current.senior),
60
58
  hierarchy,
61
59
  };
@@ -150,7 +148,7 @@ export function parseOutpostHireGrantContext(encoded, targetLaneId) {
150
148
  if (!target || !sameSenior(target.senior, expectedSenior)) {
151
149
  throw contextError("Outpost grant context does not match the target's expected senior");
152
150
  }
153
- if (record.enforcement_enabled && expectedSenior.kind === "lane" &&
151
+ if (expectedSenior.kind === "lane" &&
154
152
  (!expectedSenior.id || !byId.has(expectedSenior.id))) {
155
153
  throw contextError("Outpost grant context is missing the target's senior lane");
156
154
  }
@@ -3,8 +3,8 @@
3
3
  */
4
4
  export { AccountResolutionError, LaneNotFoundError, LaneSchemaError, LaneVersionConflict, LanesConfigError, LanesError, WorkerResolutionError, isLaneVersionConflict, isLanesError, } from "./errors.js";
5
5
  export { MAX_LANE_DEPTH } from "./constants.js";
6
- export { assignedChildHireGrants, aliveDirectChildren, hireGrantCapacityError, LANE_HIRE_GRANTS_FLAG, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, } from "./hire-grants.js";
7
- export type { HireGrantBackfillRow } from "./hire-grants.js";
6
+ export { assignedChildHireGrants, aliveDirectChildren, hireGrantCapacityError, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, } from "./grants.js";
7
+ export type { HireGrantBackfillRow } from "./grants.js";
8
8
  export { compareLaneQueueOrder, hasActiveAdmissionReservation, hasActivePendingResume, hasEarlierEligibleQueuedLane, laneOccupiesRunningSlot, laneAdmissionCapReason, laneRunsOnHost, withAllLaneAdmissionLocks, withLaneAdmissionLock, } from "./admission.js";
9
9
  export type { AdmissionCapReason, AdmissionHost } from "./admission.js";
10
10
  export { buildHostCapacitySnapshot, chooseHostForLane, parseHostCapacitySnapshot, } from "./host-capacity.js";
@@ -3,7 +3,7 @@
3
3
  */
4
4
  export { AccountResolutionError, LaneNotFoundError, LaneSchemaError, LaneVersionConflict, LanesConfigError, LanesError, WorkerResolutionError, isLaneVersionConflict, isLanesError, } from "./errors.js";
5
5
  export { MAX_LANE_DEPTH } from "./constants.js";
6
- export { assignedChildHireGrants, aliveDirectChildren, hireGrantCapacityError, LANE_HIRE_GRANTS_FLAG, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, } from "./hire-grants.js";
6
+ export { assignedChildHireGrants, aliveDirectChildren, hireGrantCapacityError, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, } from "./grants.js";
7
7
  export { compareLaneQueueOrder, hasActiveAdmissionReservation, hasActivePendingResume, hasEarlierEligibleQueuedLane, laneOccupiesRunningSlot, laneAdmissionCapReason, laneRunsOnHost, withAllLaneAdmissionLocks, withLaneAdmissionLock, } from "./admission.js";
8
8
  export { buildHostCapacitySnapshot, chooseHostForLane, parseHostCapacitySnapshot, } from "./host-capacity.js";
9
9
  export { CONFIG_SCHEMA_VERSION, ENVELOPE_DECISIONS, ENVELOPE_SCHEMA_VERSION, HISTORY_CAP, HOST_KINDS, LANE_PROVIDERS, LANE_SCHEMA_VERSION, LANE_STORY_STATUSES, LANE_STATES, LIVENESS_STATES, QUESTION_BODY_STORES, QUESTION_SCHEMA_VERSION, QUESTION_STATUSES, QUEUED_REASONS, RAISED_VIA, ROUTING_REASONS, SENIOR_KINDS, STORY_STATUSES, TERMINAL_LANE_STATES, isEnvelopeDecision, isHostKind, isLaneProvider, isLaneState, isLivenessState, isQuestionStatus, isSeniorKind, isTerminalLaneState, parseLaneEnvelope, parseLaneProvider, parseLaneState, parseLivenessState, parseMajorSchema, parseQuestionStatus, parseSeniorKind, } from "./types.js";
@@ -15,7 +15,7 @@ import { loadLanesConfig, providerQuotaDefaultHoldMs } from "./config.js";
15
15
  import { MAX_LANE_DEPTH } from "./constants.js";
16
16
  import { AccountResolutionError, LanesError, isLaneVersionConflict } from "./errors.js";
17
17
  import { loadWorkerProfile } from "./profile.js";
18
- import { LANE_HIRE_GRANTS_FLAG, terminalChildResumeHireGrantError, withHireGrantAdmission, } from "./hire-grants.js";
18
+ import { terminalChildResumeHireGrantError, withHireGrantAdmission, } from "./grants.js";
19
19
  import { classifySessionLiveness, defaultLivenessDeps, loadLivenessIndex, readDaemonPresenceConnected, unindexedClaudeSessionLiveness, } from "./liveness.js";
20
20
  import { ANSWERED_QUESTIONS_FILE, armLaneWatcher, envelopeRepairMode, isPidRecordAlive, laneRunDir, readPidRecord, readSpawnManifest, markSpawnFailure, spawnEnvelopeRepair, spawnLaneWorker, retryLaneOnAlternateAccount, } from "./spawn.js";
21
21
  import { appendCappedHistory, lanesStoreRoot, parseLaneRecord, parseQuestionRecord, readLane, readQuestion, updateLane, updateQuestion, } from "./store.js";
@@ -807,7 +807,6 @@ export async function runPassBody(hqRoot, deps) {
807
807
  }
808
808
  const readRegistryFlag = deps.readRegistryFlag ?? readLanesRegistryFlag;
809
809
  const keepAliveFlagCompanies = new Map();
810
- const keepAliveHireGrantFlagCompanies = new Map();
811
810
  for (const lane of lanes) {
812
811
  if (!(lane.state === "done" || lane.state === "interrupted" || lane.state === "killed" || lane.state === "cancelled" || lane.state === "failed") ||
813
812
  (lane.keep_alive !== true && lane.worker_profile === undefined) ||
@@ -824,30 +823,18 @@ export async function runPassBody(hqRoot, deps) {
824
823
  }
825
824
  const key = keepAliveCompanyKey(lane.company);
826
825
  keepAliveFlagCompanies.set(key, lane.company);
827
- if (lane.senior.kind === "lane" && lane.senior.id) {
828
- keepAliveHireGrantFlagCompanies.set(key, lane.company);
829
- }
830
826
  }
831
827
  const keepAliveFlagByCompany = new Map();
832
- const keepAliveHireGrantFlagByCompany = new Map();
833
828
  const env = deps.env ?? process.env;
834
- await Promise.all([
835
- ...[...keepAliveFlagCompanies.entries()].map(async ([key, company]) => {
836
- keepAliveFlagByCompany.set(key, await readRegistryFlag(LEAD_LANE_KEEP_ALIVE_FLAG, {
837
- company: company ?? undefined,
838
- env,
839
- }));
840
- }),
841
- ...[...keepAliveHireGrantFlagCompanies.entries()].map(async ([key, company]) => {
842
- keepAliveHireGrantFlagByCompany.set(key, await readRegistryFlag(LANE_HIRE_GRANTS_FLAG, {
843
- company: company ?? undefined,
844
- env,
845
- }));
846
- }),
847
- ]);
829
+ await Promise.all([...keepAliveFlagCompanies.entries()].map(async ([key, company]) => {
830
+ keepAliveFlagByCompany.set(key, await readRegistryFlag(LEAD_LANE_KEEP_ALIVE_FLAG, {
831
+ company: company ?? undefined,
832
+ env,
833
+ }));
834
+ }));
848
835
  await withAllLaneAdmissionLocks(hqRoot, async () => {
849
836
  const freshLanes = loadLaneRecords(hqRoot, summary);
850
- resumeKeepAlive(hqRoot, freshLanes, summary, deps, now, keepAliveFlagByCompany, keepAliveHireGrantFlagByCompany);
837
+ resumeKeepAlive(hqRoot, freshLanes, summary, deps, now, keepAliveFlagByCompany);
851
838
  const drainLanes = loadLaneRecords(hqRoot, summary);
852
839
  const freshQuestions = loadQuestionRecords(hqRoot, summary);
853
840
  await admitQueued(hqRoot, drainLanes, summary, deps, now);
@@ -1545,7 +1532,7 @@ function latestEnvelopeMtimeMs(hqRoot, lane, ref) {
1545
1532
  throw new LanesError("keep_alive_envelope_timestamp_unavailable", `Cannot stat latest envelope for lane ${lane.lane_id}: ${err instanceof Error ? err.message : String(err)}`, { lane_id: lane.lane_id, path: filePath });
1546
1533
  }
1547
1534
  }
1548
- function resumeKeepAlive(hqRoot, lanes, summary, deps, now, flagByCompany, hireGrantFlagByCompany) {
1535
+ function resumeKeepAlive(hqRoot, lanes, summary, deps, now, flagByCompany) {
1549
1536
  const nowMs = now.getTime();
1550
1537
  const nowIso = now.toISOString();
1551
1538
  const config = loadLanesConfig(hqRoot);
@@ -1882,8 +1869,7 @@ function resumeKeepAlive(hqRoot, lanes, summary, deps, now, flagByCompany, hireG
1882
1869
  }), deps.dryRun);
1883
1870
  const seniorId = current.senior.kind === "lane" ? current.senior.id : undefined;
1884
1871
  let queued;
1885
- if (seniorId &&
1886
- hireGrantFlagByCompany.get(keepAliveCompanyKey(current.company)) === true) {
1872
+ if (seniorId) {
1887
1873
  try {
1888
1874
  queued = withHireGrantAdmission(hqRoot, seniorId, (snapshot) => {
1889
1875
  const latestLane = readLane(hqRoot, current.lane_id);
@@ -15,7 +15,7 @@ import { fileURLToPath } from "node:url";
15
15
  import { PROVIDER_HOME_ENV, accountStatus, isLoggedOut, resolveAccount, selectBalancedAccount, } from "./accounts.js";
16
16
  import { effectiveAccountHome } from "./account-home.js";
17
17
  import { buildHostCapacitySnapshot } from "./host-capacity.js";
18
- import { selectClaudeAccount } from "./claude-account-balance.js";
18
+ import { selectClaudeAccount } from "./claude-balance.js";
19
19
  import { COMPACTION_PROMPT, compactionAfterUsage, compactionDue, compactLaneSession, } from "./compaction.js";
20
20
  import { installDrainHook } from "./hooks-install.js";
21
21
  import { loadLanesConfig, providerAutoCompactTokens, providerCompactAfterMs, } from "./config.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.227.1",
3
+ "version": "5.228.0",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {