@indigoai-us/hq-cli 5.345.24 → 5.345.26

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.
Files changed (46) hide show
  1. package/CHANGELOG.md +12 -0
  2. package/assets/scaffold/core/scripts/ontology-source-worklist.sh +12 -2
  3. package/dist/command-catalog.generated.d.ts +50 -0
  4. package/dist/command-catalog.generated.js +62 -0
  5. package/dist/commands/core-ontology-native.js +100 -16
  6. package/dist/commands/core-ontology-readme-drift-fast.d.ts +2 -0
  7. package/dist/commands/core-ontology-readme-drift-fast.js +5 -0
  8. package/dist/commands/core-qmd-reindex-after-sync-fast.d.ts +3 -0
  9. package/dist/commands/core-qmd-reindex-after-sync-fast.js +26 -0
  10. package/dist/commands/core-register-project-native.js +80 -8
  11. package/dist/commands/core.js +17 -2
  12. package/dist/commands/index-cmd.js +2 -1
  13. package/dist/commands/install-global.js +19 -4
  14. package/dist/commands/integrations-core.d.ts +4 -0
  15. package/dist/commands/integrations-core.js +15 -3
  16. package/dist/commands/lanes.js +180 -28
  17. package/dist/fast-scaffold.js +19 -0
  18. package/dist/lib/core-utils/qmd-reindex-after-sync.d.ts +1 -0
  19. package/dist/lib/core-utils/qmd-reindex-after-sync.js +4 -10
  20. package/dist/lib/lanes/grants.d.ts +0 -1
  21. package/dist/lib/lanes/grants.js +3 -6
  22. package/dist/lib/lanes/index.d.ts +2 -2
  23. package/dist/lib/lanes/index.js +2 -2
  24. package/dist/lib/lanes/keep-alive-audit.d.ts +5 -0
  25. package/dist/lib/lanes/keep-alive-audit.js +15 -1
  26. package/dist/lib/lanes/outpost-dispatch.d.ts +2 -1
  27. package/dist/lib/lanes/outpost-dispatch.js +1 -0
  28. package/dist/lib/lanes/store.js +33 -0
  29. package/dist/lib/lanes/story-ledger.d.ts +2 -0
  30. package/dist/lib/lanes/story-ledger.js +33 -0
  31. package/dist/lib/lanes/types.d.ts +10 -0
  32. package/dist/lib/lanes/types.js +1 -1
  33. package/dist/lib/search-index/background.js +2 -1
  34. package/dist/lib/search-index/collection-path-repair-flag-constants.d.ts +2 -0
  35. package/dist/lib/search-index/collection-path-repair-flag-constants.js +2 -0
  36. package/dist/lib/search-index/collection-path-repair-flag.d.ts +1 -1
  37. package/dist/lib/search-index/collection-path-repair-flag.js +2 -1
  38. package/dist/lib/search-index/index.js +1 -2
  39. package/dist/lib/search-index/reindex-single-flight-flag-constants.d.ts +3 -0
  40. package/dist/lib/search-index/reindex-single-flight-flag-constants.js +3 -0
  41. package/dist/lib/search-index/reindex-single-flight-flag.d.ts +1 -2
  42. package/dist/lib/search-index/reindex-single-flight-flag.js +2 -2
  43. package/dist/main.js +4 -1
  44. package/dist/utils/install-tree-torn.d.ts +7 -7
  45. package/dist/utils/install-tree-torn.js +10 -7
  46. package/package.json +1 -1
