History

e2e: recorded film — integration survives a real reboot (terminal cast)

A watchable companion to restart-persistence: drives the same register → reboot → survived flow through the chat theater so the on-screen tool spinner runs for the ACTUAL guest reboot. Produces a terminal.cast the viewer plays in the matrix — press-play evidence instead of trusting a green check. Runs against the cli-* VM targets; verified on cli-macos (357-event cast).

Rhys Sullivan committed 5 months ago

Commit 5bacd3c1 parent e2bff6e1 file · +128 −0Browse files

Unified · Split

e2e/cli/service-lifecycle.test.ts+128−0
@@ -0,0 +1,128 @@
1// The supervised daemon's durability as a WATCHABLE terminal recording: register
2// a REAL integration, REBOOT the daemon's machine for real (the on-screen
3// spinner runs for the actual reboot), then show the integration still there.
4// Same assertions as restart-persistence — but filmed, so you can press play
5// instead of trusting a green check. Runs against the cli-* VM targets.
6import { randomBytes } from "node:crypto";
7import { join } from "node:path";
8
9import { expect } from "@effect/vitest";
10import { Effect } from "effect";
11
12import { composePluginApi } from "@executor-js/api/server";
13import { openApiHttpPlugin } from "@executor-js/plugin-openapi/api";
14
15import { withChatTheater } from "../src/clients/chat-theater";
16import { scenario } from "../src/scenario";
17import { Api, Cli, Restart, RunDir, Target } from "../src/services";
18
19const api = composePluginApi([openApiHttpPlugin()] as const);
20
21/** Inline OpenAPI 3 spec with a single GET /ping (its server is never called). */
22const pingSpec = JSON.stringify({
23 openapi: "3.0.3",
24 info: { title: "Reboot Lifecycle API", version: "1.0.0" },
25 servers: [{ url: "http://127.0.0.1:59998" }],
26 paths: {
27 "/ping": {
28 get: {
29 operationId: "getPing",
30 summary: "Liveness ping",
31 responses: { "200": { description: "pong" } },
32 },
33 },
34 },
35});
36
37scenario(
38 "Supervised daemon · an integration survives a real machine reboot (recorded)",
39 {},
40 Effect.gen(function* () {
41 const target = yield* Target;
42 const restart = yield* Restart;
43 const { client } = yield* Api;
44 const cli = yield* Cli;
45 const runDir = yield* RunDir;
46
47 const slug = `reboot-film-${randomBytes(4).toString("hex")}`;
48
49 yield* withChatTheater(
50 cli,
51 { title: "executor — supervised daemon", record: join(runDir, "terminal.cast") },
52 (chat) =>
53 Effect.gen(function* () {
54 yield* chat.user("If this machine reboots, do my connected integrations survive?");
55 yield* chat.assistant(
56 "Let's prove it — register a real integration, reboot the daemon's machine for real, then check it's still there.",
57 );
58
59 const before = yield* client(api, yield* target.newIdentity());
60
61 const added = yield* chat.tool(
62 {
63 name: "executor call executor.openapi.addSpec",
64 input: `slug: ${slug}\nspec: inline OpenAPI (GET /ping)`,
65 result: (a) => `registered — ${a.toolCount} tool(s)`,
66 },
67 before.openapi.addSpec({
68 payload: {
69 spec: { kind: "blob", value: pingSpec },
70 slug,
71 authenticationTemplate: [],
72 },
73 }),
74 );
75 expect(added.toolCount, "the spec registered with tools").toBeGreaterThan(0);
76
77 const listed = yield* chat.tool(
78 {
79 name: "executor tools sources",
80 result: (rows) =>
81 rows.map((r) => String(r.slug)).includes(slug) ? `${slug} is listed` : "NOT listed",
82 },
83 before.integrations.list(),
84 );
85 expect(
86 listed.map((i) => String(i.slug)),
87 "listed before the reboot",
88 ).toContain(slug);
89
90 // The spinner here runs for the ENTIRE real reboot — the supervised
91 // service must auto-start at boot for this to ever return.
92 yield* chat.tool(
93 {
94 name: "reboot the daemon's machine",
95 input: "guest OS reboot — the OS service manager must auto-start the daemon at boot",
96 result: () => "back online; daemon auto-started",
97 },
98 restart(),
99 );
100
101 const after = yield* client(api, yield* target.newIdentity());
102 yield* Effect.ensuring(
103 Effect.gen(function* () {
104 const survived = yield* chat.tool(
105 {
106 name: "executor tools sources",
107 result: (rows) =>
108 rows.map((r) => String(r.slug)).includes(slug)
109 ? `${slug} SURVIVED the reboot`
110 : "VANISHED",
111 },
112 after.integrations.list(),
113 );
114 expect(
115 survived.map((i) => String(i.slug)),
116 "survived the reboot",
117 ).toContain(slug);
118 yield* chat.assistant(
119 "It survived — the OS restarted the daemon at boot and its data was intact.",
120 );
121 }),
122 // Shared guest, but ephemeral; still, never leave the spec behind.
123 after.openapi.removeSpec({ params: { slug } }).pipe(Effect.ignore),
124 );
125 }),
126 );
127 }),
128);