@indigoai-us/hq-cli 5.188.0 → 5.188.1

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,16 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [5.188.1] — 2026-09-25
6
+
7
+ ### Fixed
8
+
9
+ - `hq lanes grant` now applies grants for Outpost-hosted lanes on their owner.
10
+ The primary sends a bounded hierarchy snapshot and its evaluated hq-flags
11
+ decision; the owner revalidates the authorized senior, applies carve,
12
+ occupancy, and terminal-parent checks, then syncs the changed record to the
13
+ primary. Dispatched lane errors include sanitized owner details.
14
+
5
15
  ## [5.188.0] — 2026-09-25
6
16
 
7
17
  ### Added
@@ -6915,6 +6915,28 @@ export declare const COMMAND_CATALOG: readonly [{
6915
6915
  readonly description: "JSON on stdout (always)";
6916
6916
  }];
6917
6917
  readonly subcommands: readonly [];
6918
+ }, {
6919
+ readonly name: "grant-target";
6920
+ readonly description: "";
6921
+ readonly aliases: readonly [];
6922
+ readonly hidden: true;
6923
+ readonly usage: "[options] <lane-id>";
6924
+ readonly arguments: readonly [{
6925
+ readonly name: "lane-id";
6926
+ readonly required: true;
6927
+ readonly variadic: false;
6928
+ }];
6929
+ readonly options: readonly [{
6930
+ readonly flags: "--hire <n>";
6931
+ readonly description: "Non-negative safe integer hire grant";
6932
+ }, {
6933
+ readonly flags: "--grant-context <base64url>";
6934
+ readonly description: "Primary-authorized hire hierarchy and gate decision";
6935
+ }, {
6936
+ readonly flags: "--json";
6937
+ readonly description: "JSON on stdout (always)";
6938
+ }];
6939
+ readonly subcommands: readonly [];
6918
6940
  }, {
6919
6941
  readonly name: "grant";
6920
6942
  readonly description: "Set a lane's alive child-lane grant as its senior";
@@ -8937,6 +8937,35 @@ export const COMMAND_CATALOG = [
8937
8937
  ],
8938
8938
  "subcommands": []
8939
8939
  },
8940
+ {
8941
+ "name": "grant-target",
8942
+ "description": "",
8943
+ "aliases": [],
8944
+ "hidden": true,
8945
+ "usage": "[options] <lane-id>",
8946
+ "arguments": [
8947
+ {
8948
+ "name": "lane-id",
8949
+ "required": true,
8950
+ "variadic": false
8951
+ }
8952
+ ],
8953
+ "options": [
8954
+ {
8955
+ "flags": "--hire <n>",
8956
+ "description": "Non-negative safe integer hire grant"
8957
+ },
8958
+ {
8959
+ "flags": "--grant-context <base64url>",
8960
+ "description": "Primary-authorized hire hierarchy and gate decision"
8961
+ },
8962
+ {
8963
+ "flags": "--json",
8964
+ "description": "JSON on stdout (always)"
8965
+ }
8966
+ ],
8967
+ "subcommands": []
8968
+ },
8940
8969
  {
8941
8970
  "name": "grant",
8942
8971
  "description": "Set a lane's alive child-lane grant as its senior",
@@ -12,7 +12,7 @@ export type { OutpostIdentity } from "../lib/lanes/admission-host.js";
12
12
  export { LANE_HIRE_GRANTS_FLAG };
13
13
  import { type OutpostDispatchResult } from "../lib/lanes/outpost-dispatch.js";
14
14
  import { emitLaneDecisionEvents, type LaneMesh } from "../lib/lanes/mesh-events.js";
15
- import { LANE_HIRE_GRANTS_FLAG, MAX_LANE_DEPTH, type LaneCompanyRef, 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 { LANE_HIRE_GRANTS_FLAG, MAX_LANE_DEPTH, withLaneHireLocks, type LaneCompanyRef, type LaneRecord, type QuestionRecord, type ResolvedLaneWorker, type SeniorRef, type LivenessDeps, type MachineIdentity, type MonitorEvidenceReader, type RunCommand, type ThrottleDeps } from "../lib/lanes/index.js";
16
16
  import { questionBodyAbsPath } from "../lib/lanes/questions.js";
17
17
  import { type RunCompactionFn } from "../lib/lanes/compaction.js";
18
18
  import { type KnownModelsDeps, type ProbeCommand } from "../lib/lanes/models.js";
@@ -80,6 +80,10 @@ export interface LanesCommandDeps {
80
80
  lanePlacementEnabled?: () => boolean;
81
81
  /** Test seam for the default-off lane hire-grants rollout gate. */
82
82
  laneHireGrantsEnabled?: (company?: LaneCompanyRef | null) => boolean | Promise<boolean>;
83
+ /** Test seam for the SSM-only Outpost owner grant path. */
84
+ isSsmOwnerDispatch?: () => boolean;
85
+ /** Test seam for changing a lane between pre-lock and locked grant reads. */
86
+ withGrantHireLocks?: typeof withLaneHireLocks;
83
87
  /** Test seam for the default-off Claude account-balancing gate. */
84
88
  laneClaudeAccountBalanceEnabled?: () => boolean;
85
89
  }
@@ -13,8 +13,10 @@ import * as path from "node:path";
13
13
  import { registerOutpostPrunerCommand } from "./outpost-pruner.js";
14
14
  import { ensureCognitoToken } from "../utils/cognito-session.js";
15
15
  import { resolveLanesHqRoot } from "../lib/lanes/hq-root.js";
16
+ import { isSsmRunCommandProcess } from "../lib/lanes/ssm-command-context.js";
16
17
  import { readLanesRegistryFlag } from "../lib/lanes/registry-flag.js";
17
18
  import { resolveProcessFlagGate } from "../lib/flag-registry.js";
19
+ import { createOutpostHireGrantContext, parseOutpostHireGrantContext, sameHireGrantSenior, } from "../lib/lanes/hire-grant-dispatch.js";
18
20
  import { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity, resolveAdmissionHost, } from "../lib/lanes/admission-host.js";
19
21
  export { OUTPOST_DIR, OUTPOST_ID_PATH, readOutpostIdentity } from "../lib/lanes/admission-host.js";
20
22
  export { LANE_HIRE_GRANTS_FLAG };
@@ -81,7 +83,7 @@ function failJson(err) {
81
83
  if (err.error === "hire_grant_exhausted") {
82
84
  process.stderr.write(`hq lanes: ${err.message}\n`);
83
85
  }
84
- printJson(err.toJSON());
86
+ printJson({ ...err.toJSON(), message: err.message });
85
87
  process.exit(1);
86
88
  }
87
89
  const message = err instanceof Error ? err.message : String(err);
@@ -255,6 +257,8 @@ function resolveDeps(deps = {}) {
255
257
  fetchText: deps.fetchText ?? defaultFetchText,
256
258
  lanePlacementEnabled: deps.lanePlacementEnabled ?? defaultLanePlacementEnabled,
257
259
  laneHireGrantsEnabled: deps.laneHireGrantsEnabled ?? defaultLaneHireGrantsEnabled,
260
+ isSsmOwnerDispatch: deps.isSsmOwnerDispatch ?? (() => isSsmRunCommandProcess()),
261
+ withGrantHireLocks: deps.withGrantHireLocks ?? withLaneHireLocks,
258
262
  laneClaudeAccountBalanceEnabled: deps.laneClaudeAccountBalanceEnabled ?? defaultLaneClaudeAccountBalanceEnabled,
259
263
  };
260
264
  }
@@ -451,6 +455,81 @@ function laneHireFields(lane, lanes) {
451
455
  hire_summary: hireSummary,
452
456
  };
453
457
  }
