rollbridge 0.1.13 → 0.1.14
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 +6 -5
- package/docs/cli.md +9 -8
- package/package.json +1 -1
- package/src/cli.js +2 -1
- package/src/daemon.js +20 -2
- package/test/daemon-bootstrap.test.js +40 -7
- package/test/fixtures/dummy-app.js +1 -1
- package/test/package-metadata.test.js +39 -0
- package/tmp/worker-control/rollbridge-bootstrap/activity-3.jsonl +0 -28
- package/tmp/worker-control/rollbridge-bootstrap/current-head-review-activity.jsonl +0 -13
- package/tmp/worker-control/rollbridge-bootstrap/current-head-review-transcript.jsonl +0 -1
- package/tmp/worker-control/rollbridge-bootstrap/fs-probe-10.jsonl +0 -1
- package/tmp/worker-control/rollbridge-bootstrap/plan.md +0 -9
- package/tmp/worker-control/rollbridge-bootstrap/repair-activity.jsonl +0 -3
- package/tmp/worker-control/rollbridge-bootstrap/repair-fresh-activity.jsonl +0 -25
- package/tmp/worker-control/rollbridge-bootstrap/repair-fresh-transcript.jsonl +0 -1
- package/tmp/worker-control/rollbridge-bootstrap/repair-transcript.jsonl +0 -1
- package/tmp/worker-control/rollbridge-bootstrap/review-activity.jsonl +0 -14
- package/tmp/worker-control/rollbridge-bootstrap/review-transcript.jsonl +0 -1
- package/tmp/worker-control/rollbridge-bootstrap/terminal-repair-activity.jsonl +0 -3
- package/tmp/worker-control/rollbridge-bootstrap/terminal-repair-fresh-activity.jsonl +0 -38
- package/tmp/worker-control/rollbridge-bootstrap/terminal-repair-fresh-transcript.jsonl +0 -1
- package/tmp/worker-control/rollbridge-bootstrap/terminal-repair-transcript.jsonl +0 -1
- package/tmp/worker-control/rollbridge-bootstrap/transcript-2.jsonl +0 -1
- package/tmp/worker-control/rollbridge-bootstrap/transcript-3.jsonl +0 -1
- package/tmp/worker-control/rollbridge-bootstrap/transcript.jsonl +0 -1
package/README.md
CHANGED
|
@@ -409,11 +409,12 @@ rollbridge daemon --config /srv/ticket-server/rollbridge.js \
|
|
|
409
409
|
```
|
|
410
410
|
|
|
411
411
|
The four bootstrap inputs are all-or-nothing and use absolute config/release
|
|
412
|
-
paths. Rollbridge binds its proxy
|
|
413
|
-
|
|
414
|
-
only processes started by that attempt and exits non-zero;
|
|
415
|
-
from a previous daemon are reported as orphans and are never
|
|
416
|
-
implicitly; their live PID records remain in `statePath` for
|
|
412
|
+
paths. Rollbridge binds its proxy, activates the release through the normal
|
|
413
|
+
deploy path, then exposes the control socket and stays foreground. A failed
|
|
414
|
+
activation stops only processes started by that attempt and exits non-zero;
|
|
415
|
+
persisted processes from a previous daemon are reported as orphans and are never
|
|
416
|
+
recovered or killed implicitly; their live PID records remain in `statePath` for
|
|
417
|
+
explicit recovery.
|
|
417
418
|
|
|
418
419
|
Start the daemon only when it is not already running:
|
|
419
420
|
|
package/docs/cli.md
CHANGED
|
@@ -35,11 +35,12 @@ such as systemd (see `examples/rollbridge.service`).
|
|
|
35
35
|
|
|
36
36
|
For boot/crash recovery, pass an explicit absolute `--config` and all three
|
|
37
37
|
release options together. Rollbridge validates every bootstrap input before it
|
|
38
|
-
binds listeners or starts processes, then binds the
|
|
39
|
-
exact release through the
|
|
38
|
+
binds listeners or starts processes, then binds the proxy and activates that
|
|
39
|
+
exact release through the normal deploy path: services,
|
|
40
40
|
companions, the proxied process and health check, traffic switching, singletons,
|
|
41
|
-
and service-template refresh.
|
|
42
|
-
|
|
41
|
+
and service-template refresh. Only after activation succeeds does it expose the
|
|
42
|
+
control socket, preventing another deployment from overlapping bootstrap. It
|
|
43
|
+
then remains in the foreground with the normal signal behavior.
|
|
43
44
|
|
|
44
45
|
Bootstrap paths must be absolute and normalized, the release path must be an
|
|
45
46
|
accessible directory, and release id/revision values accept letters, numbers,
|
|
@@ -47,10 +48,10 @@ dots, underscores, and hyphens (maximum 200 characters, beginning with a letter
|
|
|
47
48
|
or number). Supplying only some bootstrap options, or an invalid value, exits
|
|
48
49
|
non-zero before listeners start. Activation failure emits a structured
|
|
49
50
|
`bootstrap activation failed` event, cleans up processes owned by that attempt,
|
|
50
|
-
and exits non-zero without
|
|
51
|
-
a previous daemon remain advisory orphans:
|
|
52
|
-
never signals those processes, and retains
|
|
53
|
-
for explicit recovery.
|
|
51
|
+
and exits non-zero without exposing the control socket or inventing an active
|
|
52
|
+
release. `statePath` entries from a previous daemon remain advisory orphans:
|
|
53
|
+
bootstrap never runs recovery and never signals those processes, and retains
|
|
54
|
+
their live PID records in `statePath` for explicit recovery.
|
|
54
55
|
|
|
55
56
|
With no release options, daemon behavior is unchanged: it starts listener-only
|
|
56
57
|
and waits for control-socket deployments.
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -39,7 +39,7 @@ export async function runCli(argv) {
|
|
|
39
39
|
const config = await loadConfig(configPath)
|
|
40
40
|
const daemon = new RollbridgeDaemon({config, configPath})
|
|
41
41
|
|
|
42
|
-
await daemon.start()
|
|
42
|
+
await daemon.start({exposeControl: !bootstrap})
|
|
43
43
|
|
|
44
44
|
const shutdown = async () => {
|
|
45
45
|
await daemon.shutdown()
|
|
@@ -52,6 +52,7 @@ export async function runCli(argv) {
|
|
|
52
52
|
if (bootstrap) {
|
|
53
53
|
try {
|
|
54
54
|
await daemon.deploy(bootstrap)
|
|
55
|
+
await daemon.exposeControl()
|
|
55
56
|
} catch {
|
|
56
57
|
daemon.logger("bootstrap activation failed", {releaseId: bootstrap.releaseId, status: "error"})
|
|
57
58
|
await daemon.shutdown()
|
package/src/daemon.js
CHANGED
|
@@ -64,11 +64,29 @@ export default class RollbridgeDaemon {
|
|
|
64
64
|
this.proxy.on("error", (error, req, res) => this.onProxyError(error, req, res))
|
|
65
65
|
}
|
|
66
66
|
|
|
67
|
-
/**
|
|
68
|
-
|
|
67
|
+
/**
|
|
68
|
+
* Starts daemon listeners.
|
|
69
|
+
* @param {{exposeControl?: boolean}} [options] - Whether to expose the control socket immediately.
|
|
70
|
+
* @returns {Promise<void>} Resolves when the requested listeners are ready.
|
|
71
|
+
*/
|
|
72
|
+
async start({exposeControl = true} = {}) {
|
|
69
73
|
await this.reportOrphans()
|
|
70
74
|
await this.startProxy()
|
|
75
|
+
if (exposeControl) await this.exposeControl()
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** @returns {Promise<void>} Exposes control commands and begins periodic state persistence. */
|
|
79
|
+
async exposeControl() {
|
|
80
|
+
if (this.stopping) throw new Error("Rollbridge is shutting down")
|
|
81
|
+
|
|
71
82
|
await this.startControlServer()
|
|
83
|
+
|
|
84
|
+
if (this.stopping) {
|
|
85
|
+
await this.closeServer(this.controlServer)
|
|
86
|
+
await fs.rm(this.config.control.path, {force: true})
|
|
87
|
+
throw new Error("Rollbridge is shutting down")
|
|
88
|
+
}
|
|
89
|
+
|
|
72
90
|
this.startStatePersistence()
|
|
73
91
|
}
|
|
74
92
|
|
|
@@ -47,7 +47,7 @@ test("daemon bootstrap activates the exact release through the foreground daemon
|
|
|
47
47
|
const child = spawnDaemon(fixture, {releaseId: "release-42", revision: "abc123"})
|
|
48
48
|
|
|
49
49
|
try {
|
|
50
|
-
await waitForLog(child, "
|
|
50
|
+
await waitForLog(child, "control socket listening")
|
|
51
51
|
const status = await sendControlCommand({command: {command: "status"}, path: fixture.socketPath})
|
|
52
52
|
const activeRelease = assertRelease(status, "release-42")
|
|
53
53
|
|
|
@@ -64,6 +64,33 @@ test("daemon bootstrap activates the exact release through the foreground daemon
|
|
|
64
64
|
}
|
|
65
65
|
})
|
|
66
66
|
|
|
67
|
+
test("daemon bootstrap does not expose control deploys until activation completes", async () => {
|
|
68
|
+
const fixture = await createFixture({healthGate: true, healthTimeoutMs: 60000})
|
|
69
|
+
const started = waitForFile(fixture.startedPath)
|
|
70
|
+
const child = spawnDaemon(fixture, {releaseId: "bootstrap-release", revision: "bootstrap123"})
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
await started
|
|
74
|
+
await assert.rejects(() => fs.stat(fixture.socketPath), {code: "ENOENT"})
|
|
75
|
+
|
|
76
|
+
await fs.writeFile(fixture.healthGatePath, "ready\n")
|
|
77
|
+
await waitForLog(child, "control socket listening")
|
|
78
|
+
|
|
79
|
+
const status = await sendControlCommand({command: {command: "status"}, path: fixture.socketPath})
|
|
80
|
+
|
|
81
|
+
assert.equal(status.activeReleaseId, "bootstrap-release")
|
|
82
|
+
assert.ok(Array.isArray(status.releases))
|
|
83
|
+
assert.equal(status.releases.length, 1)
|
|
84
|
+
assertRelease(status, "bootstrap-release")
|
|
85
|
+
|
|
86
|
+
child.kill("SIGTERM")
|
|
87
|
+
assert.equal((await once(child, "exit"))[0], 0)
|
|
88
|
+
} finally {
|
|
89
|
+
if (child.exitCode === null) child.kill("SIGKILL")
|
|
90
|
+
await fs.rm(fixture.root, {force: true, recursive: true})
|
|
91
|
+
}
|
|
92
|
+
})
|
|
93
|
+
|
|
67
94
|
test("plain daemon startup remains listener-only with no active release", async () => {
|
|
68
95
|
const fixture = await createFixture()
|
|
69
96
|
const child = spawn(process.execPath, [binPath, "daemon", "--config", fixture.configPath], {stdio: ["pipe", "pipe", "pipe"]})
|
|
@@ -182,7 +209,7 @@ test("daemon bootstrap reports but does not kill a live process from statePath",
|
|
|
182
209
|
const child = spawnDaemon(fixture, {releaseId: "recovered", revision: "def456"})
|
|
183
210
|
|
|
184
211
|
try {
|
|
185
|
-
await waitForLog(child, "
|
|
212
|
+
await waitForLog(child, "control socket listening")
|
|
186
213
|
const status = await sendControlCommand({command: {command: "status"}, path: fixture.socketPath})
|
|
187
214
|
|
|
188
215
|
assert.ok(leftover.pid !== undefined && isProcessAlive(leftover.pid))
|
|
@@ -228,10 +255,10 @@ test("failed daemon bootstrap preserves prior live process records in statePath"
|
|
|
228
255
|
})
|
|
229
256
|
|
|
230
257
|
/**
|
|
231
|
-
* @param {{healthPath?: string, healthTimeoutMs?: number, multiProcessSignal?: boolean, persistState?: boolean}} [options] - Fixture options.
|
|
232
|
-
* @returns {Promise<{configPath: string, gatePath: string, lifecyclePath: string, root: string, socketPath: string, startedPath: string, statePath: string, stoppedPath: string}>} Fixture paths.
|
|
258
|
+
* @param {{healthGate?: boolean, healthPath?: string, healthTimeoutMs?: number, multiProcessSignal?: boolean, persistState?: boolean}} [options] - Fixture options.
|
|
259
|
+
* @returns {Promise<{configPath: string, gatePath: string, healthGatePath: string, lifecyclePath: string, root: string, socketPath: string, startedPath: string, statePath: string, stoppedPath: string}>} Fixture paths.
|
|
233
260
|
*/
|
|
234
|
-
async function createFixture({healthPath = "/ping", healthTimeoutMs = 1000, multiProcessSignal = false, persistState = false} = {}) {
|
|
261
|
+
async function createFixture({healthGate = false, healthPath = "/ping", healthTimeoutMs = 1000, multiProcessSignal = false, persistState = false} = {}) {
|
|
235
262
|
const root = await fs.mkdtemp(path.join(os.tmpdir(), "rollbridge-bootstrap-"))
|
|
236
263
|
const socketPath = path.join(root, "control.sock")
|
|
237
264
|
const statePath = path.join(root, "state.json")
|
|
@@ -239,9 +266,15 @@ async function createFixture({healthPath = "/ping", healthTimeoutMs = 1000, mult
|
|
|
239
266
|
const stoppedPath = path.join(root, "stopped.pid")
|
|
240
267
|
const lifecyclePath = path.join(root, "lifecycle.jsonl")
|
|
241
268
|
const gatePath = path.join(root, "continue.fifo")
|
|
269
|
+
const healthGatePath = path.join(root, "health-ready")
|
|
242
270
|
const configPath = path.join(root, "rollbridge.js")
|
|
243
271
|
const command = `${JSON.stringify(process.execPath)} ${JSON.stringify(dummyAppPath)}`
|
|
244
272
|
const lifecycleEnv = {ROLLBRIDGE_TEST_LIFECYCLE_PATH: lifecyclePath}
|
|
273
|
+
const webEnv = {
|
|
274
|
+
ROLLBRIDGE_TEST_STARTED_PATH: startedPath,
|
|
275
|
+
ROLLBRIDGE_TEST_STOPPED_PATH: stoppedPath,
|
|
276
|
+
...(healthGate ? {ROLLBRIDGE_TEST_HEALTH_GATE_PATH: healthGatePath} : {})
|
|
277
|
+
}
|
|
245
278
|
const config = {
|
|
246
279
|
application: "bootstrap-test",
|
|
247
280
|
control: {path: socketPath},
|
|
@@ -249,7 +282,7 @@ async function createFixture({healthPath = "/ping", healthTimeoutMs = 1000, mult
|
|
|
249
282
|
{command: `trap '' TERM; printf '%s\\n' '{"event":"shutdown"}' >> ${JSON.stringify(lifecyclePath)}; kill -TERM "$ROLLBRIDGE_TEST_DAEMON_PID"; printf '{"event":"started","pid":%s,"processId":"database","replicaIndex":"0"}\\n' "$$" >> ${JSON.stringify(lifecyclePath)}; read ignored < ${JSON.stringify(gatePath)}`, env: lifecycleEnv, id: "database", policy: "service"},
|
|
250
283
|
{command: `exec ${command}`, env: lifecycleEnv, id: "worker", policy: "companion", replicas: 2},
|
|
251
284
|
{command: `exec ${command}`, env: lifecycleEnv, health: {path: healthPath, timeoutMs: healthTimeoutMs}, id: "web", policy: "proxied", port: {from: 0, to: 0}}
|
|
252
|
-
] : [{command, env:
|
|
285
|
+
] : [{command, env: webEnv, health: {path: healthPath, timeoutMs: healthTimeoutMs}, id: "web", policy: "proxied", port: {from: 0, to: 0}}],
|
|
253
286
|
proxy: {host: "127.0.0.1", port: 0},
|
|
254
287
|
...(persistState ? {statePath} : {})
|
|
255
288
|
}
|
|
@@ -264,7 +297,7 @@ async function createFixture({healthPath = "/ping", healthTimeoutMs = 1000, mult
|
|
|
264
297
|
}
|
|
265
298
|
|
|
266
299
|
await fs.writeFile(configPath, `${setup}module.exports = ${JSON.stringify(config, null, 2)}\n`)
|
|
267
|
-
return {configPath, gatePath, lifecyclePath, root, socketPath, startedPath, statePath, stoppedPath}
|
|
300
|
+
return {configPath, gatePath, healthGatePath, lifecyclePath, root, socketPath, startedPath, statePath, stoppedPath}
|
|
268
301
|
}
|
|
269
302
|
|
|
270
303
|
/**
|
|
@@ -15,7 +15,7 @@ if (process.env.ROLLBRIDGE_TEST_STARTED_PATH) {
|
|
|
15
15
|
|
|
16
16
|
const server = http.createServer((request, response) => {
|
|
17
17
|
if (request.url === "/ping") {
|
|
18
|
-
if (healthFails) {
|
|
18
|
+
if (healthFails || (process.env.ROLLBRIDGE_TEST_HEALTH_GATE_PATH && !fs.existsSync(process.env.ROLLBRIDGE_TEST_HEALTH_GATE_PATH))) {
|
|
19
19
|
response.writeHead(500, {"Content-Type": "application/json"})
|
|
20
20
|
response.end(JSON.stringify({message: "bad release"}))
|
|
21
21
|
return
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
// @ts-check
|
|
2
2
|
|
|
3
3
|
import assert from "node:assert/strict"
|
|
4
|
+
import {execFile} from "node:child_process"
|
|
4
5
|
import fs from "node:fs/promises"
|
|
6
|
+
import os from "node:os"
|
|
5
7
|
import path from "node:path"
|
|
6
8
|
import test from "node:test"
|
|
7
9
|
import {fileURLToPath} from "node:url"
|
|
10
|
+
import {promisify} from "node:util"
|
|
8
11
|
|
|
9
12
|
const repoRoot = path.resolve(path.dirname(fileURLToPath(import.meta.url)), "..")
|
|
13
|
+
const execFileAsync = promisify(execFile)
|
|
10
14
|
|
|
11
15
|
test("package.json declares publish metadata", async () => {
|
|
12
16
|
const pkg = JSON.parse(await fs.readFile(path.join(repoRoot, "package.json"), "utf8"))
|
|
@@ -27,3 +31,38 @@ test("a LICENSE file matching the declared license exists", async () => {
|
|
|
27
31
|
assert.match(license, /MIT License/)
|
|
28
32
|
assert.match(license, /Copyright \(c\) \d{4} kaspernj/)
|
|
29
33
|
})
|
|
34
|
+
|
|
35
|
+
test("package manifest excludes unexpected operational and coverage files", async (t) => {
|
|
36
|
+
const fixtureRoot = await fs.mkdtemp(path.join(os.tmpdir(), "rollbridge-pack-"))
|
|
37
|
+
t.after(() => fs.rm(fixtureRoot, {force: true, recursive: true}))
|
|
38
|
+
|
|
39
|
+
await fs.cp(repoRoot, fixtureRoot, {
|
|
40
|
+
filter: (source) => {
|
|
41
|
+
const relative = path.relative(repoRoot, source)
|
|
42
|
+
return relative !== ".git" && relative !== "node_modules" && relative !== "tmp"
|
|
43
|
+
},
|
|
44
|
+
recursive: true,
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
const unexpectedTmpPath = path.join(fixtureRoot, "tmp", "worker-control", "unexpected-transcript.jsonl")
|
|
48
|
+
const unexpectedCoveragePath = path.join(fixtureRoot, "coverage", "unexpected.txt")
|
|
49
|
+
await Promise.all([
|
|
50
|
+
fs.mkdir(path.dirname(unexpectedTmpPath), {recursive: true}),
|
|
51
|
+
fs.mkdir(path.dirname(unexpectedCoveragePath), {recursive: true}),
|
|
52
|
+
])
|
|
53
|
+
await Promise.all([
|
|
54
|
+
fs.writeFile(unexpectedTmpPath, '{"operational":"state"}\n'),
|
|
55
|
+
fs.writeFile(unexpectedCoveragePath, "unexpected coverage output\n"),
|
|
56
|
+
])
|
|
57
|
+
|
|
58
|
+
const {stdout} = await execFileAsync("npm", ["pack", "--dry-run", "--json"], {cwd: fixtureRoot})
|
|
59
|
+
/** @type {Array<{path: string}>} */
|
|
60
|
+
const packageFiles = JSON.parse(stdout)[0].files
|
|
61
|
+
const packagePaths = packageFiles.map((file) => file.path)
|
|
62
|
+
|
|
63
|
+
for (const requiredPath of ["LICENSE", "README.md", "bin/rollbridge", "package.json", "src/cli.js"]) {
|
|
64
|
+
assert.ok(packagePaths.includes(requiredPath), `expected package to include ${requiredPath}`)
|
|
65
|
+
}
|
|
66
|
+
assert.ok(!packagePaths.some((packagePath) => packagePath === "tmp" || packagePath.startsWith("tmp/")))
|
|
67
|
+
assert.ok(!packagePaths.some((packagePath) => packagePath === "coverage" || packagePath.startsWith("coverage/")))
|
|
68
|
+
})
|
|
@@ -1,28 +0,0 @@
|
|
|
1
|
-
{"type":"controller-started","pid":23,"at":1786618711548}
|
|
2
|
-
{"type":"provider-started","provider":"codex","pid":34,"at":1786618711553}
|
|
3
|
-
{"type":"session-available","provider":"codex","sessionId":"019ffac6-1176-7023-8f14-82bae745817f","at":1786618712506}
|
|
4
|
-
{"type":"activity","provider":"codex","kind":"lifecycle","at":1786618712506}
|
|
5
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786618717893}
|
|
6
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786618723182}
|
|
7
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786618730240}
|
|
8
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786618740515}
|
|
9
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786618746269}
|
|
10
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786618770657}
|
|
11
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786618811295}
|
|
12
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786618816349}
|
|
13
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786618859240}
|
|
14
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786618872926}
|
|
15
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786618889245}
|
|
16
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786618908545}
|
|
17
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786618913878}
|
|
18
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786618934507}
|
|
19
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786618940248}
|
|
20
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786618945887}
|
|
21
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786618960106}
|
|
22
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786618969417}
|
|
23
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786618976649}
|
|
24
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786618996642}
|
|
25
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619006003}
|
|
26
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619014397}
|
|
27
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786619021770}
|
|
28
|
-
{"type":"terminal","provider":"codex","state":"completed","exitCode":0,"at":1786619026150}
|
|
@@ -1,13 +0,0 @@
|
|
|
1
|
-
{"type":"controller-started","pid":23,"at":1786619870796}
|
|
2
|
-
{"type":"provider-started","provider":"codex","pid":34,"at":1786619870799}
|
|
3
|
-
{"type":"session-available","provider":"codex","sessionId":"019ffad7-bed8-7861-911f-83cd5de11373","at":1786619871013}
|
|
4
|
-
{"type":"activity","provider":"codex","kind":"lifecycle","at":1786619871014}
|
|
5
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619877574}
|
|
6
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619886232}
|
|
7
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619894201}
|
|
8
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786619899492}
|
|
9
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619905754}
|
|
10
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619921741}
|
|
11
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619931957}
|
|
12
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786619937323}
|
|
13
|
-
{"type":"terminal","provider":"codex","state":"completed","exitCode":0,"at":1786620000478}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":1,"type":"delegated_result","state":"completed","exitCode":0,"conclusion":"I’ll perform a read-only review of the full uncommitted diff, then trace the daemon bootstrap lifecycle through its existing implementation and focused tests. I’ll avoid edits and the full test suite, and report only material, reproducible issues—or `PASS`.The repaired state preservation path is present, and the focused tests cover the two accepted lifecycle fixes. I’m now checking concurrency boundaries beneath `deploy()`—especially whether `ReleaseGroup.start()` and process/service startup remain safely stoppable when shutdown overlaps activation—and validating persistence shape compatibility.- High — [src/daemon.js:747](/home/dev/rollbridge/src/daemon.js:747): shutdown snapshots `services`, `singletons`, and `startingReleases` only once, while the concurrent bootstrap deployment can continue creating processes. `ReleaseGroup.start()` has no cancellation check inside its process/replica loop ([src/release-group.js:73](/home/dev/rollbridge/src/release-group.js:73)), and singleton replacement similarly continues after each await ([src/daemon.js:539](/home/dev/rollbridge/src/daemon.js:539)). If SIGTERM arrives while an early service, companion replica, or singleton is starting/stopping, shutdown may finish its snapshot while deployment starts a later process. The signal handler then calls `process.exit(0)` ([src/cli.js:44](/home/dev/rollbridge/src/cli.js:44)), potentially leaving that detached child running and unrecorded. The new signal test only uses one release process and signals during its health wait, so it does not cover this realistic multi-process race.","continuationHandle":"019ffad7-bed8-7861-911f-83cd5de11373"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
probe\n
|
|
@@ -1,9 +0,0 @@
|
|
|
1
|
-
# Rollbridge daemon bootstrap plan
|
|
2
|
-
|
|
3
|
-
1. Add focused CLI/daemon bootstrap tests first, covering all-or-nothing and safe absolute input validation before binding, exact activation through the normal deploy path, structured failure with attempt-owned cleanup and non-zero exit, unchanged plain-daemon startup, and preservation/reporting of live `statePath` orphans.
|
|
4
|
-
2. Run the new focused test file alone and record the expected RED result before production changes.
|
|
5
|
-
3. Add the minimal foreground daemon bootstrap options and validation, then start listeners and call the existing `RollbridgeDaemon.deploy()` path; on activation failure, log a safe structured failure, shut down only objects owned by this attempt, and propagate failure.
|
|
6
|
-
4. Update the CLI reference and README daemon usage, then run the changed focused test files individually, `npm run lint`, and `npm run typecheck`.
|
|
7
|
-
5. Inspect the final diff/status and leave every change uncommitted for independent verification.
|
|
8
|
-
|
|
9
|
-
Scope note: bootstrap requires explicit `--release-path`, `--release-id`, and `--revision` together with an explicit `--config`; values must be non-empty safe tokens where applicable and paths must be absolute, normalized, and usable. No orphan recovery or process killing is part of bootstrap.
|
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
{"type":"controller-started","pid":23,"at":1786619544825}
|
|
2
|
-
{"type":"provider-started","provider":"codex","pid":34,"at":1786619544829}
|
|
3
|
-
{"type":"session-available","provider":"codex","sessionId":"019ffad2-c71a-7522-9645-c45214c2ba90","at":1786619545505}
|
|
4
|
-
{"type":"activity","provider":"codex","kind":"lifecycle","at":1786619545506}
|
|
5
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619552059}
|
|
6
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619560395}
|
|
7
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786619567749}
|
|
8
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619582034}
|
|
9
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786619609721}
|
|
10
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786619632612}
|
|
11
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619657491}
|
|
12
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786619692967}
|
|
13
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619698926}
|
|
14
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619706811}
|
|
15
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619724983}
|
|
16
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786619730097}
|
|
17
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619736678}
|
|
18
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786619742798}
|
|
19
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619748670}
|
|
20
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619754672}
|
|
21
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619764194}
|
|
22
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786619775649}
|
|
23
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619780713}
|
|
24
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786619786176}
|
|
25
|
-
{"type":"terminal","provider":"codex","state":"completed","exitCode":0,"at":1786619829038}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":1,"type":"delegated_result","state":"completed","exitCode":0,"conclusion":"I’ll resume in place and preserve the current worktree. I’ll first inspect the branch, repository guidance, lifecycle code, and existing tests; then I’ll add only focused deterministic regressions and capture their expected failures before touching production code.The root conflict is confirmed: startup reads prior live PIDs into an advisory in-memory list, but the first persistence snapshot deliberately drops that list, and shutdown then unconditionally removes the file. I’m going to make the regressions assert two boundaries directly: SIGTERM while bootstrap activation is provably in-flight, and recoverable prior PID records after bootstrap persistence plus shutdown/failure.The focused regressions are now in place without timing sleeps: the signal test gates on the daemon’s own “control socket listening” milestone, and persistence checks decode the actual state store through `liveProcesses`. I’m running only the bootstrap test file now to record the RED failures before production edits.RED is confirmed with exactly the three expected failures: bootstrap SIGTERM exits by signal instead of code 0, and both successful-shutdown and failed-activation paths lose the prior PID record. I’m now making the minimal ownership changes: register signal handling before activation, track the in-progress release as daemon-owned for shutdown, and carry advisory orphan records through snapshots and clean shutdown.The focused file is GREEN. I’m tightening the signal regression before final checks so it proves the managed child was actually started and then stopped—not just that the CLI intercepted SIGTERM early. The test will use a filesystem watch/marker handshake from the existing fixture, still with no sleeps or polling.The strengthened regression is GREEN and confirms the exact started PID received the daemon’s graceful stop before the CLI exited. The focused persistence regressions also remain GREEN. I’m moving to the requested static checks now, without running the full suite.`npm run lint` reached its bundled typecheck and found one narrow test typing issue: the async iterator’s optional `return` method. I’ve corrected that optional call and will re-run the focused test before repeating the requested checks.All requested test and static checks are now passing. I made one small documentation clarification because the durability behavior is operator-visible: live advisory orphan records remain in `statePath` for explicit recovery. I’m doing the final whitespace, diff, branch, and worktree inspection now; I will leave the existing `tmp/` and all unrelated candidate changes untouched.Implemented both HIGH lifecycle fixes on `feature/daemon-bootstrap-release`:\n\n- SIGINT/SIGTERM ownership is installed before bootstrap activation.\n- In-progress releases are daemon-owned and stopped during shutdown.\n- Prior-daemon orphan PID records remain persisted across snapshots, successful shutdown, and failed bootstrap activation.\n- Added deterministic regressions using explicit process/file handshakes—no sleeps or retries.\n- Updated documentation to describe recoverable orphan records.\n\nRED was confirmed with three expected failures before production edits. Final checks:\n\n- `node --test test/daemon-bootstrap.test.js` — 12 passed\n- `npm run lint` — passed\n- `npm run typecheck` — passed\n- `git diff --check` — passed\n\nFinal branch/status inspected. No full suite, commit, push, or deploy performed. Existing unrelated changes and `tmp/` remain untouched.","continuationHandle":"019ffad2-c71a-7522-9645-c45214c2ba90"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":1,"type":"delegated_result","state":"failed","exitCode":2,"continuationHandle":"019ffac6-1176-7023-8f14-82bae745817f"}
|
|
@@ -1,14 +0,0 @@
|
|
|
1
|
-
{"type":"controller-started","pid":23,"at":1786619089604}
|
|
2
|
-
{"type":"provider-started","provider":"codex","pid":34,"at":1786619089609}
|
|
3
|
-
{"type":"session-available","provider":"codex","sessionId":"019ffacb-d3c0-7f93-affa-c472258a1b8b","at":1786619089955}
|
|
4
|
-
{"type":"activity","provider":"codex","kind":"lifecycle","at":1786619089955}
|
|
5
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619096716}
|
|
6
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619104649}
|
|
7
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619110897}
|
|
8
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619130819}
|
|
9
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786619140752}
|
|
10
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619146978}
|
|
11
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619159062}
|
|
12
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786619164311}
|
|
13
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786619179382}
|
|
14
|
-
{"type":"terminal","provider":"codex","state":"completed","exitCode":0,"at":1786619221045}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":1,"type":"delegated_result","state":"completed","exitCode":0,"conclusion":"I’ll review the exact working-tree candidate read-only, first inventorying repository instructions and the complete diff, then tracing the affected daemon/deploy/state/recovery paths and targeted tests. I won’t modify anything or run the full suite.The candidate is small and localized, but its safety claims depend heavily on existing daemon startup and deploy cleanup behavior. I’m tracing those internals now, including persisted-state loading and signal/exit handling, and will run only focused tests relevant to this interface.Two lifecycle interactions warrant direct verification: interruption during a slow bootstrap, and what remains recoverable in `statePath` when live prior-daemon processes were only reported. I’m checking those against focused existing/new tests and source behavior; no repository files will be changed.- High — [src/cli.js](/home/dev/rollbridge/src/cli.js:42): SIGINT/SIGTERM handlers are installed only after `daemon.deploy()` completes at line 46. A service-manager stop or restart during process startup or a long health check therefore performs Node’s default termination instead of `daemon.shutdown()`. Managed children are detached ([src/managed-process.js](/home/dev/rollbridge/src/managed-process.js:86)), so they can survive while the proxy/control socket and state are left unclean. This contradicts the documented “normal signal behavior” and is not covered by the bootstrap tests.\n\n- High — [src/daemon.js](/home/dev/rollbridge/src/daemon.js:677): After prior-daemon processes are read into the in-memory orphan list, startup immediately persists a new snapshot that deliberately excludes those orphans (lines 691–700), overwriting their only durable PID records. A later clean shutdown also deletes `statePath` (lines 750–755). Thus a live prior-daemon process is not killed, but becomes unrecoverable through `rollbridge recover` once bootstrap starts—or immediately when failed activation invokes shutdown. The test at [test/daemon-bootstrap.test.js](/home/dev/rollbridge/test/daemon-bootstrap.test.js:108) checks only process liveness and in-memory status, never preservation of the persisted orphan records. This conflicts with the documentation that previous `statePath` entries remain advisory orphans.","continuationHandle":"019ffacb-d3c0-7f93-affa-c472258a1b8b"}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
{"type":"controller-started","pid":23,"at":1786620086602}
|
|
2
|
-
{"type":"provider-started","provider":"codex","pid":34,"at":1786620086605}
|
|
3
|
-
{"type":"session-available","provider":"codex","sessionId":"019ffadb-09f5-7460-bcd7-0db1e74e9935","at":1786620086862}
|
|
4
|
-
{"type":"activity","provider":"codex","kind":"lifecycle","at":1786620086862}
|
|
5
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786620091884}
|
|
6
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620098162}
|
|
7
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620108101}
|
|
8
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620130450}
|
|
9
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786620181139}
|
|
10
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620214551}
|
|
11
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620233438}
|
|
12
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620296843}
|
|
13
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786620316811}
|
|
14
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620326537}
|
|
15
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620349522}
|
|
16
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786620369610}
|
|
17
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620386448}
|
|
18
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620401793}
|
|
19
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620419491}
|
|
20
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620428258}
|
|
21
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620467781}
|
|
22
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786620478524}
|
|
23
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620514191}
|
|
24
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620519855}
|
|
25
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620542628}
|
|
26
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620597146}
|
|
27
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620603744}
|
|
28
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620610390}
|
|
29
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620615895}
|
|
30
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620629789}
|
|
31
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620635487}
|
|
32
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786620645508}
|
|
33
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620651900}
|
|
34
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620657807}
|
|
35
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620663562}
|
|
36
|
-
{"type":"activity","provider":"codex","kind":"tool","at":1786620670250}
|
|
37
|
-
{"type":"activity","provider":"codex","kind":"delta","at":1786620682665}
|
|
38
|
-
{"type":"terminal","provider":"codex","state":"completed","exitCode":0,"at":1786620740752}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":1,"type":"delegated_result","state":"completed","exitCode":0,"conclusion":"I’ll focus only on the SIGTERM/bootstrap lifecycle race, preserve the current candidate changes, and avoid commits, pushes, deploys, the full suite, and any independent review. I’ll first inspect the existing daemon lifecycle/tests, add a handshake-driven regression, and run it alone to capture the required RED before touching production code.The race boundary is in three sequential creation loops: daemon-wide services, release-owned process/replica startup, and singleton replacement. I’m adding the regression at the CLI boundary with the first service command explicitly recording a “shutdown trigger” event and sending SIGTERM to the daemon itself; later service/replica commands record their PIDs, and every managed child records graceful stop. This avoids health-wait timing and gives an event-ordered proof without sleeps or polling.The focused regression is now a genuine RED for the accepted race: after the shutdown event, both worker replicas start, the daemon exits with code 0, and neither replica records a graceful stop. I’m implementing the narrow coordination boundary now: shutdown will wait for already-admitted deploy ownership to settle, then perform a final ownership sweep; failed/interrupted deploy cleanup will also stop its release explicitly. Prior-daemon advisory orphans remain outside these owned collections.The first fix run exposed a test-handshake issue rather than a lifecycle result: the fixture’s “started” record was emitted before its SIGTERM handler was installed, so a correctly stopped child could die without writing the stop acknowledgment. I moved that readiness event to the server-listening callback. The rerun is still active, so I’m checking whether the FIFO release handshake itself is now blocked before changing any production logic further.The repaired regression is GREEN and now checks the exact two required properties for every post-shutdown-start PID: it appears in the daemon’s managed-process records, and it is no longer alive before the signal handler exits. The fix does not adopt or sweep persisted orphan PIDs; it waits only for deploy promises admitted by this daemon and re-sweeps only its service/singleton/release ownership maps. I’m running the focused bootstrap file, then the directly affected daemon/release lifecycle files only.The full focused bootstrap file revealed an important compatibility constraint: waiting for the entire deploy promise also waits out an existing 60-second health check, delaying SIGTERM shutdown. I’m narrowing the fix to cancellation at every creation boundary instead. Because JavaScript cannot interleave between the cancellation check and registering a new owned process, shutdown’s snapshot will either own the current start or the loop will abort before creating the next one; health waits may remain in flight, but cannot create anything afterward.All relevant focused files are passing: the bootstrap integration file, release-group unit file, and daemon lifecycle integration file. Normal deploy, replicas, services, singletons, failed startup cleanup, shutdown persistence, and prior-orphan reporting remain green. I’m running the requested static checks now, then I’ll inspect the exact diff and worktree status without any further review pass.`npm run lint` found one narrow JSDoc-only issue in the new handshake helper: this repository disallows `unknown` there and requires its `JsonValue` type. I’m correcting that annotation, then I’ll rerun the focused bootstrap file before repeating lint and the remaining requested checks.Implemented the terminal SIGTERM/bootstrap race repair without commit, push, deploy, full suite, or independent review.\n\nChanges:\n\n- Added cancellation checks before each service, release process/replica, and singleton creation boundary.\n- Registered in-progress releases as daemon-owned during bootstrap.\n- Interrupted deploy cleanup explicitly stops only its release and newly started services.\n- Prior-daemon advisory orphans remain untouched and recoverable.\n- Added a deterministic FIFO/event-handshake regression proving post-shutdown-start processes are recorded and dead before daemon exit.\n\nValidation:\n\n- Required RED confirmed: two worker replicas started after shutdown and survived unrecorded.\n- `node --test test/daemon-bootstrap.test.js` — 13 passed\n- `node --test test/release-group.test.js` — 3 passed\n- `node --test test/rollbridge.test.js` — 39 passed\n- `npm run lint` — passed\n- `npm run typecheck` — passed\n- `git diff --check` — passed\n\nThe first lint run found one test JSDoc type error; corrected and rerun successfully. Final status/diff inspected. Existing candidate changes and `tmp/` remain preserved and uncommitted.","continuationHandle":"019ffadb-09f5-7460-bcd7-0db1e74e9935"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":1,"type":"delegated_result","state":"failed","exitCode":2,"continuationHandle":"019ffad2-c71a-7522-9645-c45214c2ba90"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":1,"type":"delegated_result","state":"failed","exitCode":2}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":1,"type":"delegated_result","state":"completed","exitCode":0,"conclusion":"I’ll inspect the repository guidance, daemon/CLI/control activation flow, and focused tests first. I’ll then create the feature branch and operational plan before making production edits, with the required RED test run recorded.The core path is clear: control-socket `deploy` already funnels through `RollbridgeDaemon.deploy()`, which owns service startup, release health, traffic switching, template refresh, singleton replacement, and failed-attempt cleanup. I’m narrowing the bootstrap work to CLI validation/orchestration around that same method, plus tests for early rejection, successful foreground activation, failure cleanup, and `statePath` orphan safety.I’ve created `feature/daemon-bootstrap-release` and written the scoped operational plan. The interface is intentionally all-or-nothing: bootstrap mode uses explicit `--config`, `--release-path`, `--release-id`, and `--revision`; it does not invoke orphan recovery or kill anything from persisted state. Next I’m adding the focused integration tests before touching production code.The first RED run failed before exercising the feature because dependencies are not installed (`ERR_MODULE_NOT_FOUND: commander`). I’m installing the locked dependencies, then I’ll rerun the same focused file to obtain a genuine missing-behavior RED before production edits.The focused bootstrap tests are now green after the minimal CLI orchestration change, including exact metadata activation, failed health-check cleanup/non-zero exit, and live orphan preservation. I’m adding the explicit plain-daemon regression case and documenting the new foreground interface; no changelog exists in this repository, so I’m following its README/CLI-reference convention.The focused file passes all eight assertions. The first lint run found only test-file style issues (four `no-useless-escape` errors plus JSDoc warnings); I’ve corrected those without changing behavior and am rerunning the focused test and required static checks.All required checks now pass. I’m doing the final diff/status audit for accidental generated or unrelated changes, confirming the branch and uncommitted state, and reviewing the bootstrap failure/shutdown path one last time against attempt ownership and orphan safety.Implemented on new branch `feature/daemon-bootstrap-release`; all changes remain uncommitted.\n\nKey changes:\n\n- Added all-or-nothing daemon bootstrap flags: `--config`, `--release-path`, `--release-id`, and `--revision`.\n- Validates inputs before listeners/processes start.\n- Uses the existing `RollbridgeDaemon.deploy()` activation path.\n- Emits structured bootstrap failure, cleans attempt-owned processes, and exits non-zero.\n- Preserves plain daemon behavior and does not kill `statePath` orphans.\n- Added focused integration coverage and updated README/CLI documentation.\n- Added the requested untracked [plan.md](/home/dev/rollbridge/tmp/worker-control/rollbridge-bootstrap/plan.md).\n\nValidation:\n\n- RED: `node --test test/daemon-bootstrap.test.js` — failed because daemon bootstrap options were unknown.\n- Final: `node --test test/daemon-bootstrap.test.js` — 10 passed, 0 failed.\n- `npm run lint` — passed.\n- `npm run typecheck` — passed.\n- `git diff --check` — passed.\n- Full test suite was not run, as requested.\n\nModified/untracked implementation files:\n\n- [src/cli.js](/home/dev/rollbridge/src/cli.js)\n- [test/daemon-bootstrap.test.js](/home/dev/rollbridge/test/daemon-bootstrap.test.js)\n- [README.md](/home/dev/rollbridge/README.md)\n- [docs/cli.md](/home/dev/rollbridge/docs/cli.md)\n\nNo commit, push, publish, deploy, or live-service modification was performed.","continuationHandle":"019ffac6-1176-7023-8f14-82bae745817f"}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":1,"type":"delegated_result","state":"failed","exitCode":2}
|