@indigoai-us/hq-cli 5.77.13 → 5.78.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +34 -0
- package/assets/scaffold/core/scripts/archive-old-threads.sh +86 -0
- package/assets/scaffold/core/scripts/backfill-company-skill-mirrors.sh +73 -0
- package/assets/scaffold/core/scripts/backfill-workspace-mirror.sh +51 -0
- package/assets/scaffold/core/scripts/codex-skill-bridge.sh +550 -0
- package/assets/scaffold/core/scripts/detect-stale-core-policy-mirror.sh +185 -0
- package/assets/scaffold/core/scripts/hq-status-summary.sh +187 -0
- package/assets/scaffold/core/scripts/ontology-readme-drift.sh +264 -0
- package/assets/scaffold/core/scripts/qmd-reindex-after-sync.sh +93 -0
- package/assets/scaffold/core/scripts/rebuild-all-indexes.sh +69 -0
- package/assets/scaffold/core/scripts/rebuild-companies-index.sh +206 -0
- package/assets/scaffold/core/scripts/rebuild-company-knowledge-index.sh +127 -0
- package/assets/scaffold/core/scripts/rebuild-orchestrator-index.sh +86 -0
- package/assets/scaffold/core/scripts/rebuild-projects-index.sh +105 -0
- package/assets/scaffold/core/scripts/rebuild-public-knowledge-index.sh +83 -0
- package/assets/scaffold/core/scripts/rebuild-reports-index.sh +114 -0
- package/assets/scaffold/core/scripts/rebuild-social-drafts-index.sh +135 -0
- package/assets/scaffold/core/scripts/rebuild-threads-index.sh +82 -0
- package/assets/scaffold/core/scripts/rebuild-workers-index.sh +97 -0
- package/assets/scaffold/core/scripts/resize-screenshot.sh +47 -0
- package/assets/scaffold/core/scripts/token-usage-report.sh +254 -0
- package/assets/scaffold/core/scripts/worktree.sh +136 -0
- package/dist/commands/agents.js +28 -12
- package/dist/commands/api-keys.js +100 -20
- package/dist/commands/core.d.ts +90 -0
- package/dist/commands/core.js +279 -0
- package/dist/commands/files-browse.d.ts +21 -0
- package/dist/commands/files-browse.js +145 -24
- package/dist/commands/files.js +33 -7
- package/dist/commands/members.js +16 -5
- package/dist/commands/pack-install.d.ts +14 -0
- package/dist/commands/pack-install.js +51 -2
- package/dist/commands/pkg-install.js +43 -0
- package/dist/commands/run.js +8 -0
- package/dist/commands/secrets.js +125 -65
- package/dist/main.js +6 -0
- package/dist/utils/api-key-command-gate.d.ts +45 -0
- package/dist/utils/api-key-command-gate.js +51 -0
- package/dist/utils/hq-roots.d.ts +91 -0
- package/dist/utils/hq-roots.js +212 -0
- package/dist/utils/resolve-vault-credential.d.ts +63 -3
- package/dist/utils/resolve-vault-credential.js +152 -6
- package/dist/utils/run-bundled-script.d.ts +66 -0
- package/dist/utils/run-bundled-script.js +80 -0
- package/dist/utils/vault-api.d.ts +21 -0
- package/dist/utils/vault-api.js +74 -3
- package/package.json +7 -1
- package/.claude/policies/hq-cli-interactive-reads-use-sts-vend.md +0 -49
- package/.github/workflows/ci.yml +0 -46
- package/.github/workflows/publish.yml +0 -36
- package/.github/workflows/scripts/smoke-test-pkg.sh +0 -97
- package/e2e/cli.test.ts +0 -128
- package/eslint.config.js +0 -37
- package/pnpm-workspace.yaml +0 -4
- package/scripts/chmod-bins.mjs +0 -49
- package/scripts/generate-dsn.mjs +0 -26
- package/scripts/generate-scan-packages-table.mjs +0 -113
- package/scripts/smoke-sources-signals.sh +0 -103
- package/src/bin/hq-auth-refresh.ts +0 -43
- package/src/cli-version.ts +0 -13
- package/src/commands/__fixtures__/make-tar.ts +0 -126
- package/src/commands/_patterns.ts +0 -10
- package/src/commands/add.ts +0 -75
- package/src/commands/agents.test.ts +0 -527
- package/src/commands/agents.ts +0 -923
- package/src/commands/api-keys.test.ts +0 -291
- package/src/commands/api-keys.ts +0 -382
- package/src/commands/artifact-verify.test.ts +0 -177
- package/src/commands/auth.test.ts +0 -82
- package/src/commands/auth.ts +0 -213
- package/src/commands/billing.test.ts +0 -158
- package/src/commands/billing.ts +0 -146
- package/src/commands/channels.test.ts +0 -74
- package/src/commands/channels.ts +0 -91
- package/src/commands/cloud-demote.test.ts +0 -401
- package/src/commands/cloud-demote.ts +0 -278
- package/src/commands/cloud-provision.test.ts +0 -1117
- package/src/commands/cloud-provision.ts +0 -942
- package/src/commands/cloud.pull-all.test.ts +0 -711
- package/src/commands/cloud.pull-per-company.test.ts +0 -188
- package/src/commands/cloud.push-all.test.ts +0 -485
- package/src/commands/cloud.scope-excluded-warning.test.ts +0 -22
- package/src/commands/cloud.selectors.test.ts +0 -208
- package/src/commands/cloud.status.test.ts +0 -160
- package/src/commands/cloud.test.ts +0 -41
- package/src/commands/cloud.ts +0 -2130
- package/src/commands/company.test.ts +0 -202
- package/src/commands/company.ts +0 -148
- package/src/commands/creators.test.ts +0 -60
- package/src/commands/creators.ts +0 -117
- package/src/commands/crm.test.ts +0 -179
- package/src/commands/crm.ts +0 -236
- package/src/commands/db-migrate.ts +0 -55
- package/src/commands/db-provision.ts +0 -114
- package/src/commands/db-sql.ts +0 -124
- package/src/commands/db-status.ts +0 -108
- package/src/commands/db.ts +0 -26
- package/src/commands/dm.test.ts +0 -748
- package/src/commands/dm.ts +0 -1226
- package/src/commands/feedback.test.ts +0 -413
- package/src/commands/feedback.ts +0 -169
- package/src/commands/files-browse.test.ts +0 -1249
- package/src/commands/files-browse.ts +0 -1506
- package/src/commands/files-delete.test.ts +0 -484
- package/src/commands/files-recovery.test.ts +0 -370
- package/src/commands/files.test.ts +0 -722
- package/src/commands/files.ts +0 -1446
- package/src/commands/group-grants.test.ts +0 -330
- package/src/commands/group-grants.ts +0 -455
- package/src/commands/groups.test.ts +0 -44
- package/src/commands/groups.ts +0 -368
- package/src/commands/integrations.test.ts +0 -633
- package/src/commands/integrations.ts +0 -507
- package/src/commands/list.ts +0 -85
- package/src/commands/login.ts +0 -52
- package/src/commands/logout.ts +0 -30
- package/src/commands/marketplace-install.test.ts +0 -543
- package/src/commands/marketplace-security.test.ts +0 -646
- package/src/commands/mcp-registration.test.ts +0 -2787
- package/src/commands/mcp-registration.ts +0 -2612
- package/src/commands/mcp-status.test.ts +0 -483
- package/src/commands/mcp-status.ts +0 -575
- package/src/commands/mcp-status.us011.test.ts +0 -243
- package/src/commands/meetings.test.ts +0 -522
- package/src/commands/meetings.ts +0 -789
- package/src/commands/members.test.ts +0 -1474
- package/src/commands/members.ts +0 -1181
- package/src/commands/onboard-identity-guard.test.ts +0 -99
- package/src/commands/onboard-identity-guard.ts +0 -103
- package/src/commands/onboard-join.test.ts +0 -96
- package/src/commands/onboard-join.ts +0 -101
- package/src/commands/onboard-warning.test.ts +0 -26
- package/src/commands/onboard-warning.ts +0 -12
- package/src/commands/onboard.ts +0 -267
- package/src/commands/outposts-heartbeat.test.ts +0 -299
- package/src/commands/outposts-heartbeat.ts +0 -310
- package/src/commands/outposts-replica-sync.test.ts +0 -272
- package/src/commands/outposts-self-deploy.test.ts +0 -243
- package/src/commands/outposts.test.ts +0 -1039
- package/src/commands/outposts.ts +0 -1935
- package/src/commands/pack-install-secret-authorization.test.ts +0 -115
- package/src/commands/pack-install.test.ts +0 -1469
- package/src/commands/pack-install.ts +0 -2247
- package/src/commands/pack-update-cache.test.ts +0 -149
- package/src/commands/packs.test.ts +0 -88
- package/src/commands/packs.ts +0 -622
- package/src/commands/people.test.ts +0 -807
- package/src/commands/people.ts +0 -422
- package/src/commands/pkg-install.dispatch.test.ts +0 -71
- package/src/commands/pkg-install.ts +0 -215
- package/src/commands/pkg-list.test.ts +0 -79
- package/src/commands/pkg-list.ts +0 -13
- package/src/commands/pkg-remove.ts +0 -71
- package/src/commands/pkg-update.ts +0 -189
- package/src/commands/publish.test.ts +0 -650
- package/src/commands/publish.ts +0 -580
- package/src/commands/reindex.test.ts +0 -259
- package/src/commands/reindex.ts +0 -274
- package/src/commands/rescue.test.ts +0 -39
- package/src/commands/rescue.ts +0 -210
- package/src/commands/run.env-local.test.ts +0 -88
- package/src/commands/run.test.ts +0 -45
- package/src/commands/run.ts +0 -184
- package/src/commands/safe-extract.test.ts +0 -459
- package/src/commands/safe-extract.ts +0 -444
- package/src/commands/secrets-scope.test.ts +0 -56
- package/src/commands/secrets-scope.ts +0 -32
- package/src/commands/secrets.parse-destination.test.ts +0 -38
- package/src/commands/secrets.test.ts +0 -2037
- package/src/commands/secrets.ts +0 -2240
- package/src/commands/signals.ts +0 -351
- package/src/commands/skill.test.ts +0 -770
- package/src/commands/skill.ts +0 -796
- package/src/commands/sources.ts +0 -362
- package/src/commands/sync-mode.test.ts +0 -366
- package/src/commands/sync-mode.ts +0 -387
- package/src/commands/sync-narrow.test.ts +0 -633
- package/src/commands/sync-narrow.ts +0 -555
- package/src/commands/sync.ts +0 -165
- package/src/commands/team-sync.ts +0 -628
- package/src/commands/update.ts +0 -71
- package/src/commands/whoami.ts +0 -83
- package/src/commands/workers.test.ts +0 -158
- package/src/commands/workers.ts +0 -301
- package/src/index.test.ts +0 -32
- package/src/index.ts +0 -20
- package/src/lib/db/control-plane.test.ts +0 -59
- package/src/lib/db/control-plane.ts +0 -113
- package/src/lib/db/local.test.ts +0 -81
- package/src/lib/db/local.ts +0 -148
- package/src/lib/db/migrate.test.ts +0 -133
- package/src/lib/db/migrate.ts +0 -137
- package/src/lib/db/paths.test.ts +0 -112
- package/src/lib/db/paths.ts +0 -128
- package/src/lib/db/remote-engine.test.ts +0 -44
- package/src/lib/db/remote-engine.ts +0 -148
- package/src/lib/db/remote-sql.test.ts +0 -32
- package/src/lib/db/remote-sql.ts +0 -62
- package/src/lib/db/sql.test.ts +0 -106
- package/src/lib/db/sql.ts +0 -192
- package/src/lib/hq-cloud-dep.smoke.test.ts +0 -75
- package/src/lib/local-tree-diff.test.ts +0 -301
- package/src/lib/local-tree-diff.ts +0 -362
- package/src/lib/narrow-hint-banner.test.ts +0 -235
- package/src/lib/narrow-hint-banner.ts +0 -212
- package/src/lib/plan-limit-nag.test.ts +0 -317
- package/src/lib/plan-limit-nag.ts +0 -264
- package/src/main.ts +0 -371
- package/src/node-preflight.test.ts +0 -60
- package/src/node-preflight.ts +0 -67
- package/src/outpost/session-heartbeat-bounds.test.ts +0 -195
- package/src/outpost/session-heartbeat-guard.test.ts +0 -105
- package/src/outpost/session-heartbeat-publisher.test.ts +0 -178
- package/src/outpost/session-heartbeat-publisher.ts +0 -186
- package/src/outpost/session-heartbeat-retain-guard.test.ts +0 -126
- package/src/outpost/session-heartbeat.test.ts +0 -459
- package/src/outpost/session-heartbeat.ts +0 -877
- package/src/packaging.test.ts +0 -109
- package/src/run/__fixtures__/discover-schemas/example.env.schema +0 -4
- package/src/run/discover-schemas.test.ts +0 -153
- package/src/run/discover-schemas.ts +0 -79
- package/src/run/hq-plugin.test.ts +0 -371
- package/src/run/hq-plugin.ts +0 -277
- package/src/run/varlock-shape.test.ts +0 -57
- package/src/schemas/hq-package.schema.json +0 -155
- package/src/sentry-before-send.test.ts +0 -359
- package/src/sentry-before-send.ts +0 -221
- package/src/sentry-epipe.test.ts +0 -37
- package/src/sentry-release.test.ts +0 -54
- package/src/sentry.test.ts +0 -24
- package/src/sentry.ts +0 -54
- package/src/strategies/link.ts +0 -62
- package/src/strategies/merge.ts +0 -142
- package/src/types.ts +0 -146
- package/src/utils/__fixtures__/scan-packages.generated-block.sh +0 -23
- package/src/utils/auth-error.test.ts +0 -40
- package/src/utils/auth-error.ts +0 -42
- package/src/utils/billing-gate.test.ts +0 -95
- package/src/utils/billing-gate.ts +0 -228
- package/src/utils/breadcrumb-buffer.ts +0 -18
- package/src/utils/cli-telemetry.test.ts +0 -153
- package/src/utils/cli-telemetry.ts +0 -61
- package/src/utils/cognito-session.machine.test.ts +0 -109
- package/src/utils/cognito-session.refresh.test.ts +0 -84
- package/src/utils/cognito-session.test.ts +0 -328
- package/src/utils/cognito-session.ts +0 -448
- package/src/utils/company-selection-error.test.ts +0 -42
- package/src/utils/company-selection-error.ts +0 -45
- package/src/utils/contribution-table.ts +0 -83
- package/src/utils/environmental-error.test.ts +0 -45
- package/src/utils/environmental-error.ts +0 -39
- package/src/utils/epipe.test.ts +0 -28
- package/src/utils/epipe.ts +0 -29
- package/src/utils/expected-cli-error.test.ts +0 -28
- package/src/utils/expected-cli-error.ts +0 -39
- package/src/utils/feedback-diagnostics.test.ts +0 -183
- package/src/utils/feedback-diagnostics.ts +0 -123
- package/src/utils/feedback-screenshots.test.ts +0 -134
- package/src/utils/feedback-screenshots.ts +0 -124
- package/src/utils/feedback-versions.test.ts +0 -98
- package/src/utils/feedback-versions.ts +0 -68
- package/src/utils/git.ts +0 -74
- package/src/utils/integrity.ts +0 -59
- package/src/utils/intercepted-process-exit.test.ts +0 -37
- package/src/utils/intercepted-process-exit.ts +0 -36
- package/src/utils/login-provider.test.ts +0 -42
- package/src/utils/login-provider.ts +0 -30
- package/src/utils/manifest.test.ts +0 -167
- package/src/utils/manifest.ts +0 -131
- package/src/utils/pack-contributions.test.ts +0 -603
- package/src/utils/pack-contributions.ts +0 -450
- package/src/utils/people.ts +0 -215
- package/src/utils/registry-client.ts +0 -204
- package/src/utils/registry.ts +0 -90
- package/src/utils/resolve-vault-credential.test.ts +0 -69
- package/src/utils/resolve-vault-credential.ts +0 -60
- package/src/utils/sandbox-runner-client.test.ts +0 -279
- package/src/utils/sandbox-runner-client.ts +0 -260
- package/src/utils/secrets-cache.ts +0 -183
- package/src/utils/sentry-identity.ts +0 -45
- package/src/utils/settle-with-timeout.test.ts +0 -21
- package/src/utils/settle-with-timeout.ts +0 -22
- package/src/utils/user-yaml-error.test.ts +0 -24
- package/src/utils/user-yaml-error.ts +0 -30
- package/src/utils/vault-api.test.ts +0 -536
- package/src/utils/vault-api.ts +0 -382
- package/src/utils/version-check.test.ts +0 -176
- package/src/utils/version-check.ts +0 -155
- package/src/utils/version-gate.test.ts +0 -1024
- package/src/utils/version-gate.ts +0 -702
- package/test/commands/db-tenant-isolation.test.ts +0 -94
- package/test/commands/db.test.ts +0 -85
- package/test/commands/signals.test.ts +0 -207
- package/test/commands/sources.test.ts +0 -231
- package/test/e2e/smoke-install-mcp.sh +0 -113
- package/test/fixtures/hq-pack-smoke-mcp/mcp/smoke-http.json +0 -1
- package/test/fixtures/hq-pack-smoke-mcp/package.yaml +0 -11
- package/test/fixtures/signals/action_item/sample.md +0 -16
- package/test/fixtures/signals/summary/sample.md +0 -12
- package/test/fixtures/sources/meetings/sample.md +0 -25
- package/test/helpers/cli-runner.ts +0 -150
- package/test/helpers/s3-list-mock.ts +0 -79
- package/test/helpers/vault-service-mock.ts +0 -295
- package/test/sources-signals/smoke.test.ts +0 -228
- package/tsconfig.json +0 -20
- package/vitest.config.ts +0 -11
- package/vitest.e2e.config.ts +0 -7
|
@@ -1,99 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Regression tests for the onboarding identity-link guard
|
|
3
|
-
* (DEV-1695 / DEV-1701 / DEV-1721).
|
|
4
|
-
*
|
|
5
|
-
* Covers the resume-with-mismatched-sub path: a checkpoint that completed
|
|
6
|
-
* `create-person` against a `personUid` the current caller does NOT own must
|
|
7
|
-
* be detected as a recoverable mismatch instead of being allowed to loop
|
|
8
|
-
* forever at bootstrap-membership. Also covers the no-false-positive cases
|
|
9
|
-
* (no checkpoint, no adopted person, create-person not complete, person owned).
|
|
10
|
-
*
|
|
11
|
-
* Pure function — no network, no VaultClient. The resume command supplies the
|
|
12
|
-
* owned-person UID set from the JWT-scoped `/entity/by-type/person` call.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import { describe, expect, it } from "vitest";
|
|
16
|
-
import type { OnboardingCheckpoint } from "@indigoai-us/hq-onboarding";
|
|
17
|
-
|
|
18
|
-
import { detectOnboardingIdentityMismatch } from "./onboard-identity-guard.js";
|
|
19
|
-
|
|
20
|
-
const OLD_PERSON = "prs_01KRGW9JQ4D2ZHD080B931ZT0J";
|
|
21
|
-
const NEW_PERSON = "prs_01NEWOWNEDBYCURRENTSIGNIN00";
|
|
22
|
-
|
|
23
|
-
function checkpoint(
|
|
24
|
-
overrides: Partial<OnboardingCheckpoint> = {},
|
|
25
|
-
): OnboardingCheckpoint {
|
|
26
|
-
return {
|
|
27
|
-
mode: "create-company",
|
|
28
|
-
startedAt: "2026-05-13T14:36:45.668Z",
|
|
29
|
-
updatedAt: "2026-06-03T14:04:32.261Z",
|
|
30
|
-
personUid: OLD_PERSON,
|
|
31
|
-
companyUid: "cmp_01KT6WSNM6HGSH3JZGS8X1KXGS",
|
|
32
|
-
companySlug: "maximus",
|
|
33
|
-
completedSteps: ["create-person", "create-company", "provision-bucket"],
|
|
34
|
-
failedStep: "bootstrap-membership",
|
|
35
|
-
...overrides,
|
|
36
|
-
};
|
|
37
|
-
}
|
|
38
|
-
|
|
39
|
-
describe("detectOnboardingIdentityMismatch", () => {
|
|
40
|
-
it("flags the mismatch when the checkpoint's person is owned by a different sign-in", () => {
|
|
41
|
-
// The Jacob Wuertz case: checkpoint adopted prs_OLD (owned by the original
|
|
42
|
-
// Cognito sub), but the current caller only owns prs_NEW.
|
|
43
|
-
const result = detectOnboardingIdentityMismatch({
|
|
44
|
-
checkpoint: checkpoint(),
|
|
45
|
-
ownedPersonUids: [NEW_PERSON],
|
|
46
|
-
});
|
|
47
|
-
|
|
48
|
-
expect(result.kind).toBe("mismatch");
|
|
49
|
-
if (result.kind !== "mismatch") throw new Error("expected mismatch");
|
|
50
|
-
expect(result.personUid).toBe(OLD_PERSON);
|
|
51
|
-
expect(result.message).toContain(OLD_PERSON);
|
|
52
|
-
// Actionable + references the recovery paths that actually work.
|
|
53
|
-
expect(result.message).toMatch(/DEV-1695/);
|
|
54
|
-
expect(result.message).toMatch(/original/i);
|
|
55
|
-
expect(result.message).toMatch(/relink/i);
|
|
56
|
-
});
|
|
57
|
-
|
|
58
|
-
it("also flags the mismatch when the caller owns NO person entities at all", () => {
|
|
59
|
-
const result = detectOnboardingIdentityMismatch({
|
|
60
|
-
checkpoint: checkpoint(),
|
|
61
|
-
ownedPersonUids: [],
|
|
62
|
-
});
|
|
63
|
-
expect(result.kind).toBe("mismatch");
|
|
64
|
-
});
|
|
65
|
-
|
|
66
|
-
it("passes when the adopted person IS owned by the current caller", () => {
|
|
67
|
-
const result = detectOnboardingIdentityMismatch({
|
|
68
|
-
checkpoint: checkpoint({ personUid: NEW_PERSON }),
|
|
69
|
-
ownedPersonUids: [NEW_PERSON, "prs_01ANOTHERONE0000000000000000"],
|
|
70
|
-
});
|
|
71
|
-
expect(result).toEqual({ kind: "ok" });
|
|
72
|
-
});
|
|
73
|
-
|
|
74
|
-
it("passes when there is no checkpoint", () => {
|
|
75
|
-
const result = detectOnboardingIdentityMismatch({
|
|
76
|
-
checkpoint: null,
|
|
77
|
-
ownedPersonUids: [NEW_PERSON],
|
|
78
|
-
});
|
|
79
|
-
expect(result).toEqual({ kind: "ok" });
|
|
80
|
-
});
|
|
81
|
-
|
|
82
|
-
it("passes when the checkpoint has not adopted a person yet (no personUid)", () => {
|
|
83
|
-
const result = detectOnboardingIdentityMismatch({
|
|
84
|
-
checkpoint: checkpoint({ personUid: undefined, completedSteps: [] }),
|
|
85
|
-
ownedPersonUids: [],
|
|
86
|
-
});
|
|
87
|
-
expect(result).toEqual({ kind: "ok" });
|
|
88
|
-
});
|
|
89
|
-
|
|
90
|
-
it("passes when create-person is not yet complete, even with a personUid present", () => {
|
|
91
|
-
// A personUid that predates the create-person step completing is not yet
|
|
92
|
-
// the committed identity — don't false-positive on it.
|
|
93
|
-
const result = detectOnboardingIdentityMismatch({
|
|
94
|
-
checkpoint: checkpoint({ completedSteps: ["create-company"] }),
|
|
95
|
-
ownedPersonUids: [NEW_PERSON],
|
|
96
|
-
});
|
|
97
|
-
expect(result).toEqual({ kind: "ok" });
|
|
98
|
-
});
|
|
99
|
-
});
|
|
@@ -1,103 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Onboarding identity-link guard (DEV-1695 / DEV-1701 / DEV-1721).
|
|
3
|
-
*
|
|
4
|
-
* Failure mode this guards against:
|
|
5
|
-
*
|
|
6
|
-
* The onboarding orchestrator's `create-person` step resolves the caller's
|
|
7
|
-
* person entity by an email-derived slug GLOBALLY (not scoped to the caller's
|
|
8
|
-
* Cognito identity). When a user signs back in under a DIFFERENT Cognito
|
|
9
|
-
* `sub` — e.g. a new Google account, an email change, or a linked-IdP sub
|
|
10
|
-
* swap — the slug lookup still finds the person row created under the ORIGINAL
|
|
11
|
-
* sub and records it in the checkpoint's `personUid`. `create-person` is then
|
|
12
|
-
* marked complete and is never re-validated against the live identity.
|
|
13
|
-
*
|
|
14
|
-
* On every `hq onboard resume`, `create-person` is skipped as "already
|
|
15
|
-
* complete" while the stale `personUid` is carried forward. The server
|
|
16
|
-
* correctly refuses to bootstrap a company membership for a person the caller
|
|
17
|
-
* does not own (it resolves the caller's person by the live Cognito sub), so
|
|
18
|
-
* resume re-fails at `bootstrap-membership` every single time — an infinite
|
|
19
|
-
* loop with a misleading downstream error.
|
|
20
|
-
*
|
|
21
|
-
* This module is the detection half of the fix: given the local checkpoint and
|
|
22
|
-
* the set of person entities ACTUALLY owned by the current caller (the server
|
|
23
|
-
* scopes `/entity/by-type/person` by the live Cognito sub), it reports whether
|
|
24
|
-
* the checkpoint adopted a person the caller no longer owns. The resume command
|
|
25
|
-
* uses it to surface a recoverable, actionable error instead of looping.
|
|
26
|
-
*/
|
|
27
|
-
|
|
28
|
-
import type { OnboardingCheckpoint } from "@indigoai-us/hq-onboarding";
|
|
29
|
-
|
|
30
|
-
export type OnboardingIdentityCheck =
|
|
31
|
-
| { kind: "ok" }
|
|
32
|
-
| { kind: "mismatch"; personUid: string; message: string };
|
|
33
|
-
|
|
34
|
-
/**
|
|
35
|
-
* Build the human-facing recovery message for a detected mismatch.
|
|
36
|
-
*
|
|
37
|
-
* Only the two recovery paths that ACTUALLY work are offered. Deleting the
|
|
38
|
-
* checkpoint and re-running `create-company` does NOT help: the email-derived
|
|
39
|
-
* slug would re-adopt the same other-owned person row, so it is deliberately
|
|
40
|
-
* not suggested.
|
|
41
|
-
*/
|
|
42
|
-
function buildMismatchMessage(personUid: string): string {
|
|
43
|
-
return [
|
|
44
|
-
`Onboarding can't continue — an identity-link mismatch is blocking resume.`,
|
|
45
|
-
``,
|
|
46
|
-
`Your saved onboarding checkpoint is linked to person record ${personUid},`,
|
|
47
|
-
`but that record is owned by a different sign-in than the one you're using`,
|
|
48
|
-
`now. This usually means onboarding was started under one identity (one`,
|
|
49
|
-
`Google account / email) and later resumed under a different one.`,
|
|
50
|
-
``,
|
|
51
|
-
`Because the person record belongs to the original sign-in, resume can't`,
|
|
52
|
-
`bootstrap your company membership and would otherwise retry forever.`,
|
|
53
|
-
``,
|
|
54
|
-
`To recover, do ONE of the following:`,
|
|
55
|
-
` 1. Sign out and sign back in with your ORIGINAL onboarding identity,`,
|
|
56
|
-
` then re-run 'hq onboard resume'.`,
|
|
57
|
-
` 2. Ask an HQ admin to relink person ${personUid} to your current`,
|
|
58
|
-
` sign-in (reference Linear DEV-1695), then re-run 'hq onboard resume'.`,
|
|
59
|
-
``,
|
|
60
|
-
`Nothing was changed — your data is safe.`,
|
|
61
|
-
].join("\n");
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
/**
|
|
65
|
-
* Detect the person-entity-vs-Cognito-sub mismatch that wedges
|
|
66
|
-
* `hq onboard resume` into an infinite loop.
|
|
67
|
-
*
|
|
68
|
-
* Returns `{ kind: "ok" }` whenever the flow should proceed normally:
|
|
69
|
-
* - there is no checkpoint, or
|
|
70
|
-
* - the checkpoint never recorded an adopted person (`personUid` unset, or
|
|
71
|
-
* `create-person` not yet completed), or
|
|
72
|
-
* - the adopted person is among those owned by the current caller.
|
|
73
|
-
*
|
|
74
|
-
* Returns `{ kind: "mismatch", ... }` only when the checkpoint completed
|
|
75
|
-
* `create-person` with a `personUid` that the current caller does NOT own —
|
|
76
|
-
* the exact state that loops forever at `bootstrap-membership`.
|
|
77
|
-
*/
|
|
78
|
-
export function detectOnboardingIdentityMismatch(input: {
|
|
79
|
-
checkpoint: OnboardingCheckpoint | null;
|
|
80
|
-
ownedPersonUids: readonly string[];
|
|
81
|
-
}): OnboardingIdentityCheck {
|
|
82
|
-
const { checkpoint, ownedPersonUids } = input;
|
|
83
|
-
|
|
84
|
-
if (!checkpoint) return { kind: "ok" };
|
|
85
|
-
|
|
86
|
-
const adopted = checkpoint.personUid;
|
|
87
|
-
if (!adopted) return { kind: "ok" };
|
|
88
|
-
|
|
89
|
-
// Mirror the orchestrator's `isStepComplete`: a personUid that predates the
|
|
90
|
-
// create-person step completing has not been committed as the adopted
|
|
91
|
-
// identity yet, so don't treat it as a mismatch.
|
|
92
|
-
if (!checkpoint.completedSteps?.includes("create-person")) {
|
|
93
|
-
return { kind: "ok" };
|
|
94
|
-
}
|
|
95
|
-
|
|
96
|
-
if (ownedPersonUids.includes(adopted)) return { kind: "ok" };
|
|
97
|
-
|
|
98
|
-
return {
|
|
99
|
-
kind: "mismatch",
|
|
100
|
-
personUid: adopted,
|
|
101
|
-
message: buildMismatchMessage(adopted),
|
|
102
|
-
};
|
|
103
|
-
}
|
|
@@ -1,96 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Regression tests for the `hq onboard join` tokenless routing
|
|
3
|
-
* (invite-accept-local-attach).
|
|
4
|
-
*
|
|
5
|
-
* The bug: `hq onboard join` declared `--invite-token` as a commander
|
|
6
|
-
* requiredOption, so a modern (tokenless, email-keyed) invitee could not reach
|
|
7
|
-
* the action — they got "required option '--invite-token' not specified",
|
|
8
|
-
* which reads as "my HQ doesn't know I'm a member". The fix routes the
|
|
9
|
-
* no-token case to the real path (`hq sync`) and keeps the legacy token path
|
|
10
|
-
* intact. `planOnboardJoin` is a pure function — no network, no VaultClient.
|
|
11
|
-
*/
|
|
12
|
-
|
|
13
|
-
import { describe, expect, it } from "vitest";
|
|
14
|
-
|
|
15
|
-
import { planOnboardJoin, tokenlessJoinGuidance } from "./onboard-join.js";
|
|
16
|
-
|
|
17
|
-
describe("planOnboardJoin", () => {
|
|
18
|
-
it("routes a no-token invocation to the tokenless (sync) path", () => {
|
|
19
|
-
const plan = planOnboardJoin({});
|
|
20
|
-
expect(plan.kind).toBe("tokenless");
|
|
21
|
-
if (plan.kind === "tokenless") {
|
|
22
|
-
expect(plan.guidance).toBe(tokenlessJoinGuidance());
|
|
23
|
-
}
|
|
24
|
-
});
|
|
25
|
-
|
|
26
|
-
it("treats an empty / whitespace-only token as tokenless", () => {
|
|
27
|
-
expect(planOnboardJoin({ inviteToken: "" }).kind).toBe("tokenless");
|
|
28
|
-
expect(planOnboardJoin({ inviteToken: " " }).kind).toBe("tokenless");
|
|
29
|
-
});
|
|
30
|
-
|
|
31
|
-
it("ignores email / person-name on the tokenless path (sync uses cached identity)", () => {
|
|
32
|
-
// A user who copy-pastes old instructions may still pass these — they must
|
|
33
|
-
// not flip the decision or be required.
|
|
34
|
-
const plan = planOnboardJoin({
|
|
35
|
-
email: "richard@sender.agency",
|
|
36
|
-
personName: "Richard",
|
|
37
|
-
});
|
|
38
|
-
expect(plan.kind).toBe("tokenless");
|
|
39
|
-
});
|
|
40
|
-
|
|
41
|
-
it("keeps the legacy path when a token + email + person-name are all present", () => {
|
|
42
|
-
const plan = planOnboardJoin({
|
|
43
|
-
inviteToken: "tok_abc123",
|
|
44
|
-
email: "richard@sender.agency",
|
|
45
|
-
personName: "Richard",
|
|
46
|
-
});
|
|
47
|
-
expect(plan.kind).toBe("legacy");
|
|
48
|
-
if (plan.kind === "legacy") {
|
|
49
|
-
expect(plan.inviteToken).toBe("tok_abc123");
|
|
50
|
-
expect(plan.email).toBe("richard@sender.agency");
|
|
51
|
-
expect(plan.personName).toBe("Richard");
|
|
52
|
-
}
|
|
53
|
-
});
|
|
54
|
-
|
|
55
|
-
it("trims the token before deciding", () => {
|
|
56
|
-
const plan = planOnboardJoin({
|
|
57
|
-
inviteToken: " tok_abc123 ",
|
|
58
|
-
email: "r@x.co",
|
|
59
|
-
personName: "R",
|
|
60
|
-
});
|
|
61
|
-
expect(plan.kind).toBe("legacy");
|
|
62
|
-
if (plan.kind === "legacy") {
|
|
63
|
-
expect(plan.inviteToken).toBe("tok_abc123");
|
|
64
|
-
}
|
|
65
|
-
});
|
|
66
|
-
|
|
67
|
-
it("errors actionably when a token is given but email/person-name are missing", () => {
|
|
68
|
-
const plan = planOnboardJoin({ inviteToken: "tok_abc123" });
|
|
69
|
-
expect(plan.kind).toBe("error");
|
|
70
|
-
if (plan.kind === "error") {
|
|
71
|
-
expect(plan.message).toContain("--email");
|
|
72
|
-
expect(plan.message).toContain("--person-name");
|
|
73
|
-
// Steers the likely-confused user back to the tokenless path.
|
|
74
|
-
expect(plan.message).toContain("hq sync");
|
|
75
|
-
}
|
|
76
|
-
});
|
|
77
|
-
|
|
78
|
-
it("names only the missing legacy field", () => {
|
|
79
|
-
const plan = planOnboardJoin({ inviteToken: "tok_abc123", email: "r@x.co" });
|
|
80
|
-
expect(plan.kind).toBe("error");
|
|
81
|
-
if (plan.kind === "error") {
|
|
82
|
-
expect(plan.message).toContain("--person-name");
|
|
83
|
-
expect(plan.message).not.toContain("--email and");
|
|
84
|
-
}
|
|
85
|
-
});
|
|
86
|
-
});
|
|
87
|
-
|
|
88
|
-
describe("tokenlessJoinGuidance", () => {
|
|
89
|
-
it("points at both the CLI sync and the menubar Sync button, and disclaims the token", () => {
|
|
90
|
-
const g = tokenlessJoinGuidance();
|
|
91
|
-
expect(g).toContain("hq sync");
|
|
92
|
-
expect(g).toMatch(/menubar|Sync/);
|
|
93
|
-
expect(g).toContain("--invite-token");
|
|
94
|
-
expect(g.toLowerCase()).toContain("don't use a token");
|
|
95
|
-
});
|
|
96
|
-
});
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* `hq onboard join` planning helper.
|
|
3
|
-
*
|
|
4
|
-
* Modern HQ invites are email-keyed and TOKENLESS (hq-pro schemaVersion 2+):
|
|
5
|
-
* the membership row is claimed when the invitee signs into HQ with the
|
|
6
|
-
* invited email, and `hq sync` then discovers every company they belong to
|
|
7
|
-
* and pulls it locally. There is no token to redeem.
|
|
8
|
-
*
|
|
9
|
-
* The legacy path (schemaVersion ≤ 1) handed out a random `inviteToken` the
|
|
10
|
-
* invitee redeemed via `hq://accept/{token}`. `hq onboard join` historically
|
|
11
|
-
* declared `--invite-token` as a commander `requiredOption`, so a modern
|
|
12
|
-
* invitee — who has no token — could not even reach the action handler. They
|
|
13
|
-
* got `error: required option '--invite-token <token>' not specified`, which
|
|
14
|
-
* reads as "my HQ doesn't know I'm a member / I'm missing a token" when in
|
|
15
|
-
* fact the invite is fine and they simply need to sync.
|
|
16
|
-
*
|
|
17
|
-
* This module decides which path a given invocation is on and produces the
|
|
18
|
-
* tokenless guidance, as a pure function so it can be unit-tested without
|
|
19
|
-
* commander, the network, or a VaultClient (same shape as
|
|
20
|
-
* `onboard-identity-guard.ts`).
|
|
21
|
-
*/
|
|
22
|
-
|
|
23
|
-
export interface JoinTokenless {
|
|
24
|
-
kind: "tokenless";
|
|
25
|
-
/** Human-facing guidance pointing at the real (sync) path. */
|
|
26
|
-
guidance: string;
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
export interface JoinLegacy {
|
|
30
|
-
kind: "legacy";
|
|
31
|
-
inviteToken: string;
|
|
32
|
-
email: string;
|
|
33
|
-
personName: string;
|
|
34
|
-
}
|
|
35
|
-
|
|
36
|
-
export interface JoinError {
|
|
37
|
-
kind: "error";
|
|
38
|
-
message: string;
|
|
39
|
-
}
|
|
40
|
-
|
|
41
|
-
export type JoinPlan = JoinTokenless | JoinLegacy | JoinError;
|
|
42
|
-
|
|
43
|
-
export interface JoinOptionsInput {
|
|
44
|
-
inviteToken?: string;
|
|
45
|
-
email?: string;
|
|
46
|
-
personName?: string;
|
|
47
|
-
}
|
|
48
|
-
|
|
49
|
-
/**
|
|
50
|
-
* Guidance shown when `hq onboard join` is run without a token. Covers both
|
|
51
|
-
* the CLI (`hq sync`) and the HQ Sync menubar app (the Sync button), since
|
|
52
|
-
* most invitees are on the app rather than the CLI.
|
|
53
|
-
*/
|
|
54
|
-
export function tokenlessJoinGuidance(): string {
|
|
55
|
-
return [
|
|
56
|
-
"Modern HQ invites don't use a token — there's nothing to redeem.",
|
|
57
|
-
"",
|
|
58
|
-
"If you've accepted your invite (the email link or HQ Console), your",
|
|
59
|
-
"membership already exists. To pull it onto this machine, just sync:",
|
|
60
|
-
"",
|
|
61
|
-
" hq sync",
|
|
62
|
-
"",
|
|
63
|
-
"That signs you in, finds every company you've been added to, and pulls",
|
|
64
|
-
"it locally. In the HQ Sync menubar app, click Sync — same result.",
|
|
65
|
-
"",
|
|
66
|
-
"(--invite-token is only for legacy token invites and is no longer issued.)",
|
|
67
|
-
].join("\n");
|
|
68
|
-
}
|
|
69
|
-
|
|
70
|
-
/**
|
|
71
|
-
* Decide how an `hq onboard join` invocation should proceed.
|
|
72
|
-
*
|
|
73
|
-
* - No token → tokenless: the invitee belongs on the `hq sync` path. Email
|
|
74
|
-
* and person-name are irrelevant here (sync uses the cached Cognito
|
|
75
|
-
* identity), so they are ignored rather than required.
|
|
76
|
-
* - Token → legacy: the historical redeem path. `--email` and
|
|
77
|
-
* `--person-name` are still required to bootstrap the person entity; a
|
|
78
|
-
* missing one yields an actionable error instead of a commander stack trace.
|
|
79
|
-
*/
|
|
80
|
-
export function planOnboardJoin(opts: JoinOptionsInput): JoinPlan {
|
|
81
|
-
const inviteToken = (opts.inviteToken ?? "").trim();
|
|
82
|
-
if (!inviteToken) {
|
|
83
|
-
return { kind: "tokenless", guidance: tokenlessJoinGuidance() };
|
|
84
|
-
}
|
|
85
|
-
|
|
86
|
-
const email = (opts.email ?? "").trim();
|
|
87
|
-
const personName = (opts.personName ?? "").trim();
|
|
88
|
-
const missing: string[] = [];
|
|
89
|
-
if (!email) missing.push("--email");
|
|
90
|
-
if (!personName) missing.push("--person-name");
|
|
91
|
-
if (missing.length > 0) {
|
|
92
|
-
return {
|
|
93
|
-
kind: "error",
|
|
94
|
-
message:
|
|
95
|
-
`Legacy token join (--invite-token) also requires ${missing.join(" and ")}. ` +
|
|
96
|
-
"Most invites are tokenless — omit --invite-token and run `hq sync` instead.",
|
|
97
|
-
};
|
|
98
|
-
}
|
|
99
|
-
|
|
100
|
-
return { kind: "legacy", inviteToken, email, personName };
|
|
101
|
-
}
|
|
@@ -1,26 +0,0 @@
|
|
|
1
|
-
import { describe, expect, it } from "vitest";
|
|
2
|
-
|
|
3
|
-
import { buildCreateCompanyWarning } from "./onboard-warning.js";
|
|
4
|
-
|
|
5
|
-
describe("buildCreateCompanyWarning", () => {
|
|
6
|
-
it("documents the warning shown on the create-company path", () => {
|
|
7
|
-
const warning = buildCreateCompanyWarning();
|
|
8
|
-
expect(warning).toContain("NEW company");
|
|
9
|
-
expect(warning).toMatch(/does NOT join an existing one/i);
|
|
10
|
-
});
|
|
11
|
-
|
|
12
|
-
it("advises existing-company users to ask an admin or owner for an invite", () => {
|
|
13
|
-
const warning = buildCreateCompanyWarning();
|
|
14
|
-
expect(warning).toMatch(/ask your admin or owner.*invite/i);
|
|
15
|
-
});
|
|
16
|
-
|
|
17
|
-
it("tells invitees to run hq sync after accepting", () => {
|
|
18
|
-
const warning = buildCreateCompanyWarning();
|
|
19
|
-
expect(warning).toContain("accept it and run `hq sync`");
|
|
20
|
-
});
|
|
21
|
-
|
|
22
|
-
it("warns that duplicate creation leaves the user alone in a separate company", () => {
|
|
23
|
-
const warning = buildCreateCompanyWarning();
|
|
24
|
-
expect(warning).toMatch(/separate one you'd be alone in/i);
|
|
25
|
-
});
|
|
26
|
-
});
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Builds the static create-time warning surfaced by both `create-company` and
|
|
3
|
-
* `dry-run`, because users may accidentally create duplicate companies instead
|
|
4
|
-
* of joining the existing company they were invited to.
|
|
5
|
-
*/
|
|
6
|
-
export function buildCreateCompanyWarning(): string {
|
|
7
|
-
return (
|
|
8
|
-
" This creates a NEW company that you own — it does NOT join an existing one.\n" +
|
|
9
|
-
" If your company already uses HQ, do NOT create it again: ask your admin or owner to send you an invite, then accept it and run `hq sync` to pull the existing company.\n" +
|
|
10
|
-
" Creating a same-named company makes a separate one you'd be alone in.\n"
|
|
11
|
-
);
|
|
12
|
-
}
|