@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
|
@@ -1,695 +0,0 @@
|
|
|
1
|
-
import * as fs from "fs";
|
|
2
|
-
import * as path from "path";
|
|
3
|
-
import {
|
|
4
|
-
computePersonalVaultPaths,
|
|
5
|
-
readCloudBackedSlugsFromManifest,
|
|
6
|
-
} from "../personal-vault.js";
|
|
7
|
-
import type { SyncOptions, SyncProgressEvent, SyncResult } from "../cli/sync.js";
|
|
8
|
-
import type { ShareOptions, ShareResult } from "../cli/share.js";
|
|
9
|
-
import type { ConflictStrategy } from "../cli/conflict.js";
|
|
10
|
-
import type { UploadAuthor } from "../s3.js";
|
|
11
|
-
import type { VaultServiceConfig } from "../index.js";
|
|
12
|
-
import { resolvePullScope, type PullScope } from "../sync/pull-scope.js";
|
|
13
|
-
import {
|
|
14
|
-
emitCloudTelemetry,
|
|
15
|
-
type CloudTelemetryClient,
|
|
16
|
-
type TelemetryClaims,
|
|
17
|
-
} from "../telemetry-events.js";
|
|
18
|
-
import {
|
|
19
|
-
coalescePrefixes,
|
|
20
|
-
pathToScopePrefix,
|
|
21
|
-
toScopePrefixEntries,
|
|
22
|
-
type ScopePrefixEntry,
|
|
23
|
-
type ScopePrefixInput,
|
|
24
|
-
} from "../prefix-coalesce.js";
|
|
25
|
-
import { describeError } from "../lib/describe-error.js";
|
|
26
|
-
import { isTransientNetworkError } from "../lib/net-errors.js";
|
|
27
|
-
import { VaultAuthError } from "../vault-client.js";
|
|
28
|
-
import type {
|
|
29
|
-
DeletePropagationPolicy,
|
|
30
|
-
Direction,
|
|
31
|
-
RunnerEvent,
|
|
32
|
-
VaultClientSurface,
|
|
33
|
-
} from "./sync-runner.js";
|
|
34
|
-
import type { RunnerTarget } from "./sync-runner-planning.js";
|
|
35
|
-
import {
|
|
36
|
-
createCompanyState,
|
|
37
|
-
type CompanyState,
|
|
38
|
-
} from "./sync-runner-rollup.js";
|
|
39
|
-
|
|
40
|
-
export interface CompanyFanoutResult {
|
|
41
|
-
stateByCompany: Map<string, CompanyState>;
|
|
42
|
-
errors: Array<{ company: string; message: string }>;
|
|
43
|
-
companyFailures: Array<{ company: string; message: string }>;
|
|
44
|
-
transientCompanies: Array<{ company: string; message: string }>;
|
|
45
|
-
meaningfulOutcomeCount: number;
|
|
46
|
-
allConflicts: Array<{
|
|
47
|
-
company: string;
|
|
48
|
-
path: string;
|
|
49
|
-
direction: "pull" | "push";
|
|
50
|
-
}>;
|
|
51
|
-
pushPathResults: Array<{
|
|
52
|
-
relativePath: string;
|
|
53
|
-
status: "accepted" | "refused";
|
|
54
|
-
operation: "delete" | "tombstone";
|
|
55
|
-
reason?: string;
|
|
56
|
-
}>;
|
|
57
|
-
}
|
|
58
|
-
|
|
59
|
-
export async function executeCompanyFanout(options: {
|
|
60
|
-
plan: RunnerTarget[];
|
|
61
|
-
direction: Direction;
|
|
62
|
-
hqRoot: string;
|
|
63
|
-
onConflict: ConflictStrategy;
|
|
64
|
-
client: VaultClientSurface;
|
|
65
|
-
vaultConfig: VaultServiceConfig;
|
|
66
|
-
uploadAuthor?: UploadAuthor;
|
|
67
|
-
operationLockAlreadyHeld?: boolean;
|
|
68
|
-
syncFn: (options: SyncOptions) => Promise<SyncResult>;
|
|
69
|
-
shareFn: (options: ShareOptions) => Promise<ShareResult>;
|
|
70
|
-
resolveDeletePolicy: () => DeletePropagationPolicy;
|
|
71
|
-
emit: (event: RunnerEvent) => void;
|
|
72
|
-
scopePaths?: string[];
|
|
73
|
-
deleteScopePaths?: string[];
|
|
74
|
-
telemetryClient?: CloudTelemetryClient | null;
|
|
75
|
-
telemetryClaims?: TelemetryClaims | null;
|
|
76
|
-
telemetrySessionId?: string;
|
|
77
|
-
/**
|
|
78
|
-
* Ensure a company's local directory exists (recursive mkdir). Injected so
|
|
79
|
-
* tests don't touch a developer's filesystem. Defaults to `fs.mkdirSync(dir,
|
|
80
|
-
* { recursive: true })`. Called after a completed non-personal pull leg so a
|
|
81
|
-
* company a member can SEE is materialized locally even when zero files were
|
|
82
|
-
* shared with them (see the ensure block below).
|
|
83
|
-
*/
|
|
84
|
-
ensureCompanyDir?: (dir: string) => void;
|
|
85
|
-
}): Promise<CompanyFanoutResult> {
|
|
86
|
-
const ensureCompanyDir =
|
|
87
|
-
options.ensureCompanyDir ??
|
|
88
|
-
((dir: string) => {
|
|
89
|
-
fs.mkdirSync(dir, { recursive: true });
|
|
90
|
-
});
|
|
91
|
-
const doPush =
|
|
92
|
-
options.direction === "push" || options.direction === "both";
|
|
93
|
-
const doPull =
|
|
94
|
-
options.direction === "pull" || options.direction === "both";
|
|
95
|
-
const errors: Array<{ company: string; message: string }> = [];
|
|
96
|
-
const companyFailures: Array<{ company: string; message: string }> = [];
|
|
97
|
-
const transientCompanies: Array<{ company: string; message: string }> = [];
|
|
98
|
-
const allConflicts: Array<{
|
|
99
|
-
company: string;
|
|
100
|
-
path: string;
|
|
101
|
-
direction: "pull" | "push";
|
|
102
|
-
}> = [];
|
|
103
|
-
const stateByCompany = new Map<string, CompanyState>();
|
|
104
|
-
let meaningfulOutcomeCount = 0;
|
|
105
|
-
const pushPathResults: CompanyFanoutResult["pushPathResults"] = [];
|
|
106
|
-
|
|
107
|
-
for (const target of options.plan) {
|
|
108
|
-
const companyLabel = target.slug;
|
|
109
|
-
const state = createCompanyState(companyLabel);
|
|
110
|
-
stateByCompany.set(companyLabel, state);
|
|
111
|
-
|
|
112
|
-
let activePhase: "pull" | "push" = doPush && !doPull ? "push" : "pull";
|
|
113
|
-
let companyHadTransferError = false;
|
|
114
|
-
const tagAndEmit = (event: SyncProgressEvent): void => {
|
|
115
|
-
if (event.type === "plan") {
|
|
116
|
-
options.emit({
|
|
117
|
-
type: "plan",
|
|
118
|
-
company: companyLabel,
|
|
119
|
-
filesToDownload: event.filesToDownload,
|
|
120
|
-
bytesToDownload: event.bytesToDownload,
|
|
121
|
-
filesToUpload: event.filesToUpload,
|
|
122
|
-
bytesToUpload: event.bytesToUpload,
|
|
123
|
-
filesToSkip: event.filesToSkip,
|
|
124
|
-
filesToConflict: event.filesToConflict,
|
|
125
|
-
filesToDelete: event.filesToDelete,
|
|
126
|
-
});
|
|
127
|
-
} else if (event.type === "progress") {
|
|
128
|
-
if (activePhase === "push") {
|
|
129
|
-
state.filesUploaded += 1;
|
|
130
|
-
state.bytesUploaded += event.bytes;
|
|
131
|
-
} else {
|
|
132
|
-
state.filesDownloaded += 1;
|
|
133
|
-
state.bytesDownloaded += event.bytes;
|
|
134
|
-
}
|
|
135
|
-
options.emit({
|
|
136
|
-
type: "progress",
|
|
137
|
-
company: companyLabel,
|
|
138
|
-
path: event.path,
|
|
139
|
-
bytes: event.bytes,
|
|
140
|
-
direction: activePhase === "push" ? "up" : "down",
|
|
141
|
-
...(event.message ? { message: event.message } : {}),
|
|
142
|
-
...(event.deleted ? { deleted: event.deleted } : {}),
|
|
143
|
-
});
|
|
144
|
-
} else if (event.type === "conflict") {
|
|
145
|
-
options.emit({
|
|
146
|
-
type: "conflict",
|
|
147
|
-
company: companyLabel,
|
|
148
|
-
path: event.path,
|
|
149
|
-
direction: event.direction,
|
|
150
|
-
resolution: event.resolution,
|
|
151
|
-
});
|
|
152
|
-
} else if (event.type === "error") {
|
|
153
|
-
companyHadTransferError = true;
|
|
154
|
-
errors.push({
|
|
155
|
-
company: companyLabel,
|
|
156
|
-
message: event.path
|
|
157
|
-
? `${event.path}: ${event.message}`
|
|
158
|
-
: event.message,
|
|
159
|
-
});
|
|
160
|
-
options.emit({
|
|
161
|
-
type: "error",
|
|
162
|
-
company: companyLabel,
|
|
163
|
-
path: event.path,
|
|
164
|
-
message: event.message,
|
|
165
|
-
});
|
|
166
|
-
} else if (event.type === "new-files") {
|
|
167
|
-
options.emit({
|
|
168
|
-
type: "new-files",
|
|
169
|
-
company: companyLabel,
|
|
170
|
-
files: event.files,
|
|
171
|
-
});
|
|
172
|
-
} else if (event.type === "scope-excluded") {
|
|
173
|
-
options.emit({
|
|
174
|
-
type: "scope-excluded",
|
|
175
|
-
company: companyLabel,
|
|
176
|
-
count: event.count,
|
|
177
|
-
samplePaths: event.samplePaths,
|
|
178
|
-
});
|
|
179
|
-
} else if (event.type === "scope-materialization-gap") {
|
|
180
|
-
options.emit({
|
|
181
|
-
type: "scope-materialization-gap",
|
|
182
|
-
company: companyLabel,
|
|
183
|
-
count: event.count,
|
|
184
|
-
samplePaths: event.samplePaths,
|
|
185
|
-
syncMode: event.syncMode,
|
|
186
|
-
});
|
|
187
|
-
} else if (event.type === "ignore-excluded") {
|
|
188
|
-
options.emit({
|
|
189
|
-
type: "ignore-excluded",
|
|
190
|
-
company: companyLabel,
|
|
191
|
-
count: event.count,
|
|
192
|
-
totalExcluded: event.totalExcluded,
|
|
193
|
-
samplePaths: event.samplePaths,
|
|
194
|
-
});
|
|
195
|
-
} else if (event.type === "skip-invalid-scoped-key") {
|
|
196
|
-
options.emit({
|
|
197
|
-
type: "skip-invalid-scoped-key",
|
|
198
|
-
company: companyLabel,
|
|
199
|
-
path: event.path,
|
|
200
|
-
...(event.errorCode ? { errorCode: event.errorCode } : {}),
|
|
201
|
-
});
|
|
202
|
-
} else if (event.type === "skip-junk-key-spelling") {
|
|
203
|
-
options.emit({
|
|
204
|
-
type: "skip-junk-key-spelling",
|
|
205
|
-
company: companyLabel,
|
|
206
|
-
path: event.path,
|
|
207
|
-
journaledKey: event.journaledKey,
|
|
208
|
-
});
|
|
209
|
-
} else if (event.type === "not-shipped") {
|
|
210
|
-
options.emit({
|
|
211
|
-
type: "not-shipped",
|
|
212
|
-
company: companyLabel,
|
|
213
|
-
reason: event.reason,
|
|
214
|
-
count: event.count,
|
|
215
|
-
samplePaths: event.samplePaths,
|
|
216
|
-
});
|
|
217
|
-
}
|
|
218
|
-
};
|
|
219
|
-
|
|
220
|
-
try {
|
|
221
|
-
let pushResult: ShareResult = {
|
|
222
|
-
filesUploaded: 0,
|
|
223
|
-
bytesUploaded: 0,
|
|
224
|
-
filesSkipped: 0,
|
|
225
|
-
filesDeleted: 0,
|
|
226
|
-
filesTombstoned: 0,
|
|
227
|
-
filesRefusedStale: 0,
|
|
228
|
-
filesRefusedStalePaths: [],
|
|
229
|
-
filesSuppressedByTombstone: 0,
|
|
230
|
-
filesExcludedByPolicy: 0,
|
|
231
|
-
filesExcludedByScope: 0,
|
|
232
|
-
filesExcludedByIgnore: 0,
|
|
233
|
-
unreachablePaths: [],
|
|
234
|
-
linkedSubtreesNotShipped: [],
|
|
235
|
-
conflictPaths: [],
|
|
236
|
-
aborted: false,
|
|
237
|
-
};
|
|
238
|
-
let pullResult: SyncResult = {
|
|
239
|
-
filesDownloaded: 0,
|
|
240
|
-
bytesDownloaded: 0,
|
|
241
|
-
filesSkipped: 0,
|
|
242
|
-
conflicts: 0,
|
|
243
|
-
conflictPaths: [],
|
|
244
|
-
aborted: false,
|
|
245
|
-
newFiles: [],
|
|
246
|
-
newFilesCount: 0,
|
|
247
|
-
filesExcludedByPolicy: 0,
|
|
248
|
-
filesTombstoned: 0,
|
|
249
|
-
filesOutOfScope: 0,
|
|
250
|
-
scopeOrphansRemoved: 0,
|
|
251
|
-
scopeOrphansBlocked: 0,
|
|
252
|
-
changedPaths: [],
|
|
253
|
-
};
|
|
254
|
-
|
|
255
|
-
// Local (non-cloud) companies always sync to the personal vault.
|
|
256
|
-
// Cloud-backed signals (union):
|
|
257
|
-
// 1. Membership fanout: every non-personal target in the run plan
|
|
258
|
-
// is a company the operator has an active Membership for.
|
|
259
|
-
// 2. companies/manifest.yaml cloud_uid: full-HQ agent boxes often
|
|
260
|
-
// run `--personal` alone (no company fanout), so (1) is empty
|
|
261
|
-
// even when indigo is cloud-backed. Manifest cloud_uid is the
|
|
262
|
-
// durable routing signal — exclude + decommission those slugs
|
|
263
|
-
// so multi-GB team trees never re-enter the agent personal vault.
|
|
264
|
-
// Those slugs are excluded from the personal push (no double-
|
|
265
|
-
// bucketing) and decommissioned from the personal vault if a stale
|
|
266
|
-
// pre-promotion copy lingers — this is how a company "becoming
|
|
267
|
-
// cloud-true" is handled.
|
|
268
|
-
const teamSyncedSlugs = new Set(
|
|
269
|
-
options.plan
|
|
270
|
-
.filter((p) => p.personalMode !== true)
|
|
271
|
-
.map((p) => p.slug),
|
|
272
|
-
);
|
|
273
|
-
if (target.personalMode === true) {
|
|
274
|
-
for (const slug of readCloudBackedSlugsFromManifest(options.hqRoot)) {
|
|
275
|
-
teamSyncedSlugs.add(slug);
|
|
276
|
-
}
|
|
277
|
-
}
|
|
278
|
-
|
|
279
|
-
const scope: PullScope =
|
|
280
|
-
target.personalMode === true
|
|
281
|
-
? // Personal vault: single-owner, no multi-tenant sessions ACL problem,
|
|
282
|
-
// so preserve its existing full-pull behavior — no session exclusion.
|
|
283
|
-
// (The sessions push-only contract is a company-vault concern; US-006.)
|
|
284
|
-
{ syncMode: "all", excludePrefixes: [] }
|
|
285
|
-
: await resolvePullScope(
|
|
286
|
-
options.client,
|
|
287
|
-
target.uid,
|
|
288
|
-
target.slug,
|
|
289
|
-
options.hqRoot,
|
|
290
|
-
);
|
|
291
|
-
|
|
292
|
-
if (doPush) {
|
|
293
|
-
activePhase = "push";
|
|
294
|
-
const pushPrefixSet = effectivePushPrefixSet(
|
|
295
|
-
scope.prefixSet,
|
|
296
|
-
options.scopePaths ?? [],
|
|
297
|
-
);
|
|
298
|
-
const pushPaths =
|
|
299
|
-
target.personalMode === true
|
|
300
|
-
? computePersonalVaultPaths(options.hqRoot, { teamSyncedSlugs })
|
|
301
|
-
: [path.join(options.hqRoot, "companies", target.slug)];
|
|
302
|
-
const decommissionPrefixes =
|
|
303
|
-
target.personalMode === true
|
|
304
|
-
? Array.from(teamSyncedSlugs).map((slug) => `companies/${slug}`)
|
|
305
|
-
: undefined;
|
|
306
|
-
pushResult = await options.shareFn({
|
|
307
|
-
paths: pushPaths,
|
|
308
|
-
company: target.uid,
|
|
309
|
-
vaultConfig: options.vaultConfig,
|
|
310
|
-
hqRoot: options.hqRoot,
|
|
311
|
-
onConflict: options.onConflict,
|
|
312
|
-
skipUnchanged: true,
|
|
313
|
-
propagateDeletes: true,
|
|
314
|
-
propagateDeletePolicy: options.resolveDeletePolicy(),
|
|
315
|
-
// `pushPaths` are INTERNAL walk roots (the company folder / the
|
|
316
|
-
// personal-vault dirs), never user input, so an unreachable entry
|
|
317
|
-
// here is a benign race — a directory removed between the scan and
|
|
318
|
-
// the push — and must not fail an unattended background sync. Record
|
|
319
|
-
// + report it (`not-shipped` event, `ShareResult.unreachablePaths`)
|
|
320
|
-
// and carry on. The interactive `hq sync push <path>` keeps the
|
|
321
|
-
// default "error" policy, which is where a user-typed path that
|
|
322
|
-
// silently ships nothing is a real bug (feedback_a51cb63d ask #2).
|
|
323
|
-
unreachablePathPolicy: "warn",
|
|
324
|
-
onEvent: tagAndEmit,
|
|
325
|
-
...(options.uploadAuthor ? { author: options.uploadAuthor } : {}),
|
|
326
|
-
...(target.personalMode !== undefined
|
|
327
|
-
? { personalMode: target.personalMode }
|
|
328
|
-
: {}),
|
|
329
|
-
...(target.journalSlug !== undefined
|
|
330
|
-
? { journalSlug: target.journalSlug }
|
|
331
|
-
: {}),
|
|
332
|
-
...(decommissionPrefixes && decommissionPrefixes.length > 0
|
|
333
|
-
? { decommissionPrefixes }
|
|
334
|
-
: {}),
|
|
335
|
-
...(pushPrefixSet !== undefined
|
|
336
|
-
? { prefixSet: pushPrefixSet }
|
|
337
|
-
: {}),
|
|
338
|
-
...(options.deleteScopePaths?.length
|
|
339
|
-
? { deleteScopeRoots: options.deleteScopePaths }
|
|
340
|
-
: {}),
|
|
341
|
-
});
|
|
342
|
-
for (const result of pushResult.pathResults ?? []) {
|
|
343
|
-
pushPathResults.push({
|
|
344
|
-
relativePath:
|
|
345
|
-
target.personalMode === true
|
|
346
|
-
? result.path
|
|
347
|
-
: `companies/${target.slug}/${result.path}`,
|
|
348
|
-
status: result.status,
|
|
349
|
-
operation: result.operation,
|
|
350
|
-
...(result.reason ? { reason: result.reason } : {}),
|
|
351
|
-
});
|
|
352
|
-
}
|
|
353
|
-
}
|
|
354
|
-
|
|
355
|
-
if (doPull && !pushResult.aborted) {
|
|
356
|
-
activePhase = "pull";
|
|
357
|
-
const pullScope: PullScope = scope;
|
|
358
|
-
pullResult = await options.syncFn({
|
|
359
|
-
company: target.uid,
|
|
360
|
-
vaultConfig: options.vaultConfig,
|
|
361
|
-
hqRoot: options.hqRoot,
|
|
362
|
-
onConflict: options.onConflict,
|
|
363
|
-
syncMode: pullScope.syncMode,
|
|
364
|
-
scopeShrinkPolicy: "auto-recover",
|
|
365
|
-
...(options.uploadAuthor?.userSub !== undefined
|
|
366
|
-
? { callerSub: options.uploadAuthor.userSub }
|
|
367
|
-
: {}),
|
|
368
|
-
...(options.telemetryClaims ? { telemetryClaims: options.telemetryClaims } : {}),
|
|
369
|
-
...(pullScope.prefixSet !== undefined
|
|
370
|
-
? { prefixSet: pullScope.prefixSet }
|
|
371
|
-
: {}),
|
|
372
|
-
// Push-only exclusions (US-006: sessions/). Subtracted from the pull's
|
|
373
|
-
// download filter in EVERY mode — including 'all' — but NOT fed into
|
|
374
|
-
// scope-shrink, so locally-authored / pinned sessions are never
|
|
375
|
-
// orphaned. Guarded like prefixSet: only threaded when non-empty
|
|
376
|
-
// (personal-vault legs carry [] and keep their full-pull behavior).
|
|
377
|
-
...(pullScope.excludePrefixes.length > 0
|
|
378
|
-
? { excludePrefixes: pullScope.excludePrefixes }
|
|
379
|
-
: {}),
|
|
380
|
-
...(target.personalMode !== undefined
|
|
381
|
-
? { personalMode: target.personalMode }
|
|
382
|
-
: {}),
|
|
383
|
-
...(target.journalSlug !== undefined
|
|
384
|
-
? { journalSlug: target.journalSlug }
|
|
385
|
-
: {}),
|
|
386
|
-
...(target.personalMode === true ? { teamSyncedSlugs } : {}),
|
|
387
|
-
...(options.operationLockAlreadyHeld
|
|
388
|
-
? { operationLockAlreadyHeld: true }
|
|
389
|
-
: {}),
|
|
390
|
-
skipReindex: true,
|
|
391
|
-
onEvent: tagAndEmit,
|
|
392
|
-
});
|
|
393
|
-
}
|
|
394
|
-
|
|
395
|
-
const seenConflictPaths = new Set<string>();
|
|
396
|
-
const mergedConflictPaths: string[] = [];
|
|
397
|
-
for (const p of [
|
|
398
|
-
...pullResult.conflictPaths,
|
|
399
|
-
...pushResult.conflictPaths,
|
|
400
|
-
]) {
|
|
401
|
-
if (seenConflictPaths.has(p)) continue;
|
|
402
|
-
seenConflictPaths.add(p);
|
|
403
|
-
mergedConflictPaths.push(p);
|
|
404
|
-
}
|
|
405
|
-
const aborted = pullResult.aborted || pushResult.aborted;
|
|
406
|
-
|
|
407
|
-
state.filesDownloaded = pullResult.filesDownloaded;
|
|
408
|
-
state.bytesDownloaded = pullResult.bytesDownloaded;
|
|
409
|
-
state.filesTombstoned = pullResult.filesTombstoned;
|
|
410
|
-
state.scopeOrphansRemoved = pullResult.scopeOrphansRemoved;
|
|
411
|
-
state.changedPaths = normalizePullChangedPaths(
|
|
412
|
-
target,
|
|
413
|
-
pullResult.changedPaths,
|
|
414
|
-
);
|
|
415
|
-
state.filesUploaded = pushResult.filesUploaded;
|
|
416
|
-
state.bytesUploaded = pushResult.bytesUploaded;
|
|
417
|
-
state.status = companyHadTransferError
|
|
418
|
-
? "errored"
|
|
419
|
-
: aborted
|
|
420
|
-
? "aborted"
|
|
421
|
-
: "complete";
|
|
422
|
-
|
|
423
|
-
// Materialize the company directory even when the pull downloaded zero
|
|
424
|
-
// files. A member who can SEE a company should have it appear locally —
|
|
425
|
-
// `companies/<slug>/` on disk plus a manifest entry — whether or not
|
|
426
|
-
// anything has been shared with them yet. Without this, a company with no
|
|
427
|
-
// shared files (e.g. an agency-created client account, or a member on
|
|
428
|
-
// shared-mode with no grants) never gets a local directory, so the
|
|
429
|
-
// post-fanout reconciler (which gates on the directory existing) skips it
|
|
430
|
-
// and HQ "doesn't know about" a company the person is plainly a member
|
|
431
|
-
// of. Only on a clean pull leg for a real company; best-effort, mirroring
|
|
432
|
-
// the reconciler's own non-fatal posture — a mkdir failure just leaves
|
|
433
|
-
// the slug unmaterialized exactly as before.
|
|
434
|
-
if (
|
|
435
|
-
doPull &&
|
|
436
|
-
target.personalMode !== true &&
|
|
437
|
-
state.status === "complete"
|
|
438
|
-
) {
|
|
439
|
-
try {
|
|
440
|
-
ensureCompanyDir(
|
|
441
|
-
path.join(options.hqRoot, "companies", target.slug),
|
|
442
|
-
);
|
|
443
|
-
} catch (err) {
|
|
444
|
-
// Best-effort: a mkdir failure just leaves this slug unmaterialized,
|
|
445
|
-
// exactly as before this change. Never fail the (successful) pull
|
|
446
|
-
// over it — but never swallow it silently either.
|
|
447
|
-
console.warn(
|
|
448
|
-
"[sync-runner-company] ensure company directory failed (non-fatal)",
|
|
449
|
-
{
|
|
450
|
-
slug: target.slug,
|
|
451
|
-
err: err instanceof Error ? err.message : String(err),
|
|
452
|
-
},
|
|
453
|
-
);
|
|
454
|
-
}
|
|
455
|
-
}
|
|
456
|
-
|
|
457
|
-
options.emit({
|
|
458
|
-
type: "complete",
|
|
459
|
-
company: companyLabel,
|
|
460
|
-
filesDownloaded: pullResult.filesDownloaded,
|
|
461
|
-
bytesDownloaded: pullResult.bytesDownloaded,
|
|
462
|
-
filesUploaded: pushResult.filesUploaded,
|
|
463
|
-
bytesUploaded: pushResult.bytesUploaded,
|
|
464
|
-
filesSkipped: pullResult.filesSkipped + pushResult.filesSkipped,
|
|
465
|
-
filesTombstoned:
|
|
466
|
-
pushResult.filesTombstoned + pullResult.filesTombstoned,
|
|
467
|
-
filesRefusedStale: pushResult.filesRefusedStale,
|
|
468
|
-
filesRefusedStalePaths: pushResult.filesRefusedStalePaths,
|
|
469
|
-
filesExcludedByPolicy:
|
|
470
|
-
pushResult.filesExcludedByPolicy +
|
|
471
|
-
pullResult.filesExcludedByPolicy,
|
|
472
|
-
conflicts: mergedConflictPaths.length,
|
|
473
|
-
conflictPaths: mergedConflictPaths,
|
|
474
|
-
aborted,
|
|
475
|
-
newFiles: pullResult.newFiles,
|
|
476
|
-
newFilesCount: pullResult.newFilesCount,
|
|
477
|
-
filesOutOfScope: pullResult.filesOutOfScope,
|
|
478
|
-
scopeOrphansRemoved: pullResult.scopeOrphansRemoved,
|
|
479
|
-
scopeOrphansBlocked: pullResult.scopeOrphansBlocked,
|
|
480
|
-
});
|
|
481
|
-
const tombstonedCount =
|
|
482
|
-
pushResult.filesTombstoned + pullResult.filesTombstoned;
|
|
483
|
-
const hasMeaningfulOutcome =
|
|
484
|
-
state.status !== "complete" ||
|
|
485
|
-
pullResult.filesDownloaded > 0 ||
|
|
486
|
-
pushResult.filesUploaded > 0 ||
|
|
487
|
-
pushResult.filesDeleted > 0 ||
|
|
488
|
-
tombstonedCount > 0 ||
|
|
489
|
-
pullResult.scopeOrphansRemoved > 0 ||
|
|
490
|
-
mergedConflictPaths.length > 0 ||
|
|
491
|
-
pushResult.filesRefusedStale > 0 ||
|
|
492
|
-
pullResult.scopeOrphansBlocked > 0;
|
|
493
|
-
if (hasMeaningfulOutcome) {
|
|
494
|
-
meaningfulOutcomeCount += 1;
|
|
495
|
-
void emitCloudTelemetry(
|
|
496
|
-
options.telemetryClient,
|
|
497
|
-
{
|
|
498
|
-
eventName: "company_sync_leg_completed",
|
|
499
|
-
source: "sync-runner",
|
|
500
|
-
...(target.personalMode !== true ? { companyUid: target.uid } : {}),
|
|
501
|
-
...(options.telemetrySessionId
|
|
502
|
-
? { sessionId: options.telemetrySessionId }
|
|
503
|
-
: {}),
|
|
504
|
-
properties: {
|
|
505
|
-
direction: options.direction,
|
|
506
|
-
status: state.status,
|
|
507
|
-
filesDownloaded: pullResult.filesDownloaded,
|
|
508
|
-
bytesDownloaded: pullResult.bytesDownloaded,
|
|
509
|
-
filesUploaded: pushResult.filesUploaded,
|
|
510
|
-
bytesUploaded: pushResult.bytesUploaded,
|
|
511
|
-
deletedCount: pushResult.filesDeleted,
|
|
512
|
-
tombstonedCount,
|
|
513
|
-
scopeOrphansRemovedCount: pullResult.scopeOrphansRemoved,
|
|
514
|
-
refusedStaleCount: pushResult.filesRefusedStale,
|
|
515
|
-
scopeOrphansBlockedCount: pullResult.scopeOrphansBlocked,
|
|
516
|
-
conflictCount: mergedConflictPaths.length,
|
|
517
|
-
newFilesCount: pullResult.newFilesCount,
|
|
518
|
-
},
|
|
519
|
-
},
|
|
520
|
-
{ claims: options.telemetryClaims },
|
|
521
|
-
);
|
|
522
|
-
}
|
|
523
|
-
for (const p of pullResult.conflictPaths) {
|
|
524
|
-
allConflicts.push({
|
|
525
|
-
company: companyLabel,
|
|
526
|
-
path: p,
|
|
527
|
-
direction: "pull",
|
|
528
|
-
});
|
|
529
|
-
}
|
|
530
|
-
for (const p of pushResult.conflictPaths) {
|
|
531
|
-
allConflicts.push({
|
|
532
|
-
company: companyLabel,
|
|
533
|
-
path: p,
|
|
534
|
-
direction: "push",
|
|
535
|
-
});
|
|
536
|
-
}
|
|
537
|
-
} catch (err) {
|
|
538
|
-
// Auth is a run-level terminal condition, not a per-company transfer
|
|
539
|
-
// error. Stop the fanout immediately so the caller can clear the stale
|
|
540
|
-
// session and emit one auth-error event for manual and watch consumers.
|
|
541
|
-
if (err instanceof VaultAuthError) throw err;
|
|
542
|
-
const message = describeError(err);
|
|
543
|
-
const transientNetwork = isTransientNetworkError(err);
|
|
544
|
-
// A prior path-level error is deterministic and must continue to win in
|
|
545
|
-
// the all-complete status and telemetry even if the leg later encounters
|
|
546
|
-
// a retryable transport failure.
|
|
547
|
-
const transientOnly = transientNetwork && !companyHadTransferError;
|
|
548
|
-
if (transientNetwork) {
|
|
549
|
-
transientCompanies.push({ company: companyLabel, message });
|
|
550
|
-
if (transientOnly) state.status = "transient-network";
|
|
551
|
-
} else {
|
|
552
|
-
errors.push({ company: companyLabel, message });
|
|
553
|
-
companyFailures.push({ company: companyLabel, message });
|
|
554
|
-
}
|
|
555
|
-
meaningfulOutcomeCount += 1;
|
|
556
|
-
options.emit({
|
|
557
|
-
type: "complete",
|
|
558
|
-
company: companyLabel,
|
|
559
|
-
filesDownloaded: state.filesDownloaded,
|
|
560
|
-
bytesDownloaded: state.bytesDownloaded,
|
|
561
|
-
filesUploaded: state.filesUploaded,
|
|
562
|
-
bytesUploaded: state.bytesUploaded,
|
|
563
|
-
filesSkipped: 0,
|
|
564
|
-
filesTombstoned: 0,
|
|
565
|
-
filesRefusedStale: 0,
|
|
566
|
-
filesRefusedStalePaths: [],
|
|
567
|
-
filesExcludedByPolicy: 0,
|
|
568
|
-
conflicts: 0,
|
|
569
|
-
conflictPaths: [],
|
|
570
|
-
aborted: true,
|
|
571
|
-
newFiles: [],
|
|
572
|
-
newFilesCount: 0,
|
|
573
|
-
filesOutOfScope: 0,
|
|
574
|
-
scopeOrphansRemoved: 0,
|
|
575
|
-
scopeOrphansBlocked: 0,
|
|
576
|
-
changedPaths: [],
|
|
577
|
-
});
|
|
578
|
-
if (transientNetwork) {
|
|
579
|
-
// This is deliberately not an error event: a retryable transport blip
|
|
580
|
-
// must remain visible without being miscounted as a deterministic
|
|
581
|
-
// company failure (and therefore an exit-2/Sentry error). The complete
|
|
582
|
-
// event above still reports aborted:true and the pass exits 75.
|
|
583
|
-
options.emit({
|
|
584
|
-
type: "transient-network",
|
|
585
|
-
company: companyLabel,
|
|
586
|
-
path: "(company)",
|
|
587
|
-
message,
|
|
588
|
-
});
|
|
589
|
-
} else {
|
|
590
|
-
options.emit({
|
|
591
|
-
type: "error",
|
|
592
|
-
company: companyLabel,
|
|
593
|
-
path: "(company)",
|
|
594
|
-
message,
|
|
595
|
-
});
|
|
596
|
-
}
|
|
597
|
-
void emitCloudTelemetry(
|
|
598
|
-
options.telemetryClient,
|
|
599
|
-
{
|
|
600
|
-
eventName: "company_sync_leg_completed",
|
|
601
|
-
source: "sync-runner",
|
|
602
|
-
...(target.personalMode !== true ? { companyUid: target.uid } : {}),
|
|
603
|
-
...(options.telemetrySessionId
|
|
604
|
-
? { sessionId: options.telemetrySessionId }
|
|
605
|
-
: {}),
|
|
606
|
-
properties: {
|
|
607
|
-
direction: options.direction,
|
|
608
|
-
status: transientOnly ? "transient-network" : "errored",
|
|
609
|
-
filesDownloaded: state.filesDownloaded,
|
|
610
|
-
bytesDownloaded: state.bytesDownloaded,
|
|
611
|
-
filesUploaded: state.filesUploaded,
|
|
612
|
-
bytesUploaded: state.bytesUploaded,
|
|
613
|
-
deletedCount: 0,
|
|
614
|
-
tombstonedCount: 0,
|
|
615
|
-
scopeOrphansRemovedCount: 0,
|
|
616
|
-
refusedStaleCount: 0,
|
|
617
|
-
scopeOrphansBlockedCount: 0,
|
|
618
|
-
conflictCount: 0,
|
|
619
|
-
newFilesCount: 0,
|
|
620
|
-
},
|
|
621
|
-
},
|
|
622
|
-
{ claims: options.telemetryClaims },
|
|
623
|
-
);
|
|
624
|
-
}
|
|
625
|
-
}
|
|
626
|
-
|
|
627
|
-
return {
|
|
628
|
-
stateByCompany,
|
|
629
|
-
errors,
|
|
630
|
-
companyFailures,
|
|
631
|
-
transientCompanies,
|
|
632
|
-
meaningfulOutcomeCount,
|
|
633
|
-
allConflicts,
|
|
634
|
-
pushPathResults,
|
|
635
|
-
};
|
|
636
|
-
}
|
|
637
|
-
|
|
638
|
-
function effectivePushPrefixSet(
|
|
639
|
-
aclPrefixSet: readonly ScopePrefixInput[] | undefined,
|
|
640
|
-
scopePaths: readonly string[],
|
|
641
|
-
): string[] | undefined {
|
|
642
|
-
if (scopePaths.length === 0) {
|
|
643
|
-
return aclPrefixSet === undefined
|
|
644
|
-
? undefined
|
|
645
|
-
: coalescePrefixes(aclPrefixSet);
|
|
646
|
-
}
|
|
647
|
-
|
|
648
|
-
const requested = scopePaths.flatMap((p) => [
|
|
649
|
-
pathToScopePrefix(p),
|
|
650
|
-
pathToScopePrefix(p.endsWith("/") ? p : p + "/"),
|
|
651
|
-
]);
|
|
652
|
-
if (aclPrefixSet === undefined) {
|
|
653
|
-
return coalescePrefixes(requested);
|
|
654
|
-
}
|
|
655
|
-
|
|
656
|
-
const aclEntries = toScopePrefixEntries(aclPrefixSet);
|
|
657
|
-
const requestedEntries = toScopePrefixEntries(requested);
|
|
658
|
-
const intersection: ScopePrefixEntry[] = [];
|
|
659
|
-
for (const requestedEntry of requestedEntries) {
|
|
660
|
-
for (const aclEntry of aclEntries) {
|
|
661
|
-
if (entryCoversEntry(aclEntry, requestedEntry)) {
|
|
662
|
-
intersection.push(requestedEntry);
|
|
663
|
-
} else if (entryCoversEntry(requestedEntry, aclEntry)) {
|
|
664
|
-
intersection.push(aclEntry);
|
|
665
|
-
}
|
|
666
|
-
}
|
|
667
|
-
}
|
|
668
|
-
return coalescePrefixes(intersection);
|
|
669
|
-
}
|
|
670
|
-
|
|
671
|
-
function entryCoversEntry(
|
|
672
|
-
entry: ScopePrefixEntry,
|
|
673
|
-
candidate: ScopePrefixEntry,
|
|
674
|
-
): boolean {
|
|
675
|
-
if (entry.prefix === "") return true;
|
|
676
|
-
if (entry.match === "prefix") {
|
|
677
|
-
return (
|
|
678
|
-
candidate.prefix === entry.prefix ||
|
|
679
|
-
candidate.prefix.startsWith(entry.prefix)
|
|
680
|
-
);
|
|
681
|
-
}
|
|
682
|
-
return candidate.match === "exact" && candidate.prefix === entry.prefix;
|
|
683
|
-
}
|
|
684
|
-
|
|
685
|
-
function normalizePullChangedPaths(
|
|
686
|
-
target: RunnerTarget,
|
|
687
|
-
changedPaths: string[] | undefined,
|
|
688
|
-
): string[] {
|
|
689
|
-
const paths = changedPaths ?? [];
|
|
690
|
-
return paths.map((p) => {
|
|
691
|
-
const normalized = p.split(path.sep).join("/");
|
|
692
|
-
if (target.personalMode === true) return normalized;
|
|
693
|
-
return `companies/${target.slug}/${normalized}`;
|
|
694
|
-
});
|
|
695
|
-
}
|