@indigoai-us/hq-cli 5.345.155 → 5.345.159
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 +289 -0
- package/assets/scaffold/core/scripts/rebuild-company-knowledge-index.sh +110 -2
- package/assets/scaffold/core/scripts/rebuild-projects-index.sh +101 -5
- package/assets/scaffold/core/scripts/worktree.sh +4 -0
- package/dist/command-catalog.generated.d.ts +536 -7
- package/dist/command-catalog.generated.js +676 -24
- package/dist/command-registration-plan.d.ts +18 -0
- package/dist/command-registration-plan.js +4 -0
- package/dist/commands/_patterns.d.ts +17 -0
- package/dist/commands/_patterns.js +22 -0
- package/dist/commands/agent-inbox.js +2 -2
- package/dist/commands/agent-kit.js +8 -6
- package/dist/commands/bot.js +7 -4
- package/dist/commands/channel-suggest.d.ts +56 -0
- package/dist/commands/channel-suggest.js +170 -0
- package/dist/commands/channels.js +7 -1
- package/dist/commands/cloud.d.ts +45 -0
- package/dist/commands/cloud.js +150 -10
- package/dist/commands/company.js +4 -3
- package/dist/commands/core-checkpoint-native.d.ts +18 -0
- package/dist/commands/core-checkpoint-native.js +67 -2
- package/dist/commands/core-pipeline-driver-native.d.ts +12 -1
- package/dist/commands/core-pipeline-driver-native.js +131 -5
- package/dist/commands/core.js +13 -1
- package/dist/commands/crm.js +83 -6
- package/dist/commands/deploy-access.d.ts +33 -0
- package/dist/commands/deploy-access.js +322 -0
- package/dist/commands/dm-attach.d.ts +106 -0
- package/dist/commands/dm-attach.js +184 -0
- package/dist/commands/dm.d.ts +37 -0
- package/dist/commands/dm.js +172 -10
- package/dist/commands/feedback-show.d.ts +40 -0
- package/dist/commands/feedback-show.js +261 -0
- package/dist/commands/feedback.d.ts +21 -0
- package/dist/commands/feedback.js +152 -2
- package/dist/commands/files-browse.d.ts +16 -0
- package/dist/commands/files-browse.js +52 -1
- package/dist/commands/files.d.ts +24 -0
- package/dist/commands/files.js +52 -1
- package/dist/commands/integrations-connect.js +7 -3
- package/dist/commands/integrations-core.d.ts +13 -1
- package/dist/commands/integrations-core.js +49 -4
- package/dist/commands/integrations.js +4 -2
- package/dist/commands/lanes-accounts.js +16 -1
- package/dist/commands/lanes-loop.js +14 -4
- package/dist/commands/lanes-message.js +6 -4
- package/dist/commands/lanes-resume.js +36 -13
- package/dist/commands/lanes-shared.d.ts +5 -0
- package/dist/commands/lanes-shared.js +13 -3
- package/dist/commands/lanes-watch.js +1 -0
- package/dist/commands/lanes.d.ts +20 -1
- package/dist/commands/lanes.js +201 -51
- package/dist/commands/mesh.js +149 -1
- package/dist/commands/monitor.d.ts +1 -0
- package/dist/commands/monitor.js +4 -1
- package/dist/commands/repo.d.ts +41 -0
- package/dist/commands/repo.js +331 -0
- package/dist/commands/scan.d.ts +3 -1
- package/dist/commands/scan.js +7 -3
- package/dist/commands/secrets.d.ts +37 -0
- package/dist/commands/secrets.js +185 -16
- package/dist/commands/signals.js +17 -5
- package/dist/commands/staff/listings.d.ts +41 -0
- package/dist/commands/staff/listings.js +160 -0
- package/dist/commands/staff/reset-test-account.d.ts +3 -2
- package/dist/commands/staff/reset-test-account.js +4 -3
- package/dist/hq-cloud-sync-runner/bin/sync-runner.js +2629 -1036
- package/dist/hq-cloud-sync-runner/package.json +1 -1
- package/dist/lib/agent-kit/inbox-state.d.ts +7 -0
- package/dist/lib/agent-kit/inbox-state.js +5 -1
- package/dist/lib/agent-kit/mcp/tools.js +2 -1
- package/dist/lib/agent-kit/run/inbox.js +2 -1
- package/dist/lib/agent-kit/wake-secrets.d.ts +7 -0
- package/dist/lib/agent-kit/wake-secrets.js +32 -0
- package/dist/lib/agent-kit/wake.d.ts +4 -0
- package/dist/lib/agent-kit/wake.js +49 -3
- package/dist/lib/bot/inflight.d.ts +7 -1
- package/dist/lib/bot/inflight.js +10 -2
- package/dist/lib/bot/prompt.js +3 -0
- package/dist/lib/bot/run.js +72 -20
- package/dist/lib/bot/session.d.ts +14 -0
- package/dist/lib/bot/session.js +70 -0
- package/dist/lib/capacity/collectors/claude-oauth.d.ts +6 -0
- package/dist/lib/capacity/collectors/claude-oauth.js +12 -0
- package/dist/lib/capacity/lane-login.d.ts +6 -0
- package/dist/lib/capacity/lane-login.js +5 -0
- package/dist/lib/core-utils/worktree.js +2 -0
- package/dist/lib/daemon/exit-cause.d.ts +43 -0
- package/dist/lib/daemon/exit-cause.js +88 -0
- package/dist/lib/daemon/handoff.d.ts +2 -0
- package/dist/lib/daemon/handoff.js +6 -1
- package/dist/lib/daemon/hq-child-bootstrap.mjs +20 -3
- package/dist/lib/daemon/paths.d.ts +2 -0
- package/dist/lib/daemon/paths.js +1 -0
- package/dist/lib/daemon/run.js +13 -0
- package/dist/lib/daemon/supervisor.d.ts +54 -1
- package/dist/lib/daemon/supervisor.js +297 -21
- package/dist/lib/daemon/unit-holds.d.ts +22 -0
- package/dist/lib/daemon/unit-holds.js +86 -0
- package/dist/lib/doctor/checks/sync-health.d.ts +9 -0
- package/dist/lib/doctor/checks/sync-health.js +66 -1
- package/dist/lib/doctor/company-resolution-state.d.ts +30 -0
- package/dist/lib/doctor/company-resolution-state.js +89 -0
- package/dist/lib/doctor/fix/apply.d.ts +36 -6
- package/dist/lib/doctor/fix/apply.js +128 -12
- package/dist/lib/doctor/types.d.ts +1 -1
- package/dist/lib/doctor/types.js +1 -0
- package/dist/lib/flag-registry-worker-source.generated.d.ts +1 -1
- package/dist/lib/flag-registry-worker-source.generated.js +1 -1
- package/dist/lib/hook-engine/resolve.js +6 -2
- package/dist/lib/index-render/company-knowledge.d.ts +1 -1
- package/dist/lib/index-render/company-knowledge.js +4 -5
- package/dist/lib/index-render/index.js +44 -3
- package/dist/lib/index-render/projects.d.ts +1 -1
- package/dist/lib/index-render/projects.js +5 -5
- package/dist/lib/lanes/account-company.d.ts +28 -0
- package/dist/lib/lanes/account-company.js +53 -0
- package/dist/lib/lanes/accounts.d.ts +22 -3
- package/dist/lib/lanes/accounts.js +38 -2
- package/dist/lib/lanes/claude-balance.d.ts +6 -1
- package/dist/lib/lanes/claude-balance.js +6 -1
- package/dist/lib/lanes/claude-credential.d.ts +78 -0
- package/dist/lib/lanes/claude-credential.js +163 -0
- package/dist/lib/lanes/compaction.d.ts +2 -0
- package/dist/lib/lanes/compaction.js +9 -0
- package/dist/lib/lanes/config.js +6 -0
- package/dist/lib/lanes/envelope-repair-dispatch.js +2 -1
- package/dist/lib/lanes/hooks-install.js +2 -1
- package/dist/lib/lanes/host-capacity.d.ts +2 -0
- package/dist/lib/lanes/host-capacity.js +1 -0
- package/dist/lib/lanes/keep-alive.d.ts +1 -1
- package/dist/lib/lanes/keep-alive.js +3 -1
- package/dist/lib/lanes/links.js +10 -4
- package/dist/lib/lanes/loop.js +16 -1
- package/dist/lib/lanes/node-binary.d.ts +17 -0
- package/dist/lib/lanes/node-binary.js +30 -0
- package/dist/lib/lanes/outpost-dispatch.d.ts +2 -1
- package/dist/lib/lanes/outpost-dispatch.js +1 -0
- package/dist/lib/lanes/reconcile.js +4 -0
- package/dist/lib/lanes/remote-control-lane.js +2 -0
- package/dist/lib/lanes/spawn.d.ts +3 -0
- package/dist/lib/lanes/spawn.js +119 -9
- package/dist/lib/lanes/store.d.ts +6 -0
- package/dist/lib/lanes/store.js +107 -18
- package/dist/lib/lanes/types.d.ts +2 -0
- package/dist/lib/lanes/watch.js +28 -5
- package/dist/lib/lanes/watcher-main.js +4 -3
- package/dist/lib/lanes/watcher.d.ts +4 -0
- package/dist/lib/lanes/watcher.js +110 -21
- package/dist/lib/mesh/api.d.ts +32 -0
- package/dist/lib/mesh/api.js +75 -0
- package/dist/lib/secret-scan/detect.js +9 -0
- package/dist/lib/secret-scan/repo-config.d.ts +41 -0
- package/dist/lib/secret-scan/repo-config.js +237 -0
- package/dist/lib/secret-scan/rules.d.ts +7 -1
- package/dist/lib/secret-scan/rules.js +3 -1
- package/dist/lib/secret-scan/staged.d.ts +5 -0
- package/dist/lib/secret-scan/staged.js +19 -2
- package/dist/lib/secret-scan/types.d.ts +2 -0
- package/dist/lib/sync/server-owned-local-only.d.ts +29 -0
- package/dist/lib/sync/server-owned-local-only.js +75 -0
- package/dist/sentry-before-send.js +1 -0
- package/dist/utils/access-denied-hint.js +6 -0
- package/dist/utils/client-health.d.ts +34 -0
- package/dist/utils/client-health.js +96 -3
- package/dist/utils/package-use-lease.js +27 -9
- package/dist/utils/version-gate.d.ts +7 -0
- package/dist/utils/version-gate.js +66 -0
- package/package.json +8 -8
|
@@ -33,6 +33,7 @@ import * as path from "node:path";
|
|
|
33
33
|
import * as hqCloud from "@indigoai-us/hq-cloud";
|
|
34
34
|
import { loadManifestExports, MANIFEST_UNAVAILABLE_REASON, } from "../../hq-cloud-manifest.js";
|
|
35
35
|
import { readCompaniesManifestMap } from "../../work-context/repo-remote.js";
|
|
36
|
+
import { companyRecordedNotResolved } from "../company-resolution-state.js";
|
|
36
37
|
import { MANIFEST_DISABLED_ENV, manifestUploadsDisabled, } from "../../manifest-disabled.js";
|
|
37
38
|
import { CLI_VERSION } from "../../../cli-version.js";
|
|
38
39
|
import { readSyncVersion } from "../../../utils/feedback-versions.js";
|
|
@@ -59,6 +60,8 @@ function withDefaults(deps, hqRoot) {
|
|
|
59
60
|
unresolvedScopes: deps.unresolvedScopes ?? ((journals) => defaultUnresolvedScopes(hqRoot, journals)),
|
|
60
61
|
localCompanyExists: deps.localCompanyExists ??
|
|
61
62
|
((slug) => defaultLocalCompanyExists(hqRoot, slug)),
|
|
63
|
+
companySyncResolvable: deps.companySyncResolvable ??
|
|
64
|
+
((slug, lastSync) => !companyRecordedNotResolved(hqRoot, slug, deps.now().getTime(), lastSync)),
|
|
62
65
|
manifestUploadsDisabled: deps.manifestUploadsDisabled ?? (() => manifestUploadsDisabled()),
|
|
63
66
|
};
|
|
64
67
|
}
|
|
@@ -555,10 +558,62 @@ function journalResults(deps, journals) {
|
|
|
555
558
|
continue;
|
|
556
559
|
}
|
|
557
560
|
}
|
|
558
|
-
|
|
561
|
+
const freshness = journalFreshnessResult(entry, entry.slug, nowMs);
|
|
562
|
+
// A stale company journal whose company the sync service could not
|
|
563
|
+
// resolve on the last pull would send `--fix` into the same failure, and
|
|
564
|
+
// the health hook would then report it every day (feedback 9b4a5016).
|
|
565
|
+
// Only a WARN is downgraded: a fresh journal stays PASS and an unreadable
|
|
566
|
+
// one stays UNKNOWN.
|
|
567
|
+
if (freshness.status === "WARN" &&
|
|
568
|
+
isCompanyJournalSlug(entry.slug) &&
|
|
569
|
+
!companySyncResolvable(deps, entry.slug, entry.journal.lastSync)) {
|
|
570
|
+
results.push(notOnThisMachineResult(entry, checkId));
|
|
571
|
+
continue;
|
|
572
|
+
}
|
|
573
|
+
results.push(freshness);
|
|
559
574
|
}
|
|
560
575
|
return results;
|
|
561
576
|
}
|
|
577
|
+
function companySyncResolvable(deps, slug, lastSync) {
|
|
578
|
+
try {
|
|
579
|
+
return deps.companySyncResolvable(slug, lastSync);
|
|
580
|
+
}
|
|
581
|
+
catch {
|
|
582
|
+
// Probe failed: keep the WARN rather than hiding a possibly-live company.
|
|
583
|
+
return true;
|
|
584
|
+
}
|
|
585
|
+
}
|
|
586
|
+
function notOnThisMachineResult(entry, checkId) {
|
|
587
|
+
return {
|
|
588
|
+
status: "NA",
|
|
589
|
+
checkId,
|
|
590
|
+
reasonCode: "company-not-resolved",
|
|
591
|
+
target: entry.path,
|
|
592
|
+
message: `Sync journal '${entry.slug}' is not on this machine — the last hq doctor --fix pull could not find '${entry.slug}' for your HQ identity, so --fix will not pull it again for now.`,
|
|
593
|
+
remediation: `If you should still have '${entry.slug}', check you are signed in to the right HQ account (/hq-whoami) and ask its owner to re-invite you, then run \`hq sync pull --company ${entry.slug}\`; if not, delete its old sync journal (sync-journal.${entry.slug}.json in ~/.hq).`,
|
|
594
|
+
};
|
|
595
|
+
}
|
|
596
|
+
function latestTimestamp(...values) {
|
|
597
|
+
let best = null;
|
|
598
|
+
let bestMs = Number.NEGATIVE_INFINITY;
|
|
599
|
+
for (const value of values) {
|
|
600
|
+
const ms = value ? Date.parse(value) : Number.NaN;
|
|
601
|
+
if (Number.isFinite(ms) && ms > bestMs) {
|
|
602
|
+
bestMs = ms;
|
|
603
|
+
best = value;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
return best;
|
|
607
|
+
}
|
|
608
|
+
function manifestNotOnThisMachineResult(entry, checkId, slug) {
|
|
609
|
+
return {
|
|
610
|
+
status: "NA",
|
|
611
|
+
checkId,
|
|
612
|
+
reasonCode: "company-not-resolved",
|
|
613
|
+
message: `Scope '${entry.scopeKey}' (company '${slug}') is not on this machine — the last hq doctor --fix run could not find '${slug}' for your HQ identity, so --fix will not upload its manifest again for now.`,
|
|
614
|
+
remediation: `If you should still have '${slug}', check you are signed in to the right HQ account (/hq-whoami) and ask its owner to re-invite you, then run \`hq sync pull --company ${slug}\`.`,
|
|
615
|
+
};
|
|
616
|
+
}
|
|
562
617
|
// ─── Manifest upload freshness (sync-reconciliation-audit US-004) ────────────
|
|
563
618
|
/**
|
|
564
619
|
* A scope whose last manifest upload is older than this is reported stale.
|
|
@@ -651,6 +706,16 @@ function manifestResults(deps, journals) {
|
|
|
651
706
|
// Without it the row stays manual-only. Personal must be explicit: a
|
|
652
707
|
// bare `hq sync manifest` uploads the active company instead.
|
|
653
708
|
const scope = manifestUploadScope(entry);
|
|
709
|
+
// Same evidence as the journal row: a company a --fix run could not
|
|
710
|
+
// resolve is not on this machine, so its manifest is NA too. The later
|
|
711
|
+
// of the journal sync and this upload counts as a later success.
|
|
712
|
+
if (scope && scope !== "personal") {
|
|
713
|
+
const journalSync = journals.find((j) => j.slug === scope)?.journal.lastSync ?? null;
|
|
714
|
+
const latest = latestTimestamp(journalSync, entry.lastUploadAt);
|
|
715
|
+
if (!companySyncResolvable(deps, scope, latest)) {
|
|
716
|
+
return manifestNotOnThisMachineResult(entry, checkId, scope);
|
|
717
|
+
}
|
|
718
|
+
}
|
|
654
719
|
const command = `hq sync manifest${scope ? ` --scope ${scope}` : ""}`;
|
|
655
720
|
const message = `Scope '${entry.scopeKey}' last uploaded a sync manifest ${days} day${days === 1 ? "" : "s"} ago (${entry.lastUploadAt}) — the daily audit pass is not running.`;
|
|
656
721
|
// With the kill switch set the upload exits 0 and sends nothing, so the
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Companies a `hq doctor --fix` pull found the sync service cannot resolve
|
|
3
|
+
* for this machine's signed-in identity (feedback 9b4a5016).
|
|
4
|
+
*
|
|
5
|
+
* The record is evidence, not a guess: it is written only when a targeted
|
|
6
|
+
* `hq sync pull --company <slug>` failed because the service has no company
|
|
7
|
+
* by that slug for the caller, or refused the caller access to it. The sync
|
|
8
|
+
* health check then reports that company's stale journal as "not on this
|
|
9
|
+
* machine" instead of an auto-fixable WARN, so `--fix` stops pulling it and
|
|
10
|
+
* the client-health hook stops reporting it. A successful pull clears the
|
|
11
|
+
* entry, and an entry expires after {@link COMPANY_NOT_RESOLVED_TTL_MS} so a
|
|
12
|
+
* re-joined company is tried again.
|
|
13
|
+
*/
|
|
14
|
+
export declare const COMPANY_RESOLUTION_STATE_RELATIVE_PATH: string;
|
|
15
|
+
/** An unresolved-company record is retried after this long. */
|
|
16
|
+
export declare const COMPANY_NOT_RESOLVED_TTL_MS: number;
|
|
17
|
+
/**
|
|
18
|
+
* True when the output of a failed `hq sync pull --company <slug>` shows the
|
|
19
|
+
* service could not resolve the company for this caller: no company by that
|
|
20
|
+
* slug in the caller's namespace, or a 403/404 on the membership-gated
|
|
21
|
+
* credential vend. A bare 403/404 on the slug lookup is not used: a gateway
|
|
22
|
+
* or authorizer fault answers the same way. Network, timeout, and server
|
|
23
|
+
* errors are not evidence.
|
|
24
|
+
*/
|
|
25
|
+
export declare function pullOutputShowsCompanyNotResolved(output: string): boolean;
|
|
26
|
+
/** Record that a pull could not resolve `slug`, or clear it after a good pull. */
|
|
27
|
+
export declare function recordCompanyResolution(hqRoot: string, slug: string, resolved: boolean, nowMs: number): void;
|
|
28
|
+
/** True when a pull within the TTL could not resolve `slug`. Never throws. */
|
|
29
|
+
export declare function companyRecordedNotResolved(hqRoot: string, slug: string, nowMs: number, lastSyncedAt?: string | null): boolean;
|
|
30
|
+
//# sourceMappingURL=company-resolution-state.d.ts.map
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Companies a `hq doctor --fix` pull found the sync service cannot resolve
|
|
3
|
+
* for this machine's signed-in identity (feedback 9b4a5016).
|
|
4
|
+
*
|
|
5
|
+
* The record is evidence, not a guess: it is written only when a targeted
|
|
6
|
+
* `hq sync pull --company <slug>` failed because the service has no company
|
|
7
|
+
* by that slug for the caller, or refused the caller access to it. The sync
|
|
8
|
+
* health check then reports that company's stale journal as "not on this
|
|
9
|
+
* machine" instead of an auto-fixable WARN, so `--fix` stops pulling it and
|
|
10
|
+
* the client-health hook stops reporting it. A successful pull clears the
|
|
11
|
+
* entry, and an entry expires after {@link COMPANY_NOT_RESOLVED_TTL_MS} so a
|
|
12
|
+
* re-joined company is tried again.
|
|
13
|
+
*/
|
|
14
|
+
import * as fs from "node:fs";
|
|
15
|
+
import * as path from "node:path";
|
|
16
|
+
export const COMPANY_RESOLUTION_STATE_RELATIVE_PATH = path.join("workspace", "doctor", "unresolved-companies.json");
|
|
17
|
+
/** An unresolved-company record is retried after this long. */
|
|
18
|
+
export const COMPANY_NOT_RESOLVED_TTL_MS = 14 * 24 * 60 * 60 * 1000;
|
|
19
|
+
/**
|
|
20
|
+
* True when the output of a failed `hq sync pull --company <slug>` shows the
|
|
21
|
+
* service could not resolve the company for this caller: no company by that
|
|
22
|
+
* slug in the caller's namespace, or a 403/404 on the membership-gated
|
|
23
|
+
* credential vend. A bare 403/404 on the slug lookup is not used: a gateway
|
|
24
|
+
* or authorizer fault answers the same way. Network, timeout, and server
|
|
25
|
+
* errors are not evidence.
|
|
26
|
+
*/
|
|
27
|
+
export function pullOutputShowsCompanyNotResolved(output) {
|
|
28
|
+
return (/No entity in caller's namespace matches company\//.test(output) ||
|
|
29
|
+
/STS \/sts\/vend failed: 40[34]\b/.test(output));
|
|
30
|
+
}
|
|
31
|
+
function statePath(hqRoot) {
|
|
32
|
+
return path.join(hqRoot, COMPANY_RESOLUTION_STATE_RELATIVE_PATH);
|
|
33
|
+
}
|
|
34
|
+
function readState(hqRoot) {
|
|
35
|
+
try {
|
|
36
|
+
const parsed = JSON.parse(fs.readFileSync(statePath(hqRoot), "utf8"));
|
|
37
|
+
if (parsed &&
|
|
38
|
+
typeof parsed === "object" &&
|
|
39
|
+
parsed.version === 1 &&
|
|
40
|
+
typeof parsed.notResolved === "object" &&
|
|
41
|
+
parsed.notResolved !== null) {
|
|
42
|
+
const notResolved = Object.create(null);
|
|
43
|
+
for (const [slug, atMs] of Object.entries(parsed.notResolved)) {
|
|
44
|
+
if (typeof atMs === "number" && Number.isFinite(atMs))
|
|
45
|
+
notResolved[slug] = atMs;
|
|
46
|
+
}
|
|
47
|
+
return { version: 1, notResolved };
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
catch {
|
|
51
|
+
// Missing or unreadable state is "nothing recorded".
|
|
52
|
+
}
|
|
53
|
+
return { version: 1, notResolved: Object.create(null) };
|
|
54
|
+
}
|
|
55
|
+
function writeState(hqRoot, state) {
|
|
56
|
+
const target = statePath(hqRoot);
|
|
57
|
+
fs.mkdirSync(path.dirname(target), { recursive: true });
|
|
58
|
+
const temporaryPath = `${target}.${process.pid}.tmp`;
|
|
59
|
+
fs.writeFileSync(temporaryPath, `${JSON.stringify(state)}\n`, { mode: 0o600 });
|
|
60
|
+
fs.renameSync(temporaryPath, target);
|
|
61
|
+
}
|
|
62
|
+
/** Record that a pull could not resolve `slug`, or clear it after a good pull. */
|
|
63
|
+
export function recordCompanyResolution(hqRoot, slug, resolved, nowMs) {
|
|
64
|
+
const state = readState(hqRoot);
|
|
65
|
+
if (resolved) {
|
|
66
|
+
if (!Object.hasOwn(state.notResolved, slug))
|
|
67
|
+
return;
|
|
68
|
+
delete state.notResolved[slug];
|
|
69
|
+
}
|
|
70
|
+
else {
|
|
71
|
+
state.notResolved[slug] = nowMs;
|
|
72
|
+
}
|
|
73
|
+
writeState(hqRoot, state);
|
|
74
|
+
}
|
|
75
|
+
/** True when a pull within the TTL could not resolve `slug`. Never throws. */
|
|
76
|
+
export function companyRecordedNotResolved(hqRoot, slug, nowMs, lastSyncedAt) {
|
|
77
|
+
const notResolved = readState(hqRoot).notResolved;
|
|
78
|
+
if (!Object.hasOwn(notResolved, slug))
|
|
79
|
+
return false;
|
|
80
|
+
const atMs = notResolved[slug];
|
|
81
|
+
// A daemon/manual pull after this failure is stronger evidence than the
|
|
82
|
+
// cached failure, even if that successful sync has since become stale.
|
|
83
|
+
const lastSyncMs = lastSyncedAt ? Date.parse(lastSyncedAt) : Number.NaN;
|
|
84
|
+
if (Number.isFinite(lastSyncMs) && lastSyncMs > atMs)
|
|
85
|
+
return false;
|
|
86
|
+
const ageMs = nowMs - atMs;
|
|
87
|
+
return ageMs >= 0 && ageMs <= COMPANY_NOT_RESOLVED_TTL_MS;
|
|
88
|
+
}
|
|
89
|
+
//# sourceMappingURL=company-resolution-state.js.map
|
|
@@ -61,6 +61,12 @@ export interface ApplyFixesOptions {
|
|
|
61
61
|
confirm?: () => boolean | Promise<boolean>;
|
|
62
62
|
/** Clock for the backup directory name. Default: `new Date()`. */
|
|
63
63
|
now?: () => Date;
|
|
64
|
+
/**
|
|
65
|
+
* Monotonic milliseconds for the network-repair time budget. Default:
|
|
66
|
+
* `performance.now()`. Tests inject a fake clock so a slow-pull budget run
|
|
67
|
+
* does not take minutes of real time.
|
|
68
|
+
*/
|
|
69
|
+
elapsedMs?: () => number;
|
|
64
70
|
/**
|
|
65
71
|
* The dirty-tree probe: the changed paths under {@link HOOK_CONFIG_DIRS}, or
|
|
66
72
|
* [] when clean or not a git repo. Default shells out to `git status`.
|
|
@@ -121,6 +127,12 @@ export interface RefreshSyncResult {
|
|
|
121
127
|
outcome: RefreshSyncOutcome;
|
|
122
128
|
/** One-line summary for the post-fix report. */
|
|
123
129
|
message: string;
|
|
130
|
+
/**
|
|
131
|
+
* True when the pull failed because the sync service could not resolve
|
|
132
|
+
* the company for this caller (no such company, or access refused).
|
|
133
|
+
* Recorded so the next check reports the journal "not on this machine".
|
|
134
|
+
*/
|
|
135
|
+
companyNotResolved?: boolean;
|
|
124
136
|
}
|
|
125
137
|
/** Per-company bound for the default `hq sync pull` repair. */
|
|
126
138
|
export declare const REFRESH_SYNC_TIMEOUT_MS = 90000;
|
|
@@ -142,6 +154,12 @@ export interface RefreshManifestResult {
|
|
|
142
154
|
* unresolved-check next step. Absent when the upload exited 0.
|
|
143
155
|
*/
|
|
144
156
|
reason?: string;
|
|
157
|
+
/**
|
|
158
|
+
* The upload failed because the company is not visible to this identity
|
|
159
|
+
* ("No membership found"). Recorded as unresolved-company evidence only
|
|
160
|
+
* after another company succeeds in the same run.
|
|
161
|
+
*/
|
|
162
|
+
companyNotResolved?: boolean;
|
|
145
163
|
}
|
|
146
164
|
/**
|
|
147
165
|
* Why a sync check is still FAIL or WARN after `--fix`:
|
|
@@ -189,12 +207,22 @@ export interface AppliedFixBackup {
|
|
|
189
207
|
/** A newly unlocked safe repair can be picked up for at most this many passes. */
|
|
190
208
|
export declare const MAX_FIX_PASSES = 3;
|
|
191
209
|
/**
|
|
192
|
-
*
|
|
193
|
-
*
|
|
194
|
-
*
|
|
195
|
-
*
|
|
210
|
+
* Ceiling on network repairs per run. A run refreshes every resolvable stale
|
|
211
|
+
* journal up to this many; above it, `--fix` says so and the rotating cursor
|
|
212
|
+
* picks up the rest next run. Journal pulls and manifest uploads share this
|
|
213
|
+
* budget. When both kinds are pending and no upload has run yet, one slot is
|
|
214
|
+
* held for a manifest upload so stale journals cannot starve every manifest.
|
|
215
|
+
* Each repair is bounded at 90s, and unattended runs also stop starting new
|
|
216
|
+
* repairs after REFRESH_SYNC_START_BUDGET_MS (feedback 9b4a5016).
|
|
217
|
+
*/
|
|
218
|
+
export declare const MAX_REFRESH_SYNC_PLANS_PER_RUN = 10;
|
|
219
|
+
/**
|
|
220
|
+
* No new network repair starts once this much of the run has passed. Health
|
|
221
|
+
* hooks bound `--fix` at 300s (older hooks are still in the field), so the
|
|
222
|
+
* last 90s repair started here still ends by 210s, leaving room for the
|
|
223
|
+
* check passes. Fast pulls reach the count ceiling well before this.
|
|
196
224
|
*/
|
|
197
|
-
export declare const
|
|
225
|
+
export declare const REFRESH_SYNC_START_BUDGET_MS = 120000;
|
|
198
226
|
/** The outcome of an {@link applyFixes} run. */
|
|
199
227
|
export interface ApplyFixesResult {
|
|
200
228
|
/** 0 on success, decline, or a clean no-op; 1 when a dirty tree is refused. */
|
|
@@ -268,7 +296,9 @@ export declare function defaultRefreshManifest(target: RefreshManifestTarget): P
|
|
|
268
296
|
* A spawn killed by the timeout has a null status, so it is reported as a
|
|
269
297
|
* timeout rather than as an exit code.
|
|
270
298
|
*/
|
|
271
|
-
export declare function manifestUploadResult(scope: string, result: Pick<SpawnSyncReturns<string>, "error" | "status" | "signal">): RefreshManifestResult;
|
|
299
|
+
export declare function manifestUploadResult(scope: string, result: Pick<SpawnSyncReturns<string>, "error" | "status" | "signal"> & Partial<Pick<SpawnSyncReturns<string>, "stdout" | "stderr">>): RefreshManifestResult;
|
|
300
|
+
/** `hq sync manifest` output for a company this identity is not a member of. */
|
|
301
|
+
export declare function manifestOutputShowsNoMembership(output: string): boolean;
|
|
272
302
|
/**
|
|
273
303
|
* The uncommitted changes under {@link HOOK_CONFIG_DIRS}, one porcelain line
|
|
274
304
|
* each. Returns [] when the tree is clean OR when `hqRoot` is not a git repo
|
|
@@ -48,6 +48,7 @@ import { botDir } from "../../bot/paths.js";
|
|
|
48
48
|
import { readBotConfig } from "../../bot/config.js";
|
|
49
49
|
import { buildBotDaemonPaths, installedDaemonHqBinary, repairBotDaemonIfStale, rewriteBotDaemonNodePath, } from "../../bot/daemon.js";
|
|
50
50
|
import { classifyRefreshSyncOutcome, } from "./refresh-sync-outcome.js";
|
|
51
|
+
import { pullOutputShowsCompanyNotResolved, recordCompanyResolution, } from "../company-resolution-state.js";
|
|
51
52
|
import { readRefreshManifestCursor, readRefreshSyncProgress, writeRefreshManifestCursor, writeRefreshSyncCursor, writeRefreshSyncLastRun, } from "./refresh-sync-progress.js";
|
|
52
53
|
import { clientHealthShowManualOnlyEnabled } from "../client-health-reason-codes-flag.js";
|
|
53
54
|
/** The tree subtrees whose uncommitted changes block a `--fix` run. */
|
|
@@ -57,12 +58,22 @@ export const REFRESH_SYNC_TIMEOUT_MS = 90_000;
|
|
|
57
58
|
/** A newly unlocked safe repair can be picked up for at most this many passes. */
|
|
58
59
|
export const MAX_FIX_PASSES = 3;
|
|
59
60
|
/**
|
|
60
|
-
*
|
|
61
|
-
*
|
|
62
|
-
*
|
|
63
|
-
*
|
|
61
|
+
* Ceiling on network repairs per run. A run refreshes every resolvable stale
|
|
62
|
+
* journal up to this many; above it, `--fix` says so and the rotating cursor
|
|
63
|
+
* picks up the rest next run. Journal pulls and manifest uploads share this
|
|
64
|
+
* budget. When both kinds are pending and no upload has run yet, one slot is
|
|
65
|
+
* held for a manifest upload so stale journals cannot starve every manifest.
|
|
66
|
+
* Each repair is bounded at 90s, and unattended runs also stop starting new
|
|
67
|
+
* repairs after REFRESH_SYNC_START_BUDGET_MS (feedback 9b4a5016).
|
|
64
68
|
*/
|
|
65
|
-
export const MAX_REFRESH_SYNC_PLANS_PER_RUN =
|
|
69
|
+
export const MAX_REFRESH_SYNC_PLANS_PER_RUN = 10;
|
|
70
|
+
/**
|
|
71
|
+
* No new network repair starts once this much of the run has passed. Health
|
|
72
|
+
* hooks bound `--fix` at 300s (older hooks are still in the field), so the
|
|
73
|
+
* last 90s repair started here still ends by 210s, leaving room for the
|
|
74
|
+
* check passes. Fast pulls reach the count ceiling well before this.
|
|
75
|
+
*/
|
|
76
|
+
export const REFRESH_SYNC_START_BUDGET_MS = 120_000;
|
|
66
77
|
function rotateRefreshSyncPlans(plans, afterSlug) {
|
|
67
78
|
const ordered = [...plans].sort((a, b) => a.target < b.target ? -1 : a.target > b.target ? 1 : 0);
|
|
68
79
|
if (!afterSlug || ordered.length < 2)
|
|
@@ -138,8 +149,52 @@ export async function applyFixes(options) {
|
|
|
138
149
|
writeErr("HQ client-health manual-only flag lookup failed; keeping manual-only instructions hidden.\n");
|
|
139
150
|
}
|
|
140
151
|
const collect = options.runChecks ?? defaultRunChecks;
|
|
141
|
-
const
|
|
142
|
-
|
|
152
|
+
const performRefreshSync = options.refreshSync ?? defaultRefreshSync;
|
|
153
|
+
// A namespace error can also be a server response regression. Only mark a
|
|
154
|
+
// missing company when a different company succeeds in this same run; an
|
|
155
|
+
// all-company outage must stay WARN and visible to client health.
|
|
156
|
+
const successfulCompanies = new Set();
|
|
157
|
+
const unresolvedCompanyPulls = new Map();
|
|
158
|
+
const noteCompanyOutcome = (slug, ok, notResolved) => {
|
|
159
|
+
try {
|
|
160
|
+
if (ok) {
|
|
161
|
+
successfulCompanies.add(slug);
|
|
162
|
+
unresolvedCompanyPulls.delete(slug);
|
|
163
|
+
recordCompanyResolution(hqRoot, slug, true, Date.now());
|
|
164
|
+
}
|
|
165
|
+
else if (notResolved) {
|
|
166
|
+
unresolvedCompanyPulls.set(slug, Date.now());
|
|
167
|
+
}
|
|
168
|
+
for (const [pending, atMs] of unresolvedCompanyPulls) {
|
|
169
|
+
// A success for this same company cannot establish peer health:
|
|
170
|
+
// its manifest may be exposing a real membership-path failure.
|
|
171
|
+
if (![...successfulCompanies].some((successful) => successful !== pending))
|
|
172
|
+
continue;
|
|
173
|
+
recordCompanyResolution(hqRoot, pending, false, atMs);
|
|
174
|
+
unresolvedCompanyPulls.delete(pending);
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
catch {
|
|
178
|
+
// Best effort: failure to persist evidence keeps the stale WARN visible.
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
const refreshSync = async (target) => {
|
|
182
|
+
const result = await performRefreshSync(target);
|
|
183
|
+
if (target.personal)
|
|
184
|
+
return result;
|
|
185
|
+
noteCompanyOutcome(target.slug, result.ok, result.companyNotResolved === true);
|
|
186
|
+
return result;
|
|
187
|
+
};
|
|
188
|
+
const performRefreshManifest = options.refreshManifest ?? defaultRefreshManifest;
|
|
189
|
+
// Manifest uploads feed the same evidence: "No membership found" waits for
|
|
190
|
+
// a peer company to succeed, so an all-company outage stays WARN.
|
|
191
|
+
const refreshManifest = async (target) => {
|
|
192
|
+
const result = await performRefreshManifest(target);
|
|
193
|
+
if (target.scope === "personal")
|
|
194
|
+
return result;
|
|
195
|
+
noteCompanyOutcome(target.scope, result.ok, result.companyNotResolved === true);
|
|
196
|
+
return result;
|
|
197
|
+
};
|
|
143
198
|
const rewriteBotNodePath = options.rewriteBotNodePath ?? defaultRewriteBotNodePath;
|
|
144
199
|
const repairBotHqBinary = options.repairBotHqBinary ?? defaultRepairBotHqBinary;
|
|
145
200
|
// 2. Collect findings, then plan each newly unlocked allowlisted repair.
|
|
@@ -149,6 +204,21 @@ export async function applyFixes(options) {
|
|
|
149
204
|
let lastAttemptedRefreshSyncSlug = readRefreshSyncProgress(hqRoot)?.lastAttemptedSlug ?? null;
|
|
150
205
|
let refreshSyncAppliedThisRun = 0;
|
|
151
206
|
let refreshManifestAppliedThisRun = 0;
|
|
207
|
+
let refreshCeilingSkipped = 0;
|
|
208
|
+
const elapsedMs = options.elapsedMs ?? (() => performance.now());
|
|
209
|
+
const runStartedMs = elapsedMs();
|
|
210
|
+
let networkBudgetSpent = false;
|
|
211
|
+
// Only unattended (--yes) runs have a caller with a deadline. An
|
|
212
|
+
// interactive run waits on the confirmation prompt, which must not use up
|
|
213
|
+
// the budget.
|
|
214
|
+
const networkBudgetLeft = () => {
|
|
215
|
+
if (!options.yes)
|
|
216
|
+
return true;
|
|
217
|
+
if (!networkBudgetSpent && elapsedMs() - runStartedMs >= REFRESH_SYNC_START_BUDGET_MS) {
|
|
218
|
+
networkBudgetSpent = true;
|
|
219
|
+
}
|
|
220
|
+
return !networkBudgetSpent;
|
|
221
|
+
};
|
|
152
222
|
let lastAttemptedRefreshManifestScope = readRefreshManifestCursor(hqRoot);
|
|
153
223
|
const appliedKeys = new Set();
|
|
154
224
|
const planned = [];
|
|
@@ -177,20 +247,24 @@ export async function applyFixes(options) {
|
|
|
177
247
|
keysThisPass.add(key);
|
|
178
248
|
plans.push(plan);
|
|
179
249
|
}
|
|
180
|
-
const networkSlotsLeft =
|
|
250
|
+
const networkSlotsLeft = networkBudgetLeft()
|
|
251
|
+
? Math.max(0, MAX_REFRESH_SYNC_PLANS_PER_RUN - refreshSyncAppliedThisRun - refreshManifestAppliedThisRun)
|
|
252
|
+
: 0;
|
|
181
253
|
// Both kinds start after the company attempted last, so a scope that
|
|
182
254
|
// never clears cannot hold the front of the line on every run.
|
|
183
255
|
const manifestCandidates = rotateRefreshSyncPlans(plans.filter((plan) => plan.fixClass === "refresh-manifest"), lastAttemptedRefreshManifestScope);
|
|
184
256
|
// Hold one slot for an upload until one has run this invocation.
|
|
185
257
|
const manifestReserve = manifestCandidates.length > 0 && refreshManifestAppliedThisRun === 0 ? 1 : 0;
|
|
186
258
|
const seenRefreshTargets = new Set();
|
|
187
|
-
const
|
|
259
|
+
const refreshCandidates = rotateRefreshSyncPlans(plans.filter((plan) => plan.fixClass === "refresh-sync"), lastAttemptedRefreshSyncSlug).filter((plan) => {
|
|
188
260
|
if (seenRefreshTargets.has(plan.target))
|
|
189
261
|
return false;
|
|
190
262
|
seenRefreshTargets.add(plan.target);
|
|
191
263
|
return true;
|
|
192
|
-
})
|
|
193
|
-
const
|
|
264
|
+
});
|
|
265
|
+
const refreshPlans = refreshCandidates.slice(0, Math.max(0, networkSlotsLeft - manifestReserve));
|
|
266
|
+
refreshCeilingSkipped = refreshCandidates.length - refreshPlans.length;
|
|
267
|
+
const manifestPlans = manifestCandidates.slice(0, Math.max(0, networkSlotsLeft - refreshPlans.length));
|
|
194
268
|
return [
|
|
195
269
|
...plans.filter((plan) => plan.fixClass !== "refresh-sync" && plan.fixClass !== "refresh-manifest"),
|
|
196
270
|
...refreshPlans,
|
|
@@ -223,6 +297,7 @@ export async function applyFixes(options) {
|
|
|
223
297
|
write("Aborted — no changes written.\n");
|
|
224
298
|
if (showManualOnlyRemediations)
|
|
225
299
|
writeManualOnlyRemediations(findings, write);
|
|
300
|
+
writeNotOnThisMachine(findings, write);
|
|
226
301
|
const unresolved = unresolvedSyncChecks(findings, []);
|
|
227
302
|
writeUnresolvedSyncChecks(unresolved, write);
|
|
228
303
|
return empty({ refused, fixableCount, unresolved });
|
|
@@ -248,6 +323,11 @@ export async function applyFixes(options) {
|
|
|
248
323
|
// 5. Apply each plan once. Later findings with this check id + target are
|
|
249
324
|
// excluded even if this repair did not make its check pass.
|
|
250
325
|
for (const plan of plans) {
|
|
326
|
+
const network = plan.fixClass === "refresh-sync" || plan.fixClass === "refresh-manifest";
|
|
327
|
+
// Leave the plan unapplied (not in appliedKeys) so it is counted as
|
|
328
|
+
// left over and reported "not attempted" rather than as a failed repair.
|
|
329
|
+
if (network && !networkBudgetLeft())
|
|
330
|
+
continue;
|
|
251
331
|
if (plan.fixClass === "refresh-sync") {
|
|
252
332
|
// Persist before starting the bounded pull. If this process is killed,
|
|
253
333
|
// the next invocation resumes after this company instead of starving
|
|
@@ -272,6 +352,7 @@ export async function applyFixes(options) {
|
|
|
272
352
|
write("hq doctor --fix: nothing to repair — no auto-fixable findings.\n");
|
|
273
353
|
if (showManualOnlyRemediations)
|
|
274
354
|
writeManualOnlyRemediations(findings, write);
|
|
355
|
+
writeNotOnThisMachine(findings, write);
|
|
275
356
|
const unresolved = unresolvedSyncChecks(findings, []);
|
|
276
357
|
writeUnresolvedSyncChecks(unresolved, write);
|
|
277
358
|
return empty({ refused, fixableCount, unresolved });
|
|
@@ -308,9 +389,20 @@ export async function applyFixes(options) {
|
|
|
308
389
|
: null;
|
|
309
390
|
if (restoreCommand)
|
|
310
391
|
write(`To restore all changes: ${restoreCommand}\n`);
|
|
311
|
-
|
|
392
|
+
// Planning against the final findings also recounts the journals left over
|
|
393
|
+
// the refresh ceiling, so the note below reports what is actually left.
|
|
394
|
+
const remainingPlans = buildPlans(findings);
|
|
395
|
+
if (planned.at(-1)?.pass === MAX_FIX_PASSES && remainingPlans.length > 0) {
|
|
312
396
|
write(`Stopped after ${MAX_FIX_PASSES} repair passes; new auto-fixable findings remain.\n`);
|
|
313
397
|
}
|
|
398
|
+
if (refreshCeilingSkipped > 0) {
|
|
399
|
+
const limit = refreshSyncAppliedThisRun >= MAX_REFRESH_SYNC_PLANS_PER_RUN
|
|
400
|
+
? `--fix refreshes at most ${MAX_REFRESH_SYNC_PLANS_PER_RUN} in one run`
|
|
401
|
+
: `--fix stops starting pulls after ${REFRESH_SYNC_START_BUDGET_MS / 1000}s`;
|
|
402
|
+
write(`\nRefreshed ${refreshSyncAppliedThisRun} stale sync journal${refreshSyncAppliedThisRun === 1 ? "" : "s"}; ${limit}. ` +
|
|
403
|
+
`${refreshCeilingSkipped} more ${refreshCeilingSkipped === 1 ? "is" : "are"} left. Run \`hq doctor --fix\` again to refresh ${refreshCeilingSkipped === 1 ? "it" : "them"}.\n`);
|
|
404
|
+
}
|
|
405
|
+
writeNotOnThisMachine(findings, write);
|
|
314
406
|
if (showManualOnlyRemediations)
|
|
315
407
|
writeManualOnlyRemediations(findings, write);
|
|
316
408
|
const unresolved = unresolvedSyncChecks(findings, planned.map(({ plan }) => plan));
|
|
@@ -393,6 +485,19 @@ function writeUnresolvedSyncChecks(unresolved, write) {
|
|
|
393
485
|
write(` ${entry.status} ${entry.checkId}\n Next step: ${entry.nextStep}\n`);
|
|
394
486
|
}
|
|
395
487
|
}
|
|
488
|
+
/**
|
|
489
|
+
* Print the stale company journals `--fix` skipped because the company is not
|
|
490
|
+
* set up for sync on this machine, each with its one next step.
|
|
491
|
+
*/
|
|
492
|
+
function writeNotOnThisMachine(findings, write) {
|
|
493
|
+
const skipped = findings.filter((result) => result.reasonCode === "company-not-resolved");
|
|
494
|
+
if (skipped.length === 0)
|
|
495
|
+
return;
|
|
496
|
+
write("\nSkipped — not on this machine (--fix cannot pull these):\n");
|
|
497
|
+
for (const result of skipped) {
|
|
498
|
+
write(` ${result.checkId}\n Next step: ${result.remediation ?? "Run `hq doctor --only sync` for details."}\n`);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
396
501
|
/** Print the available user action for findings that `--fix` deliberately skips. */
|
|
397
502
|
function writeManualOnlyRemediations(findings, write) {
|
|
398
503
|
const manual = findings.flatMap((result) => {
|
|
@@ -497,6 +602,9 @@ export function defaultRefreshSync(target) {
|
|
|
497
602
|
ok: false,
|
|
498
603
|
outcome,
|
|
499
604
|
message: `pull of '${target.slug}' exited ${result.status}${detail ? `: ${detail}` : ""}`,
|
|
605
|
+
companyNotResolved: !target.personal &&
|
|
606
|
+
!isPersonalJournalSlug(target.slug) &&
|
|
607
|
+
pullOutputShowsCompanyNotResolved(`${result.stdout ?? ""}\n${result.stderr ?? ""}`),
|
|
500
608
|
});
|
|
501
609
|
}
|
|
502
610
|
return Promise.resolve({
|
|
@@ -544,10 +652,18 @@ export function manifestUploadResult(scope, result) {
|
|
|
544
652
|
ok: false,
|
|
545
653
|
reason: `the upload exited with code ${result.status}`,
|
|
546
654
|
message: `manifest upload for '${scope}' exited ${result.status}`,
|
|
655
|
+
...(scope !== "personal" &&
|
|
656
|
+
manifestOutputShowsNoMembership(`${result.stdout ?? ""}\n${result.stderr ?? ""}`)
|
|
657
|
+
? { companyNotResolved: true }
|
|
658
|
+
: {}),
|
|
547
659
|
};
|
|
548
660
|
}
|
|
549
661
|
return { ok: true, message: `uploaded the manifest for '${scope}'` };
|
|
550
662
|
}
|
|
663
|
+
/** `hq sync manifest` output for a company this identity is not a member of. */
|
|
664
|
+
export function manifestOutputShowsNoMembership(output) {
|
|
665
|
+
return /No membership found/.test(output) || pullOutputShowsCompanyNotResolved(output);
|
|
666
|
+
}
|
|
551
667
|
// --- Dirty-tree probe ----------------------------------------------------------
|
|
552
668
|
/**
|
|
553
669
|
* The uncommitted changes under {@link HOOK_CONFIG_DIRS}, one porcelain line
|
|
@@ -19,7 +19,7 @@ export declare const DOCTOR_STATUSES: readonly ["PASS", "FAIL", "WARN", "UNTESTE
|
|
|
19
19
|
*/
|
|
20
20
|
export type DoctorStatus = (typeof DOCTOR_STATUSES)[number];
|
|
21
21
|
/** Stable, privacy-safe diagnostics emitted by the sync health family. */
|
|
22
|
-
export declare const SYNC_REASON_CODES: readonly ["core-version-unavailable", "core-update-available", "no-local-tree", "legacy-personal-journal", "never-synced", "invalid-last-sync", "stale-threshold", "manifest-unavailable", "manifest-state-unavailable", "no-manifest-scopes", "never-uploaded", "invalid-upload-time", "unresolved-company-uid", "realtime-lag-stuck", "receiver-permission-denied", "runner-restart-loop", "device-limit-backoff", "repeated-conflicts"];
|
|
22
|
+
export declare const SYNC_REASON_CODES: readonly ["core-version-unavailable", "core-update-available", "no-local-tree", "company-not-resolved", "legacy-personal-journal", "never-synced", "invalid-last-sync", "stale-threshold", "manifest-unavailable", "manifest-state-unavailable", "no-manifest-scopes", "never-uploaded", "invalid-upload-time", "unresolved-company-uid", "realtime-lag-stuck", "receiver-permission-denied", "runner-restart-loop", "device-limit-backoff", "repeated-conflicts"];
|
|
23
23
|
export type SyncReasonCode = (typeof SYNC_REASON_CODES)[number];
|
|
24
24
|
/** Runtime type guard: true only for a member of the fixed vocabulary. */
|
|
25
25
|
export declare function isDoctorStatus(value: unknown): value is DoctorStatus;
|