458
+ function outpostCliOverride(ctx, outpostId) {
459
+ const envKey = `HQ_LANES_OUTPOST_CLI_${outpostId.toUpperCase().replace(/[^A-Z0-9_]/g, "_")}`;
460
+ const configured = ctx.env[envKey];
461
+ const hqBinary = configured === undefined ? undefined : configured.trim();
462
+ if (configured !== undefined &&
463
+ (!hqBinary ||
464
+ !path.posix.isAbsolute(hqBinary) ||
465
+ hqBinary.includes("\0") ||
466
+ hqBinary.includes("\r") ||
467
+ hqBinary.includes("\n"))) {
468
+ throw new LanesError("outpost_lane_cli_invalid", `${envKey} must be an absolute remote executable path`, { outpost_id: outpostId });
469
+ }
470
+ return hqBinary;
471
+ }
472
+ function safeOwnerErrorValue(value, key = "") {
473
+ if (/(?:env|environment|token|secret|password|credential|authorization|api[_-]?key|access[_-]?key|private[_-]?key)/i.test(key)) {
474
+ return undefined;
475
+ }
476
+ if (typeof value === "string") {
477
+ return value
478
+ .replace(/\b(Bearer|Basic)\s+[^\s,;]+/gi, "$1 [redacted]")
479
+ .replace(/\b(?:gh[pousr]_[A-Za-z0-9_]{20,}|github_pat_[A-Za-z0-9_]{20,}|AKIA[A-Z0-9]{16})\b/g, "[redacted]")
480
+ .replace(/\b["']?([A-Z][A-Z0-9_]{1,})["']?\s*[:=]\s*["']?([^\s"',;}]+)["']?/g, "$1=[redacted]");
481
+ }
482
+ if (Array.isArray(value)) {
483
+ return value.map((item) => safeOwnerErrorValue(item));
484
+ }
485
+ if (value === null || typeof value !== "object")
486
+ return value;
487
+ const safe = {};
488
+ for (const [childKey, childValue] of Object.entries(value)) {
489
+ const nested = safeOwnerErrorValue(childValue, childKey);
490
+ if (nested !== undefined)
491
+ safe[childKey] = nested;
492
+ }
493
+ return safe;
494
+ }
495
+ function ownerErrorFromOutput(stdout) {
496
+ const trimmed = stdout.trim();
497
+ if (!trimmed)
498
+ return undefined;
499
+ let parsed;
500
+ try {
501
+ parsed = JSON.parse(trimmed);
502
+ }
503
+ catch {
504
+ process.stderr.write("hq lanes: Outpost lanes dispatch returned malformed JSON\n");
505
+ return undefined;
506
+ }
507
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
508
+ return undefined;
509
+ }
510
+ const owner = parsed;
511
+ if (typeof owner.error !== "string" || !/^[a-z][a-z0-9_]{0,99}$/.test(owner.error)) {
512
+ return undefined;
513
+ }
514
+ if (safeOwnerErrorValue(owner.error) !== owner.error)
515
+ return undefined;
516
+ if (owner.error === "already_terminal" || owner.error === "outpost_self_target") {
517
+ return undefined;
518
+ }
519
+ const rawDetails = {};
520
+ for (const [key, value] of Object.entries(owner)) {
521
+ if (key !== "error" && key !== "message" && key !== "details")
522
+ rawDetails[key] = value;
523
+ }
524
+ if (owner.details !== null && typeof owner.details === "object" && !Array.isArray(owner.details)) {
525
+ Object.assign(rawDetails, owner.details);
526
+ }
527
+ const details = safeOwnerErrorValue(rawDetails);
528
+ const message = typeof owner.message === "string" && owner.message.trim()
529
+ ? String(safeOwnerErrorValue(owner.message))
530
+ : `Outpost owner returned ${owner.error}`;
531
+ return { error: owner.error, message, details };
532
+ }
454
533
  async function ownerLaneHireFields(ctx, lane) {
455
534
  if (lane.host.kind !== "outpost") {
456
535
  throw new LanesError("outpost_lane_hire_summary_invalid", "Owner summary requires an Outpost-hosted lane", {
@@ -458,17 +537,7 @@ async function ownerLaneHireFields(ctx, lane) {
458
537
  });
459
538
  }
460
539
  const outpostId = lane.host.id;
461
- const outpostCliEnvKey = `HQ_LANES_OUTPOST_CLI_${outpostId.toUpperCase().replace(/[^A-Z0-9_]/g, "_")}`;
462
- const configuredHqBinary = ctx.env[outpostCliEnvKey];
463
- const hqBinary = configuredHqBinary === undefined ? undefined : configuredHqBinary.trim();
464
- if (configuredHqBinary !== undefined &&
465
- (!hqBinary ||
466
- !path.posix.isAbsolute(hqBinary) ||
467
- hqBinary.includes("\0") ||
468
- hqBinary.includes("\r") ||
469
- hqBinary.includes("\n"))) {
470
- throw new LanesError("outpost_lane_cli_invalid", `${outpostCliEnvKey} must be an absolute remote executable path`, { outpost_id: outpostId });
471
- }
540
+ const hqBinary = outpostCliOverride(ctx, outpostId);
472
541
  let dispatched;
473
542
  try {
474
543
  dispatched = await ctx.dispatchOutpostExec({
@@ -2783,7 +2852,8 @@ async function dispatchCreatedOutpostLane(ctx, input) {
2783
2852
  const staged = await ctx.stageOutpostPayload(outpostId, bytes);
2784
2853
  const command = buildOutpostPickupCommand(staged.getUrl);
2785
2854
  const dispatched = await ctx.dispatchOutpostExec({ outpostId, command });
2786
- if (preserveQueuedOnFailure && dispatched.done !== true) {
2855
+ const dispatchedOwnerError = ownerErrorFromOutput(dispatched.stdout);
2856
+ if (preserveQueuedOnFailure && dispatched.done !== true && !dispatchedOwnerError) {
2787
2857
  throw new LanesError("outpost_dispatch_failed", "Outpost pickup exec has not finished; its outcome is unknown", {
2788
2858
  outpost_id: outpostId,
2789
2859
  lane_id: lane.lane_id,
@@ -2813,6 +2883,40 @@ async function dispatchCreatedOutpostLane(ctx, input) {
2813
2883
  await emitLaneTerminalStateSoft(ctx, cancelled);
2814
2884
  return cancelled;
2815
2885
  }
2886
+ const ownerError = dispatchedOwnerError;
2887
+ if (ownerError) {
2888
+ if (preserveQueuedOnFailure) {
2889
+ throw new LanesError("outpost_dispatch_failed", `Outpost ${outpostId} rejected lane pickup: ${ownerError.message}`, {
2890
+ outpost_id: outpostId,
2891
+ lane_id: lane.lane_id,
2892
+ command_id: dispatched.commandId,
2893
+ status: dispatched.status,
2894
+ done: dispatched.done ?? false,
2895
+ exit_code: dispatched.exitCode,
2896
+ ...(remote?.pickup_persisted === false ||
2897
+ ownerError.details.pickup_persisted === false
2898
+ ? { pickup_persisted: false }
2899
+ : {}),
2900
+ owner_error: ownerError,
2901
+ });
2902
+ }
2903
+ const failed = markDispatchFailure(hqRoot, lane.lane_id, {
2904
+ outpost_id: outpostId,
2905
+ command_id: dispatched.commandId,
2906
+ exit_code: dispatched.exitCode,
2907
+ status: dispatched.status,
2908
+ owner_error: ownerError,
2909
+ });
2910
+ await emitLaneTerminalStateSoft(ctx, failed);
2911
+ throw new LanesError(DISPATCH_TERMINAL_REASON, `Outpost ${outpostId} rejected lane pickup: ${ownerError.message}`, {
2912
+ outpost_id: outpostId,
2913
+ lane_id: failed.lane_id,
2914
+ state: failed.state,
2915
+ terminal_reason: DISPATCH_TERMINAL_REASON,
2916
+ command_id: dispatched.commandId,
2917
+ owner_error: ownerError,
2918
+ });
2919
+ }
2816
2920
  if (preserveQueuedOnFailure && remote?.pickup_persisted === false) {
2817
2921
  throw new LanesError("outpost_dispatch_failed", "Outpost pickup finished and reported that it did not persist the lane", {
2818
2922
  outpost_id: outpostId,
@@ -2980,19 +3084,24 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
2980
3084
  throw new LanesError("outpost_dispatch_failed", `Lane ${lane.lane_id} has no Outpost host to dispatch to`, { lane_id: lane.lane_id });
2981
3085
  }
2982
3086
  const outpostId = lane.host.id;
3087
+ const grantContext = action === "grant"
3088
+ ? createOutpostHireGrantContext(lane, loadAllLanes(ctx.hqRoot()), await ctx.laneHireGrantsEnabled(lane.company))
3089
+ : undefined;
2983
3090
  let dispatched;
2984
3091
  try {
2985
3092
  dispatched = await ctx.dispatchOutpostExec({
2986
3093
  outpostId,
2987
3094
  command: buildOutpostLaneCommand({
2988
- action,
3095
+ action: action === "grant" ? "grant-target" : action,
2989
3096
  laneId: lane.lane_id,
3097
+ hqBinary: action === "grant" ? outpostCliOverride(ctx, outpostId) : undefined,
2990
3098
  text: options.text,
2991
3099
  project: options.project,
2992
3100
  story: options.story,
2993
3101
  account: options.account,
2994
3102
  model: options.model,
2995
3103
  hire: options.hire,
3104
+ grantContext,
2996
3105
  compact: options.compact,
2997
3106
  }),
2998
3107
  });
@@ -3000,6 +3109,18 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
3000
3109
  catch (err) {
3001
3110
  throw new LanesError("outpost_dispatch_failed", `Could not dispatch ${action} for lane ${lane.lane_id} to Outpost ${outpostId}: ${dispatchErrorMessage(err)}`, { lane_id: lane.lane_id, outpost_id: outpostId });
3002
3111
  }
3112
+ const ownerError = ownerErrorFromOutput(dispatched.stdout);
3113
+ if (ownerError) {
3114
+ throw new LanesError("outpost_dispatch_failed", `Outpost ${outpostId} rejected the ${action} command: ${ownerError.message}`, {
3115
+ lane_id: lane.lane_id,
3116
+ outpost_id: outpostId,
3117
+ command_id: dispatched.commandId,
3118
+ status: dispatched.status,
3119
+ done: dispatched.done ?? false,
3120
+ exit_code: dispatched.exitCode,
3121
+ owner_error: ownerError,
3122
+ });
3123
+ }
3003
3124
  if (dispatched.done !== true ||
3004
3125
  dispatched.status !== "Success" ||
3005
3126
  dispatched.exitCode !== 0) {
@@ -3014,13 +3135,11 @@ async function dispatchOutpostLaneCommand(ctx, lane, action, options) {
3014
3135
  }
3015
3136
  const remote = parseRemoteJson(dispatched.stdout, "Outpost lane command");
3016
3137
  if (remote?.error !== undefined) {
3017
- throw new LanesError("outpost_remote_command_failed", typeof remote.message === "string"
3018
- ? remote.message
3019
- : `Outpost ${outpostId} rejected the ${action} command`, {
3138
+ throw new LanesError("outpost_dispatch_failed", `Outpost ${outpostId} rejected the ${action} command`, {
3020
3139
  lane_id: lane.lane_id,
3021
3140
  outpost_id: outpostId,
3022
3141
  command_id: dispatched.commandId,
3023
- remote_error: remote.error,
3142
+ owner_error: ownerErrorFromOutput(dispatched.stdout),
3024
3143
  });
3025
3144
  }
3026
3145
  const resultConfirmed = remote !== null &&
@@ -3642,6 +3761,9 @@ async function commitLaneMove(ctx, laneId, target, account) {
3642
3761
  outcome: "unknown",
3643
3762
  ...(commandId !== undefined ? { command_id: commandId } : {}),
3644
3763
  check_command: checkCommand,
3764
+ ...(dispatchDetails.owner_error !== undefined
3765
+ ? { owner_error: dispatchDetails.owner_error }
3766
+ : {}),
3645
3767
  });
3646
3768
  }
3647
3769
  let restored = false;
@@ -3682,6 +3804,9 @@ async function commitLaneMove(ctx, laneId, target, account) {
3682
3804
  source_host: before.host,
3683
3805
  target_host: target,
3684
3806
  source_restored: restored,
3807
+ ...(dispatchDetails.owner_error !== undefined
3808
+ ? { owner_error: dispatchDetails.owner_error }
3809
+ : {}),
3685
3810
  ...(rollbackError !== undefined
3686
3811
  ? { rollback_error: dispatchErrorMessage(rollbackError) }
3687
3812
  : {}),
@@ -3711,6 +3836,18 @@ async function dispatchRemoteLaneMove(ctx, source, target, account) {
3711
3836
  catch (err) {
3712
3837
  throw new LanesError("lane_move_dispatch_failed", `Could not ask Outpost ${source.host.id} to move lane ${source.lane_id}: ${dispatchErrorMessage(err)}`, { lane_id: source.lane_id, source_outpost: source.host.id });
3713
3838
  }
3839
+ const ownerError = ownerErrorFromOutput(dispatched.stdout);
3840
+ if (ownerError) {
3841
+ throw new LanesError("outpost_dispatch_failed", `Outpost ${source.host.id} rejected the lane move: ${ownerError.message}`, {
3842
+ lane_id: source.lane_id,
3843
+ source_outpost: source.host.id,
3844
+ command_id: dispatched.commandId,
3845
+ status: dispatched.status,
3846
+ done: dispatched.done ?? false,
3847
+ exit_code: dispatched.exitCode,
3848
+ owner_error: ownerError,
3849
+ });
3850
+ }
3714
3851
  if (dispatched.done !== true || dispatched.status !== "Success" || dispatched.exitCode !== 0) {
3715
3852
  throw new LanesError("lane_move_dispatch_failed", `Outpost ${source.host.id} did not confirm the lane move`, {
3716
3853
  lane_id: source.lane_id,
@@ -3830,40 +3967,54 @@ function callerMatchesSenior(ctx, seniorRef) {
3830
3967
  function forceAdmissionAuthorized(ctx, lane) {
3831
3968
  return callerMatchesSenior(ctx, lane.senior);
3832
3969
  }
3833
- async function runGrant(ctx, laneId, opts) {
3834
- if (opts.hire === undefined) {
3835
- throw new LanesError("missing_hire_grant", "--hire is required", {});
3836
- }
3837
- const hireGrant = parseHireGrant(opts.hire);
3970
+ async function applyHireGrant(ctx, laneId, hireGrant, authorization, grantContext) {
3838
3971
  const hqRoot = ctx.hqRoot();
3839
3972
  const initial = readLane(hqRoot, laneId);
3840
- if (!callerMatchesSenior(ctx, initial.senior)) {
3973
+ if (authorization === "senior" && !callerMatchesSenior(ctx, initial.senior)) {
3841
3974
  throw new LanesError("hire_grant_unauthorized", `Only senior ${seniorKey(initial.senior)} may change lane ${laneId}'s hire grant`, { lane_id: laneId, senior: initial.senior });
3842
3975
  }
3843
- if (initial.host.kind === "outpost" && !laneRunsHere(initial, reconcileHostFromCtx(ctx))) {
3844
- await dispatchOutpostLaneCommand(ctx, initial, "grant", { hire: String(hireGrant) });
3845
- return;
3976
+ if (authorization === "ssm-owner-dispatch" &&
3977
+ (initial.host.kind !== "outpost" || !laneIsHostedHere(ctx, initial))) {
3978
+ throw new LanesError("lane_remote", `Outpost grant dispatch cannot mutate lane ${laneId} on this host`, { lane_id: laneId, host: initial.host });
3846
3979
  }
3847
- const enforce = await ctx.laneHireGrantsEnabled(initial.company);
3980
+ if (authorization === "ssm-owner-dispatch" &&
3981
+ (!grantContext || !sameHireGrantSenior(initial.senior, grantContext.expected_senior))) {
3982
+ 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 });
3983
+ }
3984
+ const enforce = grantContext?.enforcement_enabled ??
3985
+ await ctx.laneHireGrantsEnabled(initial.company);
3848
3986
  const lockedLaneIds = [
3849
3987
  laneId,
3850
3988
  ...(initial.senior.kind === "lane" && initial.senior.id
3851
3989
  ? [initial.senior.id]
3852
3990
  : []),
3853
3991
  ];
3854
- const updated = withLaneHireLocks(hqRoot, lockedLaneIds, () => {
3992
+ const updated = ctx.withGrantHireLocks(hqRoot, lockedLaneIds, () => {
3855
3993
  const current = readLane(hqRoot, laneId);
3856
- if (!callerMatchesSenior(ctx, current.senior)) {
3994
+ if (authorization === "senior" && !callerMatchesSenior(ctx, current.senior)) {
3857
3995
  throw new LanesError("hire_grant_unauthorized", `Only senior ${seniorKey(current.senior)} may change lane ${laneId}'s hire grant`, { lane_id: laneId, senior: current.senior });
3858
3996
  }
3859
- const hierarchy = loadAllLanes(hqRoot);
3997
+ if (authorization === "ssm-owner-dispatch" &&
3998
+ (current.host.kind !== "outpost" || !laneIsHostedHere(ctx, current))) {
3999
+ throw new LanesError("lane_remote", `Outpost grant dispatch cannot mutate lane ${laneId} on this host`, { lane_id: laneId, host: current.host });
4000
+ }
4001
+ if (authorization === "ssm-owner-dispatch" &&
4002
+ (!grantContext || !sameHireGrantSenior(current.senior, grantContext.expected_senior))) {
4003
+ throw new LanesError("hire_grant_senior_changed", `Lane ${laneId}'s senior changed before the Outpost applied the grant update`, { lane_id: laneId, current_senior: current.senior });
4004
+ }
4005
+ const hierarchy = grantContext?.hierarchy ?? loadAllLanes(hqRoot);
3860
4006
  if (enforce && current.senior.kind === "lane") {
3861
4007
  if (!current.senior.id) {
3862
4008
  throw new LanesError("hire_grant_senior_invalid", `Lane ${laneId} has a lane senior without an id`, {
3863
4009
  lane_id: laneId,
3864
4010
  });
3865
4011
  }
3866
- const parent = readLane(hqRoot, current.senior.id);
4012
+ const parent = hierarchy.find((lane) => lane.lane_id === current.senior.id);
4013
+ if (!parent) {
4014
+ throw new LanesError(authorization === "ssm-owner-dispatch"
4015
+ ? "hire_grant_context_invalid"
4016
+ : "hire_grant_senior_invalid", `Lane ${laneId}'s senior lane is missing from the grant hierarchy`, { lane_id: laneId, senior_lane_id: current.senior.id });
4017
+ }
3867
4018
  if (isTerminalLaneState(parent.state)) {
3868
4019
  throw new LanesError("hire_grant_senior_inactive", `Senior lane ${parent.lane_id} is ${parent.state} and cannot change grants`, { senior_lane_id: parent.lane_id, state: parent.state });
3869
4020
  }
@@ -3901,9 +4052,44 @@ async function runGrant(ctx, laneId, opts) {
3901
4052
  hire_grant: hireGrant,
3902
4053
  }));
3903
4054
  });
3904
- const fields = laneHireFields(updated, loadAllLanes(hqRoot));
4055
+ return updated;
4056
+ }
4057
+ function printGrantResult(hqRoot, updated, hierarchy) {
4058
+ const fields = laneHireFields(updated, hierarchy ?? loadAllLanes(hqRoot));
3905
4059
  printJson({ ok: true, action: "grant", lane_id: updated.lane_id, lane: updated, ...fields });
3906
4060
  }
4061
+ async function runGrant(ctx, laneId, opts) {
4062
+ if (opts.hire === undefined) {
4063
+ throw new LanesError("missing_hire_grant", "--hire is required", {});
4064
+ }
4065
+ const hireGrant = parseHireGrant(opts.hire);
4066
+ const hqRoot = ctx.hqRoot();
4067
+ const initial = readLane(hqRoot, laneId);
4068
+ if (!callerMatchesSenior(ctx, initial.senior)) {
4069
+ throw new LanesError("hire_grant_unauthorized", `Only senior ${seniorKey(initial.senior)} may change lane ${laneId}'s hire grant`, { lane_id: laneId, senior: initial.senior });
4070
+ }
4071
+ if (initial.host.kind === "outpost" && !laneRunsHere(initial, reconcileHostFromCtx(ctx))) {
4072
+ await dispatchOutpostLaneCommand(ctx, initial, "grant", { hire: String(hireGrant) });
4073
+ return;
4074
+ }
4075
+ const updated = await applyHireGrant(ctx, laneId, hireGrant, "senior");
4076
+ printGrantResult(hqRoot, updated);
4077
+ }
4078
+ async function runGrantOwnerTarget(ctx, laneId, opts) {
4079
+ if (!ctx.isSsmOwnerDispatch()) {
4080
+ throw new LanesError("hire_grant_dispatch_unauthorized", "The Outpost grant target is available only to an SSM Run Command dispatch", { lane_id: laneId });
4081
+ }
4082
+ if (opts.hire === undefined) {
4083
+ throw new LanesError("missing_hire_grant", "--hire is required", {});
4084
+ }
4085
+ if (opts.grantContext === undefined) {
4086
+ throw new LanesError("hire_grant_context_invalid", "--grant-context is required", { lane_id: laneId });
4087
+ }
4088
+ const hireGrant = parseHireGrant(opts.hire);
4089
+ const grantContext = parseOutpostHireGrantContext(opts.grantContext, laneId);
4090
+ const hqRoot = ctx.hqRoot();
4091
+ printGrantResult(hqRoot, await applyHireGrant(ctx, laneId, hireGrant, "ssm-owner-dispatch", grantContext), grantContext.hierarchy);
4092
+ }
3907
4093
  async function runBackfillGrants(ctx, opts) {
3908
4094
  if (opts.apply && opts.dryRun) {
3909
4095
  throw new LanesError("hire_grant_backfill_mode_conflict", "Pass either --dry-run or --apply, not both", {});
@@ -5718,6 +5904,16 @@ async function readOutpostTerminalLane(ctx, laneId, outpostId, stopCommandId) {
5718
5904
  !commandId ||
5719
5905
  dispatched.status !== "Success" ||
5720
5906
  dispatched.exitCode !== 0) {
5907
+ const ownerError = ownerErrorFromOutput(dispatched.stdout);
5908
+ if (ownerError) {
5909
+ throw new LanesError("outpost_dispatch_failed", `Outpost ${outpostId} rejected the lane read after stop: ${ownerError.message}`, {
5910
+ lane_id: laneId,
5911
+ outpost_id: outpostId,
5912
+ command_id: stopCommandId,
5913
+ sync_command_id: commandId || null,
5914
+ owner_error: ownerError,
5915
+ });
5916
+ }
5721
5917
  throw new LanesError("outpost_delivery_unconfirmed", `Outpost ${outpostId} did not confirm a lane read after stop ${laneId}`, {
5722
5918
  lane_id: laneId,
5723
5919
  outpost_id: outpostId,
@@ -5741,6 +5937,16 @@ async function readOutpostTerminalLane(ctx, laneId, outpostId, stopCommandId) {
5741
5937
  cause: err instanceof Error ? err.message : String(err),
5742
5938
  });
5743
5939
  }
5940
+ const ownerError = ownerErrorFromOutput(dispatched.stdout);
5941
+ if (ownerError) {
5942
+ throw new LanesError("outpost_dispatch_failed", `Outpost ${outpostId} rejected the lane read after stop: ${ownerError.message}`, {
5943
+ lane_id: laneId,
5944
+ outpost_id: outpostId,
5945
+ command_id: stopCommandId,
5946
+ sync_command_id: commandId,
5947
+ owner_error: ownerError,
5948
+ });
5949
+ }
5744
5950
  if (remote?.ok !== true || remote.action !== "show") {
5745
5951
  throw new LanesError("outpost_delivery_unconfirmed", `Outpost ${outpostId} did not return lane ${laneId} after stop`, {
5746
5952
  lane_id: laneId,
@@ -5771,6 +5977,18 @@ async function dispatchOutpostLaneStop(ctx, lane) {
5771
5977
  catch (err) {
5772
5978
  throw new LanesError("outpost_dispatch_failed", `Could not dispatch stop for lane ${lane.lane_id} to Outpost ${outpostId}: ${dispatchErrorMessage(err)}`, { lane_id: lane.lane_id, outpost_id: outpostId });
5773
5979
  }
5980
+ const ownerError = ownerErrorFromOutput(dispatched.stdout);
5981
+ if (ownerError) {
5982
+ throw new LanesError("outpost_dispatch_failed", `Outpost ${outpostId} rejected the stop command: ${ownerError.message}`, {
5983
+ lane_id: lane.lane_id,
5984
+ outpost_id: outpostId,
5985
+ command_id: dispatched.commandId,
5986
+ status: dispatched.status,
5987
+ done: dispatched.done ?? false,
5988
+ exit_code: dispatched.exitCode,
5989
+ owner_error: ownerError,
5990
+ });
5991
+ }
5774
5992
  const commandId = dispatched.commandId?.trim();
5775
5993
  if (dispatched.done !== true || !commandId) {
5776
5994
  throw new LanesError("outpost_delivery_unconfirmed", `Outpost ${outpostId} did not return a completed stop command for lane ${lane.lane_id}`, {
@@ -5814,13 +6032,13 @@ async function dispatchOutpostLaneStop(ctx, lane) {
5814
6032
  syncCommandId = owner.commandId;
5815
6033
  }
5816
6034
  else if (remote?.error !== undefined) {
5817
- throw new LanesError("outpost_remote_command_failed", typeof remote.message === "string"
6035
+ throw new LanesError("outpost_dispatch_failed", typeof remote.message === "string"
5818
6036
  ? remote.message
5819
6037
  : `Outpost ${outpostId} rejected the stop command for lane ${lane.lane_id}`, {
5820
6038
  lane_id: lane.lane_id,
5821
6039
  outpost_id: outpostId,
5822
6040
  command_id: commandId,
5823
- remote_error: remote.error,
6041
+ owner_error: ownerErrorFromOutput(dispatched.stdout),
5824
6042
  });
5825
6043
  }
5826
6044
  else {
@@ -6661,6 +6879,14 @@ export function registerLanesCommand(parent, deps = {}) {
6661
6879
  .action(wrap(async (opts) => {
6662
6880
  await runCreate(ctx, opts);
6663
6881
  }));
6882
+ lanes
6883
+ .command("grant-target", { hidden: true })
6884
+ .argument("<lane-id>", "Lane id")
6885
+ .option("--hire <n>", "Non-negative safe integer hire grant")
6886
+ .option("--grant-context <base64url>", "Primary-authorized hire hierarchy and gate decision")
6887
+ .action(wrap(async (laneId, opts) => {
6888
+ await runGrantOwnerTarget(ctx, laneId, opts);
6889
+ }));
6664
6890
  lanes
6665
6891
  .command("grant")
6666
6892
  .description("Set a lane's alive child-lane grant as its senior")
@@ -0,0 +1,21 @@
1
+ import { type LaneRecord, type LaneState, type SeniorRef } from "./types.js";
2
+ declare const HIRE_GRANT_CONTEXT_SCHEMA: "hq-lane-hire-grant-context.v1";
3
+ export interface HireGrantLaneSnapshot {
4
+ lane_id: string;
5
+ state: LaneState;
6
+ senior: SeniorRef;
7
+ hire_grant?: number;
8
+ }
9
+ export interface OutpostHireGrantContext {
10
+ schema_version: typeof HIRE_GRANT_CONTEXT_SCHEMA;
11
+ enforcement_enabled: boolean;
12
+ expected_senior: SeniorRef;
13
+ hierarchy: HireGrantLaneSnapshot[];
14
+ }
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;
17
+ /** Decode and validate an owner grant context, binding it to the target lane. */
18
+ export declare function parseOutpostHireGrantContext(encoded: string, targetLaneId: string): OutpostHireGrantContext;
19
+ export declare function sameHireGrantSenior(a: SeniorRef, b: SeniorRef): boolean;
20
+ export {};
21
+ //# sourceMappingURL=hire-grant-dispatch.d.ts.map
@@ -0,0 +1,167 @@
1
+ import { aliveDirectChildren } from "./hire-grants.js";
2
+ import { LanesError } from "./errors.js";
3
+ import { isLaneState, isSeniorKind, } from "./types.js";
4
+ const HIRE_GRANT_CONTEXT_SCHEMA = "hq-lane-hire-grant-context.v1";
5
+ const MAX_CONTEXT_BYTES = 48 * 1024;
6
+ const MAX_CONTEXT_LANES = 256;
7
+ function contextError(message) {
8
+ return new LanesError("hire_grant_context_invalid", message, {});
9
+ }
10
+ function sameSenior(a, b) {
11
+ return a.kind === b.kind && a.id === b.id;
12
+ }
13
+ function copySenior(senior) {
14
+ return senior.id === undefined
15
+ ? { kind: senior.kind }
16
+ : { kind: senior.kind, id: senior.id };
17
+ }
18
+ function snapshot(lane) {
19
+ return {
20
+ lane_id: lane.lane_id,
21
+ state: lane.state,
22
+ senior: copySenior(lane.senior),
23
+ ...(lane.hire_grant === undefined ? {} : { hire_grant: lane.hire_grant }),
24
+ };
25
+ }
26
+ /** Encode only the owner-relevant hierarchy from the primary's complete store. */
27
+ export function createOutpostHireGrantContext(target, lanes, enforcementEnabled) {
28
+ const current = lanes.find((lane) => lane.lane_id === target.lane_id);
29
+ if (!current || !sameSenior(current.senior, target.senior)) {
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
+ }
32
+ const relevant = new Map();
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
+ }
45
+ }
46
+ for (const child of aliveDirectChildren(lanes, current.lane_id)) {
47
+ relevant.set(child.lane_id, child);
48
+ }
49
+ }
50
+ const hierarchy = [...relevant.values()]
51
+ .map(snapshot)
52
+ .sort((a, b) => a.lane_id.localeCompare(b.lane_id));
53
+ if (hierarchy.length > MAX_CONTEXT_LANES) {
54
+ throw new LanesError("hire_grant_hierarchy_too_large", `Lane ${target.lane_id} has too many related lanes to dispatch a bounded grant check`, { lane_id: target.lane_id, related_lane_count: hierarchy.length });
55
+ }
56
+ const context = {
57
+ schema_version: HIRE_GRANT_CONTEXT_SCHEMA,
58
+ enforcement_enabled: enforcementEnabled,
59
+ expected_senior: copySenior(current.senior),
60
+ hierarchy,
61
+ };
62
+ const encoded = Buffer.from(JSON.stringify(context), "utf8").toString("base64url");
63
+ if (Buffer.byteLength(encoded, "utf8") > MAX_CONTEXT_BYTES) {
64
+ throw new LanesError("hire_grant_hierarchy_too_large", `Lane ${target.lane_id} has too much related state to dispatch a bounded grant check`, { lane_id: target.lane_id, encoded_bytes: Buffer.byteLength(encoded, "utf8") });
65
+ }
66
+ return encoded;
67
+ }
68
+ function parseSenior(value) {
69
+ if (value === null || typeof value !== "object" || Array.isArray(value))
70
+ return null;
71
+ const record = value;
72
+ if (Object.keys(record).some((key) => key !== "kind" && key !== "id"))
73
+ return null;
74
+ if (!isSeniorKind(record.kind))
75
+ return null;
76
+ if (record.id !== undefined &&
77
+ (typeof record.id !== "string" || !record.id.trim() || record.id.trim() !== record.id))
78
+ return null;
79
+ if ((record.kind === "session" || record.kind === "lane") && record.id === undefined)
80
+ return null;
81
+ if (record.kind === "user" && record.id !== undefined)
82
+ return null;
83
+ return record.id === undefined
84
+ ? { kind: record.kind }
85
+ : { kind: record.kind, id: record.id };
86
+ }
87
+ function parseLaneSnapshot(value) {
88
+ if (value === null || typeof value !== "object" || Array.isArray(value))
89
+ return null;
90
+ const record = value;
91
+ if (Object.keys(record).some((key) => !["lane_id", "state", "senior", "hire_grant"].includes(key)))
92
+ return null;
93
+ if (typeof record.lane_id !== "string" || !record.lane_id.trim())
94
+ return null;
95
+ if (!isLaneState(record.state))
96
+ return null;
97
+ const senior = parseSenior(record.senior);
98
+ if (!senior)
99
+ return null;
100
+ if (record.hire_grant !== undefined &&
101
+ (typeof record.hire_grant !== "number" || !Number.isSafeInteger(record.hire_grant) || record.hire_grant < 0)) {
102
+ return null;
103
+ }
104
+ return {
105
+ lane_id: record.lane_id,
106
+ state: record.state,
107
+ senior,
108
+ ...(record.hire_grant === undefined ? {} : { hire_grant: record.hire_grant }),
109
+ };
110
+ }
111
+ /** Decode and validate an owner grant context, binding it to the target lane. */
112
+ export function parseOutpostHireGrantContext(encoded, targetLaneId) {
113
+ if (!encoded || !/^[A-Za-z0-9_-]+$/.test(encoded) ||
114
+ Buffer.byteLength(encoded, "utf8") > MAX_CONTEXT_BYTES ||
115
+ Buffer.from(encoded, "base64url").toString("base64url") !== encoded) {
116
+ throw contextError("Outpost grant context is not canonical base64url");
117
+ }
118
+ let raw;
119
+ try {
120
+ const decoded = new TextDecoder("utf-8", { fatal: true }).decode(Buffer.from(encoded, "base64url"));
121
+ raw = JSON.parse(decoded);
122
+ }
123
+ catch {
124
+ throw contextError("Outpost grant context is not valid UTF-8 JSON");
125
+ }
126
+ if (raw === null || typeof raw !== "object" || Array.isArray(raw)) {
127
+ throw contextError("Outpost grant context must be an object");
128
+ }
129
+ const record = raw;
130
+ if (Object.keys(record).some((key) => !["schema_version", "enforcement_enabled", "expected_senior", "hierarchy"].includes(key))) {
131
+ throw contextError("Outpost grant context contains unknown fields");
132
+ }
133
+ if (record.schema_version !== HIRE_GRANT_CONTEXT_SCHEMA ||
134
+ typeof record.enforcement_enabled !== "boolean" ||
135
+ !Array.isArray(record.hierarchy) ||
136
+ record.hierarchy.length === 0 || record.hierarchy.length > MAX_CONTEXT_LANES) {
137
+ throw contextError("Outpost grant context has an unsupported or incomplete shape");
138
+ }
139
+ const expectedSenior = parseSenior(record.expected_senior);
140
+ const hierarchy = record.hierarchy.map(parseLaneSnapshot);
141
+ if (!expectedSenior || hierarchy.some((lane) => lane === null)) {
142
+ throw contextError("Outpost grant context contains an invalid senior or lane snapshot");
143
+ }
144
+ const snapshots = hierarchy;
145
+ const byId = new Map(snapshots.map((lane) => [lane.lane_id, lane]));
146
+ if (byId.size !== snapshots.length) {
147
+ throw contextError("Outpost grant context contains duplicate lane snapshots");
148
+ }
149
+ const target = byId.get(targetLaneId);
150
+ if (!target || !sameSenior(target.senior, expectedSenior)) {
151
+ throw contextError("Outpost grant context does not match the target's expected senior");
152
+ }
153
+ if (record.enforcement_enabled && expectedSenior.kind === "lane" &&
154
+ (!expectedSenior.id || !byId.has(expectedSenior.id))) {
155
+ throw contextError("Outpost grant context is missing the target's senior lane");
156
+ }
157
+ return {
158
+ schema_version: HIRE_GRANT_CONTEXT_SCHEMA,
159
+ enforcement_enabled: record.enforcement_enabled,
160
+ expected_senior: expectedSenior,
161
+ hierarchy: snapshots,
162
+ };
163
+ }
164
+ export function sameHireGrantSenior(a, b) {
165
+ return sameSenior(a, b);
166
+ }
167
+ //# sourceMappingURL=hire-grant-dispatch.js.map
@@ -1,7 +1,8 @@
1
1
  import { type LaneRecord } from "./types.js";
2
2
  export declare const LANE_HIRE_GRANTS_FLAG = "lanes.hire-grants";
3
- export declare function aliveDirectChildren(lanes: readonly LaneRecord[], parentLaneId: string): LaneRecord[];
4
- export declare function assignedChildHireGrants(children: readonly LaneRecord[], exceptLaneId?: string): number;
3
+ export type LaneHireGrantRow = Pick<LaneRecord, "lane_id" | "state" | "senior" | "hire_grant">;
4
+ export declare function aliveDirectChildren<T extends LaneHireGrantRow>(lanes: readonly T[], parentLaneId: string): T[];
5
+ export declare function assignedChildHireGrants(children: readonly LaneHireGrantRow[], exceptLaneId?: string): number;
5
6
  export interface HireGrantAdmissionSnapshot {
6
7
  parent: LaneRecord;
7
8
  children: LaneRecord[];
@@ -27,7 +27,7 @@ export interface OutpostDispatchResult {
27
27
  stderr: string;
28
28
  }
29
29
  export interface OutpostLaneCommand {
30
- action: "message" | "resume" | "stop" | "show" | "grant";
30
+ action: "message" | "resume" | "stop" | "show" | "grant" | "grant-target";
31
31
  laneId: string;
32
32
  hqBinary?: string;
33
33
  text?: string;
@@ -36,6 +36,7 @@ export interface OutpostLaneCommand {
36
36
  account?: string;
37
37
  model?: string;
38
38
  hire?: string;
39
+ grantContext?: string;
39
40
  compact?: boolean;
40
41
  }
41
42
  export interface OutpostExecPort {
@@ -105,6 +105,7 @@ export function buildOutpostLaneCommand(input) {
105
105
  ["--account", input.account],
106
106
  ["--model", input.model],
107
107
  ["--hire", input.hire],
108
+ ["--grant-context", input.grantContext],
108
109
  ]) {
109
110
  if (value !== undefined)
110
111
  args.push(flag, value);
@@ -0,0 +1,12 @@
1
+ export interface SsmRunCommandContextDeps {
2
+ platform?: string;
3
+ parentPid?: number;
4
+ readFileSync?: (path: string, encoding: "utf8") => string;
5
+ }
6
+ /**
7
+ * True only for a Linux process descended from the root-owned SSM Run Command
8
+ * worker chain. Outpost owner-only lane mutations use this to distinguish an
9
+ * SSM dispatch from a normal local CLI invocation.
10
+ */
11
+ export declare function isSsmRunCommandProcess(deps?: SsmRunCommandContextDeps): boolean;
12
+ //# sourceMappingURL=ssm-command-context.d.ts.map
@@ -0,0 +1,52 @@
1
+ import * as fs from "node:fs";
2
+ const SSM_WORKER_CHAIN = [
3
+ "ssm-document-wo",
4
+ "ssm-agent-worke",
5
+ "amazon-ssm-agen",
6
+ ];
7
+ /**
8
+ * True only for a Linux process descended from the root-owned SSM Run Command
9
+ * worker chain. Outpost owner-only lane mutations use this to distinguish an
10
+ * SSM dispatch from a normal local CLI invocation.
11
+ */
12
+ export function isSsmRunCommandProcess(deps = {}) {
13
+ if ((deps.platform ?? process.platform) !== "linux")
14
+ return false;
15
+ const readFileSync = deps.readFileSync ?? ((filePath, encoding) => fs.readFileSync(filePath, encoding));
16
+ let pid = deps.parentPid ?? process.ppid;
17
+ const lineage = [];
18
+ for (let depth = 0; depth < 32 && pid > 1; depth += 1) {
19
+ let status;
20
+ let command;
21
+ try {
22
+ status = readFileSync(`/proc/${pid}/status`, "utf8");
23
+ command = readFileSync(`/proc/${pid}/comm`, "utf8").trim();
24
+ }
25
+ catch (err) {
26
+ const code = err !== null && typeof err === "object" && "code" in err
27
+ ? err.code
28
+ : undefined;
29
+ if (code === "ENOENT" || code === "EACCES" || code === "EPERM")
30
+ return false;
31
+ throw err;
32
+ }
33
+ const parentMatch = /^PPid:\s*(\d+)\s*$/m.exec(status);
34
+ const uidMatch = /^Uid:\s*\d+\s+(\d+)\s+\d+\s+\d+\s*$/m.exec(status);
35
+ if (!parentMatch || !uidMatch || !command)
36
+ return false;
37
+ const parentPid = Number(parentMatch[1]);
38
+ const effectiveUid = Number(uidMatch[1]);
39
+ if (!Number.isSafeInteger(parentPid) || !Number.isSafeInteger(effectiveUid))
40
+ return false;
41
+ lineage.push({ pid, parentPid, command, effectiveUid });
42
+ pid = parentPid;
43
+ }
44
+ for (let start = 0; start <= lineage.length - SSM_WORKER_CHAIN.length; start += 1) {
45
+ const chain = lineage.slice(start, start + SSM_WORKER_CHAIN.length);
46
+ if (chain.every((entry, index) => entry.effectiveUid === 0 && entry.command.startsWith(SSM_WORKER_CHAIN[index]))) {
47
+ return true;
48
+ }
49
+ }
50
+ return false;
51
+ }
52
+ //# sourceMappingURL=ssm-command-context.js.map
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@indigoai-us/hq-cli",
3
- "version": "5.188.0",
3
+ "version": "5.188.1",
4
4
  "description": "HQ by Indigo management CLI — modules and cloud sync",
5
5
  "main": "dist/index.js",
6
6
  "bin": {