@indigoai-us/hq-cloud 6.15.0 → 6.15.2
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/dist/bin/sync-mutation.d.ts +16 -0
- package/dist/bin/sync-mutation.d.ts.map +1 -0
- package/dist/bin/sync-mutation.js +60 -0
- package/dist/bin/sync-mutation.js.map +1 -0
- package/dist/bin/sync-mutation.test.d.ts +2 -0
- package/dist/bin/sync-mutation.test.d.ts.map +1 -0
- package/dist/bin/sync-mutation.test.js +165 -0
- package/dist/bin/sync-mutation.test.js.map +1 -0
- package/dist/bin/sync-runner-company.d.ts +8 -0
- package/dist/bin/sync-runner-company.d.ts.map +1 -1
- package/dist/bin/sync-runner-company.js +16 -0
- package/dist/bin/sync-runner-company.js.map +1 -1
- package/dist/bin/sync-runner-company.test.d.ts +2 -0
- package/dist/bin/sync-runner-company.test.d.ts.map +1 -0
- package/dist/bin/sync-runner-company.test.js +36 -0
- package/dist/bin/sync-runner-company.test.js.map +1 -0
- package/dist/bin/sync-runner-watch-loop.d.ts.map +1 -1
- package/dist/bin/sync-runner-watch-loop.js +98 -8
- package/dist/bin/sync-runner-watch-loop.js.map +1 -1
- package/dist/bin/sync-runner.d.ts +17 -0
- package/dist/bin/sync-runner.d.ts.map +1 -1
- package/dist/bin/sync-runner.js.map +1 -1
- package/dist/bin/sync-runner.test.js +109 -0
- package/dist/bin/sync-runner.test.js.map +1 -1
- package/dist/cli/conflict-recovery.test.d.ts +2 -0
- package/dist/cli/conflict-recovery.test.d.ts.map +1 -0
- package/dist/cli/conflict-recovery.test.js +201 -0
- package/dist/cli/conflict-recovery.test.js.map +1 -0
- package/dist/cli/conflict.d.ts +60 -0
- package/dist/cli/conflict.d.ts.map +1 -1
- package/dist/cli/conflict.js +333 -0
- package/dist/cli/conflict.js.map +1 -1
- package/dist/cli/sync.d.ts +35 -0
- package/dist/cli/sync.d.ts.map +1 -1
- package/dist/cli/sync.js +100 -0
- package/dist/cli/sync.js.map +1 -1
- package/dist/cli/sync.test.js +85 -1
- package/dist/cli/sync.test.js.map +1 -1
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -0
- package/dist/index.js.map +1 -1
- package/dist/skill-telemetry.d.ts +6 -0
- package/dist/skill-telemetry.d.ts.map +1 -1
- package/dist/skill-telemetry.js +14 -2
- package/dist/skill-telemetry.js.map +1 -1
- package/dist/skill-telemetry.test.js +79 -0
- package/dist/skill-telemetry.test.js.map +1 -1
- package/dist/sync/candidate-uploader.d.ts +88 -0
- package/dist/sync/candidate-uploader.d.ts.map +1 -0
- package/dist/sync/candidate-uploader.js +212 -0
- package/dist/sync/candidate-uploader.js.map +1 -0
- package/dist/sync/candidate-uploader.test.d.ts +2 -0
- package/dist/sync/candidate-uploader.test.d.ts.map +1 -0
- package/dist/sync/candidate-uploader.test.js +132 -0
- package/dist/sync/candidate-uploader.test.js.map +1 -0
- package/dist/sync/delta-client.d.ts +73 -0
- package/dist/sync/delta-client.d.ts.map +1 -0
- package/dist/sync/delta-client.js +201 -0
- package/dist/sync/delta-client.js.map +1 -0
- package/dist/sync/delta-client.test.d.ts +2 -0
- package/dist/sync/delta-client.test.d.ts.map +1 -0
- package/dist/sync/delta-client.test.js +97 -0
- package/dist/sync/delta-client.test.js.map +1 -0
- package/dist/sync/durable-apply.d.ts +76 -0
- package/dist/sync/durable-apply.d.ts.map +1 -0
- package/dist/sync/durable-apply.js +530 -0
- package/dist/sync/durable-apply.js.map +1 -0
- package/dist/sync/durable-apply.test.d.ts +2 -0
- package/dist/sync/durable-apply.test.d.ts.map +1 -0
- package/dist/sync/durable-apply.test.js +180 -0
- package/dist/sync/durable-apply.test.js.map +1 -0
- package/dist/sync/event-sync.d.ts +33 -1
- package/dist/sync/event-sync.d.ts.map +1 -1
- package/dist/sync/event-sync.js +149 -1
- package/dist/sync/event-sync.js.map +1 -1
- package/dist/sync/event-sync.test.js +142 -1
- package/dist/sync/event-sync.test.js.map +1 -1
- package/dist/sync/index.d.ts +2 -0
- package/dist/sync/index.d.ts.map +1 -1
- package/dist/sync/index.js +1 -0
- package/dist/sync/index.js.map +1 -1
- package/dist/sync/multipart-uploader.d.ts +99 -0
- package/dist/sync/multipart-uploader.d.ts.map +1 -0
- package/dist/sync/multipart-uploader.js +447 -0
- package/dist/sync/multipart-uploader.js.map +1 -0
- package/dist/sync/multipart-uploader.test.d.ts +2 -0
- package/dist/sync/multipart-uploader.test.d.ts.map +1 -0
- package/dist/sync/multipart-uploader.test.js +119 -0
- package/dist/sync/multipart-uploader.test.js.map +1 -0
- package/dist/sync/mutation-client.d.ts +85 -0
- package/dist/sync/mutation-client.d.ts.map +1 -0
- package/dist/sync/mutation-client.js +245 -0
- package/dist/sync/mutation-client.js.map +1 -0
- package/dist/sync/mutation-client.test.d.ts +2 -0
- package/dist/sync/mutation-client.test.d.ts.map +1 -0
- package/dist/sync/mutation-client.test.js +51 -0
- package/dist/sync/mutation-client.test.js.map +1 -0
- package/dist/sync/push-receiver.d.ts +45 -0
- package/dist/sync/push-receiver.d.ts.map +1 -1
- package/dist/sync/push-receiver.js +101 -0
- package/dist/sync/push-receiver.js.map +1 -1
- package/dist/sync/push-receiver.test.js +54 -2
- package/dist/sync/push-receiver.test.js.map +1 -1
- package/dist/sync/scope-inventory-client.d.ts +69 -0
- package/dist/sync/scope-inventory-client.d.ts.map +1 -0
- package/dist/sync/scope-inventory-client.js +210 -0
- package/dist/sync/scope-inventory-client.js.map +1 -0
- package/dist/sync/scope-inventory-client.test.d.ts +2 -0
- package/dist/sync/scope-inventory-client.test.d.ts.map +1 -0
- package/dist/sync/scope-inventory-client.test.js +94 -0
- package/dist/sync/scope-inventory-client.test.js.map +1 -0
- package/dist/sync/snapshot-client.d.ts +98 -0
- package/dist/sync/snapshot-client.d.ts.map +1 -0
- package/dist/sync/snapshot-client.js +402 -0
- package/dist/sync/snapshot-client.js.map +1 -0
- package/dist/sync/snapshot-client.test.d.ts +2 -0
- package/dist/sync/snapshot-client.test.d.ts.map +1 -0
- package/dist/sync/snapshot-client.test.js +169 -0
- package/dist/sync/snapshot-client.test.js.map +1 -0
- package/dist/sync/uploader-finalization.d.ts +97 -0
- package/dist/sync/uploader-finalization.d.ts.map +1 -0
- package/dist/sync/uploader-finalization.js +273 -0
- package/dist/sync/uploader-finalization.js.map +1 -0
- package/dist/sync/uploader-finalization.test.d.ts +2 -0
- package/dist/sync/uploader-finalization.test.d.ts.map +1 -0
- package/dist/sync/uploader-finalization.test.js +92 -0
- package/dist/sync/uploader-finalization.test.js.map +1 -0
- package/dist/telemetry.d.ts +11 -1
- package/dist/telemetry.d.ts.map +1 -1
- package/dist/telemetry.js +21 -2
- package/dist/telemetry.js.map +1 -1
- package/dist/telemetry.test.js +80 -0
- package/dist/telemetry.test.js.map +1 -1
- package/package.json +6 -1
- package/.claude/policies/hq-cloud-esm-cannot-spy-fs-builtins.md +0 -30
- package/.claude/policies/hq-cloud-strip-types-no-parameter-properties.md +0 -22
- package/.github/workflows/ci.yml +0 -84
- package/.github/workflows/publish.yml +0 -56
- package/.github/workflows/unreleased-commits-nag.yml +0 -256
- package/eslint.config.js +0 -67
- package/pnpm-workspace.yaml +0 -2
- package/scripts/presign-transport-e2e.mjs +0 -250
- package/scripts/vault-rebaseline.sh +0 -323
- package/scripts/vault-rescue.sh +0 -332
- package/src/active-company.test.ts +0 -188
- package/src/active-company.ts +0 -168
- package/src/agent-codex-instructions.test.ts +0 -332
- package/src/agent-codex-instructions.ts +0 -309
- package/src/auth.ts +0 -146
- package/src/backup-prune.test.ts +0 -98
- package/src/backup-prune.ts +0 -182
- package/src/bin/backup-prune-runner.ts +0 -33
- package/src/bin/rescue-runner.ts +0 -25
- package/src/bin/sync-runner-company.ts +0 -695
- package/src/bin/sync-runner-events.test.ts +0 -143
- package/src/bin/sync-runner-events.ts +0 -55
- package/src/bin/sync-runner-planning.test.ts +0 -311
- package/src/bin/sync-runner-planning.ts +0 -258
- package/src/bin/sync-runner-rollup.test.ts +0 -37
- package/src/bin/sync-runner-rollup.ts +0 -97
- package/src/bin/sync-runner-telemetry.ts +0 -15
- package/src/bin/sync-runner-watch-loop.ts +0 -1235
- package/src/bin/sync-runner-watch-routes.test.ts +0 -71
- package/src/bin/sync-runner-watch-routes.ts +0 -184
- package/src/bin/sync-runner.test.ts +0 -8767
- package/src/bin/sync-runner.ts +0 -2190
- package/src/cli/accept.ts +0 -124
- package/src/cli/conflict.ts +0 -119
- package/src/cli/doctor.test.ts +0 -581
- package/src/cli/doctor.ts +0 -642
- package/src/cli/index.ts +0 -49
- package/src/cli/invite.test.ts +0 -250
- package/src/cli/invite.ts +0 -214
- package/src/cli/promote.ts +0 -157
- package/src/cli/reindex-knowledge.test.ts +0 -307
- package/src/cli/reindex-knowledge.ts +0 -450
- package/src/cli/reindex.test.ts +0 -957
- package/src/cli/reindex.ts +0 -979
- package/src/cli/rescue-classify-ordering.test.ts +0 -548
- package/src/cli/rescue-clone-diagnostics.test.ts +0 -120
- package/src/cli/rescue-core.ts +0 -3011
- package/src/cli/rescue-drift-reconcile.test.ts +0 -179
- package/src/cli/rescue-drop-dir-symlink.test.ts +0 -224
- package/src/cli/rescue-exec-bit-preserve.test.ts +0 -187
- package/src/cli/rescue-hq-root-guard.test.ts +0 -232
- package/src/cli/rescue-journal-reconcile.test.ts +0 -215
- package/src/cli/rescue-mtime-preserve.test.ts +0 -203
- package/src/cli/rescue-settings-reconcile.test.ts +0 -637
- package/src/cli/rescue-snapshot.test.ts +0 -57
- package/src/cli/rescue-snapshot.ts +0 -51
- package/src/cli/rescue.reindex.test.ts +0 -63
- package/src/cli/rescue.test.ts +0 -131
- package/src/cli/rescue.ts +0 -182
- package/src/cli/share.test.ts +0 -7843
- package/src/cli/share.ts +0 -3663
- package/src/cli/sync-scope.test.ts +0 -652
- package/src/cli/sync.test.ts +0 -5207
- package/src/cli/sync.ts +0 -3470
- package/src/cli/tombstones.ts +0 -106
- package/src/cli/watch-event-push-conflict.test.ts +0 -234
- package/src/client-info.test.ts +0 -214
- package/src/client-info.ts +0 -121
- package/src/cognito-auth.test.ts +0 -712
- package/src/cognito-auth.ts +0 -1422
- package/src/company-resolver.test.ts +0 -618
- package/src/company-resolver.ts +0 -521
- package/src/context.test.ts +0 -583
- package/src/context.ts +0 -378
- package/src/daemon-worker.ts +0 -26
- package/src/daemon.ts +0 -99
- package/src/entity-resolver.test.ts +0 -315
- package/src/entity-resolver.ts +0 -180
- package/src/ignore.test.ts +0 -466
- package/src/ignore.ts +0 -469
- package/src/index.ts +0 -439
- package/src/journal.test.ts +0 -968
- package/src/journal.ts +0 -765
- package/src/lib/cloud-authoritative.test.ts +0 -45
- package/src/lib/cloud-authoritative.ts +0 -59
- package/src/lib/conflict-file.ts +0 -86
- package/src/lib/conflict-index.ts +0 -289
- package/src/lib/conflict.test.ts +0 -348
- package/src/lib/describe-error.test.ts +0 -100
- package/src/lib/describe-error.ts +0 -58
- package/src/lib/exit-codes.ts +0 -24
- package/src/lib/machine-id.test.ts +0 -231
- package/src/lib/machine-id.ts +0 -175
- package/src/lib/net-errors.test.ts +0 -65
- package/src/lib/net-errors.ts +0 -86
- package/src/lib/readlink-safe.test.ts +0 -43
- package/src/lib/readlink-safe.ts +0 -29
- package/src/local-path-codec.test.ts +0 -138
- package/src/local-path-codec.ts +0 -161
- package/src/machine-auth.test.ts +0 -1323
- package/src/manifest-reconcile.test.ts +0 -1123
- package/src/manifest-reconcile.ts +0 -518
- package/src/object-io.test.ts +0 -1221
- package/src/object-io.ts +0 -1306
- package/src/operation-lock.test.ts +0 -484
- package/src/operation-lock.ts +0 -680
- package/src/outcome-telemetry.test.ts +0 -498
- package/src/outcome-telemetry.ts +0 -639
- package/src/personal-vault-exclusions.test.ts +0 -308
- package/src/personal-vault-exclusions.ts +0 -354
- package/src/personal-vault.test.ts +0 -756
- package/src/personal-vault.ts +0 -496
- package/src/prefix-coalesce.test.ts +0 -240
- package/src/prefix-coalesce.ts +0 -273
- package/src/public-surface.test.ts +0 -117
- package/src/qmd-reindex.test.ts +0 -877
- package/src/qmd-reindex.ts +0 -842
- package/src/read-only-state-dir.test.ts +0 -188
- package/src/remote-pull.test.ts +0 -1130
- package/src/remote-pull.ts +0 -618
- package/src/s3.symlink-materialize.test.ts +0 -492
- package/src/s3.test.ts +0 -1789
- package/src/s3.ts +0 -1532
- package/src/schemas/signal-types.test.ts +0 -82
- package/src/schemas/signal-types.ts +0 -38
- package/src/schemas/source-channels.test.ts +0 -82
- package/src/schemas/source-channels.ts +0 -53
- package/src/scope-shrink.test.ts +0 -633
- package/src/scope-shrink.ts +0 -481
- package/src/signals/get.test.ts +0 -310
- package/src/signals/get.ts +0 -75
- package/src/signals/internals.ts +0 -195
- package/src/signals/list.test.ts +0 -420
- package/src/signals/list.ts +0 -79
- package/src/signals/parse.ts +0 -8
- package/src/signals/types.ts +0 -91
- package/src/skill-telemetry.test.ts +0 -1825
- package/src/skill-telemetry.ts +0 -1439
- package/src/sources/get.test.ts +0 -293
- package/src/sources/get.ts +0 -66
- package/src/sources/internals.ts +0 -198
- package/src/sources/list.test.ts +0 -402
- package/src/sources/list.ts +0 -84
- package/src/sources/parse.ts +0 -43
- package/src/sources/types.ts +0 -84
- package/src/sync/event-sync.test.ts +0 -594
- package/src/sync/event-sync.ts +0 -545
- package/src/sync/feature-flags.test.ts +0 -378
- package/src/sync/feature-flags.ts +0 -62
- package/src/sync/index.ts +0 -76
- package/src/sync/lease-client.test.ts +0 -128
- package/src/sync/lease-client.ts +0 -207
- package/src/sync/logger.test.ts +0 -242
- package/src/sync/logger.ts +0 -79
- package/src/sync/metrics.test.ts +0 -462
- package/src/sync/metrics.ts +0 -213
- package/src/sync/pull-scope.ts +0 -265
- package/src/sync/push-event.test.ts +0 -266
- package/src/sync/push-event.ts +0 -224
- package/src/sync/push-receiver.test.ts +0 -566
- package/src/sync/push-receiver.ts +0 -1048
- package/src/sync/push-transport.ts +0 -231
- package/src/sync/realtime-rollout.test.ts +0 -86
- package/src/sync/realtime-rollout.ts +0 -262
- package/src/sync/state-store.test.ts +0 -194
- package/src/sync/state-store.ts +0 -727
- package/src/sync-core.ts +0 -58
- package/src/sync-progress.test.ts +0 -94
- package/src/sync-progress.ts +0 -140
- package/src/telemetry-events.test.ts +0 -88
- package/src/telemetry-events.ts +0 -205
- package/src/telemetry.test.ts +0 -1280
- package/src/telemetry.ts +0 -1109
- package/src/types.ts +0 -314
- package/src/vault-client.test.ts +0 -1380
- package/src/vault-client.ts +0 -1694
- package/src/version.ts +0 -24
- package/src/watch-roots.test.ts +0 -278
- package/src/watch-roots.ts +0 -162
- package/src/watcher-event-gate.test.ts +0 -212
- package/src/watcher.test.ts +0 -1079
- package/src/watcher.ts +0 -1741
- package/test/e2e/sync/cross-tenant-isolation.test.ts +0 -630
- package/test/e2e/sync/skill-telemetry-oversized-transcript.test.ts +0 -124
- package/test/e2e/sync/transient-company-leg.test.ts +0 -384
- package/test/e2e/sync/windows-unreadable-link-leg.test.ts +0 -191
- package/test/e2e/watcher-real-chokidar.test.ts +0 -165
- package/test/e2e/watcher-recursive-backend.test.ts +0 -181
- package/test/e2e/watcher-scoped-coverage.test.ts +0 -381
- package/test/invite-flow.integration.test.ts +0 -244
- package/test/joiner-manifest-reconcile.integration.test.ts +0 -322
- package/test/share-sync.integration.test.ts +0 -213
- package/tsconfig.json +0 -19
- package/vitest.config.ts +0 -22
package/src/backup-prune.ts
DELETED
|
@@ -1,182 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Safety-first inventory and retention policy for HQ rescue snapshots.
|
|
3
|
-
*
|
|
4
|
-
* This file deliberately separates policy from filesystem access. In
|
|
5
|
-
* particular, `planBackupPrune` cannot remove, create, or mutate anything.
|
|
6
|
-
*/
|
|
7
|
-
import * as crypto from "crypto";
|
|
8
|
-
import * as fs from "fs";
|
|
9
|
-
import * as path from "path";
|
|
10
|
-
|
|
11
|
-
export const SNAPSHOT_SCHEMA_VERSION = 1;
|
|
12
|
-
export const SNAPSHOT_FILE = "SNAPSHOT.json";
|
|
13
|
-
export const MANAGED_SNAPSHOT_RE = /^pre-update-\d{4}-\d{2}-\d{2}T\d{2}-\d{2}-\d{2}Z-[a-f0-9]{16}-[A-Za-z0-9_-]+$/;
|
|
14
|
-
export type SnapshotStatus = "copying" | "ready" | "completed-good" | "failed-pre-mutation" | "recovery-required";
|
|
15
|
-
|
|
16
|
-
export interface SnapshotManifest {
|
|
17
|
-
schemaVersion: 1;
|
|
18
|
-
status: SnapshotStatus;
|
|
19
|
-
canonicalHqRoot: string;
|
|
20
|
-
rootHash: string;
|
|
21
|
-
runId: string;
|
|
22
|
-
creatorPid: number;
|
|
23
|
-
creatorStartedAt: string;
|
|
24
|
-
source: string;
|
|
25
|
-
ref: string;
|
|
26
|
-
sourceSha: string;
|
|
27
|
-
createdAt: string;
|
|
28
|
-
completedAt?: string;
|
|
29
|
-
includedRoots: string[];
|
|
30
|
-
fileCount: number;
|
|
31
|
-
byteCount: number;
|
|
32
|
-
}
|
|
33
|
-
|
|
34
|
-
export interface BackupEntry {
|
|
35
|
-
name: string;
|
|
36
|
-
manifest: SnapshotManifest;
|
|
37
|
-
createdMs: number;
|
|
38
|
-
completedMs: number;
|
|
39
|
-
bytes: number;
|
|
40
|
-
identity?: { dev: number; ino: number };
|
|
41
|
-
copyingOwnerLive?: boolean;
|
|
42
|
-
}
|
|
43
|
-
export interface ProtectedEntry { entry: BackupEntry; reason: string }
|
|
44
|
-
export interface DeletionCandidate { entry: BackupEntry; reason: string }
|
|
45
|
-
export interface BackupPrunePlan { candidates: DeletionCandidate[]; protected: ProtectedEntry[]; unmanaged: { name: string; reason: string }[] }
|
|
46
|
-
export interface RetentionConfig { keepGood: number; keepFailedPreMutation: number; maxAgeDays: number }
|
|
47
|
-
|
|
48
|
-
export const DEFAULT_RETENTION: RetentionConfig = { keepGood: 2, keepFailedPreMutation: 1, maxAgeDays: 7 };
|
|
49
|
-
const day = 24 * 60 * 60 * 1000;
|
|
50
|
-
|
|
51
|
-
export function canonicalRootHash(root: string): string {
|
|
52
|
-
return crypto.createHash("sha256").update(root).digest("hex").slice(0, 16);
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
export function safeRetention(input: Partial<RetentionConfig> = {}): RetentionConfig {
|
|
56
|
-
const integer = (value: unknown, fallback: number, minimum: number) =>
|
|
57
|
-
typeof value === "number" && Number.isInteger(value) && value >= minimum ? value : fallback;
|
|
58
|
-
return {
|
|
59
|
-
keepGood: integer(input.keepGood, DEFAULT_RETENTION.keepGood, 1),
|
|
60
|
-
keepFailedPreMutation: integer(input.keepFailedPreMutation, DEFAULT_RETENTION.keepFailedPreMutation, 1),
|
|
61
|
-
maxAgeDays: integer(input.maxAgeDays, DEFAULT_RETENTION.maxAgeDays, 0),
|
|
62
|
-
};
|
|
63
|
-
}
|
|
64
|
-
|
|
65
|
-
const newestFirst = (a: BackupEntry, b: BackupEntry) => b.completedMs - a.completedMs || b.createdMs - a.createdMs || b.name.localeCompare(a.name);
|
|
66
|
-
|
|
67
|
-
/** Every managed entry receives either a retention reason or a deletion reason. */
|
|
68
|
-
export function planBackupPrune(entries: BackupEntry[], nowMs: number, input: Partial<RetentionConfig> = {}): BackupPrunePlan {
|
|
69
|
-
const cfg = safeRetention(input);
|
|
70
|
-
const candidates: DeletionCandidate[] = [];
|
|
71
|
-
const protectedEntries: ProtectedEntry[] = [];
|
|
72
|
-
const byRoot = new Map<string, BackupEntry[]>();
|
|
73
|
-
for (const entry of entries) {
|
|
74
|
-
const group = byRoot.get(entry.manifest.canonicalHqRoot) ?? [];
|
|
75
|
-
group.push(entry);
|
|
76
|
-
byRoot.set(entry.manifest.canonicalHqRoot, group);
|
|
77
|
-
}
|
|
78
|
-
for (const group of byRoot.values()) {
|
|
79
|
-
const good = group.filter((e) => e.manifest.status === "completed-good").sort(newestFirst);
|
|
80
|
-
const readyOrGood = group.filter((e) => e.manifest.status === "ready" || e.manifest.status === "completed-good").sort(newestFirst);
|
|
81
|
-
const laterGood = (e: BackupEntry) => good.some((g) => g.completedMs > e.completedMs || (g.completedMs === e.completedMs && g.name > e.name));
|
|
82
|
-
const newestRecovery = group.filter((e) => e.manifest.status === "recovery-required").sort(newestFirst)[0];
|
|
83
|
-
for (const entry of group) {
|
|
84
|
-
const status = entry.manifest.status;
|
|
85
|
-
if (status === "copying") {
|
|
86
|
-
protectedEntries.push({ entry, reason: entry.copyingOwnerLive ? "copying-owner-live" : "copying-not-superseded" });
|
|
87
|
-
continue;
|
|
88
|
-
}
|
|
89
|
-
if (status === "completed-good") {
|
|
90
|
-
const rank = good.indexOf(entry);
|
|
91
|
-
if (rank < cfg.keepGood) {
|
|
92
|
-
protectedEntries.push({ entry, reason: rank === 0 ? "newest-good" : "keep-newest-good" });
|
|
93
|
-
} else if (nowMs - entry.completedMs >= cfg.maxAgeDays * day) {
|
|
94
|
-
candidates.push({ entry, reason: "completed-good-age-and-rank" });
|
|
95
|
-
} else {
|
|
96
|
-
candidates.push({ entry, reason: "completed-good-rank" });
|
|
97
|
-
}
|
|
98
|
-
continue;
|
|
99
|
-
}
|
|
100
|
-
if (status === "failed-pre-mutation") {
|
|
101
|
-
const failures = group.filter((e) => e.manifest.status === status).sort(newestFirst);
|
|
102
|
-
const rank = failures.indexOf(entry);
|
|
103
|
-
const superseded = readyOrGood.some((other) => newestFirst(other, entry) < 0);
|
|
104
|
-
if (rank < cfg.keepFailedPreMutation || !superseded) protectedEntries.push({ entry, reason: !superseded ? "failed-pre-mutation-not-superseded" : "keep-failed-pre-mutation" });
|
|
105
|
-
else candidates.push({ entry, reason: "failed-pre-mutation-superseded" });
|
|
106
|
-
continue;
|
|
107
|
-
}
|
|
108
|
-
// A recovery-required copy is recovery evidence until a later known-good run.
|
|
109
|
-
if (!laterGood(entry)) {
|
|
110
|
-
protectedEntries.push({ entry, reason: "recovery-required-no-later-good" });
|
|
111
|
-
} else if (entry === newestRecovery && nowMs - entry.completedMs < cfg.maxAgeDays * day) {
|
|
112
|
-
protectedEntries.push({ entry, reason: "newest-recovery-required-grace" });
|
|
113
|
-
} else if (nowMs - entry.completedMs >= cfg.maxAgeDays * day || entry !== newestRecovery) {
|
|
114
|
-
candidates.push({ entry, reason: "recovery-required-superseded" });
|
|
115
|
-
} else {
|
|
116
|
-
protectedEntries.push({ entry, reason: "recovery-required-grace" });
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
}
|
|
120
|
-
return { candidates, protected: protectedEntries, unmanaged: [] };
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
function validManifest(value: unknown, canonicalRoot: string): value is SnapshotManifest {
|
|
124
|
-
const x = value as Partial<SnapshotManifest>;
|
|
125
|
-
return !!x && x.schemaVersion === SNAPSHOT_SCHEMA_VERSION && typeof x.canonicalHqRoot === "string" &&
|
|
126
|
-
x.canonicalHqRoot === canonicalRoot && x.rootHash === canonicalRootHash(canonicalRoot) &&
|
|
127
|
-
typeof x.runId === "string" && /^[A-Za-z0-9_-]+$/.test(x.runId) &&
|
|
128
|
-
["copying", "ready", "completed-good", "failed-pre-mutation", "recovery-required"].includes(x.status ?? "") &&
|
|
129
|
-
typeof x.createdAt === "string" && Array.isArray(x.includedRoots) && typeof x.fileCount === "number" && typeof x.byteCount === "number";
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
export function inventoryManagedSnapshots(backupRoot: string, canonicalRoot: string, isCopyingOwnerLive: (m: SnapshotManifest) => boolean = () => false, retention: Partial<RetentionConfig> = {}): BackupPrunePlan & { entries: BackupEntry[] } {
|
|
133
|
-
const entries: BackupEntry[] = [];
|
|
134
|
-
const unmanaged: { name: string; reason: string }[] = [];
|
|
135
|
-
let names: string[] = [];
|
|
136
|
-
try { names = fs.readdirSync(backupRoot); } catch { return { entries, candidates: [], protected: [], unmanaged: [{ name: backupRoot, reason: "backup-root-unreadable" }] }; }
|
|
137
|
-
for (const name of names.sort()) {
|
|
138
|
-
// Never descend into categories such as mcp/; only direct, exact names qualify.
|
|
139
|
-
if (!MANAGED_SNAPSHOT_RE.test(name)) { unmanaged.push({ name, reason: "not-managed-snapshot-name" }); continue; }
|
|
140
|
-
const abs = path.join(backupRoot, name);
|
|
141
|
-
let st: fs.Stats;
|
|
142
|
-
try { st = fs.lstatSync(abs); } catch { unmanaged.push({ name, reason: "lstat-failed" }); continue; }
|
|
143
|
-
if (!st.isDirectory() || st.isSymbolicLink()) { unmanaged.push({ name, reason: "not-real-directory" }); continue; }
|
|
144
|
-
let manifest: unknown;
|
|
145
|
-
try { manifest = JSON.parse(fs.readFileSync(path.join(abs, SNAPSHOT_FILE), "utf8")); } catch { unmanaged.push({ name, reason: "missing-or-invalid-metadata" }); continue; }
|
|
146
|
-
if (!validManifest(manifest, canonicalRoot)) { unmanaged.push({ name, reason: "metadata-does-not-match-canonical-root" }); continue; }
|
|
147
|
-
const m = manifest;
|
|
148
|
-
const createdMs = Date.parse(m.createdAt);
|
|
149
|
-
const completedMs = Date.parse(m.completedAt ?? m.createdAt);
|
|
150
|
-
if (!Number.isFinite(createdMs) || !Number.isFinite(completedMs)) { unmanaged.push({ name, reason: "invalid-metadata-timestamp" }); continue; }
|
|
151
|
-
entries.push({ name, manifest: m, createdMs, completedMs, bytes: m.byteCount, identity: { dev: st.dev, ino: st.ino }, copyingOwnerLive: m.status === "copying" && isCopyingOwnerLive(m) });
|
|
152
|
-
}
|
|
153
|
-
const plan = planBackupPrune(entries, Date.now(), retention);
|
|
154
|
-
return { entries, ...plan, unmanaged: [...unmanaged, ...plan.unmanaged] };
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/** Revalidates strict identity then renames before recursively removing it. */
|
|
158
|
-
export function applyBackupPrune(backupRoot: string, canonicalRoot: string, plan: BackupPrunePlan, retention: Partial<RetentionConfig> = {}): { deleted: string[]; failures: string[] } {
|
|
159
|
-
const deleted: string[] = [], failures: string[] = [];
|
|
160
|
-
const lockPath = path.join(backupRoot, ".hq-backup-prune.lock");
|
|
161
|
-
let lockFd: number | undefined;
|
|
162
|
-
try { lockFd = fs.openSync(lockPath, "wx"); fs.writeSync(lockFd, `${process.pid}\n`); }
|
|
163
|
-
catch { return { deleted, failures: ["backup-root-lock-busy-or-unwritable"] }; }
|
|
164
|
-
try {
|
|
165
|
-
// This is deliberately after acquiring the backup-root lock and immediately
|
|
166
|
-
// before rename: a dry-run plan is never trusted across a filesystem race.
|
|
167
|
-
const fresh = inventoryManagedSnapshots(backupRoot, canonicalRoot, () => false, retention);
|
|
168
|
-
const freshByName = new Map(fresh.entries.map((e) => [e.name, e]));
|
|
169
|
-
const stillCandidates = new Set(fresh.candidates.map((e) => e.entry.name));
|
|
170
|
-
for (const candidate of plan.candidates) {
|
|
171
|
-
const current = freshByName.get(candidate.entry.name);
|
|
172
|
-
if (!current || !stillCandidates.has(candidate.entry.name) || current.manifest.runId !== candidate.entry.manifest.runId || current.identity?.dev !== candidate.entry.identity?.dev || current.identity?.ino !== candidate.entry.identity?.ino || current.manifest.status === "copying") { failures.push(`${candidate.entry.name}: changed-or-in-use`); continue; }
|
|
173
|
-
const from = path.join(backupRoot, current.name);
|
|
174
|
-
const tombstone = path.join(backupRoot, `.deleting-${current.manifest.runId}`);
|
|
175
|
-
try { fs.renameSync(from, tombstone); fs.rmSync(tombstone, { recursive: true, force: false }); deleted.push(current.name); }
|
|
176
|
-
catch (error) { failures.push(`${current.name}: ${error instanceof Error ? error.message : String(error)}`); }
|
|
177
|
-
}
|
|
178
|
-
} finally {
|
|
179
|
-
try { if (lockFd !== undefined) fs.closeSync(lockFd); fs.unlinkSync(lockPath); } catch { /* retained errors are safer than a delete race */ }
|
|
180
|
-
}
|
|
181
|
-
return { deleted, failures };
|
|
182
|
-
}
|
|
@@ -1,33 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/** Explicit, local-only retention command. Defaults to dry-run. */
|
|
3
|
-
import * as fs from "fs";
|
|
4
|
-
import * as path from "path";
|
|
5
|
-
import { applyBackupPrune, inventoryManagedSnapshots } from "../backup-prune.js";
|
|
6
|
-
import { acquireOperationLock, OperationLockedError } from "../operation-lock.js";
|
|
7
|
-
|
|
8
|
-
const args = process.argv.slice(2);
|
|
9
|
-
const value = (flag: string) => { const i = args.indexOf(flag); return i >= 0 ? args[i + 1] : undefined; };
|
|
10
|
-
const rootArg = value("--hq-root");
|
|
11
|
-
const backupArg = value("--backup-dir");
|
|
12
|
-
const mode = value("--mode") ?? "dry-run";
|
|
13
|
-
if (!rootArg || !["dry-run", "apply"].includes(mode)) {
|
|
14
|
-
process.stderr.write("usage: hq-backup-prune --hq-root DIR [--backup-dir DIR] [--mode dry-run|apply]\n");
|
|
15
|
-
process.exit(2);
|
|
16
|
-
}
|
|
17
|
-
const root = fs.realpathSync(rootArg);
|
|
18
|
-
const backupRoot = backupArg ? path.resolve(backupArg) : path.join(process.env.HOME ?? "", ".hq", "backups");
|
|
19
|
-
let lock;
|
|
20
|
-
try { lock = acquireOperationLock(root, "backup-prune", { timeoutSec: 0, onWaitStart: () => undefined }); }
|
|
21
|
-
catch (error) {
|
|
22
|
-
if (error instanceof OperationLockedError) { process.stderr.write("hq-backup-prune: skipped-in-use\n"); process.exit(0); }
|
|
23
|
-
throw error;
|
|
24
|
-
}
|
|
25
|
-
try {
|
|
26
|
-
const inventory = inventoryManagedSnapshots(backupRoot, root, (m) => { try { process.kill(m.creatorPid, 0); return true; } catch { return false; } });
|
|
27
|
-
const plan = { candidates: inventory.candidates, protected: inventory.protected, unmanaged: inventory.unmanaged };
|
|
28
|
-
process.stdout.write(JSON.stringify({ mode, candidates: plan.candidates.map((x) => ({ name: x.entry.name, rootHash: x.entry.manifest.rootHash, status: x.entry.manifest.status, bytes: x.entry.bytes, reason: x.reason })), protected: plan.protected.map((x) => ({ name: x.entry.name, reason: x.reason })), unmanaged: plan.unmanaged }) + "\n");
|
|
29
|
-
if (mode === "apply") {
|
|
30
|
-
const result = applyBackupPrune(backupRoot, root, plan);
|
|
31
|
-
if (result.failures.length) { process.stderr.write(result.failures.join("\n") + "\n"); process.exitCode = 1; }
|
|
32
|
-
}
|
|
33
|
-
} finally { lock.release(); }
|
package/src/bin/rescue-runner.ts
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
/**
|
|
3
|
-
* hq-rescue — machine-targeted entrypoint for the drift-preserving HQ-core
|
|
4
|
-
* re-sync ("rescue") shipped in `@indigoai-us/hq-cloud`.
|
|
5
|
-
*
|
|
6
|
-
* This is the rescue analogue of `hq-sync-runner`: the HQ Sync menubar app
|
|
7
|
-
* (Tauri + Rust) spawns it the same way it spawns the sync runner —
|
|
8
|
-
* `npx -y --package=@indigoai-us/hq-cloud@<pin> hq-rescue <flags>`
|
|
9
|
-
* — so the app invokes hq-cloud directly at runtime (pinned by the Rust
|
|
10
|
-
* `HQ_CLOUD_VERSION` constant) rather than bundling a private copy of the
|
|
11
|
-
* script. `hq rescue` in `@indigoai-us/hq-cli` is the human-facing sibling.
|
|
12
|
-
*
|
|
13
|
-
* All flags are forwarded verbatim to scripts/replace-rescue.sh — see that
|
|
14
|
-
* script (and src/cli/rescue.ts) for the supported flags:
|
|
15
|
-
* --hq-root --source --ref --floor-sha --paths --preserve
|
|
16
|
-
* --preserve-subpath --no-history-check --no-backup --backup-dir
|
|
17
|
-
* --cloud-update --dry-run --yes
|
|
18
|
-
*
|
|
19
|
-
* stdout/stderr from the script are inherited so the caller (the menubar's
|
|
20
|
-
* RescueRunResult tail capture) sees the scan log + trailing summary.
|
|
21
|
-
*/
|
|
22
|
-
import { rescue } from "../cli/rescue.js";
|
|
23
|
-
|
|
24
|
-
const { status } = rescue({ extraArgs: process.argv.slice(2) });
|
|
25
|
-
process.exit(status);
|