@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/context.ts
DELETED
|
@@ -1,378 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Entity context resolution (VLT-5 US-001).
|
|
3
|
-
*
|
|
4
|
-
* Resolves an entity (company) via vault-service, vends STS-scoped credentials,
|
|
5
|
-
* and returns an EntityContext for S3 operations. Handles auto-refresh when
|
|
6
|
-
* credentials are within 2 minutes of expiry.
|
|
7
|
-
*/
|
|
8
|
-
|
|
9
|
-
import type { EntityContext, VaultServiceConfig } from "./types.js";
|
|
10
|
-
import { buildClientHeaders } from "./client-info.js";
|
|
11
|
-
|
|
12
|
-
/** Minimum remaining TTL before auto-refresh triggers (2 minutes). */
|
|
13
|
-
const REFRESH_THRESHOLD_MS = 2 * 60 * 1000;
|
|
14
|
-
|
|
15
|
-
/** STS session duration requested from vault-service (15 minutes). */
|
|
16
|
-
const DEFAULT_SESSION_DURATION_SECONDS = 900;
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* `expiresAt` stamped on a presign-only company context (HQ-59). No STS vend
|
|
20
|
-
* happened, so there is nothing to expire — a far-future sentinel keeps
|
|
21
|
-
* `isExpiringSoon` false forever, so the auto-refresh path never re-vends (and
|
|
22
|
-
* thus never re-hits) the company `/sts/vend` route the gate keys on.
|
|
23
|
-
*/
|
|
24
|
-
const PRESIGN_NO_VEND_EXPIRES_AT = "9999-12-31T23:59:59.000Z";
|
|
25
|
-
|
|
26
|
-
/**
|
|
27
|
-
* Cached contexts.
|
|
28
|
-
*
|
|
29
|
-
* Two keying schemes share the map:
|
|
30
|
-
* - UID keys: bare `cmp_xxx` / `prs_xxx`. Globally unique by ULID, so
|
|
31
|
-
* the cached context is safe to return to any caller asking by uid.
|
|
32
|
-
* - Slug keys: `slug:${callerSub}#${slug}`. Under the per-user-namespace
|
|
33
|
-
* model on hq-pro (PR 67, live 2026-05-15), the same slug can
|
|
34
|
-
* legitimately resolve to different company UIDs for different
|
|
35
|
-
* callers. Keying slug entries by `(callerSub, slug)` prevents one
|
|
36
|
-
* caller's resolution from being served to another caller against
|
|
37
|
-
* the wrong tenant. Codex flagged this as a P1 on PR 67's hq-cloud
|
|
38
|
-
* follow-up.
|
|
39
|
-
*/
|
|
40
|
-
const contextCache = new Map<string, EntityContext>();
|
|
41
|
-
|
|
42
|
-
/**
|
|
43
|
-
* Extract the Cognito sub claim from a JWT without verifying the
|
|
44
|
-
* signature. We don't need verification here — the token already
|
|
45
|
-
* authorized the upstream API call; we're only using `sub` as a
|
|
46
|
-
* per-caller cache discriminator. If the token is malformed or
|
|
47
|
-
* missing a sub, fall back to a hash of the whole token (slightly
|
|
48
|
-
* larger key space, same correctness — different tokens still
|
|
49
|
-
* get distinct cache entries).
|
|
50
|
-
*/
|
|
51
|
-
function callerKeyFromAccessToken(accessToken: string): string {
|
|
52
|
-
const parts = accessToken.split(".");
|
|
53
|
-
if (parts.length === 3) {
|
|
54
|
-
try {
|
|
55
|
-
// base64url decode (Node-compatible, no padding required)
|
|
56
|
-
const padded = parts[1].replace(/-/g, "+").replace(/_/g, "/");
|
|
57
|
-
const payload = Buffer.from(padded, "base64").toString("utf8");
|
|
58
|
-
const claims = JSON.parse(payload) as { sub?: unknown };
|
|
59
|
-
if (typeof claims.sub === "string" && claims.sub.length > 0) {
|
|
60
|
-
return claims.sub;
|
|
61
|
-
}
|
|
62
|
-
} catch {
|
|
63
|
-
// fall through to token-hash fallback
|
|
64
|
-
}
|
|
65
|
-
}
|
|
66
|
-
// Stable fallback — same token → same key, different tokens → different keys.
|
|
67
|
-
// (Avoids importing crypto for a sha; the raw token suffix is sufficient as
|
|
68
|
-
// a cache discriminator and isn't exposed outside this process.)
|
|
69
|
-
return `tok:${accessToken.slice(-32)}`;
|
|
70
|
-
}
|
|
71
|
-
|
|
72
|
-
function slugCacheKey(callerKey: string, slug: string): string {
|
|
73
|
-
return `slug:${callerKey}#${slug}`;
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
/**
|
|
77
|
-
* Closed-set of recognised entity-UID prefixes. Adding a new entity type
|
|
78
|
-
* means appending one entry here AND extending the dispatch in
|
|
79
|
-
* `resolveEntityContext` (cmp_ → /sts/vend, prs_/agt_ → /sts/vend-self).
|
|
80
|
-
*
|
|
81
|
-
* `agt_` (US-013, unblocks US-004): a headless agent box syncs its OWN
|
|
82
|
-
* personal overlay via `hq-sync-runner --personal`, which resolves the
|
|
83
|
-
* agent's own entity (`agt_*`, bucket `hq-vault-agt-<uid>`). Without `agt_`
|
|
84
|
-
* here an agt_ uid is mistaken for a SLUG and routed through the company
|
|
85
|
-
* by-slug lookup; with it, the uid is recognised and self-vended below.
|
|
86
|
-
*/
|
|
87
|
-
export const KNOWN_UID_PREFIXES = ["cmp_", "prs_", "agt_"] as const;
|
|
88
|
-
|
|
89
|
-
/**
|
|
90
|
-
* Look up an entity by slug or UID via vault-service, then vend STS-scoped
|
|
91
|
-
* credentials for that entity. Returns an EntityContext ready for S3 ops.
|
|
92
|
-
*
|
|
93
|
-
* Caches the result and auto-refreshes when the credentials are within
|
|
94
|
-
* 2 minutes of expiry.
|
|
95
|
-
*
|
|
96
|
-
* HQ-59: when `config.companyVaultUsesPresign` is set, COMPANY (`cmp_*`)
|
|
97
|
-
* contexts skip the `POST /sts/vend` call and come back credential-less with a
|
|
98
|
-
* far-future expiry — the presign transport needs no STS creds, and skipping
|
|
99
|
-
* the vend keeps a compliant sync-runner off the company vend route. Personal
|
|
100
|
-
* (`prs_*`) contexts always vend self.
|
|
101
|
-
*/
|
|
102
|
-
export async function resolveEntityContext(
|
|
103
|
-
companyUidOrSlug: string,
|
|
104
|
-
config: VaultServiceConfig,
|
|
105
|
-
): Promise<EntityContext> {
|
|
106
|
-
// Step 0: Determine whether the input is a UID (globally unique) or a
|
|
107
|
-
// slug (per-caller). The cache key differs accordingly — see the
|
|
108
|
-
// `contextCache` jsdoc.
|
|
109
|
-
const looksLikeUid = KNOWN_UID_PREFIXES.some((p) =>
|
|
110
|
-
companyUidOrSlug.startsWith(p),
|
|
111
|
-
);
|
|
112
|
-
// Self-vend covers BOTH person (`prs_`) and agent (`agt_`) entities — each
|
|
113
|
-
// owns a personal vault gated by self-ownership at `POST /sts/vend-self`,
|
|
114
|
-
// not the membership-gated company `POST /sts/vend`. (US-013, unblocks
|
|
115
|
-
// US-004: an agent box self-vends its own `hq-vault-agt-<uid>` vault.)
|
|
116
|
-
const looksLikePerson =
|
|
117
|
-
companyUidOrSlug.startsWith("prs_") || companyUidOrSlug.startsWith("agt_");
|
|
118
|
-
|
|
119
|
-
// For slug lookups, scope the cache key by the caller. Resolving the
|
|
120
|
-
// access token here (once) doubles as a way to extract the sub for
|
|
121
|
-
// the key — same hop the downstream fetch makes anyway.
|
|
122
|
-
let cacheKey: string;
|
|
123
|
-
let callerKey: string | null = null;
|
|
124
|
-
if (looksLikeUid) {
|
|
125
|
-
cacheKey = companyUidOrSlug;
|
|
126
|
-
} else {
|
|
127
|
-
const token = await resolveAuthToken(config);
|
|
128
|
-
callerKey = callerKeyFromAccessToken(token);
|
|
129
|
-
cacheKey = slugCacheKey(callerKey, companyUidOrSlug);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Check cache — return if credentials still fresh.
|
|
133
|
-
const cached = contextCache.get(cacheKey);
|
|
134
|
-
if (cached && !isExpiringSoon(cached.expiresAt)) {
|
|
135
|
-
return cached;
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
// Step 1: Resolve entity — direct fetch by UID or namespace lookup by slug.
|
|
139
|
-
const entity = looksLikeUid
|
|
140
|
-
? await fetchEntity(companyUidOrSlug, config)
|
|
141
|
-
: await fetchEntityBySlug("company", companyUidOrSlug, config);
|
|
142
|
-
|
|
143
|
-
if (!entity.bucketName) {
|
|
144
|
-
throw new Error(
|
|
145
|
-
`Entity ${entity.uid} (${entity.slug}) has no bucket provisioned. ` +
|
|
146
|
-
`Run VLT-2 bucket provisioning first.`,
|
|
147
|
-
);
|
|
148
|
-
}
|
|
149
|
-
|
|
150
|
-
// Step 2: Dispatch credential vending by RESOLVED-uid prefix.
|
|
151
|
-
// cmp_* → POST /sts/vend (company path; membership-gated)
|
|
152
|
-
// prs_* / agt_* → POST /sts/vend-self (self path; self-ownership-gated)
|
|
153
|
-
// slug → resolves to a cmp_* (the slug path is company-only)
|
|
154
|
-
//
|
|
155
|
-
// HQ-59 vend-skip: when the run uses the presigned-URL transport for company
|
|
156
|
-
// vaults, a `cmp_*` context needs no STS creds (the presign transport
|
|
157
|
-
// authorizes every object server-side) AND a compliant sync-runner must not
|
|
158
|
-
// call the company `/sts/vend` route at all — its mere presence is the
|
|
159
|
-
// pre-6.11.6 signal the hq-pro min-version gate denies on. So skip the vend
|
|
160
|
-
// entirely and return a credential-less context with a sentinel expiry. The
|
|
161
|
-
// discriminator is the RESOLVED entity uid (`cmp_`), the exact same prefix
|
|
162
|
-
// `presignObjectIOFactory` uses to route to `PresignObjectIO` — so the
|
|
163
|
-
// vend-skip and the transport choice can never disagree. Personal vaults
|
|
164
|
-
// (`prs_*`) always vend self.
|
|
165
|
-
const isCompanyEntity = entity.uid.startsWith("cmp_");
|
|
166
|
-
const skipCompanyVend =
|
|
167
|
-
config.companyVaultUsesPresign === true && isCompanyEntity;
|
|
168
|
-
|
|
169
|
-
let ctx: EntityContext;
|
|
170
|
-
if (skipCompanyVend) {
|
|
171
|
-
ctx = {
|
|
172
|
-
uid: entity.uid,
|
|
173
|
-
slug: entity.slug,
|
|
174
|
-
bucketName: entity.bucketName,
|
|
175
|
-
region: config.region ?? "us-east-1",
|
|
176
|
-
// No STS vend on the presign path — see PRESIGN_NO_VEND_EXPIRES_AT.
|
|
177
|
-
credentials: undefined,
|
|
178
|
-
expiresAt: PRESIGN_NO_VEND_EXPIRES_AT,
|
|
179
|
-
};
|
|
180
|
-
} else {
|
|
181
|
-
const vendResult = looksLikePerson
|
|
182
|
-
? await vendSelfCredentials(entity.uid, config)
|
|
183
|
-
: await vendCredentials(entity.uid, config);
|
|
184
|
-
|
|
185
|
-
ctx = {
|
|
186
|
-
uid: entity.uid,
|
|
187
|
-
slug: entity.slug,
|
|
188
|
-
bucketName: entity.bucketName,
|
|
189
|
-
region: config.region ?? "us-east-1",
|
|
190
|
-
credentials: {
|
|
191
|
-
accessKeyId: vendResult.credentials.accessKeyId,
|
|
192
|
-
secretAccessKey: vendResult.credentials.secretAccessKey,
|
|
193
|
-
sessionToken: vendResult.credentials.sessionToken,
|
|
194
|
-
},
|
|
195
|
-
expiresAt: vendResult.expiresAt,
|
|
196
|
-
};
|
|
197
|
-
}
|
|
198
|
-
|
|
199
|
-
// Cache by UID (globally unique) and — if the caller asked by slug —
|
|
200
|
-
// by `(callerSub, slug)` so the same slug can resolve to different
|
|
201
|
-
// entities per caller without cross-caller poisoning.
|
|
202
|
-
contextCache.set(entity.uid, ctx);
|
|
203
|
-
if (callerKey) {
|
|
204
|
-
contextCache.set(slugCacheKey(callerKey, entity.slug), ctx);
|
|
205
|
-
}
|
|
206
|
-
|
|
207
|
-
return ctx;
|
|
208
|
-
}
|
|
209
|
-
|
|
210
|
-
/**
|
|
211
|
-
* Check if credentials are expiring within the refresh threshold.
|
|
212
|
-
*/
|
|
213
|
-
export function isExpiringSoon(expiresAt: string): boolean {
|
|
214
|
-
const expiryMs = new Date(expiresAt).getTime();
|
|
215
|
-
const nowMs = Date.now();
|
|
216
|
-
return expiryMs - nowMs < REFRESH_THRESHOLD_MS;
|
|
217
|
-
}
|
|
218
|
-
|
|
219
|
-
/**
|
|
220
|
-
* Force-refresh a cached context. Useful when an S3 operation fails with
|
|
221
|
-
* an expired credentials error.
|
|
222
|
-
*/
|
|
223
|
-
export async function refreshEntityContext(
|
|
224
|
-
companyUidOrSlug: string,
|
|
225
|
-
config: VaultServiceConfig,
|
|
226
|
-
): Promise<EntityContext> {
|
|
227
|
-
// Evict the entry under whichever key shape applies. UID inputs key
|
|
228
|
-
// the cache by the bare uid; slug inputs key by `(callerSub, slug)`.
|
|
229
|
-
const looksLikeUid = KNOWN_UID_PREFIXES.some((p) =>
|
|
230
|
-
companyUidOrSlug.startsWith(p),
|
|
231
|
-
);
|
|
232
|
-
if (looksLikeUid) {
|
|
233
|
-
contextCache.delete(companyUidOrSlug);
|
|
234
|
-
} else {
|
|
235
|
-
const token = await resolveAuthToken(config);
|
|
236
|
-
const callerKey = callerKeyFromAccessToken(token);
|
|
237
|
-
contextCache.delete(slugCacheKey(callerKey, companyUidOrSlug));
|
|
238
|
-
}
|
|
239
|
-
return resolveEntityContext(companyUidOrSlug, config);
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Clear the entire context cache. Useful for tests.
|
|
244
|
-
*/
|
|
245
|
-
export function clearContextCache(): void {
|
|
246
|
-
contextCache.clear();
|
|
247
|
-
}
|
|
248
|
-
|
|
249
|
-
// ---------------------------------------------------------------------------
|
|
250
|
-
// Vault-service API calls
|
|
251
|
-
// ---------------------------------------------------------------------------
|
|
252
|
-
|
|
253
|
-
interface EntityResponse {
|
|
254
|
-
uid: string;
|
|
255
|
-
slug: string;
|
|
256
|
-
bucketName?: string;
|
|
257
|
-
status: string;
|
|
258
|
-
}
|
|
259
|
-
|
|
260
|
-
interface VendResponse {
|
|
261
|
-
credentials: {
|
|
262
|
-
accessKeyId: string;
|
|
263
|
-
secretAccessKey: string;
|
|
264
|
-
sessionToken: string;
|
|
265
|
-
expiration: string;
|
|
266
|
-
};
|
|
267
|
-
expiresAt: string;
|
|
268
|
-
}
|
|
269
|
-
|
|
270
|
-
/**
|
|
271
|
-
* Resolve the current bearer token from a VaultServiceConfig. Accepts either
|
|
272
|
-
* a static string (back-compat with short-lived tools) or an async getter
|
|
273
|
-
* (long-running callers like `hq-sync-runner` pass `() => getValidAccessToken(...)`
|
|
274
|
-
* so each request picks up token refreshes that landed in
|
|
275
|
-
* `~/.hq/cognito-tokens.json` since the run started).
|
|
276
|
-
*/
|
|
277
|
-
async function resolveAuthToken(config: VaultServiceConfig): Promise<string> {
|
|
278
|
-
return typeof config.authToken === "function"
|
|
279
|
-
? config.authToken()
|
|
280
|
-
: config.authToken;
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
async function fetchEntity(
|
|
284
|
-
uid: string,
|
|
285
|
-
config: VaultServiceConfig,
|
|
286
|
-
): Promise<EntityResponse> {
|
|
287
|
-
const res = await fetch(`${config.apiUrl}/entity/${uid}`, {
|
|
288
|
-
headers: {
|
|
289
|
-
Authorization: `Bearer ${await resolveAuthToken(config)}`,
|
|
290
|
-
...buildClientHeaders(config.clientInfo),
|
|
291
|
-
},
|
|
292
|
-
});
|
|
293
|
-
if (!res.ok) {
|
|
294
|
-
const body = await res.text();
|
|
295
|
-
throw new Error(`Failed to fetch entity ${uid}: ${res.status} ${body}`);
|
|
296
|
-
}
|
|
297
|
-
const data = (await res.json()) as { entity: EntityResponse };
|
|
298
|
-
return data.entity;
|
|
299
|
-
}
|
|
300
|
-
|
|
301
|
-
async function fetchEntityBySlug(
|
|
302
|
-
type: string,
|
|
303
|
-
slug: string,
|
|
304
|
-
config: VaultServiceConfig,
|
|
305
|
-
): Promise<EntityResponse> {
|
|
306
|
-
// Resolve the slug inside the CALLER's namespace via the new
|
|
307
|
-
// `/entity/check-slug/me` endpoint added in hq-pro PR 67 (live
|
|
308
|
-
// in prod 2026-05-15). Under the per-user-namespace model the
|
|
309
|
-
// legacy `/entity/by-slug/{type}/{slug}` either over-matches
|
|
310
|
-
// (returns another tenant's entity when more than one user holds
|
|
311
|
-
// the slug) or 409s with SlugNotUniqueError — both wrong for the
|
|
312
|
-
// sync runner's "I want MY company" intent.
|
|
313
|
-
const checkUrl = `${config.apiUrl}/entity/check-slug/me?type=${encodeURIComponent(type)}&slug=${encodeURIComponent(slug)}`;
|
|
314
|
-
const checkRes = await fetch(checkUrl, {
|
|
315
|
-
headers: {
|
|
316
|
-
Authorization: `Bearer ${await resolveAuthToken(config)}`,
|
|
317
|
-
...buildClientHeaders(config.clientInfo),
|
|
318
|
-
},
|
|
319
|
-
});
|
|
320
|
-
if (!checkRes.ok) {
|
|
321
|
-
const body = await checkRes.text();
|
|
322
|
-
throw new Error(
|
|
323
|
-
`Failed to check slug ${type}/${slug}: ${checkRes.status} ${body}`,
|
|
324
|
-
);
|
|
325
|
-
}
|
|
326
|
-
const check = (await checkRes.json()) as {
|
|
327
|
-
available: boolean;
|
|
328
|
-
conflictingCompanyUid?: string;
|
|
329
|
-
};
|
|
330
|
-
if (check.available || !check.conflictingCompanyUid) {
|
|
331
|
-
// The slug isn't in the caller's namespace. From the sync
|
|
332
|
-
// runner's perspective this is a genuine "not found" — the
|
|
333
|
-
// caller doesn't have a `${slug}` to sync, even if some other
|
|
334
|
-
// user does.
|
|
335
|
-
throw new Error(
|
|
336
|
-
`No entity in caller's namespace matches ${type}/${slug}. ` +
|
|
337
|
-
`If you expected to find one, verify you're signed in to the right HQ identity.`,
|
|
338
|
-
);
|
|
339
|
-
}
|
|
340
|
-
return fetchEntity(check.conflictingCompanyUid, config);
|
|
341
|
-
}
|
|
342
|
-
|
|
343
|
-
async function postVend(
|
|
344
|
-
route: string,
|
|
345
|
-
body: Record<string, unknown>,
|
|
346
|
-
config: VaultServiceConfig,
|
|
347
|
-
): Promise<VendResponse> {
|
|
348
|
-
const res = await fetch(`${config.apiUrl}${route}`, {
|
|
349
|
-
method: "POST",
|
|
350
|
-
headers: {
|
|
351
|
-
"Content-Type": "application/json",
|
|
352
|
-
Authorization: `Bearer ${await resolveAuthToken(config)}`,
|
|
353
|
-
...buildClientHeaders(config.clientInfo),
|
|
354
|
-
},
|
|
355
|
-
body: JSON.stringify({ ...body, durationSeconds: DEFAULT_SESSION_DURATION_SECONDS }),
|
|
356
|
-
});
|
|
357
|
-
if (!res.ok) {
|
|
358
|
-
const text = await res.text();
|
|
359
|
-
throw new Error(`STS ${route} failed: ${res.status} ${text}`);
|
|
360
|
-
}
|
|
361
|
-
return (await res.json()) as VendResponse;
|
|
362
|
-
}
|
|
363
|
-
|
|
364
|
-
async function vendCredentials(
|
|
365
|
-
companyUid: string,
|
|
366
|
-
config: VaultServiceConfig,
|
|
367
|
-
): Promise<VendResponse> {
|
|
368
|
-
return postVend("/sts/vend", { companyUid }, config);
|
|
369
|
-
}
|
|
370
|
-
|
|
371
|
-
async function vendSelfCredentials(
|
|
372
|
-
personUid: string,
|
|
373
|
-
config: VaultServiceConfig,
|
|
374
|
-
): Promise<VendResponse> {
|
|
375
|
-
// Use `+` concat at the literal so guard 5 (grep "/sts/vend-self") still finds it
|
|
376
|
-
const route = "/sts/vend-self";
|
|
377
|
-
return postVend(route, { personUid }, config);
|
|
378
|
-
}
|
package/src/daemon-worker.ts
DELETED
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Daemon worker — runs as a detached child process
|
|
3
|
-
* Watches HQ directory and syncs changes to S3
|
|
4
|
-
*
|
|
5
|
-
* Day 1: not invoked by CLI surface; retained for future automatic-sync milestone.
|
|
6
|
-
* When re-enabled, this worker will need to resolve an EntityContext before
|
|
7
|
-
* constructing the active watcher/runner path. The process argv will need to
|
|
8
|
-
* include company context (slug or UID) and vault-service config.
|
|
9
|
-
*/
|
|
10
|
-
|
|
11
|
-
// Day 1: this worker still requires EntityContext-aware startup wiring.
|
|
12
|
-
// This file is retained for the automatic-sync milestone but is not functional
|
|
13
|
-
// until the daemon startup path is updated to resolve entity context.
|
|
14
|
-
|
|
15
|
-
const hqRoot = process.argv[2];
|
|
16
|
-
|
|
17
|
-
if (!hqRoot) {
|
|
18
|
-
console.error("Usage: daemon-worker <hq-root>");
|
|
19
|
-
process.exit(1);
|
|
20
|
-
}
|
|
21
|
-
|
|
22
|
-
console.error(
|
|
23
|
-
"Day 1: daemon-worker is not yet wired to entity context resolution. " +
|
|
24
|
-
"Use 'hq share' and 'hq sync' for manual sync.",
|
|
25
|
-
);
|
|
26
|
-
process.exit(1);
|
package/src/daemon.ts
DELETED
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Background sync daemon management
|
|
3
|
-
* Manages a child process that runs the file watcher
|
|
4
|
-
*
|
|
5
|
-
* Day 1: not invoked by CLI surface; retained for future automatic-sync milestone.
|
|
6
|
-
*/
|
|
7
|
-
|
|
8
|
-
import * as fs from "fs";
|
|
9
|
-
import * as path from "path";
|
|
10
|
-
import { fork } from "child_process";
|
|
11
|
-
import { fileURLToPath } from "url";
|
|
12
|
-
import type { DaemonState } from "./types.js";
|
|
13
|
-
|
|
14
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
15
|
-
const __dirname = path.dirname(__filename);
|
|
16
|
-
|
|
17
|
-
function getPidFile(hqRoot: string): string {
|
|
18
|
-
return path.join(hqRoot, ".hq-sync.pid");
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
function getStateFile(hqRoot: string): string {
|
|
22
|
-
return path.join(hqRoot, ".hq-sync-daemon.json");
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
export function isDaemonRunning(hqRoot: string): boolean {
|
|
26
|
-
const pidFile = getPidFile(hqRoot);
|
|
27
|
-
if (!fs.existsSync(pidFile)) return false;
|
|
28
|
-
|
|
29
|
-
const pid = parseInt(fs.readFileSync(pidFile, "utf-8").trim(), 10);
|
|
30
|
-
try {
|
|
31
|
-
// signal 0 tests if process exists without killing it
|
|
32
|
-
process.kill(pid, 0);
|
|
33
|
-
return true;
|
|
34
|
-
} catch {
|
|
35
|
-
// Process not running, clean up stale PID file
|
|
36
|
-
fs.unlinkSync(pidFile);
|
|
37
|
-
return false;
|
|
38
|
-
}
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export function startDaemon(hqRoot: string): void {
|
|
42
|
-
if (isDaemonRunning(hqRoot)) {
|
|
43
|
-
console.log(" Sync daemon is already running.");
|
|
44
|
-
return;
|
|
45
|
-
}
|
|
46
|
-
|
|
47
|
-
const workerScript = path.join(__dirname, "daemon-worker.js");
|
|
48
|
-
|
|
49
|
-
const child = fork(workerScript, [hqRoot], {
|
|
50
|
-
detached: true,
|
|
51
|
-
stdio: "ignore",
|
|
52
|
-
});
|
|
53
|
-
|
|
54
|
-
child.unref();
|
|
55
|
-
|
|
56
|
-
if (child.pid) {
|
|
57
|
-
// Write PID file
|
|
58
|
-
fs.writeFileSync(getPidFile(hqRoot), String(child.pid));
|
|
59
|
-
|
|
60
|
-
// Write state
|
|
61
|
-
const state: DaemonState = {
|
|
62
|
-
pid: child.pid,
|
|
63
|
-
startedAt: new Date().toISOString(),
|
|
64
|
-
hqRoot,
|
|
65
|
-
};
|
|
66
|
-
fs.writeFileSync(getStateFile(hqRoot), JSON.stringify(state, null, 2));
|
|
67
|
-
}
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
export function stopDaemon(hqRoot: string): void {
|
|
71
|
-
const pidFile = getPidFile(hqRoot);
|
|
72
|
-
if (!fs.existsSync(pidFile)) {
|
|
73
|
-
console.log(" No sync daemon running.");
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
|
|
77
|
-
const pid = parseInt(fs.readFileSync(pidFile, "utf-8").trim(), 10);
|
|
78
|
-
try {
|
|
79
|
-
process.kill(pid, "SIGTERM");
|
|
80
|
-
} catch {
|
|
81
|
-
// Already dead
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
// Clean up files
|
|
85
|
-
if (fs.existsSync(pidFile)) fs.unlinkSync(pidFile);
|
|
86
|
-
const stateFile = getStateFile(hqRoot);
|
|
87
|
-
if (fs.existsSync(stateFile)) fs.unlinkSync(stateFile);
|
|
88
|
-
}
|
|
89
|
-
|
|
90
|
-
export function getDaemonState(hqRoot: string): DaemonState | null {
|
|
91
|
-
const stateFile = getStateFile(hqRoot);
|
|
92
|
-
if (!fs.existsSync(stateFile)) return null;
|
|
93
|
-
try {
|
|
94
|
-
const content = fs.readFileSync(stateFile, "utf-8");
|
|
95
|
-
return JSON.parse(content) as DaemonState;
|
|
96
|
-
} catch {
|
|
97
|
-
return null;
|
|
98
|
-
}
|
|
99
|
-
}
|