@@ -59,6 +59,8 @@ export class IntegrationsCliError extends Error {
59
59
  gatewaySubcommand;
60
60
  /** Fixed vocabulary describing the shape of an upstream JSON-RPC error. */
61
61
  gatewayErrorShape;
62
+ /** Fixed classification for malformed gateway envelopes. */
63
+ gatewayFailureCode;
62
64
  /**
63
65
  * HQ's own OAuth callback URL, carried on the errors that say a provider
64
66
  * refused to register HQ automatically. The provider must register the exact
@@ -83,6 +85,8 @@ export class IntegrationsCliError extends Error {
83
85
  this.gatewaySubcommand = opts.gatewaySubcommand;
84
86
  if (opts.gatewayErrorShape !== undefined)
85
87
  this.gatewayErrorShape = opts.gatewayErrorShape;
88
+ if (opts.gatewayFailureCode !== undefined)
89
+ this.gatewayFailureCode = opts.gatewayFailureCode;
86
90
  if (opts.redirectUri !== undefined) {
87
91
  Object.defineProperty(this, "redirectUri", {
88
92
  value: opts.redirectUri,
@@ -523,6 +527,15 @@ export async function callGateway(token, params, idempotencyKey) {
523
527
  }
524
528
  if (terminalMessage?.error) {
525
529
  const gatewayError = gatewayErrorDetails(terminalMessage.error);
530
+ const invalidEnvelope = gatewayError.rpcCode === undefined && !gatewayError.message;
531
+ const gatewayFailureCode = invalidEnvelope
532
+ ? "invalid_envelope"
533
+ : undefined;
534
+ const userMessage = invalidEnvelope
535
+ ? "Integration gateway returned an invalid response. Try again. If it keeps happening, contact support."
536
+ : gatewayError.message ||
537
+ expectedGatewayErrorFallback(gatewayError.rpcCode) ||
538
+ "Integration gateway returned an error.";
526
539
  // The gateway's error text is minted UPSTREAM (hq-pro, the integration
527
540
  // factory, and beyond it the third-party provider), so it is untrusted:
528
541
  // scrub credentials and bound the length here, at the throw site, because
@@ -531,13 +544,12 @@ export async function callGateway(token, params, idempotencyKey) {
531
544
  // The chain is idempotent, so the unexpected path scrubbing again is a
532
545
  // no-op. This preserves PR #298's user-visible diagnostic; it only makes
533
546
  // it safe on the newly-expected path.
534
- throw new IntegrationsCliError(gatewayError.message ||
535
- expectedGatewayErrorFallback(gatewayError.rpcCode) ||
536
- "Integration gateway returned an error.", {
547
+ throw new IntegrationsCliError(userMessage, {
537
548
  expected: isExpectedGatewayError(gatewayError.rpcCode),
538
549
  status: terminalStatus,
539
550
  gatewaySubcommand,
540
551
  gatewayErrorShape: gatewayError.shape,
552
+ ...(gatewayFailureCode ? { gatewayFailureCode } : {}),
541
553
  // Carry the protocol code to the capture site as a bounded grouping
542
554
  // discriminator (HQ-CLI collision, Sentry 7642756130). This does not
543
555
  // alter the `expected` decision above; it only labels the error.
@@ -62,19 +62,21 @@ import { createLaneMesh, emitLaneDecisionEvents, emitLaneTerminalStateEvent, lan
62
62
  import { sessionIdFromEnv } from "../lib/mesh/live/session-identity.js";
63
63
  import { listLaneReviews } from "../lib/lanes/reviews.js";
64
64
  import { effectiveAccountHome } from "../lib/lanes/account-home.js";
65
- import { AccountResolutionError, compareLaneQueueOrder, LANE_PROVIDERS, LANE_STATES, QUESTION_STATUSES, SENIOR_KINDS, accountStatus, appendCappedHistory, canonicalizeHome, createLane, decideAdmission, defaultLivenessDeps, defaultSystemdUserDir, defaultThrottleDeps, installReconcileTimer, providerPathEnv, probeDrainHookRegistration, isLanesError, isLaneVersionConflict, isTerminalLaneState, isFinishedResumableHireChild, aliveDirectChildren, childrenHoldingHireGrantCarve, laneHoldsHireGrantCarve, assignedChildHireGrants, hireGrantCapacityError, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, laneStoryLedgerEnabled, validateProjectStories, buildLaneStatusSnapshot, formatLaneStatusHuman, listLaneIds, listQuestionIds, loadLanesConfig, laneRunsHere, MAX_LANE_DEPTH, MONITOR_ERROR_EXIT_CODE, readLiveMonitorEvidence, resolveMachineIdentity as lookupMachineIdentity, loadLivenessIndex, parseLaneProvider, parseLaneRecord, parseLaneState, parseQuestionRecord, parseSeniorKind, readAllLanes, readLane, sanitizeHostComponent, retryUpdateLane, syncLaneRecordFromOwner, readQuestion, resolveAccount, resolveLaneWorker, ignoredWorkerProfileFields, loadWorkerProfile, loadWorkerProfileWithRegistry, registryWorkerRecord, runReconcilePass, DEAD_HOST_MS, sessionLiveness, tryReadLane, uninstallReconcileTimer, updateLane, updateQuestion, dropboxDir, laneDeliveryMechanism, hasActiveAdmissionReservation, hasActivePendingResume, hasRecordedSessionId, hasEarlierEligibleQueuedLane, laneAdmissionCapReason, providerSupportsSessionResume, providerSessionPortable, sessionNotPortableMessage, withLaneAdmissionLock, withAllLaneAdmissionLocks, readWorkerRunResult, resumeCommandRefusal, withLaneHireLocks, raiseLaneQuestion, sha256Text, } from "../lib/lanes/index.js";
65
+ import { AccountResolutionError, compareLaneQueueOrder, LANE_PROVIDERS, LANE_STATES, QUESTION_STATUSES, SENIOR_KINDS, accountStatus, appendCappedHistory, canonicalizeHome, createLane, decideAdmission, defaultLivenessDeps, defaultSystemdUserDir, defaultThrottleDeps, installReconcileTimer, providerPathEnv, probeDrainHookRegistration, isLanesError, isLaneVersionConflict, isTerminalLaneState, isFinishedResumableHireChild, childrenHoldingHireGrantCarve, laneHoldsHireGrantCarve, assignedChildHireGrants, hireGrantCapacityError, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, laneStoryLedgerEnabled, syncLaneToProjectStoryLedger, removeLaneFromProjectStoryLedger, validateProjectStories, buildLaneStatusSnapshot, formatLaneStatusHuman, listLaneIds, listQuestionIds, loadLanesConfig, laneRunsHere, MAX_LANE_DEPTH, MONITOR_ERROR_EXIT_CODE, readLiveMonitorEvidence, resolveMachineIdentity as lookupMachineIdentity, loadLivenessIndex, parseLaneProvider, parseLaneRecord, parseLaneState, parseQuestionRecord, parseSeniorKind, readAllLanes, readLane, sanitizeHostComponent, retryUpdateLane, syncLaneRecordFromOwner, readQuestion, resolveAccount, resolveLaneWorker, ignoredWorkerProfileFields, loadWorkerProfile, loadWorkerProfileWithRegistry, registryWorkerRecord, runReconcilePass, DEAD_HOST_MS, sessionLiveness, tryReadLane, uninstallReconcileTimer, updateLane, updateQuestion, dropboxDir, laneDeliveryMechanism, hasActiveAdmissionReservation, hasActivePendingResume, hasRecordedSessionId, hasEarlierEligibleQueuedLane, laneAdmissionCapReason, providerSupportsSessionResume, providerSessionPortable, sessionNotPortableMessage, withLaneAdmissionLock, withAllLaneAdmissionLocks, readWorkerRunResult, resumeCommandRefusal, withLaneHireLocks, raiseLaneQuestion, sha256Text, } from "../lib/lanes/index.js";
66
66
  import { laneStories, storyWarning, } from "../lib/lanes/stories.js";
67
67
  import { resolveLaneKeepAlive } from "../lib/lanes/profile.js";
68
68
  import { laneCompanyUidSource, resolveLaneCompany } from "../lib/lanes/lane-company.js";
69
69
  import { doneKeepAliveVerdict, hasPendingKeepAliveWork, keepAliveNextCheckAtMs, keepAliveExclusion, LANE_SUPERSESSION_LINEAGE_AWARE_FLAG, loadRetiredLaneIds, } from "../lib/lanes/keep-alive.js";
70
70
  import { questionBodyAbsPath, resolveQuestionCompanyWithFallback, resolveProjectQuestionCompany, } from "../lib/lanes/questions.js";
71
71
  import { assertQuestionBodySecretSafe } from "../lib/lanes/questions.js";
72
+ import { writeQuestionBody } from "../lib/lanes/questions.js";
72
73
  import { compactionAfterUsage, } from "../lib/lanes/compaction.js";
73
74
  import { parseCliEffort, resolveLaneEffort, } from "../lib/lanes/effort.js";
74
75
  import { LanesError } from "../lib/lanes/errors.js";
75
76
  import { readLatestEnvelopeRecord } from "../lib/lanes/watcher.js";
76
77
  import { loadQuotaState, quotaBlockFor } from "../lib/lanes/quota.js";
77
78
  import { SESSION_MOVE_REASON } from "../lib/lanes/move-account.js";
79
+ import { readCompaniesManifestMap } from "../lib/work-context/repo-remote.js";
78
80
  import { resolveManagerStatusTarget, } from "../lib/lanes/manager-status-gate.js";
79
81
  import { blockerFreshnessFingerprint, resolveBlockerFreshnessForLane, } from "../lib/lanes/blocker-freshness.js";
80
82
  import { resolveKrFreshnessForShow } from "../lib/lanes/kr-freshness.js";
@@ -496,7 +498,7 @@ function loadAllLanes(hqRoot) {
496
498
  function laneHireFields(lane, lanes, aliveChildrenByParent, finishedChildrenDoNotCount = false) {
497
499
  const aliveChildCount = aliveChildrenByParent
498
500
  ? aliveChildrenByParent.get(lane.lane_id) ?? 0
499
- : aliveDirectChildren(lanes, lane.lane_id, finishedChildrenDoNotCount).length;
501
+ : childrenHoldingHireGrantCarve(lanes, lane.lane_id, finishedChildrenDoNotCount).length;
500
502
  const hireGrant = lane.hire_grant ?? 0;
501
503
  const hireSummary = `hire ${aliveChildCount}/${hireGrant}`;
502
504
  return {
@@ -1046,9 +1048,13 @@ async function withResumeHireAdmission(ctx, lane, fn, grantContext) {
1046
1048
  const seniorLaneId = lane.senior.kind === "lane" ? lane.senior.id : undefined;
1047
1049
  const resumeSenior = seniorLaneId
1048
1050
  ? grantContext?.hierarchy.find((entry) => entry.lane_id === seniorLaneId) ??
1049
- (listLaneIds(hqRoot).includes(seniorLaneId) ? readLane(hqRoot, seniorLaneId) : lane)
1051
+ (listLaneIds(hqRoot).includes(seniorLaneId) ? readLane(hqRoot, seniorLaneId) : undefined)
1050
1052
  : lane;
1051
- finishedChildrenDoNotCount = await hireGrantFinishedChildrenDoNotCount(ctx, resumeSenior);
1053
+ if (resumeSenior) {
1054
+ finishedChildrenDoNotCount = await hireGrantFinishedChildrenDoNotCount(ctx, resumeSenior);
1055
+ }
1056
+ // A missing senior record keeps the live-only flag off, so finished
1057
+ // children continue to count. Never borrow the child's company for this gate.
1052
1058
  }
1053
1059
  const shouldCheckGrant = isTerminalLaneState(lane.state) ||
1054
1060
  (finishedChildrenDoNotCount && isFinishedResumableHireChild(lane.state));
@@ -2134,6 +2140,16 @@ function companyBindingFromSessionMeta(meta) {
2134
2140
  return { binding: "none" };
2135
2141
  return { binding: "company", slug, uid };
2136
2142
  }
2143
+ function canonicalCompanySlugForUid(hqRoot, uid, senior) {
2144
+ const companies = readCompaniesManifestMap(hqRoot);
2145
+ const matches = companies
2146
+ ? Object.entries(companies).filter(([, entry]) => entry?.cloud_uid === uid).map(([slug]) => slug)
2147
+ : [];
2148
+ if (matches.length !== 1) {
2149
+ throw new LanesError("senior_company_unresolved", `Cannot resolve senior ${seniorKey(senior)} to one company slug from uid ${uid}`, { senior, company_uid: uid, matches });
2150
+ }
2151
+ return matches[0];
2152
+ }
2137
2153
  function serializeSeniorCompany(seniorCompany) {
2138
2154
  if (seniorCompany.binding === "unknown")
2139
2155
  return null;
@@ -3064,9 +3080,17 @@ async function runCreate(ctx, opts) {
3064
3080
  }
3065
3081
  const senior = opts.senior ? parseSeniorRef(opts.senior) : defaultSenior(ctx);
3066
3082
  assertSeniorExists(hqRoot, senior, ctx);
3067
- const company = companyFlag
3083
+ let company = companyFlag
3068
3084
  ? await ctx.resolveCompany(companyFlag)
3069
3085
  : null;
3086
+ const seniorCompany = companyOfSenior(hqRoot, senior, ctx);
3087
+ if (company === null && seniorCompany.binding === "company") {
3088
+ const seniorCompanyIdentifier = seniorCompany.slug ?? (seniorCompany.uid
3089
+ ? canonicalCompanySlugForUid(hqRoot, seniorCompany.uid, senior)
3090
+ : undefined);
3091
+ if (seniorCompanyIdentifier)
3092
+ company = await ctx.resolveCompany(seniorCompanyIdentifier);
3093
+ }
3070
3094
  const callerAuthorization = requestedHireGrant !== undefined || ctx.env.HQ_LANE_ID?.trim()
3071
3095
  ? callerSeniorAuthorization(ctx, senior)
3072
3096
  : { authorized: true };
@@ -3122,7 +3146,6 @@ async function runCreate(ctx, opts) {
3122
3146
  if (requestedPlacement !== undefined || defaultPlacement !== undefined) {
3123
3147
  await assertPlacementEnabled(ctx, resolvePlacementCompany(), placementEnabled);
3124
3148
  }
3125
- const seniorCompany = companyOfSenior(hqRoot, senior, ctx);
3126
3149
  assertSameTenant(company, senior, seniorCompany);
3127
3150
  const createStoryWarnings = [];
3128
3151
  const knownAbsentStories = new Set();
@@ -4333,7 +4356,7 @@ function laneRunsHereForOperatorMutation(ctx, lane) {
4333
4356
  function assertLaneRunsHereForOperatorMutation(ctx, lane, action) {
4334
4357
  if (laneRunsHereForOperatorMutation(ctx, lane))
4335
4358
  return;
4336
- throw new LanesError("lane_remote", `Cannot ${action} lane ${lane.lane_id} from this host; its state is owned by ${lane.host.kind}:${lane.host.id}`, { lane_id: lane.lane_id, host: lane.host, action });
4359
+ throw new LanesError("lane_remote", `Cannot ${action} lane ${lane.lane_id} from this host; its state is owned by ${lane.host.kind === "outpost" ? `Outpost ${lane.host.id}` : `host ${lane.host.id}`}. Run this command on the owning host.`, { lane_id: lane.lane_id, host: lane.host, action });
4337
4360
  }
4338
4361
  async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior = {}) {
4339
4362
  if (lane.host.kind !== "outpost") {
@@ -4399,9 +4422,11 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
4399
4422
  ? "reparent-target"
4400
4423
  : action === "approve"
4401
4424
  ? "approve-target"
4402
- : action,
4425
+ : action === "repair-company"
4426
+ ? "repair-company-target"
4427
+ : action,
4403
4428
  laneId: lane.lane_id,
4404
- hqBinary: action === "grant" || action === "reparent"
4429
+ hqBinary: action === "grant" || action === "reparent" || action === "repair-company"
4405
4430
  ? outpostCliOverride(ctx, outpostId)
4406
4431
  : undefined,
4407
4432
  text: options.text,
@@ -4416,6 +4441,7 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
4416
4441
  hire: options.hire,
4417
4442
  senior: options.senior,
4418
4443
  reason: options.reason,
4444
+ company: options.company,
4419
4445
  grantContext: contexts.grantContext,
4420
4446
  profileRepinContext: contexts.profileRepinContext,
4421
4447
  failedResumeContext: contexts.failedResumeContext,
@@ -4534,23 +4560,28 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
4534
4560
  : action === "approve"
4535
4561
  ? remote.checkpoint === options.checkpoint &&
4536
4562
  remote.lane !== null && typeof remote.lane === "object" && !Array.isArray(remote.lane)
4537
- : action === "reparent"
4538
- ? remote.lane !== null && typeof remote.lane === "object" &&
4539
- Array.isArray(remote.changed_descendant_ids)
4540
- : (((remote.delivery_status === "worker_spawned" ||
4541
- remote.delivery_status === "queued_on_target_account") &&
4542
- remote.lane !== undefined) ||
4543
- (remote.delivery_status === "queued_on_admission" &&
4544
- remote.lane !== null &&
4545
- typeof remote.lane === "object" &&
4546
- !Array.isArray(remote.lane) &&
4547
- remote.lane.state === "queued") ||
4548
- (remote.delivery_status === "queued_for_reconcile" &&
4549
- remote.lane !== null &&
4550
- typeof remote.lane === "object" &&
4551
- !Array.isArray(remote.lane) &&
4552
- remote.lane.state === "queued" &&
4553
- remote.lane.queued_reason !== "worker_profile_changed")));
4563
+ : action === "repair-company"
4564
+ ? remote.lane !== null && typeof remote.lane === "object" && !Array.isArray(remote.lane) &&
4565
+ remote.lane.company !== null &&
4566
+ remote.lane.company !== undefined &&
4567
+ remote.lane.company.slug === options.company
4568
+ : action === "reparent"
4569
+ ? remote.lane !== null && typeof remote.lane === "object" &&
4570
+ Array.isArray(remote.changed_descendant_ids)
4571
+ : (((remote.delivery_status === "worker_spawned" ||
4572
+ remote.delivery_status === "queued_on_target_account") &&
4573
+ remote.lane !== undefined) ||
4574
+ (remote.delivery_status === "queued_on_admission" &&
4575
+ remote.lane !== null &&
4576
+ typeof remote.lane === "object" &&
4577
+ !Array.isArray(remote.lane) &&
4578
+ remote.lane.state === "queued") ||
4579
+ (remote.delivery_status === "queued_for_reconcile" &&
4580
+ remote.lane !== null &&
4581
+ typeof remote.lane === "object" &&
4582
+ !Array.isArray(remote.lane) &&
4583
+ remote.lane.state === "queued" &&
4584
+ remote.lane.queued_reason !== "worker_profile_changed")));
4554
4585
  if (!resultConfirmed) {
4555
4586
  throw new LanesError("outpost_delivery_unconfirmed", `Outpost ${outpostId} completed exec without a confirmed ${action} result for lane ${lane.lane_id}`, {
4556
4587
  lane_id: lane.lane_id,
@@ -4609,7 +4640,7 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options, behavior =
4609
4640
  printJson(response);
4610
4641
  return response;
4611
4642
  }
4612
- if (action === "approve" || action === "reparent" || action === "resume") {
4643
+ if (action === "approve" || action === "reparent" || action === "resume" || action === "repair-company") {
4613
4644
  const ownerLane = dispatchedOwnerLane(ctx, lane.lane_id, outpostId, remote.lane, behavior);
4614
4645
  const descendants = [];
4615
4646
  if (action === "reparent") {
@@ -6198,7 +6229,7 @@ async function runBackfillGrants(ctx, opts) {
6198
6229
  const allLanes = loadAllLanes(hqRoot);
6199
6230
  const profileIds = new Set(allLanes
6200
6231
  .filter((lane) => !isTerminalLaneState(lane.state) &&
6201
- (aliveDirectChildren(allLanes, lane.lane_id).length > 0 ||
6232
+ (childrenHoldingHireGrantCarve(allLanes, lane.lane_id).length > 0 ||
6202
6233
  lane.worker_profile === "hq-lanes-manager"))
6203
6234
  .map((lane) => lane.worker_profile)
6204
6235
  .filter((id) => Boolean(id)));
@@ -7121,6 +7152,10 @@ function runReparentLocked(ctx, laneId, opts, finishedChildrenDoNotCount = false
7121
7152
  throw new LanesError("lane_terminal", `Cannot re-parent a lane in terminal state ${lane.state}`, { lane_id: laneId, state: lane.state });
7122
7153
  }
7123
7154
  const seniorCompany = companyOfSenior(hqRoot, nextSenior, ctx);
7155
+ if (lane.company === null && seniorCompany.binding === "company") {
7156
+ const company = seniorCompany.slug ?? seniorCompany.uid ?? "<company>";
7157
+ throw new LanesError("lane_company_missing", `Lane ${laneId} has no company attribution. Repair it with hq lanes repair-company ${laneId} --company ${company}, then retry reparent.`, { lane_id: laneId, senior: nextSenior, senior_company: serializeSeniorCompany(seniorCompany) });
7158
+ }
7124
7159
  assertSameTenant(lane.company, nextSenior, seniorCompany, { lane_id: laneId });
7125
7160
  if (nextSenior.kind === "lane" && seniorKey(lane.senior) !== seniorKey(nextSenior)) {
7126
7161
  const parent = readLane(hqRoot, nextSenior.id);
@@ -7239,6 +7274,108 @@ function runReparentLocked(ctx, laneId, opts, finishedChildrenDoNotCount = false
7239
7274
  .map((descendant) => descendant.laneId),
7240
7275
  };
7241
7276
  }
7277
+ async function runRepairCompany(ctx, laneId, opts) {
7278
+ const lane = readLane(ctx.hqRoot(), laneId);
7279
+ if (lane.host.kind === "outpost" && !laneRunsHere(lane, reconcileHostFromCtx(ctx))) {
7280
+ const companySlug = opts.company?.trim();
7281
+ if (!companySlug)
7282
+ throw new LanesError("missing_company", "--company is required", {});
7283
+ const company = await ctx.resolveCompany(companySlug);
7284
+ await dispatchOutpostLaneCommand(ctx, lane, "repair-company", { company: company.slug });
7285
+ return;
7286
+ }
7287
+ await repairCompanyOnOwner(ctx, laneId, opts);
7288
+ }
7289
+ async function repairCompanyOnOwner(ctx, laneId, opts) {
7290
+ const hqRoot = ctx.hqRoot();
7291
+ const companySlug = opts.company?.trim();
7292
+ if (!companySlug) {
7293
+ throw new LanesError("missing_company", "--company is required", {});
7294
+ }
7295
+ const company = await ctx.resolveCompany(companySlug);
7296
+ const initial = readLane(hqRoot, laneId);
7297
+ assertLaneRunsHereForOperatorMutation(ctx, initial, "repair company for");
7298
+ const initialSeniorCompany = companyOfSenior(hqRoot, initial.senior, ctx);
7299
+ if (initialSeniorCompany.binding === "company" && !sameTenant(company, initial.senior, initialSeniorCompany)) {
7300
+ throw new LanesError("lane_company_conflict", `Cannot repair lane ${laneId} to ${company.slug}: its senior belongs to a different company`, { lane_id: laneId, requested_company: company.slug, senior_company: serializeSeniorCompany(initialSeniorCompany) });
7301
+ }
7302
+ if (initial.mesh_company && !sameLaneCompany(initial.mesh_company, company)) {
7303
+ throw new LanesError("lane_company_conflict", `Cannot repair lane ${laneId} to ${company.slug}: its mesh company is ${initial.mesh_company.slug}`, { lane_id: laneId, requested_company: company.slug, mesh_company: initial.mesh_company });
7304
+ }
7305
+ const pendingQuestionMigrations = loadAllQuestions(hqRoot)
7306
+ .filter((entry) => entry.lane_id === laneId && entry.status === "pending")
7307
+ .map((question) => {
7308
+ if (question.company && !sameLaneCompany(question.company, company)) {
7309
+ throw new LanesError("lane_company_conflict", `Cannot repair lane ${laneId}: pending question ${question.question_id} belongs to ${question.company.slug}`, { lane_id: laneId, question_id: question.question_id, question_company: question.company, requested_company: company.slug });
7310
+ }
7311
+ if (question.company && sameLaneCompany(question.company, company))
7312
+ return null;
7313
+ const oldBodyPath = questionBodyAbsPath(hqRoot, question);
7314
+ const body = readQuestionBody(hqRoot, question);
7315
+ if (!body) {
7316
+ throw new LanesError("question_body_missing", `Cannot repair lane ${laneId}: pending question ${question.question_id} has no readable body`, { lane_id: laneId, question_id: question.question_id });
7317
+ }
7318
+ return { question, oldBodyPath, body };
7319
+ })
7320
+ .filter((entry) => entry !== null);
7321
+ const at = ctx.now().toISOString();
7322
+ casUpdateLane(hqRoot, laneId, (current) => {
7323
+ assertLaneRunsHereForOperatorMutation(ctx, current, "repair company for");
7324
+ const seniorCompany = companyOfSenior(hqRoot, current.senior, ctx);
7325
+ if (seniorCompany.binding === "company" && !sameTenant(company, current.senior, seniorCompany)) {
7326
+ throw new LanesError("lane_company_conflict", `Cannot repair lane ${laneId} to ${company.slug}: its senior belongs to a different company`, { lane_id: laneId, requested_company: company.slug, senior_company: serializeSeniorCompany(seniorCompany) });
7327
+ }
7328
+ if (current.mesh_company && !sameLaneCompany(current.mesh_company, company)) {
7329
+ throw new LanesError("lane_company_conflict", `Cannot repair lane ${laneId} to ${company.slug}: its mesh company is ${current.mesh_company.slug}`, { lane_id: laneId, requested_company: company.slug, mesh_company: current.mesh_company });
7330
+ }
7331
+ if (current.company !== null) {
7332
+ if (sameLaneCompany(current.company, company))
7333
+ return current;
7334
+ throw new LanesError("lane_company_already_set", `Lane ${laneId} already belongs to ${current.company.slug}; company repair only fills missing attribution`, { lane_id: laneId, company: current.company.slug, requested_company: company.slug });
7335
+ }
7336
+ return {
7337
+ ...current,
7338
+ company,
7339
+ company_history: appendCappedHistory(current.company_history ?? [], {
7340
+ from: null,
7341
+ to: company,
7342
+ at,
7343
+ by: sessionIdFromEnv(ctx.env) ?? "user",
7344
+ reason: "repair_company",
7345
+ }),
7346
+ };
7347
+ });
7348
+ const migratedQuestionRefs = [];
7349
+ for (const { question, oldBodyPath, body } of pendingQuestionMigrations) {
7350
+ const bodyRef = writeQuestionBody(hqRoot, {
7351
+ ...question,
7352
+ company,
7353
+ body: { ...question.body, store: "company" },
7354
+ }, body);
7355
+ casUpdateQuestion(hqRoot, question.question_id, (current) => {
7356
+ if (current.lane_id !== laneId || current.status !== "pending" || current.company !== null) {
7357
+ throw new LanesError("question_changed_during_company_repair", `Pending question ${question.question_id} changed during company repair`, { lane_id: laneId, question_id: question.question_id });
7358
+ }
7359
+ return { ...current, company, body: bodyRef };
7360
+ });
7361
+ fs.rmSync(oldBodyPath, { force: true });
7362
+ migratedQuestionRefs.push({ question_id: question.question_id, project_id: question.project_id, story_id: question.story_id });
7363
+ }
7364
+ const repairedLane = readLane(hqRoot, laneId);
7365
+ syncLaneToProjectStoryLedger(hqRoot, repairedLane, migratedQuestionRefs);
7366
+ removeLaneFromProjectStoryLedger(hqRoot, initial);
7367
+ printJson({ ok: true, action: "repair-company", lane_id: laneId, lane: repairedLane, migrated_pending_questions: migratedQuestionRefs.length });
7368
+ }
7369
+ async function runRepairCompanyOwnerTarget(ctx, laneId, opts) {
7370
+ if (!ctx.isSsmOwnerDispatch()) {
7371
+ throw new LanesError("outpost_repair_company_dispatch_unauthorized", "The Outpost company-repair target is available only to an SSM Run Command dispatch", { lane_id: laneId });
7372
+ }
7373
+ const lane = readLane(ctx.hqRoot(), laneId);
7374
+ if (lane.host.kind !== "outpost" || !laneIsHostedHere(ctx, lane)) {
7375
+ throw new LanesError("lane_remote", `Outpost repair-company target cannot mutate lane ${laneId} on this host`, { lane_id: laneId, host: lane.host });
7376
+ }
7377
+ await repairCompanyOnOwner(ctx, laneId, opts);
7378
+ }
7242
7379
  const CANONICAL_LOG_STREAMS = [
7243
7380
  "watcher.err",
7244
7381
  "watcher.out",
@@ -8671,6 +8808,13 @@ export function registerLanesCommand(parent, deps = {}) {
8671
8808
  .action(wrap(async (laneId, opts) => {
8672
8809
  await runReparentOwnerTarget(ctx, laneId, opts);
8673
8810
  }));
8811
+ lanes
8812
+ .command("repair-company-target", { hidden: true })
8813
+ .argument("<lane-id>", "Lane id")
8814
+ .requiredOption("--company <slug>", "Company slug to assign")
8815
+ .action(wrap(async (laneId, opts) => {
8816
+ await runRepairCompanyOwnerTarget(ctx, laneId, opts);
8817
+ }));
8674
8818
  lanes
8675
8819
  .command("grant-target", { hidden: true })
8676
8820
  .argument("<lane-id>", "Lane id")
@@ -8976,6 +9120,14 @@ export function registerLanesCommand(parent, deps = {}) {
8976
9120
  .action(wrap(async (laneId, opts) => {
8977
9121
  await runReparent(ctx, laneId, opts);
8978
9122
  }));
9123
+ lanes
9124
+ .command("repair-company")
9125
+ .description("Set missing company attribution on a legacy lane")
9126
+ .argument("<lane-id>", "Lane id")
9127
+ .requiredOption("--company <slug>", "Company slug to assign")
9128
+ .action(wrap(async (laneId, opts) => {
9129
+ await runRepairCompany(ctx, laneId, opts);
9130
+ }));
8979
9131
  lanes
8980
9132
  .command("logs")
8981
9133
  .description("Print a lane's local logs")
@@ -143,6 +143,25 @@ export const FAST_SCAFFOLD_SCRIPTS = [
143
143
  shellCwd: "caller",
144
144
  shellHqRoot: "none",
145
145
  },
146
+ {
147
+ name: "ontology-readme-drift",
148
+ kind: "native",
149
+ modulePath: "./commands/core-ontology-readme-drift-fast.js",
150
+ exportName: "runOntologyReadmeDriftFast",
151
+ flagKey: "hq-core-native-fast-route",
152
+ asset: "core/scripts/ontology-readme-drift.sh",
153
+ root: "live",
154
+ },
155
+ {
156
+ name: "qmd-reindex-after-sync",
157
+ kind: "native",
158
+ modulePath: "./commands/core-qmd-reindex-after-sync-fast.js",
159
+ exportName: "runQmdReindexAfterSyncFast",
160
+ flagKey: "hq-core-native-fast-route",
161
+ asset: "core/scripts/qmd-reindex-after-sync.sh",
162
+ root: "cwd",
163
+ requiresNodeNetworkCompatibility: true,
164
+ },
146
165
  {
147
166
  name: "rebuild-index",
148
167
  kind: "native",
@@ -1,6 +1,7 @@
1
1
  import { type EmbedLockDependencies } from "../search-index/embed-lock.js";
2
2
  import { type LoadGateDependencies } from "../search-index/load-gate.js";
3
3
  import { type UtilityIo } from "./common.js";
4
+ export declare const QMD_REINDEX_STARTUP_WARNING_FLAG = "qmd.reindex-startup-warning";
4
5
  export type QmdReindexOptions = UtilityIo & {
5
6
  cwd?: string;
6
7
  /** Test seams; default to the real search-index implementations. */
@@ -16,15 +16,9 @@ import { reconcileCollections, resolveQmdBin, resolveQmdHome, runQmd } from "../
16
16
  import { applyIndexSizeLimit } from "../search-index/max-doc-bytes.js";
17
17
  import { defaultEmbedLockDependencies, runWithEmbedLock, } from "../search-index/embed-lock.js";
18
18
  import { LoadGateConfigurationError, checkEmbedLoad, defaultLoadGateDependencies, recordSuccessfulEmbed, } from "../search-index/load-gate.js";
19
- import { resolveProcessFlagGate } from "../flag-registry.js";
20
- import { qmdReindexSingleFlightEnabled } from "../search-index/reindex-single-flight-flag.js";
21
19
  import { isQmdNativeBindingError, QMD_NATIVE_BINDING_REMEDY, } from "../../utils/qmd-native-binding-error.js";
22
20
  import { ioFor, line } from "./common.js";
23
- const QMD_REINDEX_STARTUP_WARNING_FLAG = "qmd.reindex-startup-warning";
24
- const QMD_REINDEX_STARTUP_WARNING_FLAG_FALLBACK = false;
25
- function qmdStartupFailureWarningsEnabled() {
26
- return resolveProcessFlagGate(QMD_REINDEX_STARTUP_WARNING_FLAG, { fallback: QMD_REINDEX_STARTUP_WARNING_FLAG_FALLBACK }, () => QMD_REINDEX_STARTUP_WARNING_FLAG_FALLBACK);
27
- }
21
+ export const QMD_REINDEX_STARTUP_WARNING_FLAG = "qmd.reindex-startup-warning";
28
22
  export function qmdReindexAfterSync(args = [], options = {}) {
29
23
  let hqRoot = "";
30
24
  let embed = false;
@@ -32,7 +26,7 @@ export function qmdReindexAfterSync(args = [], options = {}) {
32
26
  if (arg === "--embed")
33
27
  embed = true;
34
28
  else if (arg.startsWith("--"))
35
- continue; // unknown flags are ignored, as in the shell
29
+ continue; // long options are ignored, matching the shell oracle
36
30
  else if (!hqRoot)
37
31
  hqRoot = arg;
38
32
  }
@@ -55,7 +49,7 @@ export function qmdReindexAfterSync(args = [], options = {}) {
55
49
  const warnOnNativeStartupFailure = (error) => {
56
50
  if (startupFailureWarningShown ||
57
51
  !isQmdNativeBindingError(error) ||
58
- !(options.startupFailureWarningsEnabled ?? qmdStartupFailureWarningsEnabled)())
52
+ !(options.startupFailureWarningsEnabled ?? (() => false))())
59
53
  return;
60
54
  startupFailureWarningShown = true;
61
55
  line(stderr, `hq: search index was not updated after sync; ${QMD_NATIVE_BINDING_REMEDY}`);
@@ -99,7 +93,7 @@ export function qmdReindexAfterSync(args = [], options = {}) {
99
93
  recordSuccessfulEmbed(home, loadGateDependencies);
100
94
  return undefined;
101
95
  };
102
- if ((options.singleFlightEnabled ?? qmdReindexSingleFlightEnabled)()) {
96
+ if ((options.singleFlightEnabled ?? (() => false))()) {
103
97
  const lockDependencies = options.embedLockDependencies ?? defaultEmbedLockDependencies();
104
98
  const home = resolveQmdHome(lockDependencies.env);
105
99
  try {
@@ -3,7 +3,6 @@ import { LanesError } from "./errors.js";
3
3
  export type LaneHireGrantRow = Pick<LaneRecord, "lane_id" | "state" | "senior" | "hire_grant">;
4
4
  export declare function isFinishedResumableHireChild(state: LaneRecord["state"]): boolean;
5
5
  export declare function laneHoldsHireGrantCarve(lane: Pick<LaneHireGrantRow, "state">, finishedChildrenDoNotCount?: boolean): boolean;
6
- export declare function aliveDirectChildren<T extends LaneHireGrantRow>(lanes: readonly T[], parentLaneId: string, finishedChildrenDoNotCount?: boolean): T[];
7
6
  /** Children whose existing hire grants occupy their senior's carve. */
8
7
  export declare function childrenHoldingHireGrantCarve<T extends LaneHireGrantRow>(lanes: readonly T[], parentLaneId: string, finishedChildrenDoNotCount?: boolean): T[];
9
8
  export declare function assignedChildHireGrants(children: readonly LaneHireGrantRow[], exceptLaneId?: string): number;
@@ -8,9 +8,6 @@ export function laneHoldsHireGrantCarve(lane, finishedChildrenDoNotCount = false
8
8
  return !isTerminalLaneState(lane.state) &&
9
9
  !(finishedChildrenDoNotCount && isFinishedResumableHireChild(lane.state));
10
10
  }
11
- export function aliveDirectChildren(lanes, parentLaneId, finishedChildrenDoNotCount = false) {
12
- return childrenHoldingHireGrantCarve(lanes, parentLaneId, finishedChildrenDoNotCount);
13
- }
14
11
  /** Children whose existing hire grants occupy their senior's carve. */
15
12
  export function childrenHoldingHireGrantCarve(lanes, parentLaneId, finishedChildrenDoNotCount = false) {
16
13
  return lanes.filter((lane) => lane.senior.kind === "lane" &&
@@ -119,7 +116,7 @@ export function planHireGrantBackfill(lanes, profileMaxConcurrent) {
119
116
  const candidates = lanes.filter((lane) => {
120
117
  if (isTerminalLaneState(lane.state))
121
118
  return false;
122
- return aliveDirectChildren(lanes, lane.lane_id).length > 0 ||
119
+ return childrenHoldingHireGrantCarve(lanes, lane.lane_id).length > 0 ||
123
120
  lane.worker_profile === "hq-lanes-manager";
124
121
  });
125
122
  const candidateIds = new Set(candidates.map((lane) => lane.lane_id));
@@ -133,7 +130,7 @@ export function planHireGrantBackfill(lanes, profileMaxConcurrent) {
133
130
  throw new LanesError("hire_grant_backfill_cycle", `Cannot plan hire grants for cyclic lane hierarchy at ${lane.lane_id}`, { lane_id: lane.lane_id });
134
131
  }
135
132
  active.add(lane.lane_id);
136
- const children = aliveDirectChildren(lanes, lane.lane_id);
133
+ const children = childrenHoldingHireGrantCarve(lanes, lane.lane_id);
137
134
  const allocated = children.reduce((sum, child) => {
138
135
  if (child.hire_grant !== undefined)
139
136
  return sum + child.hire_grant;
@@ -151,7 +148,7 @@ export function planHireGrantBackfill(lanes, profileMaxConcurrent) {
151
148
  proposalFor(lane);
152
149
  return candidates
153
150
  .map((lane) => {
154
- const children = aliveDirectChildren(lanes, lane.lane_id);
151
+ const children = childrenHoldingHireGrantCarve(lanes, lane.lane_id);
155
152
  const profileMax = lane.worker_profile
156
153
  ? profileMaxConcurrent.get(lane.worker_profile) ?? null
157
154
  : null;
@@ -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, childrenHoldingHireGrantCarve, isFinishedResumableHireChild, laneHoldsHireGrantCarve, hireGrantCapacityError, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, } from "./grants.js";
6
+ export { assignedChildHireGrants, childrenHoldingHireGrantCarve, isFinishedResumableHireChild, laneHoldsHireGrantCarve, hireGrantCapacityError, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, } from "./grants.js";
7
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";
@@ -16,7 +16,7 @@ export { DEFAULT_ACCOUNT_HOMES, DEFAULT_AUTO_COMPACT_TOKENS, DEFAULT_COMPACT_AFT
16
16
  export type { ConfigLoadDeps, ConfigWriteDeps } from "./config.js";
17
17
  export { resolveLanesHqRoot } from "./hq-root.js";
18
18
  export type { LanesHqRootOpts } from "./hq-root.js";
19
- export { LANE_STORY_LEDGER_FLAG, buildLaneStatusSnapshot, formatLaneStatusHuman, laneStoryLedgerEnabled, resolveProjectStoryLedger, syncLaneToProjectStoryLedger, validateProjectStories, } from "./story-ledger.js";
19
+ export { LANE_STORY_LEDGER_FLAG, buildLaneStatusSnapshot, formatLaneStatusHuman, laneStoryLedgerEnabled, resolveProjectStoryLedger, syncLaneToProjectStoryLedger, removeLaneFromProjectStoryLedger, validateProjectStories, } from "./story-ledger.js";
20
20
  export type { LaneStatusSnapshot, ProjectStoryLedger, StoryLedgerWriteDeps } from "./story-ledger.js";
21
21
  export { buildOutpostPickupCommand, parseOutpostPickupPayload, } from "./outpost-dispatch.js";
22
22
  export type { OutpostDispatchResult, OutpostPickupPayload, } from "./outpost-dispatch.js";
@@ -3,14 +3,14 @@
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, childrenHoldingHireGrantCarve, isFinishedResumableHireChild, laneHoldsHireGrantCarve, hireGrantCapacityError, planHireGrantBackfill, terminalChildResumeHireGrantError, withHireGrantAdmission, } from "./grants.js";
6
+ export { assignedChildHireGrants, childrenHoldingHireGrantCarve, isFinishedResumableHireChild, laneHoldsHireGrantCarve, 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";
10
10
  export { ACTIVE_LANE_STORY_STATUSES, activeLaneStories, laneStories, laneStory, storyKey, storyStatusForEnvelope, storyWarning, withCurrentStory, withLaneStories, } from "./stories.js";
11
11
  export { DEFAULT_ACCOUNT_HOMES, DEFAULT_AUTO_COMPACT_TOKENS, DEFAULT_COMPACT_AFTER_MS, DEFAULT_CPU_BUSY_PAUSE, DEFAULT_CPU_BUSY_RESUME, DEFAULT_LIVE_WITHIN_MS, DEFAULT_LIVENESS_WINDOWS, DEFAULT_MAX_QUEUED, DEFAULT_MIN_AVAILABLE_MEM_MB, DEFAULT_PROVIDER_MAX_RUNNING, DEFAULT_SILENT_LOST_MS, DEFAULT_TURN_START_LOST_MS, LANES_CONFIG_REL, accountMaxRunning, canonicalizeHome, documentedDefaultConfig, expandHome, lanesConfigPath, loadLanesConfig, parseLanesConfig, providerCompactAfterMs, providerAutoCompactTokens, providerQuotaDefaultHoldMs, DEFAULT_QUOTA_HOLD_MS, writeLanesConfig, } from "./config.js";
12
12
  export { resolveLanesHqRoot } from "./hq-root.js";
13
- export { LANE_STORY_LEDGER_FLAG, buildLaneStatusSnapshot, formatLaneStatusHuman, laneStoryLedgerEnabled, resolveProjectStoryLedger, syncLaneToProjectStoryLedger, validateProjectStories, } from "./story-ledger.js";
13
+ export { LANE_STORY_LEDGER_FLAG, buildLaneStatusSnapshot, formatLaneStatusHuman, laneStoryLedgerEnabled, resolveProjectStoryLedger, syncLaneToProjectStoryLedger, removeLaneFromProjectStoryLedger, validateProjectStories, } from "./story-ledger.js";
14
14
  export { buildOutpostPickupCommand, parseOutpostPickupPayload, } from "./outpost-dispatch.js";
15
15
  export { LANES_STORE_REL, LANE_RECORDS_DIR, PENDING_INDEX_DIR, QUESTION_RECORDS_DIR, appendCappedHistory, appendLaneAttribution, appendLaneSeniorHistory, appendQuestionRouting, createLane, createQuestion, isConflictSidecarName, laneRecordPath, lanesStoreRoot, listLaneIds, listQuestionIds, mergeLaneRecords, mergeQuestionRecords, mintLaneId, mintQuestionId, parseLaneRecord, parseQuestionRecord, pendingMarkerPath, questionRecordPath, readAllLanes, readLane, readQuestion, resolveStoreHqRoot, sanitizeHostComponent, tryReadLane, tryReadQuestion, retryUpdateLane, retryUpdateQuestion, syncLaneRecordFromOwner, updateLane, updateQuestion, withLaneHireLocks, } from "./store.js";
16
16
  export { DEFAULT_LANE_WATCH_INTERVAL_MS, MAX_LANE_WATCH_INTERVAL_MS, MAX_WATCHED_LANES, MAX_WATCH_OUTPUT_LINES, runLaneWatch, } from "./watch.js";
@@ -37,6 +37,11 @@ interface KeepAliveAuditError {
37
37
  export interface KeepAliveAuditResult {
38
38
  ok: true;
39
39
  since: string;
40
+ companyless_lead_lanes: Array<{
41
+ lane: string;
42
+ project: string;
43
+ reason: "has_juniors" | "can_hire_juniors";
44
+ }>;
40
45
  rows: KeepAliveAuditRow[];
41
46
  errors: KeepAliveAuditError[];
42
47
  summary: KeepAliveAuditSummary;
@@ -478,7 +478,19 @@ export function buildKeepAliveAudit(input) {
478
478
  if (sinceMs === undefined || !Number.isFinite(sinceMs)) {
479
479
  throw new LanesError("invalid_since", "--since must be a valid ISO date-time", { since: input.since });
480
480
  }
481
- const lanes = readAllLanes(input.hqRoot).filter((lane) => (input.laneId === undefined || lane.lane_id === input.laneId) &&
481
+ const allLanes = readAllLanes(input.hqRoot);
482
+ const companylessLeadLanes = allLanes
483
+ .filter((lane) => lane.company === null && (input.laneId === undefined || lane.lane_id === input.laneId))
484
+ .map((lane) => {
485
+ const hasJuniors = allLanes.some((candidate) => candidate.senior.kind === "lane" && candidate.senior.id === lane.lane_id);
486
+ const canHireJuniors = (lane.hire_grant ?? 0) > 0;
487
+ if (!hasJuniors && !canHireJuniors)
488
+ return null;
489
+ return { lane: lane.lane_id, project: lane.project_id, reason: hasJuniors ? "has_juniors" : "can_hire_juniors" };
490
+ })
491
+ .filter((entry) => entry !== null)
492
+ .sort((a, b) => a.lane.localeCompare(b.lane));
493
+ const lanes = allLanes.filter((lane) => (input.laneId === undefined || lane.lane_id === input.laneId) &&
482
494
  typeof lane.hire_grant === "number" && Number.isFinite(lane.hire_grant) && lane.hire_grant > 0);
483
495
  const rows = [];
484
496
  const errors = [];
@@ -544,6 +556,7 @@ export function buildKeepAliveAudit(input) {
544
556
  return {
545
557
  ok: true,
546
558
  since: iso(sinceMs),
559
+ companyless_lead_lanes: companylessLeadLanes,
547
560
  rows,
548
561
  summary: {
549
562
  by_verdict: byVerdict,
@@ -581,6 +594,7 @@ export function formatKeepAliveAuditText(result) {
581
594
  `Since ${result.since}; ${counts}`,
582
595
  `not_restarted=${result.summary.not_restarted}, late_restarts_after_window=${result.summary.late_restarts_after_window}, late_over_10m=${result.summary.late_over_10m}, early_wake_by_others=${result.summary.early_wake_by_others}, self_wake=${result.summary.self_wake}`,
583
596
  `Stopped and not restarted on their own: ${result.summary.stopped_and_not_restarted_on_their_own}`,
597
+ ...result.companyless_lead_lanes.map((lane) => `Company-less lead ${lane.lane} (${lane.project}): ${lane.reason}; repair with hq lanes repair-company ${lane.lane} --company <slug>`),
584
598
  ...result.errors.map((error) => `Error ${error.lane} (${error.error}) at ${error.path}: ${error.message}`),
585
599
  ].join("\n");
586
600
  }
@@ -27,7 +27,7 @@ export interface OutpostDispatchResult {
27
27
  stderr: string;
28
28
  }
29
29
  export interface OutpostLaneCommand {
30
- action: "message" | "resume" | "stop" | "show" | "logs" | "grant" | "grant-target" | "reparent-target" | "approve-target";
30
+ action: "message" | "resume" | "stop" | "show" | "logs" | "grant" | "grant-target" | "reparent-target" | "approve-target" | "repair-company-target";
31
31
  laneId: string;
32
32
  hqBinary?: string;
33
33
  text?: string;
@@ -46,6 +46,7 @@ export interface OutpostLaneCommand {
46
46
  approvalContext?: string;
47
47
  senior?: string;
48
48
  reason?: string;
49
+ company?: string;
49
50
  compact?: boolean;
50
51
  tail?: string;
51
52
  json?: boolean;
@@ -210,6 +210,7 @@ export function buildOutpostLaneCommand(input) {
210
210
  ["--failed-resume-context", input.failedResumeContext],
211
211
  ["--senior", input.senior],
212
212
  ["--reason", input.reason],
213
+ ["--company", input.company],
213
214
  ]) {
214
215
  if (value !== undefined)
215
216
  args.push(flag, value);