@openparachute/hub 0.7.7-rc.9 → 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 +210 -38
- package/src/__tests__/migrate.test.ts +3 -1
- 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 +17 -26
- 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 +22 -92
- package/src/__tests__/setup-wizard.test.ts +301 -22
- package/src/__tests__/setup.test.ts +8 -13
- 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 +9 -7
- 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 +72 -27
- package/src/commands/migrate.ts +5 -1
- package/src/commands/setup.ts +0 -2
- package/src/commands/wizard-transcription.ts +24 -0
- package/src/commands/wizard.ts +34 -1
- package/src/help.ts +0 -1
- 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/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 +18 -47
- package/src/services-manifest.ts +9 -42
- 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
|
@@ -219,6 +219,80 @@ describe("deriveWizardState", () => {
|
|
|
219
219
|
}
|
|
220
220
|
});
|
|
221
221
|
|
|
222
|
+
test("real-version vault row with paths:[] is NOT a real vault (e2e boot-spawned-child fix)", async () => {
|
|
223
|
+
// The Tier-1 e2e wizard-skip root cause. When the hub boot-spawns the vault
|
|
224
|
+
// MODULE child, vault's selfRegister writes its services.json row at the
|
|
225
|
+
// REAL package version (e.g. 0.7.4-rc.1, NOT SEED_VERSION) but with
|
|
226
|
+
// `paths: []` because no vault instance exists yet. The SEED_VERSION
|
|
227
|
+
// sentinel alone did NOT catch this — a bare `version !== SEED_VERSION`
|
|
228
|
+
// check read the empty-paths row as a real vault, so the wizard skipped its
|
|
229
|
+
// vault step and `--vault-name` was ignored. hasRealVault must be
|
|
230
|
+
// INSTANCE-aware: no mount path → not a real vault (the #478 empty-paths
|
|
231
|
+
// discrimination findExistingVault already uses).
|
|
232
|
+
const db = openHubDb(hubDbPath(h.dir));
|
|
233
|
+
try {
|
|
234
|
+
await createUser(db, "owner", "pw");
|
|
235
|
+
writeManifest(
|
|
236
|
+
{
|
|
237
|
+
services: [
|
|
238
|
+
{
|
|
239
|
+
name: "parachute-vault",
|
|
240
|
+
version: "0.7.4-rc.1",
|
|
241
|
+
port: 1940,
|
|
242
|
+
paths: [],
|
|
243
|
+
health: "/health",
|
|
244
|
+
},
|
|
245
|
+
],
|
|
246
|
+
},
|
|
247
|
+
h.manifestPath,
|
|
248
|
+
);
|
|
249
|
+
const s = deriveWizardState({
|
|
250
|
+
db,
|
|
251
|
+
manifestPath: h.manifestPath,
|
|
252
|
+
readExposeStateFn: h.readExposeStateFn,
|
|
253
|
+
});
|
|
254
|
+
expect(s.hasRealVault).toBe(false);
|
|
255
|
+
expect(s.hasVault).toBe(false);
|
|
256
|
+
expect(s.step).toBe("vault");
|
|
257
|
+
} finally {
|
|
258
|
+
db.close();
|
|
259
|
+
}
|
|
260
|
+
});
|
|
261
|
+
|
|
262
|
+
test("real-version vault row WITH a mount path IS a real vault (positive control)", async () => {
|
|
263
|
+
// The counterpart to the empty-paths case: a genuinely-created vault carries
|
|
264
|
+
// a `/vault/<name>` path at a real version, so hasRealVault must be true and
|
|
265
|
+
// the wizard advances past the vault step.
|
|
266
|
+
const db = openHubDb(hubDbPath(h.dir));
|
|
267
|
+
try {
|
|
268
|
+
await createUser(db, "owner", "pw");
|
|
269
|
+
writeManifest(
|
|
270
|
+
{
|
|
271
|
+
services: [
|
|
272
|
+
{
|
|
273
|
+
name: "parachute-vault",
|
|
274
|
+
version: "0.7.4-rc.1",
|
|
275
|
+
port: 1940,
|
|
276
|
+
paths: ["/vault/e2e"],
|
|
277
|
+
health: "/health",
|
|
278
|
+
},
|
|
279
|
+
],
|
|
280
|
+
},
|
|
281
|
+
h.manifestPath,
|
|
282
|
+
);
|
|
283
|
+
const s = deriveWizardState({
|
|
284
|
+
db,
|
|
285
|
+
manifestPath: h.manifestPath,
|
|
286
|
+
readExposeStateFn: h.readExposeStateFn,
|
|
287
|
+
});
|
|
288
|
+
expect(s.hasRealVault).toBe(true);
|
|
289
|
+
expect(s.hasVault).toBe(true);
|
|
290
|
+
expect(s.step).toBe("expose");
|
|
291
|
+
} finally {
|
|
292
|
+
db.close();
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
|
|
222
296
|
test("expose step when admin + vault exist but expose mode not set yet (hub#268 Item 2)", async () => {
|
|
223
297
|
const db = openHubDb(hubDbPath(h.dir));
|
|
224
298
|
try {
|
|
@@ -1712,13 +1786,15 @@ describe("handleSetupVaultPost", () => {
|
|
|
1712
1786
|
}
|
|
1713
1787
|
});
|
|
1714
1788
|
|
|
1715
|
-
test("
|
|
1789
|
+
test("second POST while supervisor is running but vault UNREGISTERED — synthesize success, no 2nd bun add, no double provision (N2 race)", async () => {
|
|
1716
1790
|
// Reviewer-flagged race: two concurrent POSTs before either seeds
|
|
1717
|
-
// services.json both pass `state.hasVault === false
|
|
1718
|
-
//
|
|
1719
|
-
//
|
|
1720
|
-
//
|
|
1721
|
-
//
|
|
1791
|
+
// services.json both pass `state.hasVault === false`. The supervisor is
|
|
1792
|
+
// already running vault (a prior POST's runInstall spawned it) but the
|
|
1793
|
+
// services.json row hasn't been written yet — so `vaultAlreadyRegistered`
|
|
1794
|
+
// is false. The wizard must NOT fire a second `bun add -g` AND must NOT
|
|
1795
|
+
// fire `provisionVault` (which would `parachute install vault` a second
|
|
1796
|
+
// time in this pre-seed window — reviewer N2). Instead it synthesizes a
|
|
1797
|
+
// succeeded op and lets the in-flight install create the vault.
|
|
1722
1798
|
const db = openHubDb(hubDbPath(h.dir));
|
|
1723
1799
|
try {
|
|
1724
1800
|
const user = await createUser(db, "owner", "pw");
|
|
@@ -1734,8 +1810,9 @@ describe("handleSetupVaultPost", () => {
|
|
|
1734
1810
|
});
|
|
1735
1811
|
const csrf = setCookie(get, CSRF_COOKIE_NAME) ?? "";
|
|
1736
1812
|
// Real supervisor with the never-exits spawn stub from makeSupervisor.
|
|
1737
|
-
// Pre-spawn vault so `supervisor.get("vault").status === "starting"
|
|
1738
|
-
//
|
|
1813
|
+
// Pre-spawn vault so `supervisor.get("vault").status === "starting"`.
|
|
1814
|
+
// The harness manifest is EMPTY (vault not registered) — the pre-seed
|
|
1815
|
+
// window.
|
|
1739
1816
|
const supervisor = makeSupervisor();
|
|
1740
1817
|
await supervisor.start({ short: "vault", cmd: ["bun", "noop"] });
|
|
1741
1818
|
const runCalls: string[][] = [];
|
|
@@ -1743,6 +1820,17 @@ describe("handleSetupVaultPost", () => {
|
|
|
1743
1820
|
runCalls.push([...cmd]);
|
|
1744
1821
|
return 0;
|
|
1745
1822
|
};
|
|
1823
|
+
// provisionVault must NOT be called in the unregistered race window.
|
|
1824
|
+
const provisionCalls: string[] = [];
|
|
1825
|
+
const provisionVaultImpl = async (name: string) => {
|
|
1826
|
+
provisionCalls.push(name);
|
|
1827
|
+
return {
|
|
1828
|
+
ok: true as const,
|
|
1829
|
+
created: true,
|
|
1830
|
+
entry: { name, url: `https://hub.example/vault/${name}`, version: "1.0.0" },
|
|
1831
|
+
createJson: null,
|
|
1832
|
+
};
|
|
1833
|
+
};
|
|
1746
1834
|
const post = await handleSetupVaultPost(
|
|
1747
1835
|
req("/admin/setup/vault", {
|
|
1748
1836
|
method: "POST",
|
|
@@ -1763,17 +1851,17 @@ describe("handleSetupVaultPost", () => {
|
|
|
1763
1851
|
supervisor,
|
|
1764
1852
|
registry: getDefaultOperationsRegistry(),
|
|
1765
1853
|
run: stubbedRun,
|
|
1854
|
+
provisionVaultImpl,
|
|
1766
1855
|
},
|
|
1767
1856
|
);
|
|
1768
1857
|
expect(post.status).toBe(303);
|
|
1769
1858
|
const location = post.headers.get("location") ?? "";
|
|
1770
1859
|
expect(location).toMatch(/^\/admin\/setup\?op=/);
|
|
1771
|
-
// Yield
|
|
1772
|
-
//
|
|
1773
|
-
// the op went straight to `succeeded` with the canonical
|
|
1774
|
-
// "already supervised" log line.
|
|
1860
|
+
// Yield for any background promise. Then assert: no `bun add`, NO
|
|
1861
|
+
// provisionVault call (no double-install), op synthesized succeeded.
|
|
1775
1862
|
await new Promise((r) => setTimeout(r, 50));
|
|
1776
1863
|
expect(runCalls.length).toBe(0);
|
|
1864
|
+
expect(provisionCalls).toEqual([]);
|
|
1777
1865
|
const opId = new URL(location, "http://x").searchParams.get("op") ?? "";
|
|
1778
1866
|
const op = getDefaultOperationsRegistry().get(opId);
|
|
1779
1867
|
expect(op?.status).toBe("succeeded");
|
|
@@ -1783,6 +1871,200 @@ describe("handleSetupVaultPost", () => {
|
|
|
1783
1871
|
}
|
|
1784
1872
|
});
|
|
1785
1873
|
|
|
1874
|
+
test("boot-spawned vault child (real version, paths:[]) — wizard create actually provisions the named vault (e2e wizard-skip fix)", async () => {
|
|
1875
|
+
// The exact Tier-1 e2e bug. The hub boot-spawns the vault MODULE child so it
|
|
1876
|
+
// can selfRegister; that child writes a services.json row at the REAL
|
|
1877
|
+
// package version (NOT SEED_VERSION) with `paths: []` because no vault
|
|
1878
|
+
// instance exists yet, AND the supervisor already reports vault "running".
|
|
1879
|
+
// Pre-fix: hasRealVault read the real-version row as a real vault, the
|
|
1880
|
+
// wizard skipped its vault step, and even if it POSTed, the already-
|
|
1881
|
+
// supervised short-circuit synthesized a succeeded op that created NOTHING
|
|
1882
|
+
// — `--vault-name e2e` was silently dropped. Post-fix: the empty-paths row
|
|
1883
|
+
// reads as no-real-vault (hasRealVault=false), the POST is honored, and the
|
|
1884
|
+
// short-circuit drives provisionVault to actually create the named vault.
|
|
1885
|
+
const db = openHubDb(hubDbPath(h.dir));
|
|
1886
|
+
try {
|
|
1887
|
+
const user = await createUser(db, "owner", "pw");
|
|
1888
|
+
const { createSession, SESSION_COOKIE_NAME: SC } = await import("../sessions.ts");
|
|
1889
|
+
const session = createSession(db, { userId: user.id });
|
|
1890
|
+
// Boot-spawned child's self-registered row: real version, paths: [].
|
|
1891
|
+
writeManifest(
|
|
1892
|
+
{
|
|
1893
|
+
services: [
|
|
1894
|
+
{
|
|
1895
|
+
name: "parachute-vault",
|
|
1896
|
+
version: "0.7.4-rc.1",
|
|
1897
|
+
port: 1940,
|
|
1898
|
+
paths: [],
|
|
1899
|
+
health: "/health",
|
|
1900
|
+
},
|
|
1901
|
+
],
|
|
1902
|
+
},
|
|
1903
|
+
h.manifestPath,
|
|
1904
|
+
);
|
|
1905
|
+
// deriveWizardState must NOT treat the empty-paths real-version row as a
|
|
1906
|
+
// real vault (this is what un-skipped the wizard's vault step).
|
|
1907
|
+
const derived = deriveWizardState({
|
|
1908
|
+
db,
|
|
1909
|
+
manifestPath: h.manifestPath,
|
|
1910
|
+
readExposeStateFn: h.readExposeStateFn,
|
|
1911
|
+
});
|
|
1912
|
+
expect(derived.hasRealVault).toBe(false);
|
|
1913
|
+
expect(derived.step).toBe("vault");
|
|
1914
|
+
|
|
1915
|
+
const get = handleSetupGet(req("/admin/setup"), {
|
|
1916
|
+
db,
|
|
1917
|
+
manifestPath: h.manifestPath,
|
|
1918
|
+
configDir: h.dir,
|
|
1919
|
+
readExposeStateFn: h.readExposeStateFn,
|
|
1920
|
+
issuer: "https://hub.example",
|
|
1921
|
+
registry: getDefaultOperationsRegistry(),
|
|
1922
|
+
});
|
|
1923
|
+
const csrf = setCookie(get, CSRF_COOKIE_NAME) ?? "";
|
|
1924
|
+
// Supervisor already running the boot-spawned vault module child.
|
|
1925
|
+
const supervisor = makeSupervisor();
|
|
1926
|
+
await supervisor.start({ short: "vault", cmd: ["bun", "noop"] });
|
|
1927
|
+
const runCalls: string[][] = [];
|
|
1928
|
+
const provisionCalls: string[] = [];
|
|
1929
|
+
const provisionVaultImpl = async (name: string) => {
|
|
1930
|
+
provisionCalls.push(name);
|
|
1931
|
+
return {
|
|
1932
|
+
ok: true as const,
|
|
1933
|
+
created: true,
|
|
1934
|
+
entry: { name, url: `https://hub.example/vault/${name}`, version: "0.7.4-rc.1" },
|
|
1935
|
+
createJson: null,
|
|
1936
|
+
};
|
|
1937
|
+
};
|
|
1938
|
+
const post = await handleSetupVaultPost(
|
|
1939
|
+
req("/admin/setup/vault", {
|
|
1940
|
+
method: "POST",
|
|
1941
|
+
body: new URLSearchParams({
|
|
1942
|
+
[CSRF_FIELD_NAME]: csrf,
|
|
1943
|
+
mode: "create",
|
|
1944
|
+
vault_name: "e2e",
|
|
1945
|
+
}).toString(),
|
|
1946
|
+
headers: {
|
|
1947
|
+
"content-type": "application/x-www-form-urlencoded",
|
|
1948
|
+
cookie: `${CSRF_COOKIE_NAME}=${csrf}; ${SC}=${session.id}`,
|
|
1949
|
+
},
|
|
1950
|
+
}),
|
|
1951
|
+
{
|
|
1952
|
+
db,
|
|
1953
|
+
manifestPath: h.manifestPath,
|
|
1954
|
+
configDir: h.dir,
|
|
1955
|
+
readExposeStateFn: h.readExposeStateFn,
|
|
1956
|
+
issuer: "https://hub.example",
|
|
1957
|
+
supervisor,
|
|
1958
|
+
registry: getDefaultOperationsRegistry(),
|
|
1959
|
+
run: async (cmd) => {
|
|
1960
|
+
runCalls.push([...cmd]);
|
|
1961
|
+
return 0;
|
|
1962
|
+
},
|
|
1963
|
+
provisionVaultImpl,
|
|
1964
|
+
},
|
|
1965
|
+
);
|
|
1966
|
+
expect(post.status).toBe(303);
|
|
1967
|
+
const location = post.headers.get("location") ?? "";
|
|
1968
|
+
expect(location).toMatch(/^\/admin\/setup\?op=/);
|
|
1969
|
+
await new Promise((r) => setTimeout(r, 50));
|
|
1970
|
+
// The named vault WAS provisioned (not silently dropped) …
|
|
1971
|
+
expect(provisionCalls).toEqual(["e2e"]);
|
|
1972
|
+
// … and no `bun add -g` fired (module already supervised).
|
|
1973
|
+
expect(runCalls.some((c) => c.join(" ").includes("bun add -g"))).toBe(false);
|
|
1974
|
+
const opId = new URL(location, "http://x").searchParams.get("op") ?? "";
|
|
1975
|
+
const op = getDefaultOperationsRegistry().get(opId);
|
|
1976
|
+
expect(op?.status).toBe("succeeded");
|
|
1977
|
+
expect(op?.log.join("\n")).toContain('vault "e2e" created');
|
|
1978
|
+
} finally {
|
|
1979
|
+
db.close();
|
|
1980
|
+
}
|
|
1981
|
+
});
|
|
1982
|
+
|
|
1983
|
+
test("import mode on an already-supervised box is REJECTED (no empty vault + fake success) — review must-fix", async () => {
|
|
1984
|
+
// On a boot-spawned box (vault module already supervised), the short-circuit
|
|
1985
|
+
// create path provisions an EMPTY vault. An IMPORT request must NOT be run
|
|
1986
|
+
// through it: that would leave an empty vault named after the import target
|
|
1987
|
+
// + a false "vault created" banner (a data-integrity footgun). The wizard
|
|
1988
|
+
// rejects import with an actionable message + a FAILED op instead.
|
|
1989
|
+
const db = openHubDb(hubDbPath(h.dir));
|
|
1990
|
+
try {
|
|
1991
|
+
const user = await createUser(db, "owner", "pw");
|
|
1992
|
+
const { createSession, SESSION_COOKIE_NAME: SC } = await import("../sessions.ts");
|
|
1993
|
+
const session = createSession(db, { userId: user.id });
|
|
1994
|
+
writeManifest(
|
|
1995
|
+
{
|
|
1996
|
+
services: [
|
|
1997
|
+
{
|
|
1998
|
+
name: "parachute-vault",
|
|
1999
|
+
version: "0.7.4-rc.1",
|
|
2000
|
+
port: 1940,
|
|
2001
|
+
paths: [],
|
|
2002
|
+
health: "/health",
|
|
2003
|
+
},
|
|
2004
|
+
],
|
|
2005
|
+
},
|
|
2006
|
+
h.manifestPath,
|
|
2007
|
+
);
|
|
2008
|
+
const get = handleSetupGet(req("/admin/setup"), {
|
|
2009
|
+
db,
|
|
2010
|
+
manifestPath: h.manifestPath,
|
|
2011
|
+
configDir: h.dir,
|
|
2012
|
+
readExposeStateFn: h.readExposeStateFn,
|
|
2013
|
+
issuer: "https://hub.example",
|
|
2014
|
+
registry: getDefaultOperationsRegistry(),
|
|
2015
|
+
});
|
|
2016
|
+
const csrf = setCookie(get, CSRF_COOKIE_NAME) ?? "";
|
|
2017
|
+
const supervisor = makeSupervisor();
|
|
2018
|
+
await supervisor.start({ short: "vault", cmd: ["bun", "noop"] });
|
|
2019
|
+
const provisionCalls: string[] = [];
|
|
2020
|
+
const post = await handleSetupVaultPost(
|
|
2021
|
+
req("/admin/setup/vault", {
|
|
2022
|
+
method: "POST",
|
|
2023
|
+
headers: {
|
|
2024
|
+
accept: "application/json",
|
|
2025
|
+
"content-type": "application/json",
|
|
2026
|
+
cookie: `${CSRF_COOKIE_NAME}=${csrf}; ${SC}=${session.id}`,
|
|
2027
|
+
},
|
|
2028
|
+
body: JSON.stringify({
|
|
2029
|
+
[CSRF_FIELD_NAME]: csrf,
|
|
2030
|
+
mode: "import",
|
|
2031
|
+
vault_name: "fromgit",
|
|
2032
|
+
remote_url: "https://example.com/repo.git",
|
|
2033
|
+
import_mode: "merge",
|
|
2034
|
+
}),
|
|
2035
|
+
}),
|
|
2036
|
+
{
|
|
2037
|
+
db,
|
|
2038
|
+
manifestPath: h.manifestPath,
|
|
2039
|
+
configDir: h.dir,
|
|
2040
|
+
readExposeStateFn: h.readExposeStateFn,
|
|
2041
|
+
issuer: "https://hub.example",
|
|
2042
|
+
supervisor,
|
|
2043
|
+
registry: getDefaultOperationsRegistry(),
|
|
2044
|
+
provisionVaultImpl: async (name: string) => {
|
|
2045
|
+
provisionCalls.push(name);
|
|
2046
|
+
return {
|
|
2047
|
+
ok: true as const,
|
|
2048
|
+
created: true,
|
|
2049
|
+
entry: { name, url: `https://hub.example/vault/${name}`, version: "0.7.4-rc.1" },
|
|
2050
|
+
createJson: null,
|
|
2051
|
+
};
|
|
2052
|
+
},
|
|
2053
|
+
},
|
|
2054
|
+
);
|
|
2055
|
+
expect(post.status).toBe(200);
|
|
2056
|
+
const body = (await post.json()) as { op_id?: string; message?: string };
|
|
2057
|
+
expect(body.message ?? "").toContain("isn't supported");
|
|
2058
|
+
// Import was NOT provisioned (no empty vault created).
|
|
2059
|
+
expect(provisionCalls).toEqual([]);
|
|
2060
|
+
// The op is FAILED, not a fake success.
|
|
2061
|
+
const op = body.op_id ? getDefaultOperationsRegistry().get(body.op_id) : undefined;
|
|
2062
|
+
expect(op?.status).toBe("failed");
|
|
2063
|
+
} finally {
|
|
2064
|
+
db.close();
|
|
2065
|
+
}
|
|
2066
|
+
});
|
|
2067
|
+
|
|
1786
2068
|
test("create on a SEED-placeholder box: does NOT short-circuit + drives the supervisor to start vault (hub#607 + hub#608)", async () => {
|
|
1787
2069
|
// hub#607 + hub#608 coupled fresh-operator flow. On an init'd box,
|
|
1788
2070
|
// services.json already carries a `parachute-vault` placeholder at
|
|
@@ -2066,19 +2348,16 @@ describe("handleSetupVaultPost", () => {
|
|
|
2066
2348
|
test("scribe transcribe=local resolves to the host platform's backend (the Linux trap fix)", async () => {
|
|
2067
2349
|
// The bug: `local` mapped UNCONDITIONALLY to parakeet-mlx (macOS-only),
|
|
2068
2350
|
// silently broken on every Linux box. After the fix it resolves to the
|
|
2069
|
-
// platform backend.
|
|
2070
|
-
//
|
|
2071
|
-
|
|
2072
|
-
const
|
|
2073
|
-
const expected = platformLocalProvider(process.platform);
|
|
2351
|
+
// platform backend. The expectation uses the same host RAM decision so
|
|
2352
|
+
// constrained CI boxes correctly assert the cloud fallback instead.
|
|
2353
|
+
const { decideLocalProvider, readAvailableRamMib } = await import("../scribe-config.ts");
|
|
2354
|
+
const decision = decideLocalProvider(process.platform, readAvailableRamMib());
|
|
2074
2355
|
const { response } = await postVaultWithFields(h, { scribe_provider: "local" });
|
|
2075
2356
|
expect(response.status).toBe(303);
|
|
2076
2357
|
const cfg = readScribeConfig(h.dir);
|
|
2077
|
-
|
|
2078
|
-
|
|
2079
|
-
|
|
2080
|
-
expect(cfg?.transcribe).toEqual({ provider: expected });
|
|
2081
|
-
}
|
|
2358
|
+
expect(cfg?.transcribe).toEqual({
|
|
2359
|
+
provider: decision.ok ? decision.provider : decision.steerTo,
|
|
2360
|
+
});
|
|
2082
2361
|
});
|
|
2083
2362
|
});
|
|
2084
2363
|
|
|
@@ -185,18 +185,14 @@ describe("setup", () => {
|
|
|
185
185
|
}
|
|
186
186
|
});
|
|
187
187
|
|
|
188
|
-
test("fresh box: the offered
|
|
188
|
+
test("fresh box: the offered list excludes deprecated notes and removed Agent/runner", async () => {
|
|
189
189
|
const h = makeHarness();
|
|
190
190
|
try {
|
|
191
|
-
// 'all' picks every
|
|
192
|
-
//
|
|
193
|
-
// 2026-06-25) while runner never even reaches the survey (registry
|
|
194
|
-
// removal, 2026-07-01) — keeping vault/scribe/surface/agent. Only vault +
|
|
195
|
-
// scribe have pre-install follow-up prompts (vault name, scribe provider);
|
|
196
|
-
// surface + agent have none — so the scripted answers below are complete.
|
|
191
|
+
// 'all' picks every offered service. The clean-box offer excludes notes
|
|
192
|
+
// (deprecated) and retired Agent/runner, keeping vault/scribe/surface/app.
|
|
197
193
|
const availability = scriptedAvailability([
|
|
198
|
-
"all",
|
|
199
|
-
"default", // vault name
|
|
194
|
+
"all",
|
|
195
|
+
"default", // vault name
|
|
200
196
|
"1", // scribe provider
|
|
201
197
|
]);
|
|
202
198
|
const code = await setup({
|
|
@@ -210,20 +206,19 @@ describe("setup", () => {
|
|
|
210
206
|
},
|
|
211
207
|
});
|
|
212
208
|
expect(code).toBe(0);
|
|
213
|
-
// The "Available to install" banner must NOT list notes / runner.
|
|
214
209
|
const joined = h.logs.join("\n");
|
|
215
210
|
const availableBlock = joined.slice(joined.indexOf("Available to install:"));
|
|
216
211
|
expect(availableBlock).not.toMatch(/\bnotes\b/);
|
|
217
212
|
expect(availableBlock).not.toMatch(/\brunner\b/);
|
|
218
|
-
|
|
213
|
+
expect(availableBlock).not.toMatch(/\bagent\b/);
|
|
219
214
|
const installedShorts = h.calls.map((c) => c.short);
|
|
220
215
|
expect(installedShorts).not.toContain("notes");
|
|
221
216
|
expect(installedShorts).not.toContain("runner");
|
|
222
|
-
|
|
217
|
+
expect(installedShorts).not.toContain("agent");
|
|
223
218
|
expect(installedShorts).toContain("vault");
|
|
224
219
|
expect(installedShorts).toContain("scribe");
|
|
225
220
|
expect(installedShorts).toContain("surface");
|
|
226
|
-
expect(installedShorts).toContain("
|
|
221
|
+
expect(installedShorts).toContain("app");
|
|
227
222
|
} finally {
|
|
228
223
|
h.cleanup();
|
|
229
224
|
}
|
|
@@ -225,11 +225,6 @@ describe("status — per-module URL deep-links (manifestRowBase / urlForEntry)",
|
|
|
225
225
|
expect(out).toMatch(/:5173\/notes/);
|
|
226
226
|
});
|
|
227
227
|
|
|
228
|
-
test("channel row prints port + /channel mount", async () => {
|
|
229
|
-
const out = await urlFor("parachute-channel", 1943, ["/channel"]);
|
|
230
|
-
expect(out).toMatch(/:1943\/channel/);
|
|
231
|
-
});
|
|
232
|
-
|
|
233
228
|
test("unknown third-party service falls back to bare host:port + paths[0]", async () => {
|
|
234
229
|
const { path, configDir, cleanup } = makeTempPath();
|
|
235
230
|
try {
|