@indigoai-us/hq-cli 5.345.163 → 5.345.165

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 (41) hide show
  1. package/CHANGELOG.md +36 -0
  2. package/dist/command-catalog.generated.d.ts +66 -0
  3. package/dist/command-catalog.generated.js +82 -0
  4. package/dist/commands/billing-sponsor.d.ts +20 -0
  5. package/dist/commands/billing-sponsor.js +116 -0
  6. package/dist/commands/billing-usage.d.ts +16 -2
  7. package/dist/commands/billing-usage.js +63 -9
  8. package/dist/commands/billing.d.ts +34 -0
  9. package/dist/commands/billing.js +108 -1
  10. package/dist/commands/deploy-domains.d.ts +2 -0
  11. package/dist/commands/deploy-domains.js +18 -3
  12. package/dist/commands/scan.d.ts +6 -0
  13. package/dist/commands/scan.js +11 -1
  14. package/dist/lib/billing/billing-status-lines.d.ts +14 -0
  15. package/dist/lib/billing/billing-status-lines.js +43 -0
  16. package/dist/lib/billing/usage-only.d.ts +18 -0
  17. package/dist/lib/billing/usage-only.js +18 -1
  18. package/dist/lib/doctor/checks/sync-runtime.d.ts +34 -0
  19. package/dist/lib/doctor/checks/sync-runtime.js +217 -8
  20. package/dist/lib/doctor/fix/apply.js +1 -0
  21. package/dist/lib/doctor/fix/refresh-sync-outcome.d.ts +4 -1
  22. package/dist/lib/doctor/fix/refresh-sync-outcome.js +15 -0
  23. package/dist/lib/doctor/fix/refresh-sync-progress.d.ts +42 -0
  24. package/dist/lib/doctor/fix/refresh-sync-progress.js +100 -0
  25. package/dist/lib/doctor/json-output.d.ts +7 -0
  26. package/dist/lib/doctor/json-output.js +8 -1
  27. package/dist/lib/doctor/types.d.ts +1 -1
  28. package/dist/lib/doctor/types.js +1 -0
  29. package/dist/lib/mesh/live/daemon/work-subscribe.d.ts +1 -1
  30. package/dist/lib/mesh/live/daemon/work-subscribe.js +121 -38
  31. package/dist/lib/mesh/live/paths.d.ts +1 -0
  32. package/dist/lib/mesh/live/paths.js +1 -0
  33. package/dist/lib/secret-scan/detect.js +2 -0
  34. package/dist/lib/secret-scan/probes.d.ts +13 -0
  35. package/dist/lib/secret-scan/probes.js +69 -0
  36. package/dist/lib/secret-scan/repo-config.d.ts +24 -0
  37. package/dist/lib/secret-scan/repo-config.js +159 -29
  38. package/dist/lib/secret-scan/rules.d.ts +2 -0
  39. package/dist/lib/secret-scan/staged.d.ts +15 -1
  40. package/dist/lib/secret-scan/staged.js +46 -4
  41. package/package.json +7 -7
@@ -30,8 +30,9 @@ import { vaultApiFetch, getCompanyUid } from "../utils/vault-api.js";
30
30
  import { mintPaymentLink } from "../utils/billing-gate.js";
31
31
  import { openUsageSwitch } from "../utils/team-upgrade.js";
32
32
  import { registerBillingSponsorCommands } from "./billing-sponsor.js";
33
- import { fetchCompanyPricingPlan, isUsageOnly, usageOnlyUpgradeLines, } from "../lib/billing/usage-only.js";
33
+ import { fetchCompanyPricingPlan, isUsageOnly, pricingPlanFromBody, readBillingState, usageOnlyUpgradeLines, } from "../lib/billing/usage-only.js";
34
34
  import { fetchUsageCard } from "../lib/billing/usage-card.js";
35
+ import { accountStatusLines } from "../lib/billing/billing-status-lines.js";
35
36
  import { registerBillingUsageCommand } from "./billing-usage.js";
