@openparachute/hub 0.7.7-rc.8 → 0.7.7
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/README.md +7 -7
- package/package.json +4 -12
- package/src/__tests__/account-api.test.ts +29 -2
- package/src/__tests__/account-session.test.ts +9 -1
- package/src/__tests__/account-token.test.ts +26 -2
- package/src/__tests__/admin-connections-credentials.test.ts +41 -0
- package/src/__tests__/admin-lock.test.ts +3 -14
- package/src/__tests__/admin-module-token.test.ts +10 -30
- package/src/__tests__/admin-surfaces.test.ts +21 -0
- package/src/__tests__/api-hub-upgrade.test.ts +11 -0
- package/src/__tests__/api-mint-token.test.ts +25 -0
- package/src/__tests__/api-modules-ops.test.ts +34 -29
- package/src/__tests__/api-modules.test.ts +50 -58
- package/src/__tests__/api-revoke-token.test.ts +23 -0
- package/src/__tests__/api-settings-hub-origin.test.ts +12 -0
- package/src/__tests__/api-settings-root-redirect.test.ts +159 -4
- package/src/__tests__/api-tokens.test.ts +44 -0
- package/src/__tests__/audience-gate.test.ts +24 -0
- package/src/__tests__/bearer-scheme-casing.test.ts +110 -0
- package/src/__tests__/chrome-strip.test.ts +18 -1
- package/src/__tests__/doctor.test.ts +10 -17
- package/src/__tests__/hub-command.test.ts +70 -2
- package/src/__tests__/hub-server.test.ts +322 -1
- package/src/__tests__/hub-settings.test.ts +110 -6
- package/src/__tests__/hub.test.ts +29 -0
- package/src/__tests__/install.test.ts +359 -5
- package/src/__tests__/migrate.test.ts +3 -1
- package/src/__tests__/notes-serve.test.ts +216 -0
- package/src/__tests__/oauth-handlers.test.ts +19 -8
- package/src/__tests__/operator-token.test.ts +1 -2
- package/src/__tests__/port-assign.test.ts +37 -17
- package/src/__tests__/root-serve.test.ts +139 -0
- package/src/__tests__/scope-explanations.test.ts +0 -2
- package/src/__tests__/serve-boot.test.ts +25 -36
- package/src/__tests__/service-spec-discovery.test.ts +30 -35
- package/src/__tests__/services-manifest.test.ts +372 -132
- package/src/__tests__/setup-wizard.test.ts +301 -22
- package/src/__tests__/setup.test.ts +13 -14
- package/src/__tests__/status.test.ts +0 -5
- package/src/__tests__/surface-notes-alias.test.ts +296 -0
- package/src/__tests__/wizard-transcription.test.ts +35 -0
- package/src/__tests__/wizard.test.ts +79 -0
- package/src/account-api.ts +6 -0
- package/src/admin-connections.ts +4 -2
- package/src/admin-lock.ts +1 -2
- package/src/admin-module-token.ts +13 -8
- package/src/admin-surfaces.ts +2 -1
- package/src/api-hub-upgrade.ts +2 -1
- package/src/api-mint-token.ts +2 -1
- package/src/api-modules-ops.ts +2 -1
- package/src/api-modules.ts +10 -8
- package/src/api-revoke-token.ts +2 -1
- package/src/api-settings-hub-origin.ts +2 -1
- package/src/api-settings-root-redirect.ts +97 -20
- package/src/api-tokens.ts +2 -1
- package/src/audience-gate.ts +2 -1
- package/src/chrome-strip.ts +16 -4
- package/src/commands/hub.ts +104 -0
- package/src/commands/install.ts +97 -8
- package/src/commands/migrate.ts +5 -1
- package/src/commands/setup.ts +9 -6
- package/src/commands/wizard-transcription.ts +24 -0
- package/src/commands/wizard.ts +34 -1
- package/src/help.ts +6 -6
- package/src/hub-server.ts +111 -54
- package/src/hub-settings.ts +147 -0
- package/src/hub.ts +64 -31
- package/src/module-ops-client.ts +2 -1
- package/src/notes-serve.ts +73 -31
- package/src/oauth-handlers.ts +1 -11
- package/src/operator-token.ts +0 -1
- package/src/origin-check.ts +2 -2
- package/src/root-serve.ts +156 -0
- package/src/scope-explanations.ts +2 -5
- package/src/service-spec.ts +129 -74
- package/src/services-manifest.ts +112 -52
- package/src/setup-wizard.ts +144 -31
- package/src/surface-notes-alias.ts +126 -0
- package/src/__tests__/admin-agent-token.test.ts +0 -173
- package/src/admin-agent-token.ts +0 -147
|
@@ -395,46 +395,39 @@ describe("bootSupervisedModules", () => {
|
|
|
395
395
|
});
|
|
396
396
|
});
|
|
397
397
|
|
|
398
|
-
//
|
|
399
|
-
//
|
|
400
|
-
//
|
|
401
|
-
|
|
402
|
-
// the reverse-proxy (which reads services.json) routes correctly.
|
|
403
|
-
describe("reconcilePortToCanonical (channel#41 — module now agent)", () => {
|
|
398
|
+
// A transiently wrong services.json port for a fixed-port first-party module can
|
|
399
|
+
// self-perpetuate through supervisor env, probes, and proxy routing. The boot path
|
|
400
|
+
// snaps supported modules back to canonical and persists the fix.
|
|
401
|
+
describe("reconcilePortToCanonical", () => {
|
|
404
402
|
let h: Harness;
|
|
405
403
|
beforeEach(() => {
|
|
406
404
|
h = makeHarness();
|
|
407
405
|
});
|
|
408
406
|
afterEach(() => h.cleanup());
|
|
409
407
|
|
|
410
|
-
|
|
411
|
-
|
|
412
|
-
const DRIFTED_CHANNEL: ServiceEntry = {
|
|
413
|
-
name: "parachute-agent",
|
|
408
|
+
const DRIFTED_SCRIBE: ServiceEntry = {
|
|
409
|
+
name: "parachute-scribe",
|
|
414
410
|
port: 19415,
|
|
415
|
-
paths: ["/
|
|
411
|
+
paths: ["/scribe"],
|
|
416
412
|
health: "/health",
|
|
417
413
|
version: "0.1.0",
|
|
418
414
|
};
|
|
419
415
|
|
|
420
416
|
test("snaps a drifted fixed-port row back to canonical + persists it", () => {
|
|
421
|
-
writeManifest({ services: [
|
|
417
|
+
writeManifest({ services: [DRIFTED_SCRIBE] }, h.manifestPath);
|
|
422
418
|
const logs: string[] = [];
|
|
423
419
|
|
|
424
|
-
const reconciled = reconcilePortToCanonical(
|
|
420
|
+
const reconciled = reconcilePortToCanonical(DRIFTED_SCRIBE, h.manifestPath, (l) =>
|
|
425
421
|
logs.push(l),
|
|
426
422
|
);
|
|
427
423
|
|
|
428
|
-
|
|
429
|
-
expect(reconciled.port).toBe(1941);
|
|
430
|
-
// And it's PERSISTED — the proxy reads services.json, so the row itself must
|
|
431
|
-
// now point at 1941 or `/agent/*` keeps routing to the dead 19415.
|
|
424
|
+
expect(reconciled.port).toBe(1943);
|
|
432
425
|
const onDisk = readManifestLenient(h.manifestPath).services.find(
|
|
433
|
-
(s) => s.name === "parachute-
|
|
426
|
+
(s) => s.name === "parachute-scribe",
|
|
434
427
|
);
|
|
435
|
-
expect(onDisk?.port).toBe(
|
|
428
|
+
expect(onDisk?.port).toBe(1943);
|
|
436
429
|
expect(
|
|
437
|
-
logs.some((l) => l.includes("reconciled") && l.includes("19415") && l.includes("
|
|
430
|
+
logs.some((l) => l.includes("reconciled") && l.includes("19415") && l.includes("1943")),
|
|
438
431
|
).toBe(true);
|
|
439
432
|
});
|
|
440
433
|
|
|
@@ -460,31 +453,30 @@ describe("reconcilePortToCanonical (channel#41 — module now agent)", () => {
|
|
|
460
453
|
});
|
|
461
454
|
|
|
462
455
|
test("does NOT steal the canonical port when another row already holds it", () => {
|
|
463
|
-
// Another row
|
|
464
|
-
//
|
|
465
|
-
// drift; the supervisor's squatter detection surfaces it.
|
|
456
|
+
// Another row occupies Scribe's canonical 1943, so reconciliation must not
|
|
457
|
+
// steal it; leave the drift for squatter diagnostics.
|
|
466
458
|
const squatter: ServiceEntry = {
|
|
467
459
|
name: "parachute-vault",
|
|
468
|
-
port:
|
|
460
|
+
port: 1943,
|
|
469
461
|
paths: ["/vault/default"],
|
|
470
462
|
health: "/vault/default/health",
|
|
471
463
|
version: "0.4.5",
|
|
472
464
|
};
|
|
473
|
-
writeManifest({ services: [squatter,
|
|
465
|
+
writeManifest({ services: [squatter, DRIFTED_SCRIBE] }, h.manifestPath);
|
|
474
466
|
const logs: string[] = [];
|
|
475
467
|
|
|
476
|
-
const out = reconcilePortToCanonical(
|
|
468
|
+
const out = reconcilePortToCanonical(DRIFTED_SCRIBE, h.manifestPath, (l) => logs.push(l));
|
|
477
469
|
|
|
478
470
|
expect(out.port).toBe(19415); // unchanged
|
|
479
471
|
const onDisk = readManifestLenient(h.manifestPath).services.find(
|
|
480
|
-
(s) => s.name === "parachute-
|
|
472
|
+
(s) => s.name === "parachute-scribe",
|
|
481
473
|
);
|
|
482
474
|
expect(onDisk?.port).toBe(19415); // not rewritten
|
|
483
475
|
expect(logs.some((l) => l.includes("held by another row"))).toBe(true);
|
|
484
476
|
});
|
|
485
477
|
|
|
486
|
-
test("boot path injects
|
|
487
|
-
writeManifest({ services: [
|
|
478
|
+
test("boot path injects and persists the supported module's canonical port", async () => {
|
|
479
|
+
writeManifest({ services: [DRIFTED_SCRIBE] }, h.manifestPath);
|
|
488
480
|
const recorder = makeRecorder();
|
|
489
481
|
const sup = new Supervisor({ spawnFn: recorder.spawn });
|
|
490
482
|
|
|
@@ -493,15 +485,12 @@ describe("reconcilePortToCanonical (channel#41 — module now agent)", () => {
|
|
|
493
485
|
configDir: h.dir,
|
|
494
486
|
});
|
|
495
487
|
|
|
496
|
-
|
|
497
|
-
|
|
498
|
-
expect(recorder.calls[0]?.short).toBe("agent");
|
|
499
|
-
expect(recorder.calls[0]?.env?.PORT).toBe("1941");
|
|
500
|
-
// services.json row is reconciled → proxy routes /agent/* to 1941.
|
|
488
|
+
expect(recorder.calls[0]?.short).toBe("scribe");
|
|
489
|
+
expect(recorder.calls[0]?.env?.PORT).toBe("1943");
|
|
501
490
|
const onDisk = readManifestLenient(h.manifestPath).services.find(
|
|
502
|
-
(s) => s.name === "parachute-
|
|
491
|
+
(s) => s.name === "parachute-scribe",
|
|
503
492
|
);
|
|
504
|
-
expect(onDisk?.port).toBe(
|
|
493
|
+
expect(onDisk?.port).toBe(1943);
|
|
505
494
|
});
|
|
506
495
|
|
|
507
496
|
test("boot path leaves a non-drifted vault row's port untouched", async () => {
|
|
@@ -2,6 +2,8 @@ import { describe, expect, test } from "bun:test";
|
|
|
2
2
|
import {
|
|
3
3
|
FIRST_PARTY_FALLBACKS,
|
|
4
4
|
KNOWN_MODULES,
|
|
5
|
+
PORT_RESERVATIONS,
|
|
6
|
+
RETIRED_MODULES,
|
|
5
7
|
discoverableShorts,
|
|
6
8
|
findServiceByShort,
|
|
7
9
|
focusForShort,
|
|
@@ -25,20 +27,18 @@ describe("discoverableShorts", () => {
|
|
|
25
27
|
expect(shorts.length).toBe(new Set(shorts).size);
|
|
26
28
|
});
|
|
27
29
|
|
|
28
|
-
test("includes
|
|
30
|
+
test("includes the supported module set and excludes retired Agent", () => {
|
|
29
31
|
const shorts = discoverableShorts();
|
|
30
|
-
for (const s of ["vault", "scribe", "surface", "
|
|
32
|
+
for (const s of ["vault", "scribe", "surface", "app", "notes"]) {
|
|
31
33
|
expect(shorts).toContain(s);
|
|
32
34
|
}
|
|
35
|
+
expect(shorts).not.toContain("agent");
|
|
33
36
|
});
|
|
34
37
|
|
|
35
38
|
test("FIRST_PARTY_FALLBACKS shorts lead KNOWN_MODULES shorts (registry order)", () => {
|
|
36
39
|
const shorts = discoverableShorts();
|
|
37
|
-
// notes (
|
|
38
|
-
// boundary D3) appears before vault (KNOWN_MODULES) in the union.
|
|
40
|
+
// notes (a FALLBACK) appears before vault (KNOWN_MODULES) in the union.
|
|
39
41
|
expect(shorts.indexOf("notes")).toBeLessThan(shorts.indexOf("vault"));
|
|
40
|
-
// agent rides in KNOWN_MODULES now but is still discoverable.
|
|
41
|
-
expect(shorts).toContain("agent");
|
|
42
42
|
});
|
|
43
43
|
});
|
|
44
44
|
|
|
@@ -106,9 +106,30 @@ describe("runner registry removal (2026-07-01)", () => {
|
|
|
106
106
|
});
|
|
107
107
|
});
|
|
108
108
|
|
|
109
|
+
describe("Agent module retirement (2026-07-15)", () => {
|
|
110
|
+
test("is no longer known, discoverable, or resolvable", () => {
|
|
111
|
+
expect(isKnownModuleShort("agent")).toBe(false);
|
|
112
|
+
expect(discoverableShorts()).not.toContain("agent");
|
|
113
|
+
expect("agent" in KNOWN_MODULES).toBe(false);
|
|
114
|
+
expect(shortNameForManifest("parachute-agent")).toBeUndefined();
|
|
115
|
+
expect(shortNameForManifest("parachute-channel")).toBeUndefined();
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
test("retires every historical row name and releases port 1941", () => {
|
|
119
|
+
expect(RETIRED_MODULES.agent).toBeDefined();
|
|
120
|
+
expect(RETIRED_MODULES["parachute-agent"]).toBeDefined();
|
|
121
|
+
expect(RETIRED_MODULES["parachute-channel"]).toBeDefined();
|
|
122
|
+
expect(PORT_RESERVATIONS.find((entry) => entry.port === 1941)).toEqual({
|
|
123
|
+
port: 1941,
|
|
124
|
+
name: "unassigned",
|
|
125
|
+
status: "reserved",
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
});
|
|
129
|
+
|
|
109
130
|
describe("isKnownModuleShort", () => {
|
|
110
131
|
test("true for every known module (the install/config gate)", () => {
|
|
111
|
-
for (const s of ["vault", "scribe", "surface", "
|
|
132
|
+
for (const s of ["vault", "scribe", "surface", "app", "notes"]) {
|
|
112
133
|
expect(isKnownModuleShort(s)).toBe(true);
|
|
113
134
|
}
|
|
114
135
|
});
|
|
@@ -119,12 +140,6 @@ describe("isKnownModuleShort", () => {
|
|
|
119
140
|
});
|
|
120
141
|
});
|
|
121
142
|
|
|
122
|
-
// Regression: services.json rows carry the MANIFEST name (`parachute-agent`),
|
|
123
|
-
// not the bare short (`agent`). The connection/channels wiring used to do
|
|
124
|
-
// `services.find((s) => s.name === "agent")`, which never matched the on-disk
|
|
125
|
-
// row → agentOrigin null → a spurious "agent module is not installed" when
|
|
126
|
-
// linking a vault-backed channel. findServiceByShort resolves through the
|
|
127
|
-
// short↔manifest map so the lookup hits the real row.
|
|
128
143
|
describe("findServiceByShort", () => {
|
|
129
144
|
const services = [
|
|
130
145
|
{ name: "parachute-vault-default", port: 1940 },
|
|
@@ -132,29 +147,9 @@ describe("findServiceByShort", () => {
|
|
|
132
147
|
{ name: "parachute-scribe", port: 1943 },
|
|
133
148
|
];
|
|
134
149
|
|
|
135
|
-
test("
|
|
136
|
-
const found = findServiceByShort(services, "agent");
|
|
137
|
-
expect(found?.name).toBe("parachute-agent");
|
|
138
|
-
expect(found?.port).toBe(1941);
|
|
139
|
-
});
|
|
140
|
-
|
|
141
|
-
test("the naive `name === short` comparison would have missed it (the bug)", () => {
|
|
142
|
-
// The exact pre-fix predicate: a bare short never matches a manifest-named row.
|
|
143
|
-
expect(services.find((s) => s.name === "agent")).toBeUndefined();
|
|
144
|
-
// The fix finds it.
|
|
145
|
-
expect(findServiceByShort(services, "agent")).toBeDefined();
|
|
146
|
-
});
|
|
147
|
-
|
|
148
|
-
test("resolves a legacy parachute-channel row to short `agent` (rename back-compat)", () => {
|
|
149
|
-
// Un-upgraded operators carry a `parachute-channel` row; the
|
|
150
|
-
// LEGACY_MANIFEST_ALIASES fallback keeps it routing to the agent module
|
|
151
|
-
// until the daemon re-registers under `parachute-agent`.
|
|
152
|
-
const legacy = [{ name: "parachute-channel", port: 1941 }];
|
|
153
|
-
expect(findServiceByShort(legacy, "agent")?.port).toBe(1941);
|
|
154
|
-
});
|
|
155
|
-
|
|
156
|
-
test("resolves scribe too, and returns undefined for an absent module", () => {
|
|
150
|
+
test("resolves supported modules and not retired Agent rows", () => {
|
|
157
151
|
expect(findServiceByShort(services, "scribe")?.port).toBe(1943);
|
|
152
|
+
expect(findServiceByShort(services, "agent")).toBeUndefined();
|
|
158
153
|
expect(findServiceByShort(services, "runner")).toBeUndefined();
|
|
159
154
|
});
|
|
160
155
|
});
|