@indigoai-us/hq-cli 5.77.12 → 5.77.13
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 +37 -0
- package/dist/commands/outposts-heartbeat.d.ts +96 -0
- package/dist/commands/outposts-heartbeat.js +188 -0
- package/dist/commands/outposts.js +3 -0
- package/dist/outpost/session-heartbeat-publisher.d.ts +76 -0
- package/dist/outpost/session-heartbeat-publisher.js +117 -0
- package/dist/outpost/session-heartbeat.d.ts +210 -0
- package/dist/outpost/session-heartbeat.js +657 -0
- package/dist/utils/vault-api.d.ts +8 -1
- package/dist/utils/vault-api.js +3 -2
- package/package.json +3 -1
- package/src/commands/outposts-heartbeat.test.ts +299 -0
- package/src/commands/outposts-heartbeat.ts +310 -0
- package/src/commands/outposts.ts +4 -0
- package/src/outpost/session-heartbeat-bounds.test.ts +195 -0
- package/src/outpost/session-heartbeat-guard.test.ts +105 -0
- package/src/outpost/session-heartbeat-publisher.test.ts +178 -0
- package/src/outpost/session-heartbeat-publisher.ts +186 -0
- package/src/outpost/session-heartbeat-retain-guard.test.ts +126 -0
- package/src/outpost/session-heartbeat.test.ts +459 -0
- package/src/outpost/session-heartbeat.ts +877 -0
- package/src/packaging.test.ts +45 -0
- package/src/utils/vault-api.ts +13 -2
package/src/packaging.test.ts
CHANGED
|
@@ -61,4 +61,49 @@ describe("packaging: runtime dependencies", () => {
|
|
|
61
61
|
expect(pkg.dependencies).toHaveProperty("@aws-sdk/client-s3");
|
|
62
62
|
expect(pkg.devDependencies).not.toHaveProperty("@aws-sdk/client-s3");
|
|
63
63
|
});
|
|
64
|
+
|
|
65
|
+
it("ships the IoT data-plane client imported by the session heartbeat", () => {
|
|
66
|
+
expect(pkg.dependencies).toHaveProperty("@aws-sdk/client-iot-data-plane");
|
|
67
|
+
expect(pkg.devDependencies).not.toHaveProperty(
|
|
68
|
+
"@aws-sdk/client-iot-data-plane",
|
|
69
|
+
);
|
|
70
|
+
});
|
|
71
|
+
});
|
|
72
|
+
|
|
73
|
+
/**
|
|
74
|
+
* Regression pin for the 2026-07 dead-heartbeat incident.
|
|
75
|
+
*
|
|
76
|
+
* The Outpost's systemd unit invoked `outpost-session-heartbeat-runner`, a
|
|
77
|
+
* command that was never written. The provisioning test asserted only that the
|
|
78
|
+
* generated user-data CONTAINED that string, so it passed for the entire life
|
|
79
|
+
* of the bug while the box logged "command not found" every five seconds and
|
|
80
|
+
* the box audit reported the service healthy.
|
|
81
|
+
*
|
|
82
|
+
* The lesson: asserting that a command NAME appears somewhere proves nothing.
|
|
83
|
+
* Assert the command RESOLVES. If `hq outposts heartbeat` is ever renamed,
|
|
84
|
+
* un-registered, or dropped from the build, this fails here — before a box is
|
|
85
|
+
* provisioned against it.
|
|
86
|
+
*/
|
|
87
|
+
describe("packaging: on-box commands the Outpost user-data invokes", () => {
|
|
88
|
+
it("registers `hq outposts heartbeat`", async () => {
|
|
89
|
+
const { Command } = await import("commander");
|
|
90
|
+
const { registerOutpostsCommand } = await import("./commands/outposts.js");
|
|
91
|
+
|
|
92
|
+
const program = new Command();
|
|
93
|
+
registerOutpostsCommand(program);
|
|
94
|
+
|
|
95
|
+
const outposts = program.commands.find((c) => c.name() === "outposts");
|
|
96
|
+
expect(outposts, "`hq outposts` must be registered").toBeDefined();
|
|
97
|
+
|
|
98
|
+
const heartbeat = outposts?.commands.find((c) => c.name() === "heartbeat");
|
|
99
|
+
expect(heartbeat, "`hq outposts heartbeat` must be registered").toBeDefined();
|
|
100
|
+
|
|
101
|
+
// The flags the systemd unit passes must all exist, or the unit dies on an
|
|
102
|
+
// "unknown option" at boot — a failure mode indistinguishable from the one
|
|
103
|
+
// this test exists to prevent.
|
|
104
|
+
const flags = (heartbeat?.options ?? []).map((o) => o.long);
|
|
105
|
+
expect(flags).toEqual(
|
|
106
|
+
expect.arrayContaining(["--once", "--interval", "--home", "--api-base-url"]),
|
|
107
|
+
);
|
|
108
|
+
});
|
|
64
109
|
});
|
package/src/utils/vault-api.ts
CHANGED
|
@@ -5,6 +5,13 @@ import { CompanySelectionError } from './company-selection-error.js';
|
|
|
5
5
|
import { recordPlanLimitStatus } from '../lib/plan-limit-nag.js';
|
|
6
6
|
|
|
7
7
|
export interface VaultApiOptions {
|
|
8
|
+
/**
|
|
9
|
+
* Control-plane origin. Defaults to DEFAULT_VAULT_API_URL. Set it when a
|
|
10
|
+
* caller has been pointed at another deployment, so identity and every
|
|
11
|
+
* other call resolve against the SAME plane — a token minted for one and
|
|
12
|
+
* sent to another is simply rejected, which reads as an auth failure.
|
|
13
|
+
*/
|
|
14
|
+
baseUrl?: string;
|
|
8
15
|
token: string;
|
|
9
16
|
path: string;
|
|
10
17
|
method?: string;
|
|
@@ -72,7 +79,7 @@ async function peekPlanLimitStatus(response: Response): Promise<Response> {
|
|
|
72
79
|
}
|
|
73
80
|
|
|
74
81
|
export async function vaultApiFetch(opts: VaultApiOptions): Promise<Response> {
|
|
75
|
-
const url = new URL(opts.path, DEFAULT_VAULT_API_URL);
|
|
82
|
+
const url = new URL(opts.path, opts.baseUrl ?? DEFAULT_VAULT_API_URL);
|
|
76
83
|
if (opts.query) {
|
|
77
84
|
for (const [k, v] of Object.entries(opts.query)) {
|
|
78
85
|
url.searchParams.set(k, v);
|
|
@@ -335,10 +342,14 @@ interface PersonEntity {
|
|
|
335
342
|
|
|
336
343
|
// Same selection rule as the backend's `resolveCallerPersonUid`: ascending by
|
|
337
344
|
// createdAt, tie-break by uid ascending. Returns the `prs_*` UID.
|
|
338
|
-
export async function resolveCallerPersonUid(
|
|
345
|
+
export async function resolveCallerPersonUid(
|
|
346
|
+
token: string,
|
|
347
|
+
baseUrl?: string,
|
|
348
|
+
): Promise<string> {
|
|
339
349
|
const res = await vaultApiFetch({
|
|
340
350
|
token,
|
|
341
351
|
path: '/entity/by-type/person',
|
|
352
|
+
baseUrl,
|
|
342
353
|
});
|
|
343
354
|
if (!res.ok) {
|
|
344
355
|
throw new Error("Failed to fetch person entity — run `hq login` and try again");
|