36
37
  const CHARGED_ON = {
37
38
  team: "the Team subscription",
@@ -62,6 +63,94 @@ export function formatDatabaseBilling(db) {
62
63
  }
63
64
  return lines;
64
65
  }
66
+ function asRecord(value) {
67
+ return value !== null && typeof value === "object" && !Array.isArray(value)
68
+ ? value
69
+ : null;
70
+ }
71
+ /**
72
+ * The plan a person can act on, in words. The summary's `billingStatus.plan`
73
+ * is the Stripe webhook's label and says "usage" for ANY non-Workforce
74
+ * subscription (a metered usage subscription on a company that still prices
75
+ * as a flat plan reads "usage" there, hq-pro summary.ts). The plan the server
76
+ * actually prices by is `billingState.kind` (hq-pro #5088) or, on an older
77
+ * server, `pricingPlan`; `billingStatus.plan` only separates Workforce from
78
+ * the rest. DEV-5179: a reporter read "usage" off the status JSON while
79
+ * `hq billing usage` said "flat plan"; both were right about different fields.
80
+ */
81
+ export function describePlan(summary) {
82
+ const state = readBillingState(summary);
83
+ if (state) {
84
+ switch (state.kind) {
85
+ case "usage":
86
+ return "usage pricing";
87
+ case "usage-trial":
88
+ return state.trial
89
+ ? `usage pricing (trial, ${state.trial.daysLeft} days left)`
90
+ : "usage pricing (trial)";
91
+ case "legacy-payer":
92
+ // legacy-payer also covers invoice-billed companies, which have no
93
+ // Workforce subscription behind them.
94
+ return asRecord(summary.billingStatus)?.plan === "team"
95
+ ? "Workforce (flat plan)"
96
+ : "flat plan (billed by invoice)";
97
+ case "legacy-free":
98
+ return "Starter (free)";
99
+ case "enterprise":
100
+ return "Enterprise";
101
+ case "comped":
102
+ return "comped (not billed)";
103
+ }
104
+ }
105
+ if (pricingPlanFromBody(summary) === "usage-only")
106
+ return "usage pricing";
107
+ if (pricingPlanFromBody(summary) === "current") {
108
+ // An older server says "flat plan" but not which one: only a Workforce
109
+ // subscription is visible here, so anything else is named no further.
110
+ return asRecord(summary.billingStatus)?.plan === "team" ? "Workforce (flat plan)" : "flat plan";
111
+ }
112
+ // The server did not say which plan prices this company. Naming one here
113
+ // would guess, and a guess of "Starter (free)" told a paying company it was
114
+ // on the free plan.
115
+ return PLAN_UNAVAILABLE;
116
+ }
117
+ export const PLAN_UNAVAILABLE = "unavailable right now";
118
+ /** True when the plan description already says the company prices by usage. */
119
+ function isUsagePricingLabel(label) {
120
+ return label.startsWith("usage pricing");
121
+ }
122
+ /**
123
+ * A scheduled switch to usage pricing (hq-pro US-014 `GET
124
+ * /v1/billing/usage-switch`), or null. The route is owner-only and absent on
125
+ * older servers. A refusal (403, 404) reads as "nothing scheduled"; any other
126
+ * failure is "unreadable" so the status view can say so instead of implying
127
+ * no switch. The status view never fails on it.
128
+ */
129
+ export async function fetchScheduledUsageSwitch(token, companyUid) {
130
+ try {
131
+ const res = await vaultApiFetch({
132
+ token,
133
+ path: "/v1/billing/usage-switch",
134
+ query: { companyUid },
135
+ });
136
+ if (res.status === 403 || res.status === 404)
137
+ return null;
138
+ if (!res.ok)
139
+ return "unreadable";
140
+ const body = asRecord(await res.json());
141
+ const phase = body?.phase;
142
+ const switchDate = body?.switchDate;
143
+ if ((phase === "scheduled" || phase === "preview") &&
144
+ typeof switchDate === "string" &&
145
+ Number.isFinite(Date.parse(switchDate))) {
146
+ return { switchDate, phase };
147
+ }
148
+ return null;
149
+ }
150
+ catch {
151
+ return "unreadable";
152
+ }
153
+ }
65
154
  function fail(err) {
66
155
  console.error(chalk.red("Error:"), err instanceof Error ? err.message : String(err));
67
156
  process.exit(1);
@@ -101,6 +190,18 @@ export function registerBillingCommand(program) {
101
190
  }
102
191
  const sub = summary.subscriptionStatus ?? "none";
103
192
  console.log(`${chalk.bold("Subscription")}: ${sub}`);
193
+ const planLabel = describePlan(summary);
194
+ console.log(`${chalk.bold("Plan")}: ${planLabel}`);
195
+ if (!isUsagePricingLabel(planLabel) && planLabel !== PLAN_UNAVAILABLE) {
196
+ const scheduled = await fetchScheduledUsageSwitch(token, companyUid);
197
+ if (scheduled === "unreadable") {
198
+ console.log(" Switch to usage pricing: could not be read right now.");
199
+ }
200
+ else if (scheduled) {
201
+ console.log(` Switch to usage pricing scheduled for ${scheduled.switchDate.slice(0, 10)}` +
202
+ (scheduled.phase === "preview" ? " (preview window open)." : "."));
203
+ }
204
+ }
104
205
  const pm = summary.defaultPaymentMethod;
105
206
  if (pm?.present) {
106
207
  const detail = pm.brand && pm.last4 ? `${pm.brand} ••••${pm.last4}` : "on file";
@@ -110,6 +211,12 @@ export function registerBillingCommand(program) {
110
211
  console.log(`${chalk.bold("Card")}: ${chalk.yellow("no card on file")} — ` +
111
212
  `run \`hq billing checkout\` to add one before provisioning paid resources.`);
112
213
  }
214
+ const state = readBillingState(summary);
215
+ if (state && (state.kind === "usage" || state.kind === "usage-trial")) {
216
+ const { card } = await fetchUsageCard();
217
+ for (const line of accountStatusLines(state, card))
218
+ console.log(line);
219
+ }
113
220
  for (const line of formatDatabaseBilling(summary.databases))
114
221
  console.log(line);
115
222
  }
@@ -17,6 +17,8 @@ export declare const deployDomainsPrompt: {
17
17
  };
18
18
  /** The sentence `buy` adds when the order's outcome is not known. */
19
19
  export declare function resumeOrderSentence(idempotencyKey: string): string;
20
+ /** Shown when hq-pro registered the domain but could not capture the card charge. */
21
+ export declare function retryChargeSentence(idempotencyKey: string): string;
20
22
  interface Binding {
21
23
  appId: string;
22
24
  orgSlug?: string;
@@ -117,6 +117,10 @@ const SETTLED_PURCHASE_CODES = new Set([
117
117
  export function resumeOrderSentence(idempotencyKey) {
118
118
  return `The order may still be in progress. Run the same command again with --idempotency-key ${idempotencyKey} to finish or check it. Do not start a new order.`;
119
119
  }
120
+ /** Shown when hq-pro registered the domain but could not capture the card charge. */
121
+ export function retryChargeSentence(idempotencyKey) {
122
+ return `Run the same command again with --idempotency-key ${idempotencyKey} to retry the charge. Do not start a new order.`;
123
+ }
120
124
  /** The registrant fields hq-pro accepts; nothing else from the file is sent. */
121
125
  const REGISTRANT_FIELDS = [
122
126
  "firstName", "lastName", "companyName", "address1", "address2",
@@ -312,16 +316,18 @@ async function resolveTarget(token, options) {
312
316
  if (!binding.orgSlug && options.company === undefined) {
313
317
  throw new DeployDomainsError("The folder's link does not name a company. Pass --company <slug>, or re-link the folder.", "COMPANY_REQUIRED");
314
318
  }
315
- if (binding.orgSlug && options.company !== undefined && options.company !== binding.orgSlug) {
319
+ if (binding.orgSlug && options.company !== undefined && options.company.toLowerCase() !== binding.orgSlug) {
316
320
  throw new DeployDomainsError(`This folder is linked to an app in '${binding.orgSlug}', not '${displayText(options.company)}'. Drop --company, or pass --app <name or id>.`, "COMPANY_MISMATCH");
317
321
  }
318
322
  return {
319
323
  appId: binding.appId,
320
- orgSlug: resolveSlug(options.company, binding.orgSlug),
324
+ // A matching --company may differ only in case; the link's slug is canonical.
325
+ orgSlug: binding.orgSlug ?? resolveSlug(options.company, undefined),
321
326
  appLabel: binding.appId,
322
327
  };
323
328
  }
324
329
  const HOSTNAME_LABEL = /^[a-z0-9](?:[a-z0-9-]*[a-z0-9])?$/;
330
+ const ALL_DIGITS = /^\d+$/;
325
331
  /**
326
332
  * Normalize a domain to the ASCII form the server stores, using the same
327
333
  * hostname rule hq-deploy's validateAppHostname enforces: at least two
@@ -344,6 +350,13 @@ export function normalizeDomain(raw) {
344
350
  if (labels.length < 2 || labels.some((label) => label.length > 63 || !HOSTNAME_LABEL.test(label))) {
345
351
  throw invalid();
346
352
  }
353
+ // domainToASCII applies WHATWG IPv4 parsing ("1.2" becomes "1.0.0.2",
354
+ // "0x7f.1" becomes "127.0.0.1"), and a name it cannot parse as one comes
355
+ // back empty. No real top-level domain is numeric, so a numeric last label
356
+ // after the conversion is an address.
357
+ if (ALL_DIGITS.test(labels[labels.length - 1])) {
358
+ throw invalid();
359
+ }
347
360
  return domain;
348
361
  }
349
362
  function domainRoute(appId, domain, suffix = "") {
@@ -727,8 +740,10 @@ async function buyAction(domainArg, options) {
727
740
  }
728
741
  else if (body.paymentCaptured === false) {
729
742
  // hq-pro registered the name, but capturing the card authorization failed.
743
+ // A retry with the same key retrieves the same authorization, replays the
744
+ // registration for this order and captures again (hq-pro domain-purchase.ts).
730
745
  console.log(`Registered ${displayText(domain)}.${expires} The card was authorized for ${usd(expectedPriceUsd)}, ` +
731
- `but the charge has not completed yet. HQ billing follows up on it. Do not buy the domain again.`);
746
+ `but the charge did not complete. ${retryChargeSentence(idempotencyKey)}`);
732
747
  }
733
748
  else {
734
749
  console.log(`Bought ${displayText(domain)} for ${usd(expectedPriceUsd)}.${expires}`);
@@ -4,6 +4,9 @@
4
4
  * hq scan secrets scan the staged changes of the current repo
5
5
  * hq scan secrets <file...> scan whole files instead
6
6
  * hq scan secrets --json findings as JSON
7
+ * hq scan secrets --repo <dir> [--snapshot-root <dir>] [--allowlist-rev <rev>] <file...>
8
+ * scan files with that repo's committed
9
+ * .gitleaks.toml allowlists (used by pr-check)
7
10
  *
8
11
  * Rules: the vendored gitleaks default rule set (MIT, vendor/gitleaks/) plus
9
12
  * HQ's own token patterns. No network, no account, no quota. A staged scan also
@@ -18,6 +21,9 @@ import { Command } from "commander";
18
21
  interface ScanSecretsOptions {
19
22
  json?: boolean;
20
23
  staged?: boolean;
24
+ repo?: string;
25
+ snapshotRoot?: string;
26
+ allowlistRev?: string;
21
27
  }
22
28
  export declare function runScanSecrets(paths: string[], opts: ScanSecretsOptions, cwd?: string): number;
23
29
  export declare function registerScanCommand(program: Command): void;
@@ -4,6 +4,9 @@
4
4
  * hq scan secrets scan the staged changes of the current repo
5
5
  * hq scan secrets <file...> scan whole files instead
6
6
  * hq scan secrets --json findings as JSON
7
+ * hq scan secrets --repo <dir> [--snapshot-root <dir>] [--allowlist-rev <rev>] <file...>
8
+ * scan files with that repo's committed
9
+ * .gitleaks.toml allowlists (used by pr-check)
7
10
  *
8
11
  * Rules: the vendored gitleaks default rule set (MIT, vendor/gitleaks/) plus
9
12
  * HQ's own token patterns. No network, no account, no quota. A staged scan also
@@ -31,7 +34,11 @@ function report(findings, scope) {
31
34
  export function runScanSecrets(paths, opts, cwd = process.cwd()) {
32
35
  let findings;
33
36
  try {
34
- findings = paths.length && !opts.staged ? scanFiles(paths) : scanStaged(cwd);
37
+ if (opts.snapshotRoot !== undefined && opts.repo === undefined)
38
+ throw new ScanInputError("--snapshot-root needs --repo");
39
+ if (opts.allowlistRev !== undefined && opts.repo === undefined)
40
+ throw new ScanInputError("--allowlist-rev needs --repo");
41
+ findings = paths.length && !opts.staged ? scanFiles(paths, { repo: opts.repo, snapshotRoot: opts.snapshotRoot, allowlistRev: opts.allowlistRev }) : scanStaged(cwd);
35
42
  }
36
43
  catch (err) {
37
44
  const msg = err instanceof ScanInputError || err instanceof ScanConfigError ? err.message : `scan failed: ${err instanceof Error ? err.message : String(err)}`;
@@ -56,6 +63,9 @@ export function registerScanCommand(program) {
56
63
  .argument("[files...]", "Scan these files instead of the staged changes")
57
64
  .option("--staged", "Scan the staged changes (default when no files are given)")
58
65
  .option("--json", "Print findings as JSON")
66
+ .option("--repo <dir>", "With files: apply this repo's committed .gitleaks.toml allowlists, matching repo-relative paths")
67
+ .option("--snapshot-root <dir>", "With --repo: files under <dir>/<commit>/<path> are matched as <path>")
68
+ .option("--allowlist-rev <rev>", "With --repo: read .gitleaks.toml from this commit instead of HEAD")
59
69
  .action((files, opts) => {
60
70
  process.exitCode = runScanSecrets(files ?? [], opts);
61
71
  });
@@ -0,0 +1,14 @@
1
+ /**
2
+ * The account lines `hq billing status`, `hq billing usage` and
3
+ * `hq billing usage --card` print from hq-pro's shared `billingState` (US-025).
4
+ * One helper so every surface shows the same trial usage, included usage,
5
+ * spend limit and card prompt for the same state.
6
+ */
7
+ import { type UsageCard } from "./usage-card.js";
8
+ import type { SharedBillingState } from "./usage-only.js";
9
+ /**
10
+ * Lines for one billing state. Legacy, enterprise and comped accounts get no
11
+ * lines. The member view carries no money figures, so it gets none either.
12
+ */
13
+ export declare function accountStatusLines(state: SharedBillingState | null, card?: UsageCard): string[];
14
+ //# sourceMappingURL=billing-status-lines.d.ts.map
@@ -0,0 +1,43 @@
1
+ /**
2
+ * The account lines `hq billing status`, `hq billing usage` and
3
+ * `hq billing usage --card` print from hq-pro's shared `billingState` (US-025).
4
+ * One helper so every surface shows the same trial usage, included usage,
5
+ * spend limit and card prompt for the same state.
6
+ */
7
+ import { BUNDLED_USAGE_CARD, formatCardUsd } from "./usage-card.js";
8
+ function usd(cents) {
9
+ return `$${(cents / 100).toFixed(2)}`;
10
+ }
11
+ function onUsagePricing(state) {
12
+ return state.kind === "usage" || state.kind === "usage-trial";
13
+ }
14
+ /**
15
+ * Lines for one billing state. Legacy, enterprise and comped accounts get no
16
+ * lines. The member view carries no money figures, so it gets none either.
17
+ */
18
+ export function accountStatusLines(state, card = BUNDLED_USAGE_CARD) {
19
+ if (!state || !onUsagePricing(state))
20
+ return [];
21
+ const lines = [];
22
+ if (state.kind === "usage-trial" && state.trial) {
23
+ lines.push(`Trial: ${state.trial.daysLeft} days left (ends ${state.trial.endsAt.slice(0, 10)})`);
24
+ if (state.view === "full" && state.trial.usedCents !== null) {
25
+ const capCents = state.trial.capCents ?? card.trial.capUsd * 100;
26
+ lines.push(`Trial usage: ${usd(state.trial.usedCents)} of the ${usd(capCents)} trial cap`);
27
+ }
28
+ }
29
+ if (state.view !== "full")
30
+ return lines;
31
+ lines.push(`Included usage: ${formatCardUsd(card.includedUsd)} each month`);
32
+ if (state.spendLimit) {
33
+ lines.push(state.spendLimit.cents === null
34
+ ? "Spend limit: none set"
35
+ : `Spend limit: ${usd(state.spendLimit.cents)} a month`);
36
+ }
37
+ if (state.cardPrompt) {
38
+ lines.push(`Add a card: usage is past the ${formatCardUsd(card.includedUsd)} included this month. ` +
39
+ "Run `hq billing checkout` to keep working past it.");
40
+ }
41
+ return lines;
42
+ }
43
+ //# sourceMappingURL=billing-status-lines.js.map
@@ -35,10 +35,28 @@ export interface SharedBillingState {
35
35
  kind: BillingStateKind;
36
36
  scope: "company" | "person";
37
37
  view: "full" | "member";
38
+ /**
39
+ * The usage trial, or null. `capCents` is the trial's usage cap and
40
+ * `usedCents` what the trial has used so far; both are null when the server
41
+ * does not say (member view, older server).
42
+ */
38
43
  trial: {
39
44
  daysLeft: number;
40
45
  endsAt: string;
46
+ capCents: number | null;
47
+ usedCents: number | null;
48
+ } | null;
49
+ /**
50
+ * The monthly spend limit. `cents: null` means no limit is set. The whole
51
+ * field is null off usage pricing and in the member view.
52
+ */
53
+ spendLimit: {
54
+ cents: number | null;
41
55
  } | null;
56
+ /** Whether a card is on file. Null when the server does not say. */
57
+ cardOnFile: boolean | null;
58
+ /** Usage passed the included allowance with no card on file. */
59
+ cardPrompt: boolean;
42
60
  /** A pause the server is applying now, or null. */
43
61
  paused: {
44
62
  reason: string;
@@ -65,7 +65,21 @@ function readTrial(value) {
65
65
  !Number.isFinite(Date.parse(trial.endsAt))) {
66
66
  return undefined;
67
67
  }
68
- return { daysLeft: trial.daysLeft, endsAt: trial.endsAt };
68
+ return {
69
+ daysLeft: trial.daysLeft,
70
+ endsAt: trial.endsAt,
71
+ capCents: centsOrNull(trial.capCents),
72
+ usedCents: centsOrNull(trial.usedCents),
73
+ };
74
+ }
75
+ function centsOrNull(value) {
76
+ return typeof value === "number" && Number.isFinite(value) && value >= 0 ? value : null;
77
+ }
78
+ function readSpendLimit(value) {
79
+ const limit = asRecord(value);
80
+ if (!limit)
81
+ return null;
82
+ return { cents: centsOrNull(limit.cents) };
69
83
  }
70
84
  function readPaused(value) {
71
85
  const paused = asRecord(value);
@@ -102,6 +116,9 @@ export function readBillingState(body) {
102
116
  scope: state.scope,
103
117
  view: state.view,
104
118
  trial,
119
+ spendLimit: readSpendLimit(state.spendLimit),
120
+ cardOnFile: typeof state.cardOnFile === "boolean" ? state.cardOnFile : null,
121
+ cardPrompt: state.cardPrompt === true,
105
122
  paused: readPaused(state.paused),
106
123
  actions: {
107
124
  addCard: actions?.addCard === true,
@@ -1,13 +1,47 @@
1
+ import { readSessionCompanyLock } from "../../session-company-lock.js";
1
2
  import type { CheckResult } from "../types.js";
2
3
  export declare const SYNC_RUNTIME_WINDOW_MS: number;
3
4
  /** Ignore timing and restart signals until a newly started receiver has settled. */
4
5
  export declare const SYNC_RUNTIME_RESTART_WARMUP_MS: number;
6
+ /** Most refused vault keys named by `sync.runtime.pull-access-denied`. */
7
+ export declare const PULL_DENIED_KEY_CAP = 10;
8
+ /** A key refused on at least this many pulls in the hour is a standing condition, not a one-off. */
9
+ export declare const PULL_DENIED_MIN_REPEATS = 2;
5
10
  export interface SyncRuntimeOptions {
6
11
  now: number;
7
12
  conflictCount?: number;
13
+ /**
14
+ * Company scopes whose refused vault keys may be named. `undefined` means
15
+ * the session holds no company lock, so every scope may be named. Keys in
16
+ * any other scope (including `personal` when it is not listed) are counted
17
+ * but never named, so `hq doctor` in a session locked to company A cannot
18
+ * print company B's file names from the machine-wide sync log.
19
+ */
20
+ nameableCompanies?: readonly string[];
8
21
  }
22
+ /**
23
+ * Which cloud call a receiver permission refusal came from. Closed vocabulary
24
+ * derived from the hq-cloud receiver's event names, so the breakdown can be
25
+ * reported without the error prose.
26
+ *
27
+ * - `subscribe`: `POST /v1/sync/subscribe` itself answered 403 while the
28
+ * receiver re-vended its queue credentials.
29
+ * - `sqs-receive` / `sqs-visibility` / `sqs-delete`: the per-device queue
30
+ * credentials vended by subscribe were refused by SQS. One bad vend
31
+ * repeats on every poll and lease heartbeat until the runner restarts.
32
+ * - `cloudwatch-metric`: the latency metric publisher was refused.
33
+ * - `targeted-pull`: the object pull after a change event failed; the
34
+ * refused keys are named by `sync.runtime.pull-access-denied`.
35
+ * - `runner-error`: a runner `error` record outside the receiver.
36
+ */
37
+ export type ReceiverRefusalCall = "subscribe" | "sqs-receive" | "sqs-visibility" | "sqs-delete" | "cloudwatch-metric" | "targeted-pull" | "runner-error" | "other";
9
38
  /** Classify the last hour of sync log lines into privacy-safe results. */
10
39
  export declare function analyzeSyncRuntimeLog(lines: readonly string[], options: SyncRuntimeOptions): CheckResult[];
40
+ /**
41
+ * Company scopes this session may see named: `undefined` (all) when there is
42
+ * no session or no company lock; nothing when the lock cannot be verified.
43
+ */
44
+ export declare function sessionNameableCompanies(env: NodeJS.ProcessEnv, readLock?: typeof readSessionCompanyLock): readonly string[] | undefined;
11
45
  /** Read the machine's sync logs and client health file. */
12
46
  export declare function defaultSyncRuntimeResults(now: Date, hqHome?: string, env?: NodeJS.ProcessEnv): CheckResult[];
13
47
  //# sourceMappingURL=sync-runtime.d.ts.map