darkwing: re-pin window rows 177-185 packet, STATE.md (sha256 b586f11c9b6f1329), W.txt (edd4622c5c45ef3f), S1/S2/S2b/B snapshots, matrices, W10 browser check and P3 rehearsals (#1683; P19 386)

NEW 490e2771d live for mosaic-stack, activation 20261020T075800Z-490e2771d3d0, store 3i. W0-W10 pass;
stops at W9 (assistant idle stop, mos-0948) and S2b/B (old chief end rows in the backup, mos-0950).
This commit is contained in:
2026-10-20 03:27:30 -05:00
parent 9ddd8a92c9
commit 21aa1cab0b
285 changed files with 17823 additions and 0 deletions
@@ -0,0 +1,5 @@
copy 490e2771d: roles 6, projects 1, arbiters 2
HOLDS chief instance.name yes
HOLDS pm instance.name no
LOAD_OK
# exit 0
@@ -0,0 +1,2 @@
chief rc78c9700c8e9 claude-code since 2026-10-20T01:41:04.929Z
# exit 0
@@ -0,0 +1,251 @@
{
"settings loaded": {
"pass": true
},
"settings": {
"banner": "Tester Console. Changes are recorded as tester, never as the human.",
"h1": "Settings",
"forms": [
"conv-form",
"routes-form",
"instance-add-form",
"instance-change-form",
"tester-name-form",
"tester-name-form",
"tester-name-form",
"tester-name-form",
"tester-name-form",
"tester-name-form"
],
"convFormHidden": true,
"panels": [
"Routing",
"Role instances",
"Seat names"
],
"instancesTable": true,
"nameForms": 6,
"routesForm": true,
"routesSubmitEnabled": true,
"forbidden": [],
"tokenLimitsPanel": false
},
"tester banner": {
"pass": true,
"detail": "Tester Console. Changes are recorded as tester, never as the human."
},
"instance, routes and name editors present": {
"pass": true
},
"only tester forms (conv-form is index.html:21, global and hidden)": {
"pass": true,
"detail": [
"conv-form",
"routes-form",
"instance-add-form",
"instance-change-form",
"tester-name-form",
"tester-name-form",
"tester-name-form",
"tester-name-form",
"tester-name-form",
"tester-name-form"
]
},
"no launch settings, assistant, limit reset or send": {
"pass": true,
"detail": []
},
"no Token limits panel (plan W10 item 2, amended)": {
"pass": true
},
"board": {
"busViewHidden": true,
"banner": true,
"headings": [
"Waiting on you –",
"All sessions –"
],
"unavailable": false,
"length": 179,
"forms": [
"conv-form"
]
},
"board reads": {
"pass": true,
"detail": {
"busViewHidden": true,
"banner": true,
"headings": [
"Waiting on you –",
"All sessions –"
],
"unavailable": false,
"length": 179,
"forms": [
"conv-form"
]
}
},
"chat": {
"hash": "#/chat",
"banner": true,
"threads": [
{
"href": "#/chat/threads/d0ba7a66-aa3f-4918-9518-94132bce6b28",
"name": "w9",
"instance": "assistant"
}
],
"conversationOpen": false,
"forms": [
"conv-form",
"chat-project-form"
]
},
"chat lists the W9 thread": {
"pass": true,
"detail": [
{
"href": "#/chat/threads/d0ba7a66-aa3f-4918-9518-94132bce6b28",
"name": "w9",
"instance": "assistant"
}
]
},
"W9 thread page not opened": {
"pass": true,
"detail": "#/chat"
},
"tester banner on chat": {
"pass": true
},
"requests": [
{
"method": "GET",
"url": "/api/config",
"refused": false
},
{
"method": "GET",
"url": "/api/config",
"refused": false
},
{
"method": "GET",
"url": "/api/bus/messages",
"refused": false
},
{
"method": "GET",
"url": "/api/bus-status",
"refused": false
},
{
"method": "GET",
"url": "/api/bus/messages",
"refused": false
},
{
"method": "GET",
"url": "/api/settings",
"refused": false
},
{
"method": "GET",
"url": "/api/bus/usage",
"refused": false
},
{
"method": "GET",
"url": "/api/settings",
"refused": false
},
{
"method": "GET",
"url": "/api/bus/usage",
"refused": false
},
{
"method": "GET",
"url": "/api/bus/names",
"refused": false
},
{
"method": "GET",
"url": "/api/bus/names",
"refused": false
},
{
"method": "GET",
"url": "/api/bus/instances",
"refused": false
},
{
"method": "GET",
"url": "/api/bus/instances",
"refused": false
},
{
"method": "GET",
"url": "/api/bus/routes",
"refused": false
},
{
"method": "GET",
"url": "/api/bus/routes",
"refused": false
},
{
"method": "GET",
"url": "/api/bus/limits",
"refused": false
},
{
"method": "GET",
"url": "/api/bus/limits",
"refused": false
},
{
"method": "GET",
"url": "/api/bus/inbox",
"refused": false
},
{
"method": "GET",
"url": "/api/bus/agents",
"refused": false
},
{
"method": "GET",
"url": "/api/bus/messages",
"refused": false
},
{
"method": "GET",
"url": "/api/chat/projects",
"refused": false
},
{
"method": "GET",
"url": "/api/chat/threads",
"refused": false
},
{
"method": "GET",
"url": "/api/business",
"refused": false
}
],
"no write through the listener": {
"pass": true,
"detail": []
},
"no request to the W9 thread messages or open": {
"pass": true,
"detail": []
}
}
W10 browser: pass
# exit 0
@@ -0,0 +1,2 @@
chief r34520a581318 claude-code since 2026-10-20T07:58:02.204Z
# exit 0
@@ -0,0 +1,7 @@
PASS markdown-preview.js is served and equals the copy's
PASS /api/config on loopback: send=false edit=false
PASS project.files through the Console: head=ae0b17c9586e51da7bc1b7e548c6acf913a55665 files=2 spec=true error=none
PASS project.file spec/north-star.md through the Console: commit=ae0b17c9586e51da7bc1b7e548c6acf913a55665 length=99 error=none
PASS loopback save refused: HTTP 403 error=read-only sent=false
CONSOLE_PASS
# exit 0
@@ -0,0 +1,3 @@
r34520a581318 mosaic-stack/chief claude-code claude-opus-5-5 by jason since 2026-10-20T07:58:02.199Z running
r7e99838f9f93 mosaic-stack/assistant claude-code claude-opus-5-5 by jason since 2026-10-20T08:01:14.335Z running
# exit 0
@@ -0,0 +1,37 @@
[
{
"business": "mosaic-stack",
"instance": "chief",
"run": "r34520a581318",
"launchedBy": "jason",
"harness": "claude-code",
"model": "claude-opus-5-5",
"provider": "anthropic",
"modelKey": "opus",
"pid": 555940,
"startTime": "58427238",
"runnerPid": 555941,
"runnerStartTime": "58427238",
"runDir": "/home/jwoltje/.mosaic-dev/launches/mosaic-stack/r34520a581318",
"startedAt": "2026-10-20T07:58:02.199Z",
"live": true
},
{
"business": "mosaic-stack",
"instance": "assistant",
"thread": "d0ba7a66-aa3f-4918-9518-94132bce6b28",
"run": "r7e99838f9f93",
"launchedBy": "jason",
"harness": "claude-code",
"model": "claude-opus-5-5",
"provider": "anthropic",
"modelKey": "opus",
"pid": 586561,
"startTime": "58446451",
"runnerPid": 586562,
"runnerStartTime": "58446452",
"runDir": "/home/jwoltje/.mosaic-dev/launches/mosaic-stack/r7e99838f9f93",
"startedAt": "2026-10-20T08:01:14.335Z",
"live": true
}
]
@@ -0,0 +1,19 @@
[
{
"business": "mosaic-stack",
"instance": "chief",
"run": "r34520a581318",
"launchedBy": "jason",
"harness": "claude-code",
"model": "claude-opus-5-5",
"provider": "anthropic",
"modelKey": "opus",
"pid": 555940,
"startTime": "58427238",
"runnerPid": 555941,
"runnerStartTime": "58427238",
"runDir": "/home/jwoltje/.mosaic-dev/launches/mosaic-stack/r34520a581318",
"startedAt": "2026-10-20T07:58:02.199Z",
"live": true
}
]
@@ -0,0 +1,2 @@
r34520a581318 mosaic-stack/chief claude-code claude-opus-5-5 by jason since 2026-10-20T07:58:02.199Z running
# exit 0
@@ -0,0 +1,2 @@
[]
# exit 0
@@ -0,0 +1,2 @@
created project window-20261020-0801 id 8fe35a6d-ebe8-4607-ba67-7625f2ef09f6
# exit 0
@@ -0,0 +1,231 @@
# W9 start 2026-10-20T08:01:03Z
# plan b05eb85e, tool blob f8051d81cc18, console-check.sh d068fe02, wrapper on NEW: checked
pointer: /home/jwoltje/.mosaic-dev/state/stack-active.json
{"copy":"490e2771d3d057ab76991e2c7859cd251857e64c","business":"mosaic-stack","pm":true,"claude":"/home/jwoltje/.local/bin/claude","console":["--port","7332","--public-host","mosaic.woltje.com","--auth-header","X-authentik-username","--allow-user","jason.woltje","--send","--tester-port","7333"],"schema":"3i","activation":"20261020T075800Z-490e2771d3d0","at":"2026-10-20T07:58:02Z"}
log (last five lines):
{"at":"2026-10-20T01:37:11Z","op":"activate","from":"5fa9cc8f2c66b52408b652b59c3070f7210b44d0","to":"89af38a83afa22a8a4f0d368a0a4030ad15a0efa","backup":"20261020T013708Z-89af38a83afa","activation":"20261020T013708Z-89af38a83afa","result":"ok","pm":"pending"}
{"at":"2026-10-20T01:41:03Z","op":"pm-on","from":"89af38a83afa22a8a4f0d368a0a4030ad15a0efa","to":"89af38a83afa22a8a4f0d368a0a4030ad15a0efa","backup":null,"activation":"20261020T014103Z-89af38a83afa","result":"started"}
{"at":"2026-10-20T01:41:05Z","op":"pm-on","from":"89af38a83afa22a8a4f0d368a0a4030ad15a0efa","to":"89af38a83afa22a8a4f0d368a0a4030ad15a0efa","backup":null,"activation":"20261020T014103Z-89af38a83afa","result":"ok","pm":"on"}
{"at":"2026-10-20T07:58:00Z","op":"activate","from":"89af38a83afa22a8a4f0d368a0a4030ad15a0efa","to":"490e2771d3d057ab76991e2c7859cd251857e64c","backup":null,"activation":"20261020T075800Z-490e2771d3d0","result":"started"}
{"at":"2026-10-20T07:58:02Z","op":"activate","from":"89af38a83afa22a8a4f0d368a0a4030ad15a0efa","to":"490e2771d3d057ab76991e2c7859cd251857e64c","backup":"20261020T075800Z-490e2771d3d0","activation":"20261020T075800Z-490e2771d3d0","result":"ok"}
check:
bus: /home/jwoltje/.local/share/mosaic-stack/copies/490e2771d3d057ab76991e2c7859cd251857e64c
Console: /home/jwoltje/.local/share/mosaic-stack/copies/490e2771d3d057ab76991e2c7859cd251857e64c
CLI: /home/jwoltje/.local/share/mosaic-stack/copies/490e2771d3d057ab76991e2c7859cd251857e64c
result: pass
bus status:
host: mosaic-stack, pid 555397, running, since 2026-10-20T07:58:01.329Z, notifier off
socket: present
writer.lock: pid 555495
boot: organization file loaded at 2026-10-20T07:58:01.321Z
unit flags: match the pointer
drift: 4 commit(s) on refactor since the pointer's copy
# exit 0
PASS markdown-preview.js is served and equals the copy's
PASS /api/config on loopback: send=false edit=false
PASS project.files through the Console: head=ae0b17c9586e51da7bc1b7e548c6acf913a55665 files=2 spec=true error=none
PASS project.file spec/north-star.md through the Console: commit=ae0b17c9586e51da7bc1b7e548c6acf913a55665 length=99 error=none
PASS loopback save refused: HTTP 403 error=read-only sent=false
CONSOLE_PASS
# exit 0
# proxy proof off lines since W5: 1
r34520a581318 mosaic-stack/chief claude-code claude-opus-5-5 by jason since 2026-10-20T07:58:02.199Z running
# exit 0
[
{
"business": "mosaic-stack",
"instance": "chief",
"run": "r34520a581318",
"launchedBy": "jason",
"harness": "claude-code",
"model": "claude-opus-5-5",
"provider": "anthropic",
"modelKey": "opus",
"pid": 555940,
"startTime": "58427238",
"runnerPid": 555941,
"runnerStartTime": "58427238",
"runDir": "/home/jwoltje/.mosaic-dev/launches/mosaic-stack/r34520a581318",
"startedAt": "2026-10-20T07:58:02.199Z",
"live": true
}
]
# exit 0
# one running chief from this start: run r34520a581318
chief r34520a581318 claude-code since 2026-10-20T07:58:02.204Z
# exit 0
sent 580f50ed to chief
chief (RESULT, re 580f50ed) 90fb6939:
Re-pin 490e2771d3d057ab76991e2c7859cd251857e64c noted: I made no launches, staffing requests or organization changes, and this test message authorizes nothing.
# exit 0
created project window-20261020-0801 id 8fe35a6d-ebe8-4607-ba67-7625f2ef09f6
# exit 0
created window-20261020-0801/w9 id d0ba7a66-aa3f-4918-9518-94132bce6b28 instance assistant
# exit 0
# project window-20261020-0801, thread d0ba7a66-aa3f-4918-9518-94132bce6b28
sent dc002b67 to window-20261020-0801/w9
assistant (RESULT, re dc002b67) 48cdd10b:
The assistant got the [mos test] message from the tester and is replying here; it authorizes nothing, and nothing was sent or changed.
# exit 0
# reply received 2026-10-20T08:01:18Z
[
{
"seq": 553,
"id": "dc002b67-3f24-419b-a544-2848850aba79",
"at": "2026-10-20T08:01:12.155Z",
"business": "mosaic-stack",
"from_role": "tester",
"from_run": "tester-cli",
"to_role": "assistant",
"class": "REQUEST",
"in_reply_to": null,
"decision": null,
"corrects": null,
"body": "[mos test] reply with one line, then stop",
"to_name": null
},
{
"seq": 554,
"id": "48cdd10b-40b3-4847-a149-716f296ce17d",
"at": "2026-10-20T08:01:17.854Z",
"business": "mosaic-stack",
"from_role": "assistant",
"from_run": "r7e99838f9f93",
"to_role": "tester",
"class": "RESULT",
"in_reply_to": "dc002b67-3f24-419b-a544-2848850aba79",
"decision": null,
"corrects": null,
"body": "The assistant got the [mos test] message from the tester and is replying here; it authorizes nothing, and nothing was sent or changed.",
"to_name": null
}
]
# exit 0
# thread read: 2 messages: tester->assistant REQUEST, assistant->tester RESULT
window-20261020-0801/w9 assistant d0ba7a66
# exit 0
[]
# exit 0
{
"total": {
"input": 22,
"output": 1770,
"cacheWrite": 70303,
"cacheRead": 89559,
"unknownTurns": 1
},
"byRun": [
{
"run": "r8fba7e598156",
"input": 0,
"output": 0,
"cacheWrite": 0,
"cacheRead": 0,
"unknownTurns": 1
},
{
"run": "r4a96e2142e4d",
"input": 2,
"output": 52,
"cacheWrite": 11750,
"cacheRead": 0,
"unknownTurns": 0
},
{
"run": "rc78c9700c8e9",
"input": 14,
"output": 1519,
"cacheWrite": 18187,
"cacheRead": 89559,
"unknownTurns": 0
},
{
"run": "r293f33bcbc63",
"input": 2,
"output": 44,
"cacheWrite": 18595,
"cacheRead": 0,
"unknownTurns": 0
},
{
"run": "r34520a581318",
"input": 2,
"output": 111,
"cacheWrite": 11913,
"cacheRead": 0,
"unknownTurns": 0
},
{
"run": "r7e99838f9f93",
"input": 2,
"output": 44,
"cacheWrite": 9858,
"cacheRead": 0,
"unknownTurns": 0
}
],
"byInstance": [
{
"instance": "chief",
"input": 18,
"output": 1682,
"cacheWrite": 41850,
"cacheRead": 89559,
"unknownTurns": 1
},
{
"instance": "coder",
"input": 2,
"output": 44,
"cacheWrite": 18595,
"cacheRead": 0,
"unknownTurns": 0
},
{
"instance": "assistant",
"input": 2,
"output": 44,
"cacheWrite": 9858,
"cacheRead": 0,
"unknownTurns": 0
}
],
"byProvider": [
{
"provider": "anthropic",
"input": 22,
"output": 1770,
"cacheWrite": 70303,
"cacheRead": 89559,
"unknownTurns": 1
}
],
"byModel": [
{
"provider": "anthropic",
"model": "claude-opus-5-5",
"input": 22,
"output": 1770,
"cacheWrite": 70303,
"cacheRead": 89559,
"unknownTurns": 1
}
],
"byAccount": [
{
"account": null,
"input": 22,
"output": 1770,
"cacheWrite": 70303,
"cacheRead": 89559,
"unknownTurns": 1
}
]
}
# exit 0
# human-thread probe: skipped, S1 has 0 threads and 0 message_threads rows, so no human thread exists to read
r34520a581318 mosaic-stack/chief claude-code claude-opus-5-5 by jason since 2026-10-20T07:58:02.199Z running
r7e99838f9f93 mosaic-stack/assistant claude-code claude-opus-5-5 by jason since 2026-10-20T08:01:14.335Z running
# exit 0
# STOP: the assistant run r7e99838f9f93 is still running 10 minutes after the reply; a stop is Mos's call. S2 waits.
w9 exit 4
@@ -0,0 +1,21 @@
pointer: /home/jwoltje/.mosaic-dev/state/stack-active.json
{"copy":"490e2771d3d057ab76991e2c7859cd251857e64c","business":"mosaic-stack","pm":true,"claude":"/home/jwoltje/.local/bin/claude","console":["--port","7332","--public-host","mosaic.woltje.com","--auth-header","X-authentik-username","--allow-user","jason.woltje","--send","--tester-port","7333"],"schema":"3i","activation":"20261020T075800Z-490e2771d3d0","at":"2026-10-20T07:58:02Z"}
log (last five lines):
{"at":"2026-10-20T01:37:11Z","op":"activate","from":"5fa9cc8f2c66b52408b652b59c3070f7210b44d0","to":"89af38a83afa22a8a4f0d368a0a4030ad15a0efa","backup":"20261020T013708Z-89af38a83afa","activation":"20261020T013708Z-89af38a83afa","result":"ok","pm":"pending"}
{"at":"2026-10-20T01:41:03Z","op":"pm-on","from":"89af38a83afa22a8a4f0d368a0a4030ad15a0efa","to":"89af38a83afa22a8a4f0d368a0a4030ad15a0efa","backup":null,"activation":"20261020T014103Z-89af38a83afa","result":"started"}
{"at":"2026-10-20T01:41:05Z","op":"pm-on","from":"89af38a83afa22a8a4f0d368a0a4030ad15a0efa","to":"89af38a83afa22a8a4f0d368a0a4030ad15a0efa","backup":null,"activation":"20261020T014103Z-89af38a83afa","result":"ok","pm":"on"}
{"at":"2026-10-20T07:58:00Z","op":"activate","from":"89af38a83afa22a8a4f0d368a0a4030ad15a0efa","to":"490e2771d3d057ab76991e2c7859cd251857e64c","backup":null,"activation":"20261020T075800Z-490e2771d3d0","result":"started"}
{"at":"2026-10-20T07:58:02Z","op":"activate","from":"89af38a83afa22a8a4f0d368a0a4030ad15a0efa","to":"490e2771d3d057ab76991e2c7859cd251857e64c","backup":"20261020T075800Z-490e2771d3d0","activation":"20261020T075800Z-490e2771d3d0","result":"ok"}
check:
bus: /home/jwoltje/.local/share/mosaic-stack/copies/490e2771d3d057ab76991e2c7859cd251857e64c
Console: /home/jwoltje/.local/share/mosaic-stack/copies/490e2771d3d057ab76991e2c7859cd251857e64c
CLI: /home/jwoltje/.local/share/mosaic-stack/copies/490e2771d3d057ab76991e2c7859cd251857e64c
result: pass
bus status:
host: mosaic-stack, pid 555397, running, since 2026-10-20T07:58:01.329Z, notifier off
socket: present
writer.lock: pid 555495
boot: organization file loaded at 2026-10-20T07:58:01.321Z
unit flags: match the pointer
drift: 4 commit(s) on refactor since the pointer's copy
# exit 0
@@ -0,0 +1,4 @@
sent 580f50ed to chief
chief (RESULT, re 580f50ed) 90fb6939:
Re-pin 490e2771d3d057ab76991e2c7859cd251857e64c noted: I made no launches, staffing requests or organization changes, and this test message authorizes nothing.
# exit 0
@@ -0,0 +1,4 @@
sent dc002b67 to window-20261020-0801/w9
assistant (RESULT, re dc002b67) 48cdd10b:
The assistant got the [mos test] message from the tester and is replying here; it authorizes nothing, and nothing was sent or changed.
# exit 0
@@ -0,0 +1,32 @@
[
{
"seq": 553,
"id": "dc002b67-3f24-419b-a544-2848850aba79",
"at": "2026-10-20T08:01:12.155Z",
"business": "mosaic-stack",
"from_role": "tester",
"from_run": "tester-cli",
"to_role": "assistant",
"class": "REQUEST",
"in_reply_to": null,
"decision": null,
"corrects": null,
"body": "[mos test] reply with one line, then stop",
"to_name": null
},
{
"seq": 554,
"id": "48cdd10b-40b3-4847-a149-716f296ce17d",
"at": "2026-10-20T08:01:17.854Z",
"business": "mosaic-stack",
"from_role": "assistant",
"from_run": "r7e99838f9f93",
"to_role": "tester",
"class": "RESULT",
"in_reply_to": "dc002b67-3f24-419b-a544-2848850aba79",
"decision": null,
"corrects": null,
"body": "The assistant got the [mos test] message from the tester and is replying here; it authorizes nothing, and nothing was sent or changed.",
"to_name": null
}
]
@@ -0,0 +1,2 @@
created window-20261020-0801/w9 id d0ba7a66-aa3f-4918-9518-94132bce6b28 instance assistant
# exit 0
@@ -0,0 +1,2 @@
window-20261020-0801/w9 assistant d0ba7a66
# exit 0
@@ -0,0 +1,117 @@
{
"total": {
"input": 22,
"output": 1770,
"cacheWrite": 70303,
"cacheRead": 89559,
"unknownTurns": 1
},
"byRun": [
{
"run": "r8fba7e598156",
"input": 0,
"output": 0,
"cacheWrite": 0,
"cacheRead": 0,
"unknownTurns": 1
},
{
"run": "r4a96e2142e4d",
"input": 2,
"output": 52,
"cacheWrite": 11750,
"cacheRead": 0,
"unknownTurns": 0
},
{
"run": "rc78c9700c8e9",
"input": 14,
"output": 1519,
"cacheWrite": 18187,
"cacheRead": 89559,
"unknownTurns": 0
},
{
"run": "r293f33bcbc63",
"input": 2,
"output": 44,
"cacheWrite": 18595,
"cacheRead": 0,
"unknownTurns": 0
},
{
"run": "r34520a581318",
"input": 2,
"output": 111,
"cacheWrite": 11913,
"cacheRead": 0,
"unknownTurns": 0
},
{
"run": "r7e99838f9f93",
"input": 2,
"output": 44,
"cacheWrite": 9858,
"cacheRead": 0,
"unknownTurns": 0
}
],
"byInstance": [
{
"instance": "chief",
"input": 18,
"output": 1682,
"cacheWrite": 41850,
"cacheRead": 89559,
"unknownTurns": 1
},
{
"instance": "coder",
"input": 2,
"output": 44,
"cacheWrite": 18595,
"cacheRead": 0,
"unknownTurns": 0
},
{
"instance": "assistant",
"input": 2,
"output": 44,
"cacheWrite": 9858,
"cacheRead": 0,
"unknownTurns": 0
}
],
"byProvider": [
{
"provider": "anthropic",
"input": 22,
"output": 1770,
"cacheWrite": 70303,
"cacheRead": 89559,
"unknownTurns": 1
}
],
"byModel": [
{
"provider": "anthropic",
"model": "claude-opus-5-5",
"input": 22,
"output": 1770,
"cacheWrite": 70303,
"cacheRead": 89559,
"unknownTurns": 1
}
],
"byAccount": [
{
"account": null,
"input": 22,
"output": 1770,
"cacheWrite": 70303,
"cacheRead": 89559,
"unknownTurns": 1
}
]
}
# exit 0
@@ -0,0 +1,2 @@
r34520a581318 mosaic-stack/chief claude-code claude-opus-5-5 by jason since 2026-10-20T07:58:02.199Z running
# exit 0
@@ -0,0 +1,19 @@
[
{
"business": "mosaic-stack",
"instance": "chief",
"run": "r34520a581318",
"launchedBy": "jason",
"harness": "claude-code",
"model": "claude-opus-5-5",
"provider": "anthropic",
"modelKey": "opus",
"pid": 555940,
"startTime": "58427238",
"runnerPid": 555941,
"runnerStartTime": "58427238",
"runDir": "/home/jwoltje/.mosaic-dev/launches/mosaic-stack/r34520a581318",
"startedAt": "2026-10-20T07:58:02.199Z",
"live": true
}
]
@@ -0,0 +1,150 @@
# W9b start 2026-10-20T08:14:39Z: waiting for the launcher to end r7e99838f9f93 (thread d0ba7a66-aa3f-4918-9518-94132bce6b28), no stop by hand
# r7e99838f9f93 not live in launches list --json by 2026-10-20T08:16:22Z (5 s poll)
r34520a581318 mosaic-stack/chief claude-code claude-opus-5-5 by jason since 2026-10-20T07:58:02.199Z running
# exit 0
## W9b launches log lines for r7e99838f9f93 (at, event, instance, thread, run, reason, exitCode, signal only)
at=2026-10-20T08:01:14.335Z event=launch instance=assistant thread=d0ba7a66-aa3f-4918-9518-94132bce6b28 run=r7e99838f9f93
at=2026-10-20T08:16:18.082Z event=thread-idle instance=assistant thread=d0ba7a66-aa3f-4918-9518-94132bce6b28 run=r7e99838f9f93
at=2026-10-20T08:16:18.103Z event=end instance=assistant thread=d0ba7a66-aa3f-4918-9518-94132bce6b28 run=r7e99838f9f93 reason=stopped exitCode=0 signal=null
## W9b bus and webui journal since 2026-10-20 08:01:00 UTC, lines naming r7e99838f9f93 or thread-idle
2026-10-20T03:16:18.103495-05:00 dragon-lin mosaic[555397]: mosaic-bus: run r7e99838f9f93 (assistant) ended: stopped
(grep exit 0)
# SNAP sha256 639fd28a checked before S2 2026-10-20T08:16:22Z
{
"schema": "3i",
"tables": {
"decision_events": {
"rows": 20,
"maxSeq": 20,
"sha256": "14be3e2fd9ab8d4dd6b7c7accf8dd04566342e6b5a86202d805d88b5bff1aad1"
},
"decisions": {
"rows": 20,
"maxSeq": 20,
"sha256": "936dbee20a152fa023f899de834cbbde2dbfaa645639911106afee78a158f2ef"
},
"deliveries": {
"rows": 1687,
"maxSeq": 1687,
"sha256": "3b5f3b83e296f3301d7174192088972a611e2298289d0d4baf892c39efd0d79e"
},
"events": {
"rows": 1431,
"maxSeq": 1431,
"sha256": "fb228c40810daba8dd4ba29af3a41cf5f10d0fe17854e67b09b371b00be11b51"
},
"instance_names": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"message_threads": {
"rows": 2,
"maxSeq": null
},
"messages": {
"rows": 554,
"maxSeq": 554
},
"meta": {
"rows": 3,
"maxSeq": null,
"sha256": "89fec56e7293d19e0b2f6cd8c0ef745ebf99e4115abeaea7fbdbc98a261332d9"
},
"projects": {
"rows": 1,
"maxSeq": 1,
"sha256": "a3945f7462895e0e302029c542d384a6d33e559da900e6461d78bbcd7cc28b16"
},
"role_claims": {
"rows": 93,
"maxSeq": 93,
"sha256": "b160aa10ceeed2625499d595727754a76a2c5dbbbf81a0a1569f343ba5a67234"
},
"task_comment_cursors": {
"rows": 171,
"maxSeq": 171,
"sha256": "35266badf56193856863e62f83091045020ca0f0b69d8a4e977fc28a56bfad34"
},
"task_snapshots": {
"rows": 359,
"maxSeq": 359,
"sha256": "eae6f62a6b3fcb33ad923ff8413b2d3b63a05eec92d5dcafc9d4c5c084ced965"
},
"thread_claims": {
"rows": 2,
"maxSeq": 2,
"sha256": "6046c7088779aa2f6e9b3fd2c7520404b1678a4fcae71c2b9157a008bba22a5f"
},
"thread_opens": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"threads": {
"rows": 1,
"maxSeq": 1,
"sha256": "3043721efd386f23dfdaad4acf41e0d553ff6182f0068c2133a615141bcf772a"
},
"usage_records": {
"rows": 16,
"maxSeq": 16,
"sha256": "928a8df17ee6dd2ff58cc0c01c15359f64ea46e325b7e3c186b2ada74e74aaac",
"byRun": [
{
"business": "mosaic-stack",
"run": "r8fba7e598156",
"input": "0",
"output": "0",
"unknownTurns": 1
},
{
"business": "mosaic-stack",
"run": "r4a96e2142e4d",
"input": "2",
"output": "52",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "rc78c9700c8e9",
"input": "14",
"output": "1519",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r293f33bcbc63",
"input": "2",
"output": "44",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r34520a581318",
"input": "2",
"output": "111",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r7e99838f9f93",
"input": "2",
"output": "44",
"unknownTurns": 0
}
]
}
},
"untimed": [
"message_threads",
"meta",
"task_comment_cursors"
]
}
# exit 0
# S2.json sha256 58e864daaebb8f63113e55620e816ae9bc3da41c38af518caf1503fef3edc25d
# W9b end 2026-10-20T08:16:22Z
w9b exit 0
@@ -0,0 +1,3 @@
{"table":"events","s1MaxSeq":"1424","prefixRows":1424,"prefixSha256":"bfc78646bc5afea94182c59fc67b48afed8965c46d27ba1904062fde4b35d11f","s1Sha256":"bfc78646bc5afea94182c59fc67b48afed8965c46d27ba1904062fde4b35d11f","same":true,"above":[{"seq":"1425","at":"2026-10-20T07:58:00.238Z","kind":"session.ended","actor_role":"chief","actor_run":"rc78c9700c8e9"}]}
{"table":"role_claims","s1MaxSeq":"91","prefixRows":91,"prefixSha256":"adc44befe8daa12f53ee7984fb36067f2d0ba15665ddb25a21716a03460c3bd0","s1Sha256":"adc44befe8daa12f53ee7984fb36067f2d0ba15665ddb25a21716a03460c3bd0","same":true,"above":[{"seq":"92","at":"2026-10-20T07:58:00.217Z","role":"chief","op":"release","holder_run":"rc78c9700c8e9","by":"rc78c9700c8e9","reason":null}]}
# exit 0
@@ -0,0 +1,362 @@
# W9c start 2026-10-20T08:18:46Z
# SNAP sha256 639fd28a checked before S2b; backup bus.sqlite 1729da1b, no -wal or -shm
{
"schema": "3i",
"backupSchema": "3i",
"tables": {
"decision_events": {
"rows": 20,
"maxSeq": 20,
"sha256": "14be3e2fd9ab8d4dd6b7c7accf8dd04566342e6b5a86202d805d88b5bff1aad1",
"vsBackup": {
"backupRows": 20,
"backupMaxSeq": 20,
"newRows": 0,
"priorSame": true
}
},
"decisions": {
"rows": 20,
"maxSeq": 20,
"sha256": "936dbee20a152fa023f899de834cbbde2dbfaa645639911106afee78a158f2ef",
"vsBackup": {
"backupRows": 20,
"backupMaxSeq": 20,
"newRows": 0,
"priorSame": true
}
},
"deliveries": {
"rows": 1687,
"maxSeq": 1687,
"sha256": "3b5f3b83e296f3301d7174192088972a611e2298289d0d4baf892c39efd0d79e",
"vsBackup": {
"backupRows": 1673,
"backupMaxSeq": 1673,
"newRows": 14,
"priorSame": true
}
},
"events": {
"rows": 1431,
"maxSeq": 1431,
"sha256": "fb228c40810daba8dd4ba29af3a41cf5f10d0fe17854e67b09b371b00be11b51",
"vsBackup": {
"backupRows": 1425,
"backupMaxSeq": 1425,
"newRows": 6,
"priorSame": true
}
},
"instance_names": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"vsBackup": {
"backupRows": 0,
"backupMaxSeq": null,
"newRows": 0,
"priorSame": true
}
},
"message_threads": {
"rows": 2,
"maxSeq": null,
"vsBackup": {
"backupRows": 0,
"same": false
}
},
"messages": {
"rows": 554,
"maxSeq": 554,
"vsBackup": {
"backupRows": 550,
"backupMaxSeq": 550,
"newRows": 4,
"priorSame": true
}
},
"meta": {
"rows": 3,
"maxSeq": null,
"sha256": "89fec56e7293d19e0b2f6cd8c0ef745ebf99e4115abeaea7fbdbc98a261332d9",
"vsBackup": {
"backupRows": 3,
"keysChanged": []
}
},
"projects": {
"rows": 1,
"maxSeq": 1,
"sha256": "a3945f7462895e0e302029c542d384a6d33e559da900e6461d78bbcd7cc28b16",
"vsBackup": {
"backupRows": 0,
"backupMaxSeq": null,
"newRows": 1,
"priorSame": true
}
},
"role_claims": {
"rows": 93,
"maxSeq": 93,
"sha256": "b160aa10ceeed2625499d595727754a76a2c5dbbbf81a0a1569f343ba5a67234",
"vsBackup": {
"backupRows": 92,
"backupMaxSeq": 92,
"newRows": 1,
"priorSame": true
}
},
"task_comment_cursors": {
"rows": 171,
"maxSeq": 171,
"sha256": "35266badf56193856863e62f83091045020ca0f0b69d8a4e977fc28a56bfad34",
"vsBackup": {
"backupRows": 171,
"backupMaxSeq": 171,
"newRows": 0,
"priorSame": true
}
},
"task_snapshots": {
"rows": 359,
"maxSeq": 359,
"sha256": "eae6f62a6b3fcb33ad923ff8413b2d3b63a05eec92d5dcafc9d4c5c084ced965",
"vsBackup": {
"backupRows": 359,
"backupMaxSeq": 359,
"newRows": 0,
"priorSame": true
}
},
"thread_claims": {
"rows": 2,
"maxSeq": 2,
"sha256": "6046c7088779aa2f6e9b3fd2c7520404b1678a4fcae71c2b9157a008bba22a5f",
"vsBackup": {
"backupRows": 0,
"backupMaxSeq": null,
"newRows": 2,
"priorSame": true
}
},
"thread_opens": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"vsBackup": {
"backupRows": 0,
"backupMaxSeq": null,
"newRows": 0,
"priorSame": true
}
},
"threads": {
"rows": 1,
"maxSeq": 1,
"sha256": "3043721efd386f23dfdaad4acf41e0d553ff6182f0068c2133a615141bcf772a",
"vsBackup": {
"backupRows": 0,
"backupMaxSeq": null,
"newRows": 1,
"priorSame": true
}
},
"usage_records": {
"rows": 16,
"maxSeq": 16,
"sha256": "928a8df17ee6dd2ff58cc0c01c15359f64ea46e325b7e3c186b2ada74e74aaac",
"byRun": [
{
"business": "mosaic-stack",
"run": "r8fba7e598156",
"input": "0",
"output": "0",
"unknownTurns": 1
},
{
"business": "mosaic-stack",
"run": "r4a96e2142e4d",
"input": "2",
"output": "52",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "rc78c9700c8e9",
"input": "14",
"output": "1519",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r293f33bcbc63",
"input": "2",
"output": "44",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r34520a581318",
"input": "2",
"output": "111",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r7e99838f9f93",
"input": "2",
"output": "44",
"unknownTurns": 0
}
],
"vsBackup": {
"backupRows": 12,
"backupMaxSeq": 12,
"newRows": 4,
"priorSame": true
}
}
},
"untimed": [
"message_threads",
"meta",
"task_comment_cursors"
],
"onlyInBackup": [],
"stopTables": [
"deliveries",
"message_threads",
"messages",
"projects",
"role_claims",
"thread_claims",
"threads",
"usage_records"
]
}
# exit 0
# S2b.json sha256 a9e8d6dfa636d16cd02711c082e4df3c148ef740bf25f308d6e8a8e263c0dfe6
# SNAP sha256 639fd28a checked before B
{
"schema": "3i",
"tables": {
"decision_events": {
"rows": 20,
"maxSeq": 20,
"sha256": "14be3e2fd9ab8d4dd6b7c7accf8dd04566342e6b5a86202d805d88b5bff1aad1"
},
"decisions": {
"rows": 20,
"maxSeq": 20,
"sha256": "936dbee20a152fa023f899de834cbbde2dbfaa645639911106afee78a158f2ef"
},
"deliveries": {
"rows": 1673,
"maxSeq": 1673,
"sha256": "2c215e2204e5fff174e934dd9771b4020c6a5c9914306600207f1cb7531f1c1b"
},
"events": {
"rows": 1425,
"maxSeq": 1425,
"sha256": "b7468b3e540af6b77d3c49cf9058ed51e825c2ed62e0d3bb4bfcd5da6619b9c2"
},
"instance_names": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"message_threads": {
"rows": 0,
"maxSeq": null
},
"messages": {
"rows": 550,
"maxSeq": 550
},
"meta": {
"rows": 3,
"maxSeq": null,
"sha256": "89fec56e7293d19e0b2f6cd8c0ef745ebf99e4115abeaea7fbdbc98a261332d9"
},
"projects": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"role_claims": {
"rows": 92,
"maxSeq": 92,
"sha256": "cca248b09706cb6dec193227af66f8c7995666518f13e65e1826ff09b0281619"
},
"task_comment_cursors": {
"rows": 171,
"maxSeq": 171,
"sha256": "35266badf56193856863e62f83091045020ca0f0b69d8a4e977fc28a56bfad34"
},
"task_snapshots": {
"rows": 359,
"maxSeq": 359,
"sha256": "eae6f62a6b3fcb33ad923ff8413b2d3b63a05eec92d5dcafc9d4c5c084ced965"
},
"thread_claims": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"thread_opens": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"threads": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"usage_records": {
"rows": 12,
"maxSeq": 12,
"sha256": "47f91b3130472b982b09689ebc72f78bd5aa56e59b275341d83f8e2fdea9b872",
"byRun": [
{
"business": "mosaic-stack",
"run": "r8fba7e598156",
"input": "0",
"output": "0",
"unknownTurns": 1
},
{
"business": "mosaic-stack",
"run": "r4a96e2142e4d",
"input": "2",
"output": "52",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "rc78c9700c8e9",
"input": "14",
"output": "1519",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r293f33bcbc63",
"input": "2",
"output": "44",
"unknownTurns": 0
}
]
}
},
"untimed": [
"message_threads",
"meta",
"task_comment_cursors"
]
}
# exit 0
# B.json sha256 42553b7471e2d5e35845ad9f010d6e5711baa5509aadd43d39cd12eeaa2a15c9
# backup bus.sqlite still 1729da1b, no -wal or -shm; W9c end 2026-10-20T08:18:47Z
@@ -0,0 +1,118 @@
{
"schema": "3i",
"tables": {
"decision_events": {
"rows": 20,
"maxSeq": 20,
"sha256": "14be3e2fd9ab8d4dd6b7c7accf8dd04566342e6b5a86202d805d88b5bff1aad1"
},
"decisions": {
"rows": 20,
"maxSeq": 20,
"sha256": "936dbee20a152fa023f899de834cbbde2dbfaa645639911106afee78a158f2ef"
},
"deliveries": {
"rows": 1673,
"maxSeq": 1673,
"sha256": "2c215e2204e5fff174e934dd9771b4020c6a5c9914306600207f1cb7531f1c1b"
},
"events": {
"rows": 1425,
"maxSeq": 1425,
"sha256": "b7468b3e540af6b77d3c49cf9058ed51e825c2ed62e0d3bb4bfcd5da6619b9c2"
},
"instance_names": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"message_threads": {
"rows": 0,
"maxSeq": null
},
"messages": {
"rows": 550,
"maxSeq": 550
},
"meta": {
"rows": 3,
"maxSeq": null,
"sha256": "89fec56e7293d19e0b2f6cd8c0ef745ebf99e4115abeaea7fbdbc98a261332d9"
},
"projects": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"role_claims": {
"rows": 92,
"maxSeq": 92,
"sha256": "cca248b09706cb6dec193227af66f8c7995666518f13e65e1826ff09b0281619"
},
"task_comment_cursors": {
"rows": 171,
"maxSeq": 171,
"sha256": "35266badf56193856863e62f83091045020ca0f0b69d8a4e977fc28a56bfad34"
},
"task_snapshots": {
"rows": 359,
"maxSeq": 359,
"sha256": "eae6f62a6b3fcb33ad923ff8413b2d3b63a05eec92d5dcafc9d4c5c084ced965"
},
"thread_claims": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"thread_opens": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"threads": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"usage_records": {
"rows": 12,
"maxSeq": 12,
"sha256": "47f91b3130472b982b09689ebc72f78bd5aa56e59b275341d83f8e2fdea9b872",
"byRun": [
{
"business": "mosaic-stack",
"run": "r8fba7e598156",
"input": "0",
"output": "0",
"unknownTurns": 1
},
{
"business": "mosaic-stack",
"run": "r4a96e2142e4d",
"input": "2",
"output": "52",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "rc78c9700c8e9",
"input": "14",
"output": "1519",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r293f33bcbc63",
"input": "2",
"output": "44",
"unknownTurns": 0
}
]
}
},
"untimed": [
"message_threads",
"meta",
"task_comment_cursors"
]
}
@@ -0,0 +1,28 @@
## P1 values
# start 2026-10-20T07:19:33.013Z
# command: (cd $REPO && <clean env> node /mnt/storage/src/mosaic-stack/agents/darkwing/work/window-rows-177-185/p1.mjs)
bus.tester true
plugins kinds ["channel","harness","usage-source"]
plugins usage-source ["transcript"]
plugins channel ["cli","webui"]
plugins harness ["claude-code"]
launch.by "chief"
launch.instances includes assistant true
role assistant vars.harness "claude-code"
role chief vars.harness "claude-code"
role coder vars.harness "claude-code"
role cto vars.harness "claude-code"
role pm vars.harness "claude-code"
role reviewer vars.harness "claude-code"
roles naming pi 0
# exit 0, end 2026-10-20T07:19:33.039Z
## P1 business-check OLD
# start 2026-10-20T07:19:33.041Z
# command: (cd $REPO && <clean env> node /mnt/storage/src/mosaic-stack/agents/sage/work/window-plan/repin-de352dfaf/business-check.mjs 89af38a83afa22a8a4f0d368a0a4030ad15a0efa)
copy 89af38a83: roles 6, projects 1, arbiters 2
HOLDS chief instance.name yes
HOLDS pm instance.name no
LOAD_OK
# exit 0, end 2026-10-20T07:19:33.079Z
@@ -0,0 +1,7 @@
# P4 2026-10-20T08:23:54Z
DropInPaths=
ExecStart={ path=/home/jwoltje/.local/bin/chisel ; argv[]=/home/jwoltje/.local/bin/chisel client --fingerprint ce+ISFvMAhuoCRDuc7riIf+hYz87TLHk/C9C/H8QsUg= --keepalive 25s --max-retry-interval 10s http://10.1.1.227:8080 R:0.0.0.0:7332:127.0.0.1:7332 ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }
# exit 0
# R: specs (1): R:0.0.0.0:7332:127.0.0.1:7332
P4 PASS: one R: spec R:0.0.0.0:7332:127.0.0.1:7332, DropInPaths empty
# exit 0
@@ -0,0 +1,6 @@
# P4 2026-10-20T07:37:43Z
DropInPaths=
ExecStart={ path=/home/jwoltje/.local/bin/chisel ; argv[]=/home/jwoltje/.local/bin/chisel client --fingerprint ce+ISFvMAhuoCRDuc7riIf+hYz87TLHk/C9C/H8QsUg= --keepalive 25s --max-retry-interval 10s http://10.1.1.227:8080 R:0.0.0.0:7332:127.0.0.1:7332 ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }
# exit 0
# R: specs (1): R:0.0.0.0:7332:127.0.0.1:7332
P4 PASS: one R: spec R:0.0.0.0:7332:127.0.0.1:7332, DropInPaths empty
@@ -0,0 +1,8 @@
# P5 2026-10-20T07:37:43Z; HEAD 2ea05700bff619a2a6fed9951c8f3289ce39a37e
# git diff --quiet 490e2771d3d057ab76991e2c7859cd251857e64c HEAD -- scripts/stack-release.sh scripts/webui-service.sh scripts/bus-service.sh scripts/stack-copy.sh: exit 0
# git status --porcelain (exit 0): []
# scripts/stack-release.sh HEAD blob f8051d81cc18aedb7b6f2e743ec0c3f505e0e44b NEW blob f8051d81cc18aedb7b6f2e743ec0c3f505e0e44b
# scripts/webui-service.sh HEAD blob d34af09dccdee16430d185826a966785734776e0 NEW blob d34af09dccdee16430d185826a966785734776e0
# scripts/bus-service.sh HEAD blob f71250204dfeceb51beeea5941ebbf3a33ce4b92 NEW blob f71250204dfeceb51beeea5941ebbf3a33ce4b92
# scripts/stack-copy.sh HEAD blob 24bbb02a6aa8d7857da7be3b6815105a9fef629f NEW blob 24bbb02a6aa8d7857da7be3b6815105a9fef629f
P5 PASS
@@ -0,0 +1,8 @@
# P6 matrix on OLD 2026-10-20T07:37:49Z; pointer copy 89af38a83afa22a8a4f0d368a0a4030ad15a0efa
a 200 ok=true
b 401 error="sign-in required"
c 401 error="sign-in required"
d 403 error="proxy Host refused"
e 403 error="tester listener required"
f 403 error="proxy header refused"
g 200 non-json 6454 bytes
@@ -0,0 +1,118 @@
{
"schema": "3i",
"tables": {
"decision_events": {
"rows": 20,
"maxSeq": 20,
"sha256": "14be3e2fd9ab8d4dd6b7c7accf8dd04566342e6b5a86202d805d88b5bff1aad1"
},
"decisions": {
"rows": 20,
"maxSeq": 20,
"sha256": "936dbee20a152fa023f899de834cbbde2dbfaa645639911106afee78a158f2ef"
},
"deliveries": {
"rows": 1673,
"maxSeq": 1673,
"sha256": "2c215e2204e5fff174e934dd9771b4020c6a5c9914306600207f1cb7531f1c1b"
},
"events": {
"rows": 1424,
"maxSeq": 1424,
"sha256": "bfc78646bc5afea94182c59fc67b48afed8965c46d27ba1904062fde4b35d11f"
},
"instance_names": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"message_threads": {
"rows": 0,
"maxSeq": null
},
"messages": {
"rows": 550,
"maxSeq": 550
},
"meta": {
"rows": 3,
"maxSeq": null,
"sha256": "89fec56e7293d19e0b2f6cd8c0ef745ebf99e4115abeaea7fbdbc98a261332d9"
},
"projects": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"role_claims": {
"rows": 91,
"maxSeq": 91,
"sha256": "adc44befe8daa12f53ee7984fb36067f2d0ba15665ddb25a21716a03460c3bd0"
},
"task_comment_cursors": {
"rows": 171,
"maxSeq": 171,
"sha256": "35266badf56193856863e62f83091045020ca0f0b69d8a4e977fc28a56bfad34"
},
"task_snapshots": {
"rows": 359,
"maxSeq": 359,
"sha256": "eae6f62a6b3fcb33ad923ff8413b2d3b63a05eec92d5dcafc9d4c5c084ced965"
},
"thread_claims": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"thread_opens": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"threads": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"usage_records": {
"rows": 12,
"maxSeq": 12,
"sha256": "47f91b3130472b982b09689ebc72f78bd5aa56e59b275341d83f8e2fdea9b872",
"byRun": [
{
"business": "mosaic-stack",
"run": "r8fba7e598156",
"input": "0",
"output": "0",
"unknownTurns": 1
},
{
"business": "mosaic-stack",
"run": "r4a96e2142e4d",
"input": "2",
"output": "52",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "rc78c9700c8e9",
"input": "14",
"output": "1519",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r293f33bcbc63",
"input": "2",
"output": "44",
"unknownTurns": 0
}
]
}
},
"untimed": [
"message_threads",
"meta",
"task_comment_cursors"
]
}
@@ -0,0 +1,132 @@
{
"schema": "3i",
"tables": {
"decision_events": {
"rows": 20,
"maxSeq": 20,
"sha256": "14be3e2fd9ab8d4dd6b7c7accf8dd04566342e6b5a86202d805d88b5bff1aad1"
},
"decisions": {
"rows": 20,
"maxSeq": 20,
"sha256": "936dbee20a152fa023f899de834cbbde2dbfaa645639911106afee78a158f2ef"
},
"deliveries": {
"rows": 1687,
"maxSeq": 1687,
"sha256": "3b5f3b83e296f3301d7174192088972a611e2298289d0d4baf892c39efd0d79e"
},
"events": {
"rows": 1431,
"maxSeq": 1431,
"sha256": "fb228c40810daba8dd4ba29af3a41cf5f10d0fe17854e67b09b371b00be11b51"
},
"instance_names": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"message_threads": {
"rows": 2,
"maxSeq": null
},
"messages": {
"rows": 554,
"maxSeq": 554
},
"meta": {
"rows": 3,
"maxSeq": null,
"sha256": "89fec56e7293d19e0b2f6cd8c0ef745ebf99e4115abeaea7fbdbc98a261332d9"
},
"projects": {
"rows": 1,
"maxSeq": 1,
"sha256": "a3945f7462895e0e302029c542d384a6d33e559da900e6461d78bbcd7cc28b16"
},
"role_claims": {
"rows": 93,
"maxSeq": 93,
"sha256": "b160aa10ceeed2625499d595727754a76a2c5dbbbf81a0a1569f343ba5a67234"
},
"task_comment_cursors": {
"rows": 171,
"maxSeq": 171,
"sha256": "35266badf56193856863e62f83091045020ca0f0b69d8a4e977fc28a56bfad34"
},
"task_snapshots": {
"rows": 359,
"maxSeq": 359,
"sha256": "eae6f62a6b3fcb33ad923ff8413b2d3b63a05eec92d5dcafc9d4c5c084ced965"
},
"thread_claims": {
"rows": 2,
"maxSeq": 2,
"sha256": "6046c7088779aa2f6e9b3fd2c7520404b1678a4fcae71c2b9157a008bba22a5f"
},
"thread_opens": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"
},
"threads": {
"rows": 1,
"maxSeq": 1,
"sha256": "3043721efd386f23dfdaad4acf41e0d553ff6182f0068c2133a615141bcf772a"
},
"usage_records": {
"rows": 16,
"maxSeq": 16,
"sha256": "928a8df17ee6dd2ff58cc0c01c15359f64ea46e325b7e3c186b2ada74e74aaac",
"byRun": [
{
"business": "mosaic-stack",
"run": "r8fba7e598156",
"input": "0",
"output": "0",
"unknownTurns": 1
},
{
"business": "mosaic-stack",
"run": "r4a96e2142e4d",
"input": "2",
"output": "52",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "rc78c9700c8e9",
"input": "14",
"output": "1519",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r293f33bcbc63",
"input": "2",
"output": "44",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r34520a581318",
"input": "2",
"output": "111",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r7e99838f9f93",
"input": "2",
"output": "44",
"unknownTurns": 0
}
]
}
},
"untimed": [
"message_threads",
"meta",
"task_comment_cursors"
]
}
@@ -0,0 +1,236 @@
{
"schema": "3i",
"backupSchema": "3i",
"tables": {
"decision_events": {
"rows": 20,
"maxSeq": 20,
"sha256": "14be3e2fd9ab8d4dd6b7c7accf8dd04566342e6b5a86202d805d88b5bff1aad1",
"vsBackup": {
"backupRows": 20,
"backupMaxSeq": 20,
"newRows": 0,
"priorSame": true
}
},
"decisions": {
"rows": 20,
"maxSeq": 20,
"sha256": "936dbee20a152fa023f899de834cbbde2dbfaa645639911106afee78a158f2ef",
"vsBackup": {
"backupRows": 20,
"backupMaxSeq": 20,
"newRows": 0,
"priorSame": true
}
},
"deliveries": {
"rows": 1687,
"maxSeq": 1687,
"sha256": "3b5f3b83e296f3301d7174192088972a611e2298289d0d4baf892c39efd0d79e",
"vsBackup": {
"backupRows": 1673,
"backupMaxSeq": 1673,
"newRows": 14,
"priorSame": true
}
},
"events": {
"rows": 1431,
"maxSeq": 1431,
"sha256": "fb228c40810daba8dd4ba29af3a41cf5f10d0fe17854e67b09b371b00be11b51",
"vsBackup": {
"backupRows": 1425,
"backupMaxSeq": 1425,
"newRows": 6,
"priorSame": true
}
},
"instance_names": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"vsBackup": {
"backupRows": 0,
"backupMaxSeq": null,
"newRows": 0,
"priorSame": true
}
},
"message_threads": {
"rows": 2,
"maxSeq": null,
"vsBackup": {
"backupRows": 0,
"same": false
}
},
"messages": {
"rows": 554,
"maxSeq": 554,
"vsBackup": {
"backupRows": 550,
"backupMaxSeq": 550,
"newRows": 4,
"priorSame": true
}
},
"meta": {
"rows": 3,
"maxSeq": null,
"sha256": "89fec56e7293d19e0b2f6cd8c0ef745ebf99e4115abeaea7fbdbc98a261332d9",
"vsBackup": {
"backupRows": 3,
"keysChanged": []
}
},
"projects": {
"rows": 1,
"maxSeq": 1,
"sha256": "a3945f7462895e0e302029c542d384a6d33e559da900e6461d78bbcd7cc28b16",
"vsBackup": {
"backupRows": 0,
"backupMaxSeq": null,
"newRows": 1,
"priorSame": true
}
},
"role_claims": {
"rows": 93,
"maxSeq": 93,
"sha256": "b160aa10ceeed2625499d595727754a76a2c5dbbbf81a0a1569f343ba5a67234",
"vsBackup": {
"backupRows": 92,
"backupMaxSeq": 92,
"newRows": 1,
"priorSame": true
}
},
"task_comment_cursors": {
"rows": 171,
"maxSeq": 171,
"sha256": "35266badf56193856863e62f83091045020ca0f0b69d8a4e977fc28a56bfad34",
"vsBackup": {
"backupRows": 171,
"backupMaxSeq": 171,
"newRows": 0,
"priorSame": true
}
},
"task_snapshots": {
"rows": 359,
"maxSeq": 359,
"sha256": "eae6f62a6b3fcb33ad923ff8413b2d3b63a05eec92d5dcafc9d4c5c084ced965",
"vsBackup": {
"backupRows": 359,
"backupMaxSeq": 359,
"newRows": 0,
"priorSame": true
}
},
"thread_claims": {
"rows": 2,
"maxSeq": 2,
"sha256": "6046c7088779aa2f6e9b3fd2c7520404b1678a4fcae71c2b9157a008bba22a5f",
"vsBackup": {
"backupRows": 0,
"backupMaxSeq": null,
"newRows": 2,
"priorSame": true
}
},
"thread_opens": {
"rows": 0,
"maxSeq": null,
"sha256": "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855",
"vsBackup": {
"backupRows": 0,
"backupMaxSeq": null,
"newRows": 0,
"priorSame": true
}
},
"threads": {
"rows": 1,
"maxSeq": 1,
"sha256": "3043721efd386f23dfdaad4acf41e0d553ff6182f0068c2133a615141bcf772a",
"vsBackup": {
"backupRows": 0,
"backupMaxSeq": null,
"newRows": 1,
"priorSame": true
}
},
"usage_records": {
"rows": 16,
"maxSeq": 16,
"sha256": "928a8df17ee6dd2ff58cc0c01c15359f64ea46e325b7e3c186b2ada74e74aaac",
"byRun": [
{
"business": "mosaic-stack",
"run": "r8fba7e598156",
"input": "0",
"output": "0",
"unknownTurns": 1
},
{
"business": "mosaic-stack",
"run": "r4a96e2142e4d",
"input": "2",
"output": "52",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "rc78c9700c8e9",
"input": "14",
"output": "1519",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r293f33bcbc63",
"input": "2",
"output": "44",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r34520a581318",
"input": "2",
"output": "111",
"unknownTurns": 0
},
{
"business": "mosaic-stack",
"run": "r7e99838f9f93",
"input": "2",
"output": "44",
"unknownTurns": 0
}
],
"vsBackup": {
"backupRows": 12,
"backupMaxSeq": 12,
"newRows": 4,
"priorSame": true
}
}
},
"untimed": [
"message_threads",
"meta",
"task_comment_cursors"
],
"onlyInBackup": [],
"stopTables": [
"deliveries",
"message_threads",
"messages",
"projects",
"role_claims",
"thread_claims",
"threads",
"usage_records"
]
}
@@ -0,0 +1,202 @@
# Re-pin window rows 177-185, STATE report (W11)
Operator and tester: Darkwing. Plan: `agents/sage/work/window-plan/repin-rows-177-185.md` draft 4 at 9ddd8a92c,
sha256 b05eb85e357ecb7d1c267c537a3a8c6fef82967368520101ce842d1677e871a1. Tool blob f8051d81cc18
(`scripts/stack-release.sh`), checked before W0, W5, W9 and W10. OLD 89af38a83afa22a8a4f0d368a0a4030ad15a0efa,
NEW 490e2771d3d057ab76991e2c7859cd251857e64c (the row 185 landing). Every stamp below comes from `date -u` or
from the transcript W.txt in this directory. Written 2026-10-20 after W10 (mos-0952).
## Outcome
NEW is live for mosaic-stack with its PM. The store stays on 3i and the tester listener is on 127.0.0.1:7333.
W0 to W10 passed and nothing was rolled back. Mos accepted every step. The window had two stops, both reported
to Mos before going on. The W9 assistant run was still up 10 minutes after its reply. The B snapshot differed
from S1 by the old chief's end rows. Both had a known cause, explained below. Nobody acted as Jason. There was
no `--by jason`, no pi, no Save and no `mosaic name`, and `bus.tester` was not touched.
## Stamps and activation
| Step | Start (UTC) | Result |
| --- | --- | --- |
| P1 values, business-check OLD | 07:19:33.013Z | values as planned, LOAD_OK |
| P3 rehearsal run 3, limits rerun, proofs | 07:32:23Z, 07:46:44Z to 07:49:16Z | PASS, see `p3/NOTES.md` |
| P4, P5, P6 | 07:37:43Z, 07:37:43Z, 07:37:49Z | P4 PASS, P5 PASS (HEAD 2ea05700b), matrix-old.txt |
| W0 copy NEW, business-check NEW | 07:53:51.633Z | copied, LOAD_OK |
| W0b business file copy | 07:53:53Z | sha256 1e49bdf0 |
| W1 idle check, start stamp, S1 | 07:53:53Z | chief idle; S1 2fd8b96c |
| W2 status | 07:53:53.535Z | pointer OLD, pm true, 3i, check pass, unit flags match |
| W3 plan | 07:53:53.741Z | exit 0, matches P3 run 3 except the root and live-flag lines (7, 61-63) |
| W4 unit read | 07:53:53.879Z | OLD units, no drop-ins, bus `--pm`, no quote or backslash |
| W5 activate (mos-0946 go 07:57:23Z) | 07:58:00.006Z, end 07:58:02.738Z | exit 0, gate passed, activate ok |
| W5 status | 07:58:22.071Z | pointer NEW, pm true, 3i, check pass, unit flags match |
| W9 checks and turns (mos-0947 go 08:00:50Z) | 08:01:03Z | pass (mos-0948) |
| W9 run end and S2 | 08:16:18.103Z, S2 08:16:22Z | ended by the launcher; S2 58e864da |
| S2b and B (mos-0949) | 08:18:46Z | S2b a9e8d6df, B 42553b74; stop, then accepted (mos-0950) |
| W10 browser, matrix, P4 (mos-0950 go) | 08:23:32.268Z to 08:23:54.592Z | pass (mos-0952) |
| W11 reads | after 08:25Z | units, tunnel, business file sha |
Activation id 20261020T075800Z-490e2771d3d0. Pointer `at` 2026-10-20T07:58:02Z.
## Backups and hashes
- Store backup: `~/.mosaic-dev/backups/stack/20261020T075800Z-490e2771d3d0`. STAMP 2026-10-20T07:58:00.381Z,
SCHEMA 3i, DATA_ROOT /home/jwoltje/.mosaic-dev. SHA256SUMS sha256
399f710540430750bf3f1fb488824b4239be841f2f91e4668a2d749ad2065683, `sha256sum -c` OK in `bus/`. bus.sqlite
sha256 1729da1b4e87ec4ec7768590580a439b7507aa31b6b4591fd47d13bbb12c103d. It had no -wal or -shm before and
after the S2b and B reads, and its hash didn't change across them.
- Rollback set: `~/.mosaic-dev/state/stack-rollback/20261020T075800Z-490e2771d3d0`.
- Business file: 1e49bdf05e62607972958f5b7298cfd600c7825812370e26d7c2a842f2af9b93 at P1, at W0b and again at
W11. The W0b copy is `~/.config/mosaic-dev/businesses/mosaic-stack.json.pre-window-20261020T075353Z` with the
same hash. Nothing in the window wrote it.
- `backups.txt` holds both lines (sha256 427d7f55).
## Pointer before and after
Before (W2): copy 89af38a83afa, pm true, schema 3i, activation 20261020T014103Z-89af38a83afa, at
2026-10-20T01:41:05Z. Console flags `--port 7332 --public-host mosaic.woltje.com --auth-header
X-authentik-username --allow-user jason.woltje --send --tester-port 7333`. Bus pid 1514133.
After (W5 and W9 status): copy 490e2771d3d0, pm true, schema 3i, activation 20261020T075800Z-490e2771d3d0, at
2026-10-20T07:58:02Z. The Console flags are the same, with no `--proof-header` or `--proof-file`. Bus pid
555397, since 07:58:01.329Z. Log line `activate ... "backup":"20261020T075800Z-490e2771d3d0","result":"ok"`.
## Unit ExecStart lines (no Environment values)
W4, before:
```
bus: .../copies/89af38a83afa22a8a4f0d368a0a4030ad15a0efa/scripts/mosaic bus start mosaic-stack --pm
webui: /usr/bin/node .../copies/89af38a83afa22a8a4f0d368a0a4030ad15a0efa/packages/webui/src/cli.mjs serve --checkout /mnt/storage/src/mosaic-stack --port 7332 --public-host mosaic.woltje.com --auth-header X-authentik-username --allow-user jason.woltje --send --tester-port 7333
```
W11, after. Both units active, DropInPaths empty, WorkingDirectory the NEW copy:
```
bus: .../copies/490e2771d3d057ab76991e2c7859cd251857e64c/scripts/mosaic bus start mosaic-stack --pm (MainPID 555397)
webui: /usr/bin/node .../copies/490e2771d3d057ab76991e2c7859cd251857e64c/packages/webui/src/cli.mjs serve --checkout /mnt/storage/src/mosaic-stack --port 7332 --public-host mosaic.woltje.com --auth-header X-authentik-username --allow-user jason.woltje --send --tester-port 7333 (MainPID 555401)
```
The bus Environment has 2 assignments with no quote or backslash. The values are not recorded.
Tunnel (mos-0952): `mosaic-preview-tunnel.service` ActiveState=active, SubState=running, MainPID=1317,
ActiveEnterTimestamp Tue 2026-10-13 08:40:19 CDT. The window didn't touch it. The `pid=0` in both P4 reads is
ExecStart's static field, not the running process.
## S1, S2 and the per-row check
S1 is W1's held read at 07:53:53Z. S2 is the held read after the assistant run ended, at 08:16:22Z. S2b is a held
read against the W5 backup, and B is the backup alone with `--stopped`. The schema is 3i in every one. SNAP
(`snapshot.mjs`, 639fd28a) was checked before S1, S2, S2b and B.
| Table | S1 rows | B rows | S2 rows | S2 vs S1 |
| --- | --- | --- | --- | --- |
| decisions, decision_events | 20, 20 | 20, 20 | 20, 20 | same sha256 |
| task_snapshots, task_comment_cursors | 359, 171 | 359, 171 | 359, 171 | same sha256 |
| meta, instance_names, thread_opens | 3, 0, 0 | 3, 0, 0 | 3, 0, 0 | same sha256 |
| deliveries | 1673 | 1673 | 1687 | +14 |
| events | 1424 | 1425 | 1431 | +7 |
| role_claims | 91 | 92 | 93 | +2 |
| messages | 550 | 550 | 554 | +4 (counts only) |
| message_threads | 0 | 0 | 2 | +2 (counts only) |
| usage_records | 12 | 12 | 16 | +4 |
| projects, threads, thread_claims | 0, 0, 0 | 0, 0, 0 | 1, 1, 2 | new |
For every grown table with a seq, maxSeq grew by exactly the row delta, so S2 lost no row. usage byRun keeps S1's
four runs with the same sums and adds the chief r34520a581318 (2/111) and the assistant r7e99838f9f93 (2/44).
S2b. Every table with a seq has priorSame true, onlyInBackup is empty, meta keysChanged is empty, and
message_threads went from 0 to 2. So no row at or below the backup's maxSeq changed in any table. Changed old
rows: 0. stopTables lists only the tables with new rows.
B against S1. Twelve tables are equal. events (1425 against 1424) and role_claims (92 against 91) are not. Under
mos-0949 that stopped the window before W10, and I reported it. `bprefix.mjs` hashed the backup's rows at or below
S1's maxSeq the same way SNAP does. Both prefixes equal S1: events bfc78646, role_claims adc44bef. The extra rows
are role_claims 92 (07:58:00.217Z, chief release, holder and by rc78c9700c8e9) and events 1425
(07:58:00.238Z, session.ended, chief rc78c9700c8e9). Both come from OLD's `broker.mjs` endLaunch (:484, release
:499, event :505) when W5 stopped the old chief. The launches log shows the end at 07:58:00.246Z, before the
backup STAMP at 07:58:00.381Z. mos-0950 accepted this and recorded that S1 and the backup are not equal by design.
The chain holds row by row: S1 is a prefix of the backup, and the backup is a prefix of S2.
## W9 output
- status: pointer NEW, check pass. console-check NEW (repo copy d068fe02): CONSOLE_PASS. The journal since
07:58:00 UTC has 1 `Proxy: proxy proof off` line.
- launches list: one running chief, r34520a581318, since 07:58:02.199Z. `agents --tester`: chief idle.
- `talk chief --tester`: sent 580f50ed, RESULT 90fb6939 with one line and no staffing request.
- `project create window-20261020-0801 --tester`: id 8fe35a6d-ebe8-4607-ba67-7625f2ef09f6.
- `thread create window-20261020-0801 w9 --tester`: id d0ba7a66-aa3f-4918-9518-94132bce6b28, instance assistant.
- `talk --thread --wait 180 --tester`: sent dc002b67, RESULT 48cdd10b. The reply arrived at 08:01:18Z.
- `thread read --json --tester`: 2 messages, a tester to assistant REQUEST and an assistant to tester RESULT.
`threads --tester` lists the thread. `C_NEW names --tester --json`: `[]`.
- `usage --tester --json`: new byRun entries for the chief and the assistant, as in S2.
- Human-thread probe: skipped. S1 has 0 threads and 0 message_threads rows, so there was no human thread to read.
- The assistant run r7e99838f9f93 (launched 08:01:14.335Z) was still up at 08:11:28Z. I stopped and reported
(STOP 1). mos-0948 said not to stop it, since the launcher ends a thread run after `threadIdleSeconds`, 900 s by
default (`business.mjs:20`, launcher threadPass). The launches log has `thread-idle` at 08:16:18.082Z and `end`
reason=stopped exitCode=0 at 08:16:18.103Z. The bus journal has `run r7e99838f9f93 (assistant) ended:
stopped`. Nothing was stopped by hand.
## W10 output
- Browser (`w10-browser.mjs` b653c2ce, harness `packages/webui/tests/browser.mjs` same as 490e2771d): 12 of 12
checks pass. Settings shows the tester banner and the Routing, Role instances and Seat names panels. It has no
Token limits panel and no launch settings, assistant, limit reset or send. The Board reads, with the chief held
and idle. `#/chat` lists thread w9 (assistant) at `#/chat/threads/d0ba7a66-...`. The thread page was never
opened. There were 23 requests, all GET, none refused and none to the W9 thread. Log `.w10-browser-run.log`
a60258ab; screenshots w10-settings.png 575ea467, w10-board.png 716efa0f, w10-chat.png 4e383493.
- Matrix: `matrix-new.txt` is byte-identical to `matrix-old.txt`, both sha256 93c07e15:
```
a 200 ok=true
b 401 error="sign-in required"
c 401 error="sign-in required"
d 403 error="proxy Host refused"
e 403 error="tester listener required"
f 403 error="proxy header refused"
g 200 non-json 6454 bytes
```
index.html is the same in OLD and NEW, so g's size matches. No request carried jason.woltje or a credential.
The proof value is a fixed 32-letter dummy.
- P4 again (`P4-W10.txt`): PASS, the same as P4 apart from w.sh's exit line.
## Stops
1. W9, 08:11:28Z: the assistant run was still up 10 minutes after its reply, so a stop was Mos's call. mos-0948
ruled it a plan gap, not a fault. The plan's 10-minute rule doesn't account for the 900 s idle stop the
launcher applies by design.
2. S2b and B, 08:19:04Z: B didn't match S1. I stopped before W10 under mos-0949. mos-0950 confirmed the stop was
correct and the cause is by design (above).
## Deviations and notes
- console-check.sh ran from the repo copy `agents/sage/work/window-plan/repin-de352dfaf/` (d068fe02) after its
SCRIPTS.sha256 check, not from the plan's `$P`. Darkwing doesn't read `/mnt/storage/scratch/sage-*`.
- business-check.mjs (eece4d46) ran from the same repo copy, for the same reason.
- The human-thread probe was skipped, as noted in W9.
- `bprefix.mjs` (adae9dc7) is a script outside the plan. It opened only the backup, with immutable=1 and readOnly.
- Mos's expectation in mos-0949 was wrong. The chief release is an appended role_claims row, not an in-place
update, and deliveries showed no change to an old row (mos-0950).
- `by jason` on `launches list` lines is the launcher's record of `b.human`. The tester started nothing as Jason.
It is a display item (mos-0948).
- mos-0938 time bounds: mos-0934 went out before 07:37:04Z, mos-0936 before 07:37:58Z and mos-0937 before
07:42:17Z. Mos's live limits GET on 7333 ran before 07:37:04Z.
- P3 scratch deviations, none of which apply to live. The PM launch was emulated by a stand-in that holds no
claim and whose session has no provider, so `limits` refused on both copies until proof 2 set an exact
provider and model. The example business file has no `thinking` var, so the assistant thread refused `no-route`
until proof 1 added one. Scratch has no session credential, so the assistant start refused
`no-session-credential` on both copies once the route was set. The claude binary is a fixture. Detail and
hashes are in `p3/NOTES.md`.
- My slip: there was no interim report between 07:58:28Z and 08:11:39Z, which breaks Mos's 10-minute report
rule. After mos-0948 no wait ran past 10 minutes.
## Files
Transcript W.txt (final sha256 below). S1.json 2fd8b96c, S2.json 58e864da, S2b.json a9e8d6df, B.json 42553b74,
W3-plan.txt fe61fae5, P4.txt b3479e0d, P4-W10.txt ff845006, P5.txt 0f4cfc1b, P6-run.txt e4a1d56b, matrix-old.txt
and matrix-new.txt 93c07e15, `.w9b-run.log`, `.w9c-run.log`, `.w9c-bprefix.txt` 5cc5feec,
`.w10-browser-run.log` a60258ab, and the per-step `.w0-*`, `.w1-*` and `.w9-*` captures. Scripts: w.sh a45baeb5, wsteps.sh 95b55320, w9.sh be39da24, w9b.sh 871aee2c,
w9c.sh c4e08d78, bprefix.mjs adae9dc7, p1.mjs a19a3639, p1.sh 71ea61c6, p456.sh af6a9293, w10-browser.mjs
b653c2ce, snapshot.mjs 639fd28a.
W.txt final sha256: edd4622c5c45ef3fd4d1a832d7484b14384f0001257e6d3a1bd2f3cfd403b59a
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,63 @@
plan: activate for mosaic-stack
from: 89af38a83afa22a8a4f0d368a0a4030ad15a0efa
to: 490e2771d3d057ab76991e2c7859cd251857e64c
store schema: 3i (read from the store the running bus holds)
target schema: 3i
migration: none; the store is at the target's version
backup: the run backs up the stopped store to /home/jwoltje/.mosaic-dev/backups/stack/<activation>/ before any migration
commits: the run adds 52 (newest first, at most 200):
490e2771d row 185 (#1683): every activate backs up the stopped store
6c88bc666 sessions: Filbert row 185 r1 review, approve
b5d662cab plans: mos-0929 rulings on the mos-0928 re-pin plan (Q1-Q3, Part B list)
52ce57dfe sessions: Sage mos-0928 plan, row 185 filed and briefed
f38186943 queue: revs 2183-2184: row 185 (#1683) added and briefed (Sage)
10e1bb495 plans: row 185 brief, every activate backs up the stopped store (#1683); mos-0928 window plan
9f5153deb sessions: Dewey row 184 rebase and in-review; Sage mos-0870 assignment
055ca6938 queue: revs 2178-2182: row 184 in-review on e7e2c763 and request comment 29395 (Dewey 2178-2179); Filbert approve comment 29396 (2180); landing note (87aca4cb8, patch-id ad846db177b3, gate passed) and done (Sage 2181-2182)
87aca4cb8 row 184 (#1682): tester thread verbs in the CLI
dd7656cbf sessions: Filbert row 184 r1 review, approve
3da362982 queue: revs 2173-2177: row 178 landing note (b2b33aee5, patch-id 88e56a8a3afd, gate passed) and done (Sage 2173, 2175); row 182 Filbert approve comment 29388 (2174), landing note (33ab5eeb9, patch-id fa9a8988ebfc, gate passed) and done (Sage 2176-2177)
ef09f8b3f sessions: Darkwing rows 180-182 and Rocko rows 138-178 pending lines
33ab5eeb9 row 182 (#1680): a tester message reaches only a thread the tester owns
7fc98cf19 sessions: Filbert row 182 r1 review, approve
b2b33aee5 row 178 (#1676, P19 385): the Console checks a proxy proof header on public Hosts
3cfa333e4 sessions: Dewey row 178 r2 review, row 184 build
02482094a queue: revs 2154-2172: rows 182 and 184 briefs, reviewers filbert, owners Darkwing and Dewey, briefed (Sage 2154-2161); 182 start (Darkwing 2162), 184 start (Dewey 2163); row 183 landing note (d1272e714, patch-id af1cad0940dc) and done (Sage 2164-2165); row 178 r2 in-review (Rocko 2166-2167), review request comment 29384 (Sage 2168-2169), Dewey approve comment 29386 (2170); row 182 in-review, request comment 29387 (Darkwing 2171-2172)
74ce7061d sessions: Researcher row 183 build, review and landing
d1272e714 row 183 (#1681): chat.mjs names the transport it is given
841c56d05 plans: rows 182 and 184 briefs set owners and reviewers
9a3f101b0 sessions: Dewey rows 180 r2, 178 r1, 183 r1 reviews
931651f2d queue: revs 2144-2153: row 181 Filbert approve #1679 comment 29372 (2144), landing note (ef48597d0, patch-id 1fef78100554, gate passed) and done (Sage 2151-2152); row 178 Dewey changes comment 29374 (2145), reopened (Rocko 2146); row 183 in-review (Researcher 2147-2148), review request comment 29375 (Sage 2149-2150), Dewey approve comment 29376 (2153)
ef48597d0 row 181 (#1679, P19 390): the restore guard counts every row a restore drops
d7184aeac sessions: Filbert row 181 r1 review, approve
cbe4c8265 queue: revs 2135-2143: row 181 round 1 in-review #1679 comment 29370 (Darkwing 2135-2136); rows 182-184 brief re-pins to 3459e01c3358 (Sage 2137-2139); row 183 reviewer Dewey, owner Researcher, briefed (Sage 2140-2142), started (Researcher 2143)
5a8c1340c plans: row 183 brief sets owner Researcher and reviewer Dewey
fcfb82da2 queue: revs 2129-2134: row 181 briefed (Sage 2129) and started (Darkwing 2130); row 178 round 1 in-review (Rocko 2131-2132) and review request #1676 comment 29368 (Sage 2133-2134)
311705d5b sessions: Sage rows 177 landing, 178 briefing, 180 landing, 181 briefing, 182-184 added
a757f8fe6 queue: revs 2127-2128: row 180 landing note (1905e385c, patch-id 31b1d502444e, gate passed) and done (Sage)
1905e385c row 180 (#1678, P19 391): activation ids within one second and plan-mode refusals
828d14a0c queue: revs 2113-2126: row 178 brief re-pin, owner Rocko, reviewer Dewey, briefed (Sage 2113-2114, 2116-2117), started (Rocko 2125); row 180 round 2 in-review and resolve (Darkwing 2115, 2118), Dewey approve #1678 comment 29361 (2126); rows 182-184 added with issues #1680-#1682 (Sage 2119-2124)
c7421cabd plans: row 178 brief sets owner Rocko and reviewer Dewey and orders its landing after rows 180/181; briefs for the three row 177 follow-ups (N1 tester thread owner check, N2 chat.mjs strings, tester CLI thread verbs)
895c7270c sessions: Dewey row 177 round 2 build and row 180 round 1 review, changes
7f519242e queue: revs 2103-2112: row 180 to in-review round 1 (Darkwing 2103-2104), Dewey changes #1678 comment 29352 (2109), round 2 start (Darkwing 2110); row 177 round 2 in-review #1675 comment 29350 (Dewey 2105-2107), Filbert approve 29351 (2108), landing note and done (Sage 2111-2112)
9d9d95485 row 177 round 2 (#1675): a tester-thread run's decision.raise refuses tester-thread-human when it routes to the human; F2 dispatch comment, F3 tester-writes header and name
1f994257a row 177 (#1675, Q173): tester threads
a2eebf997 sessions: Filbert row 177 r2 review, approve
c0d6bc7d5 queue: rev 2102: row 177 round 1 verdict changes, #1675 comment 29347 (Filbert)
72c0e22cc sessions: Filbert row 177 r1 review, changes
9241a9738 sessions: Darkwing W5-W11, cleanup and packet push lines (33), Rocko (9) and Researcher (1) pending lines, and Sage after-window rows 180/181
a6a7d8a90 queue: revs 2096-2100: rows 180 (#1678, P19 391 + row 176 N1/N2, Darkwing, briefed) and 181 (#1679, P19 390 restore guard, after 180) added by Sage (2096-2098); row 177 to in-review round 1, request #1675 comment 29343 (Dewey 2099-2100)
c7f3beb17 docs: row B brief amended per mos-0927 R1: item 4 classifies every table in the target schema into one of four lists, item 1 records UPDATEs that do not move the time column (#1679)
236af5271 docs: brief for two stack-release rows after the merged B+C window: row A (P19 391 activation ids, Dewey row 176 N1/N2), row B (P19 390 restore guard, after row A)
c5ae4d7ac darkwing: merged B+C window packet, STATE.md (sha256 7be42b6bcab5657d), W0-W11 transcripts, S1/S2/S2b snapshots, P3 rehearsals and snapshot.mjs checks (#1673, #1674; P19 386, 382)
88f3253d9 sage: merged B+C window STATE checked and filed (P19 386 c324, 382 c325 done, #1673 29334, #1674 29335); #394 doc fixes: carry-past-row64.md runs from REPO held to the pinned sha, plan line 222 and W8 row amended (mos-0924)
ac1e16a99 sessions: Dewey row 177 design sent to Sage
e9aa33a77 sessions: Sage merged B+C plan draft 9, W0 go, row 177 start
c49de08e8 sage: merged B+C window plan draft 9 (sha256 523f20e03be8aa11), Mos's formal check closed with the W0 go (mos-0907); A1-A4 text fixes to draft 8 (mos-0906)
d3996adea queue: revs 2093-2094: row 177 brief re-pinned to blob f146cd772 at dfd3e5823 and moved to briefed for Dewey (Sage 2093, 2094)
dfd3e5823 sage: row 177 brief re-pinned to 89af38a83 after rows 175 and 176 moved broker.mjs; point 6 names the tester listener's chat refusal (serve.mjs:442) and 7332's x-mosaic-tester refusal (:454)
b61335ad6 sessions, build-log: Sage row 176 gate, landing and records, merged window plan draft 8
3faa30184 queue: revs 2083-2092: row 176 round 2 claimed and moved to in-review on candidate a4c728fc (Rocko 2083, 2085, 2086), Dewey's round 1 note and round 2 approval, comment 29322 (Dewey 2084, 2089), the round 2 review request posted as jarvis (Sage 2087, 2088), landing notes and the done move on 89af38a83 (Sage 2090-2092)
bus: scripts/bus-service.sh install --copy 490e2771d3d057ab76991e2c7859cd251857e64c --copies /home/jwoltje/.local/share/mosaic-stack/copies --pm --claude /home/jwoltje/.local/bin/claude
Console: scripts/webui-service.sh install --copy 490e2771d3d057ab76991e2c7859cd251857e64c --copies /home/jwoltje/.local/share/mosaic-stack/copies --port 7332 --public-host mosaic.woltje.com --auth-header X-authentik-username --allow-user jason.woltje --send --tester-port 7333
CLI: scripts/stack-copy.sh wrapper 490e2771d3d057ab76991e2c7859cd251857e64c --copies /home/jwoltje/.local/share/mosaic-stack/copies --bin /home/jwoltje/.npm-global/bin
@@ -0,0 +1,2 @@
W0b 2026-10-20T07:53:53Z business file copy /home/jwoltje/.config/mosaic-dev/businesses/mosaic-stack.json.pre-window-20261020T075353Z sha256 1e49bdf05e62607972958f5b7298cfd600c7825812370e26d7c2a842f2af9b93 (source /home/jwoltje/.config/mosaic-dev/businesses/mosaic-stack.json sha256 1e49bdf05e62607972958f5b7298cfd600c7825812370e26d7c2a842f2af9b93)
W5 2026-10-20T07:58:22Z activation 20261020T075800Z-490e2771d3d0 backup /home/jwoltje/.mosaic-dev/backups/stack/20261020T075800Z-490e2771d3d0 STAMP 2026-10-20T07:58:00.381Z SCHEMA 3i DATA_ROOT /home/jwoltje/.mosaic-dev SHA256SUMS sha256 399f710540430750bf3f1fb488824b4239be841f2f91e4668a2d749ad2065683 (bus.sqlite 1729da1b4e87ec4ec7768590580a439b7507aa31b6b4591fd47d13bbb12c103d, sha256sum -c OK in bus/)
@@ -0,0 +1,40 @@
// Window rows 177-185, mos-0949 B check: B (the W5 backup) has one more events row and one more
// role_claims row than S1. This hashes the backup's rows at or below S1's maxSeq exactly as
// snapshot.mjs read() hashes a table (columns in name order, rows in seq order, JSON lines, bigints as
// strings), so each prefix sha256 compares with S1's table sha256. It also prints the rows above S1's
// maxSeq: events by seq, at, kind, actor_role and actor_run only (no body or subject); role_claims by
// seq, at, role, op, holder_run, by and reason.
// Opens only the backup, immutable=1 and readOnly, and refuses when -wal or -shm is present.
// usage: node bprefix.mjs /abs/backup/bus.sqlite /abs/S1.json
import { DatabaseSync } from 'node:sqlite';
import { createHash } from 'node:crypto';
import { existsSync, readFileSync } from 'node:fs';
import { pathToFileURL } from 'node:url';
const [path, s1Path] = process.argv.slice(2);
for (const f of [path + '-wal', path + '-shm']) if (existsSync(f)) { console.error(`${f} is present`); process.exit(1); }
const s1 = JSON.parse(readFileSync(s1Path, 'utf8')).tables;
const db = new DatabaseSync(pathToFileURL(path).href + '?immutable=1', { readOnly: true });
const q = (name) => `"${name.replaceAll('"', '""')}"`;
const value = (v) => (typeof v === 'bigint' ? String(v) : v instanceof Uint8Array ? { hex: Buffer.from(v).toString('hex') } : v);
const SHOW = { events: ['seq', 'at', 'kind', 'actor_role', 'actor_run'], role_claims: ['seq', 'at', 'role', 'op', 'holder_run', 'by', 'reason'] };
let ok = true;
for (const table of Object.keys(SHOW)) {
const cols = db.prepare('SELECT name FROM pragma_table_info(?)').all(table).map((c) => c.name).sort();
const limit = BigInt(s1[table].maxSeq);
const stmt = db.prepare(`SELECT ${cols.map(q).join(',')} FROM ${q(table)} ORDER BY seq`);
stmt.setReadBigInts(true);
const h = createHash('sha256');
let n = 0;
const above = [];
for (const row of stmt.iterate()) {
if (row.seq <= limit) { h.update(JSON.stringify(cols.map((c) => value(row[c]))) + '\n'); n++; }
else above.push(Object.fromEntries(SHOW[table].map((c) => [c, value(row[c])])));
}
const sha = h.digest('hex');
const same = sha === s1[table].sha256 && n === s1[table].rows;
if (!same) ok = false;
console.log(JSON.stringify({ table, s1MaxSeq: String(limit), prefixRows: n, prefixSha256: sha, s1Sha256: s1[table].sha256, same, above }));
}
db.close();
process.exit(ok ? 0 : 2);
@@ -0,0 +1,7 @@
a 200 ok=true
b 401 error="sign-in required"
c 401 error="sign-in required"
d 403 error="proxy Host refused"
e 403 error="tester listener required"
f 403 error="proxy header refused"
g 200 non-json 6454 bytes
@@ -0,0 +1,7 @@
a 200 ok=true
b 401 error="sign-in required"
c 401 error="sign-in required"
d 403 error="proxy Host refused"
e 403 error="tester listener required"
f 403 error="proxy header refused"
g 200 non-json 6454 bytes
@@ -0,0 +1,23 @@
# plan sha256 2194b9b043210464add701a42df6c2abe295fef88b9131f2647fb6cfb9d98713
# business-check.mjs sha256 eece4d4671e1ce15ba81e1654c641925ac4988b343687b062897392285a8a1a7; SCRIPTS.sha256 line: eece4d4671e1ce15ba81e1654c641925ac4988b343687b062897392285a8a1a7 business-check.mjs
# business file sha256 1e49bdf05e62607972958f5b7298cfd600c7825812370e26d7c2a842f2af9b93, mtime 2026-10-19 20:39:58.506635141 -0500
bus.tester true
plugins kinds ["channel","harness","usage-source"]
plugins usage-source ["transcript"]
plugins channel ["cli","webui"]
plugins harness ["claude-code"]
launch.by "chief"
launch.instances includes assistant true
role assistant vars.harness "claude-code"
role chief vars.harness "claude-code"
role coder vars.harness "claude-code"
role cto vars.harness "claude-code"
role pm vars.harness "claude-code"
role reviewer vars.harness "claude-code"
roles naming pi 0
# exit 0
copy 89af38a83: roles 6, projects 1, arbiters 2
HOLDS chief instance.name yes
HOLDS pm instance.name no
LOAD_OK
# exit 0
@@ -0,0 +1,21 @@
// Window rows 177-185, P1: the live business file, read only. Prints the plan's P1 values and nothing else:
// bus.tester, the plug-in lists, launch.by, whether launch.instances includes assistant, and each role's
// vars.harness and vars["fallback.harness"]. No other value from the file.
import { readFileSync } from 'node:fs';
const f = `${process.env.HOME}/.config/mosaic-dev/businesses/mosaic-stack.json`;
const b = JSON.parse(readFileSync(f, 'utf8'));
console.log(`bus.tester ${JSON.stringify(b.vars?.['bus.tester'])}`);
const p = b.plugins ?? {};
console.log(`plugins kinds ${JSON.stringify(Object.keys(p).sort())}`);
for (const k of ['usage-source', 'channel', 'harness']) console.log(`plugins ${k} ${JSON.stringify(p[k])}`);
console.log(`launch.by ${JSON.stringify(b.launch?.by)}`);
const inst = b.launch?.instances;
const names = Array.isArray(inst) ? inst.map((i) => (typeof i === 'string' ? i : i?.name)) : Object.keys(inst ?? {});
console.log(`launch.instances includes assistant ${names.includes('assistant')}`);
let pi = 0;
for (const [r, v] of Object.entries(b.roles ?? {}).sort()) {
const h = v?.vars?.harness, fh = v?.vars?.['fallback.harness'];
if (h === 'pi' || fh === 'pi') pi++;
console.log(`role ${r} vars.harness ${JSON.stringify(h)}${fh === undefined ? '' : ` fallback.harness ${JSON.stringify(fh)}`}`);
}
console.log(`roles naming pi ${pi}`);
+12
View File
@@ -0,0 +1,12 @@
#!/usr/bin/env bash
# Window rows 177-185, P1 (repin-rows-177-185.md draft 2, sha256 2194b9b0). Read only; nothing stopped.
# business-check.mjs runs from the repo copy, whose sha256 is the one SCRIPTS.sha256 lists for $P (P0).
set -uo pipefail
REPO=/mnt/storage/src/mosaic-stack WIN=$REPO/agents/darkwing/work/window-rows-177-185
OLD=89af38a83afa22a8a4f0d368a0a4030ad15a0efa
BC=$REPO/agents/sage/work/window-plan/repin-de352dfaf/business-check.mjs
echo "# plan sha256 $(sha256sum < $REPO/agents/sage/work/window-plan/repin-rows-177-185.md | cut -c1-64)"
echo "# business-check.mjs sha256 $(sha256sum < $BC | cut -c1-64); SCRIPTS.sha256 line: $(grep ' business-check.mjs$' $REPO/agents/sage/work/window-plan/repin-de352dfaf/SCRIPTS.sha256)"
echo "# business file sha256 $(sha256sum < $HOME/.config/mosaic-dev/businesses/mosaic-stack.json | cut -c1-64), mtime $(stat -c %y $HOME/.config/mosaic-dev/businesses/mosaic-stack.json)"
$WIN/w.sh P1.txt "P1 values" node $WIN/p1.mjs
$WIN/w.sh P1.txt "P1 business-check OLD" node $BC $OLD
@@ -0,0 +1,61 @@
# P3 rehearsal notes (Darkwing, 2026-10-20)
Plan: agents/sage/work/window-plan/repin-rows-177-185.md at 2ea05700b, sha256 2194b9b043210464add701a42df6c2abe295fef88b9131f2647fb6cfb9d98713, tool blob f8051d81cc18. OLD 89af38a83, NEW 490e2771d. Scratch root ~/darkwing-scratch/w185/p3, ports 47394-47397.
## Runs
| run | result | SHA256SUMS sha256 |
|---|---|---|
| run-20261020T073019Z | stopped at W3 on my check, not the tool | 95f0beb02dde9ac3d4e7927597d4e69410674b21e1f64ffb4a50206c8e19c00b |
| run-20261020T073102Z | stopped at W5 on my check, not the tool | 04b315663c6e0665574c1664a20c56f2779ff030e2f8548e57d7b59d3e209ba1 |
| run-20261020T073223Z | P3: PASS, 34 files | 31a78d5d76fb05a0322a04d07430ed1436f554d6e212a57a6c2e14fcc11421be |
Run 1. My W3 check read the plan through a filter that drops the `commits:` line, so the count check never matched. The plan printed `commits: the run adds 52`. Fixed with a fixed-string match on the raw output.
Run 2. My proof-flag check grepped the whole Console unit file and caught NEW's comment line that names `--proof-header`. Fixed by checking the ExecStart line only. The ExecStart had no proof flag in any run.
Each run starts from a fresh scratch root, so run 3 owes nothing to runs 1 and 2.
## Run 3 results
- W3 plan: from OLD, to NEW, `migration: none`, the backup line, 52 commits, the Console line carries OLD's flags with `--tester-port` and no proof flags, the bus line has `--pm`. Output in W3-plan.txt.
- W5: activation 20261020T073145Z-490e2771d3d0, the backup SHA256SUMS checked OK, the pointer is on NEW at 3i with pm true and OLD's Console flags, and both ExecStart lines name the NEW copy.
- W9: the tester's thread message was stored (W9g exit 1, no reply in 20 s); the thread read and threads list show it; reading the human thread refused thread-not-owned (exit 2).
- S2 against the W5 backup: priorSame false on no table, instance_names unchanged, S2 CHECK OK.
- (1) plain rollback: R1pre and R1 are EQUAL on all 16 tables, schema 3i, and no post-attempt store was made.
- (3) OLD reading NEW's rows: threads, agents and project list all exit 0 on OLD's CLI. R1 and R1post differ only in events (2 to 3).
- (2) restore: A2 activation 20261020T073217Z-490e2771d3d0, then one project made after its stamp. D3 stopTables is ["projects"]. R2b without --events refused post-backup-events with "0 event(s), 1 projects row(s) ... then --events 1" (exit 3). R2c with --events 1 restored, and the post-attempt store is kept.
- F: pointer on OLD at 3i, pm true, OLD's flags; the F snapshot against the A2 backup has empty stopTables and rowsAfter 0 on every table; p3-after-stamp is gone and window-p3 is kept.
- The live store stat was the same before and after, the docker network list did not change, and teardown left 0 processes with the four ports closed.
## Findings
1. O4, OLD's real Console on its tester port. These returned 200: /healthz, /api/config, and /api/bus/ inbox, tasks, agents, messages, usage, names, instances, routes, credentials. /api/instances returned 405 "method not allowed". /api/bus/decisions, /api/bus/limits and /api/bus/replies returned 400 "invalid-request". I think these four need a parameter or another method and my probe sent a bare GET, so I read this as a gap in my probe, not OLD failing on NEW's rows. I have not proved that.
2. OLD's reads in (3) added one event row (events 2 to 3). I did not identify which call wrote it. The live window's R1post compare should expect events to move.
## Scratch deviations
- The PM launch is emulated: a stand-in writes the `launched chief` journal line, and the chief holds no claim. So W9d's talk to chief refused tester-target-not-running (exit 3), where the live window expects it to be stored.
- The claude binary is a fixture, so no assistant reply can come in W9g.
- Scratch Console flags use scratch values (port 47394, tester port 47395, host p3.scratch.invalid, user p3-scratch-user). The checks compare against the scratch OLD pointer's own flags.
## Limits rerun and the route proofs (sage-1242, mos-0936, mos-0937, mos-0940)
All times come from date -u. Each run used a fresh root, moved aside when it finished (~/darkwing-scratch/w185/p3-lim1, p3-lim2, p3-proof1; the last root stays at p3/).
| output | what | SHA256SUMS sha256 |
|---|---|---|
| lim-20261020T074605Z | first attempt, exit 126: lim.sh was not executable, nothing past seed ran | b2997ea921183ad807ac8d8c9458bb8c1ae0d6a72a4ae82a89f1b94ea18d4d6c |
| lim-20261020T074644Z | three-point rerun, run 3's business file | 077dd1eb4a6d42b66f5e4fca901d1b5a77e987d388d037d6fe02c7988c1fa670 |
| lim-20261020T074854Z | proof 1: thinking "low" on every role, with A2 | 1d83557374a85f00f3542598b38e3183cb829bf714572d757cab33e806e15e98 |
| lim-20261020T074916Z | proof 2: proof 1 plus an emulated session with an exact provider and model | 8ae2bb4b2ae3e37d856bff73e5102685734c512f5ed42b91b4ada1040774e00e |
Limits. The rerun gave 400 invalid-request at A (OLD before NEW), B (NEW after W5) and C (OLD after R1b), on the Console tester port and on the CLI alike. view() throws "running session has no exact provider/model; reconcile before launching" (business/src/launch.mjs usageCounts). The session it trips on is the stand-in's emulated chief session, which had model "emulated" and no provider. Proof 2 gave 200 at all three points with no events row. NEW is not the cause.
Events. Every refused tester read writes one action.refused event (actor tester): each limits read while the emulated session lacked a provider, and each replies read with an unknown id. Run 3's seq 3 was O4's limits GET.
no-route. The example business file has no thinking var on any role, and bundle.mjs needs harness, model and thinking. With thinking set (both proofs), no-route is 0. The assistant thread's start then refuses no-session-credential on NEW after W5, on OLD after R1b and on NEW after A2, because scratch has no claude credential. OLD resolves a route for the thread NEW created.
Slips. The scratch repo clone was checked out to 89af38a83 by mistake after the rerun ended and was put back on 490e2771d from the reflog within a minute; no run used it in between. My status at 07:48Z to Mos was an estimate, not a date -u read. The rerun's driver log (lim-all-20261020T074628Z.log) sits in p3-lim1 rather than p3-lim2.
Scratch deviations for W11: the example has no thinking var, the stand-in's emulated session has no provider unless PM_EXACT is set, and scratch has no session credential.
@@ -0,0 +1,30 @@
1860a026ec30d685df2f21d8aee6922148a13979b1c3c1521f350a4cc4380af9 ./b/all.sh
6b25b038e3d7f4d12d169613a6f7e214d2c4f69e34fb98d446f4adad3876e44d ./b/claude
bbb6dbe133f9974db5e88def4cc2859d9d6a14c0cb58c4c3806a99f382ca3183 ./b/cmp.mjs
90e464119c5991480f097ad2904b8054bf1ef40297ac16e4a179ed4c4e83f5cb ./b/d3report.mjs
442470138e1a3d67fc8ab332c0319e9ad8d9eae0f8ee2ecf14c53f6a28519f69 ./b/env.sh
f41c5eb72f182ba052d5e2f2954bed223c10984e512afd68b405a2bb8790d944 ./b/ev.mjs
89c201e59d7bb6427041734757d170df7c9b39d96d8cb680a57fbb9eff5ab5aa ./b/healthz.cjs
fb1f19bb5a464d575ffdeb133a9a2df7fdf507218e683fddaaa170280d5c9fed ./b/jget.mjs
e1dcabdd3e43cae6f885a44fc1fe357406c00d896c03fff889f8b61dd4e15683 ./b/journalctl
f6b139e855575def6b4485f6a4737036773699e631a5956178740f29ddc0d34e ./b/lim-all.sh
2edffb03582975d530c7582f59f841bb3d85e6a43d97d5c413afa8792fae8c7b ./b/limits-diag.mjs
03776823419943dab8dbd7223c26957dcb642ed83e22e3cd8324f2dae65ca62c ./b/lim.sh
2aed6b4762c6f48cd338f15134822118dddac9ce6246ef5c4fc9faff6f80178e ./b/load-check.mjs
fb3036039d835dd93c22f2e0be93f3bac601b4e283e52fba95c3da5f363849c7 ./b/mkbiz.mjs
ee146f9feebcbc03a5b7582cc189321588c3e5cc12cd919df2062489e42eb5f1 ./b/prep.sh
75b88cbd68752a347e7e824f2464b4bf9d4bbd29267f76a045c2527c67bd8189 ./b/routes.mjs
493ca4ecd26d2a21de2bf24830241fba6275790de44d8c88a55770e78b30614e ./b/run.sh
d6ee41b67f97469f6dbabc95ee8a91b4724fd955b43bead0b65c4453eff287d2 ./b/s2check.mjs
84859226712d3d2858a3f30fb9b397c8ca01029a829829df4c06afb5a9262977 ./b/seed.sh
699029215ac022768dcd5c91877cd5f68bd5e2c37284a55ce6187ff44a68d3bf ./b/store-seed.mjs
e3669292be33f8b8298d9d1a00ba19e5ca42e3b50fc995007c9fc4587d0df6cd ./b/systemctl
30908331d33adead6e32b9da5b77ca29283f42446636527946541618331dc8a2 ./b/systemd-analyze
6e1a3fe0d11192fd62dd5fc0ff17afff02a41d9d4b2a64c8b854a25f7119ab3f ./b/teardown.sh
0a4884269bd5914075294e803262078bd7aa12c8b8e119b1aa7b8d58fb8c5f72 ./b/wait-run.sh
b3a59cabad922206911f53bbbd664c2409f1356947e580e97a990d60220d6f09 ./docker-networks-after.txt
b3a59cabad922206911f53bbbd664c2409f1356947e580e97a990d60220d6f09 ./docker-networks-before.txt
ad548514aa1e6baf360ac597596e780e76d46253a038e609ea530c9b09d5629f ./journal.jsonl
03e8f126cf15517ec0be03f9dac2c66cc1f639ed973f74a3066c3166faa53d54 ./lim-live-store-stat.txt
0a50a49e270bf011b82e5ed8a0c1e6e33d14e95a8cac982b184efd6dafc9fcb1 ./p3-steps.log
26c7a3b078f2aa08fb67d9dbdbf4a3de2d66d202c23e709ce3bf77fb8edc7eaa ./p3-teardown.txt
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# P3 driver: the live-store stat (mos-0878), prep, seed, run, teardown, the stat again, the Docker network
# names before and after, then the outputs copied to $WIN/p3 with SHA256SUMS. Started only behind
# wait-run.sh (busy.sh) on Sage's go. NEW=<sha> TP=1 for the run on the row 176 tool blob.
set -uo pipefail
. /home/jwoltje/darkwing-scratch/w185/p3/b/env.sh
[ ! -e "$R/out" ] || { echo "all.sh: $R/out exists; move it aside first" >&2; exit 1; }
mkdir -p "$R/out"
S=$R/out/p3-live-store-stat.txt
LIVE=(/home/jwoltje/.mosaic-dev/bus/bus.sqlite /home/jwoltje/.mosaic-dev/bus)
{ echo "# before prep, $(date -u +%FT%TZ)"; stat -c '%n %Y' "${LIVE[@]}"; } > "$S"
docker network ls --format '{{.Name}}' | sort > "$R/out/docker-networks-before.txt"
rc=0
"$B/prep.sh" && "$B/seed.sh" && "$B/run.sh" || rc=$?
"$B/teardown.sh"
docker network ls --format '{{.Name}}' | sort > "$R/out/docker-networks-after.txt"
{ echo "# after teardown, $(date -u +%FT%TZ)"; stat -c '%n %Y' "${LIVE[@]}"; } >> "$S"
cat "$S"
if [ "$(sed -n 2,3p "$S")" = "$(sed -n 5,6p "$S")" ]; then echo "live store stat: match"; else echo "live store stat: DIFFER"; rc=1; fi
if cmp -s "$R/out/docker-networks-before.txt" "$R/out/docker-networks-after.txt"; then echo "docker networks: unchanged"; else echo "docker networks: CHANGED"; diff "$R/out/docker-networks-before.txt" "$R/out/docker-networks-after.txt"; fi
echo "all.sh: run exit $rc"
W=$WIN/p3/run-$(date -u +%Y%m%dT%H%M%SZ)
mkdir -p "$W" && cp -p "$R"/out/*.txt "$R"/out/*.json "$R/out/p3-steps.log" "$W"/ 2>/dev/null; cp -pr "$R/out/snap" "$W"/
cp -p "$B"/*.sh "$B"/*.mjs "$B"/*.cjs "$B"/systemctl "$B"/journalctl "$B"/systemd-analyze "$B"/claude "$W"/ 2>/dev/null; mkdir -p "$W/b" && mv "$W"/*.sh "$W"/*.mjs "$W"/*.cjs "$W"/systemctl "$W"/journalctl "$W"/systemd-analyze "$W"/claude "$W/b"/ 2>/dev/null
rm -f "$W/last.txt"
(cd "$W" && find . -type f ! -name SHA256SUMS | sort | xargs sha256sum > SHA256SUMS)
echo "outputs: $W ($(wc -l < "$W/SHA256SUMS") files, SHA256SUMS sha256 $(sha256sum < "$W/SHA256SUMS" | cut -c1-64))"
exit $rc
@@ -0,0 +1,3 @@
#!/bin/sh
# Fake claude for the scratch rehearsal. It never runs a model; the PM launch is emulated.
echo "9.9.9 (Claude Code fixture)"
@@ -0,0 +1,18 @@
// P3: compares two snapshot.mjs outputs table by table, over every table either one lists (rows,
// maxSeq, sha256 where printed, and byRun for usage_records). Across a schema change meta compares
// on rows only, since the migration sets schema_version and schema_digest.
// usage: node cmp.mjs A.json B.json. Prints one line per table and exits 1 if any differ.
import { readFileSync } from 'node:fs';
const [a, b] = process.argv.slice(2).map((p) => JSON.parse(readFileSync(p, 'utf8')));
const short = (h) => (h === undefined ? 'counts-only' : h.slice(0, 12));
let differ = 0;
for (const t of [...new Set([...Object.keys(a.tables), ...Object.keys(b.tables)])].sort()) {
const x = a.tables[t], y = b.tables[t];
if (!x || !y) { console.log(`DIFFER ${t}: only in ${x ? 'A' : 'B'}`); differ = 1; continue; }
const keys = t === 'meta' && a.schema !== b.schema ? ['rows'] : ['rows', 'maxSeq', 'sha256'];
const same = keys.every((k) => x[k] === y[k]) && JSON.stringify(x.byRun ?? null) === JSON.stringify(y.byRun ?? null);
console.log(`${same ? 'EQUAL' : 'DIFFER'} ${t}: rows ${x.rows}/${y.rows}, maxSeq ${x.maxSeq}/${y.maxSeq}, sha256 ${short(x.sha256)}/${short(y.sha256)}${keys.length === 1 ? ' (rows only across the schema change)' : ''}`);
if (!same) differ = 1;
}
console.log(`schema ${a.schema}/${b.schema}`);
process.exit(differ);
@@ -0,0 +1,22 @@
// P3, D3 (plan draft 7, mos-0899 A1): the stop decision from one snapshot.mjs --after --backup output.
// Prints stopTables, every table with priorSame or same false, every nonzero rowsAfter, every
// untimed table whose rows differ from the backup's, and eventsAfter. Counts and table names only.
// usage: node d3report.mjs D3.json (or the document on stdin). Exits 0 when stopTables is empty, 3 when it is not, 1 on a bad document.
import { readFileSync } from 'node:fs';
const s = JSON.parse(readFileSync(process.argv[2] ?? 0, "utf8"));
if (!Array.isArray(s.stopTables) || !s.tables || !Array.isArray(s.untimed)) {
console.log('D3 report: not a --backup snapshot');
process.exit(1);
}
const t = Object.entries(s.tables);
const changed = t.filter(([, x]) => x.vsBackup && (x.vsBackup.priorSame === false || x.vsBackup.same === false)).map(([n]) => n);
const after = t.filter(([, x]) => (x.rowsAfter ?? 0) > 0).map(([n, x]) => `${n} ${x.rowsAfter}`);
const untimed = s.untimed.filter((n) => s.tables[n].rows !== s.tables[n].vsBackup?.backupRows)
.map((n) => `${n} ${s.tables[n].vsBackup?.backupRows ?? 'absent'}->${s.tables[n].rows}`);
console.log(`D3 stopTables ${JSON.stringify(s.stopTables)}${s.onlyInBackup?.length ? `; only in the backup ${JSON.stringify(s.onlyInBackup)}` : ''}`);
console.log(`D3 priorSame or same false: ${changed.join(', ') || 'none'}`);
console.log(`D3 rowsAfter nonzero: ${after.join(', ') || 'none'}`);
console.log(`D3 untimed count differences (backup->now): ${untimed.join(', ') || 'none'}`);
console.log(`D3 eventsAfter ${s.eventsAfter}`);
console.log(s.stopTables.length ? 'D3 STOP (mos-0899 A1): stopTables is not empty; a real window stops here before the restore' : 'D3 stopTables empty: only events can have moved');
process.exit(s.stopTables.length ? 3 : 0);
@@ -0,0 +1,23 @@
# Sourced by every P3 script (window rows 177-185, repin-rows-177-185.md P3): the scratch root and a clean env
# for every call. OLD is the live copy 89af38a83 at 3i, NEW is row 185's landing 490e2771d.
R=/home/jwoltje/darkwing-scratch/w185/p3
B=$R/b
RB=$R/root
REPO=$R/repo
COPIES=$R/copies
OLD=89af38a83afa22a8a4f0d368a0a4030ad15a0efa
NEW=490e2771d3d057ab76991e2c7859cd251857e64c
PORT=47394
TPORT=47395
# (3): OLD's real Console, started by hand after the plain rollback, on two more scratch ports.
OPORT=47396
OTPORT=47397
WIN=/mnt/storage/src/mosaic-stack/agents/darkwing/work/window-rows-177-185
SNAP=$WIN/snapshot.mjs
SNAP_SHA=639fd28a2674aaa03ce824e3394b4052f21cdef7d6fa49bae61a9927ed341e67
LOGF=$R/out/p3-steps.log
# env -i keeps every live MOSAIC_*, XDG_*, credential and pi variable out; HOME is scratch too.
E=(env -i HOME=$RB/home PATH=$B:/usr/local/bin:/usr/bin:/bin TMPDIR=$RB/tmp LANG=C.UTF-8
MOSAIC_CONFIG=$RB/cfg/config.json XDG_CONFIG_HOME=$RB/xcfg XDG_DATA_HOME=$RB/xdata
XDG_STATE_HOME=$RB/xstate XDG_RUNTIME_DIR=$RB/run SD_STATE=$RB/sd SD_DATA=$RB/data)
step() { echo "$(date -u +%FT%TZ) $*" >> "$LOGF"; }
@@ -0,0 +1,8 @@
// Limits rerun (mos-0934 item 2): the events rows after seq N on a scratch store, kind, actor and at only, never
// the body or subject. The last line is "max <seq>". usage: node ev.mjs DB N
import { DatabaseSync } from "node:sqlite";
const [db, after] = process.argv.slice(2);
const d = new DatabaseSync(db, { readOnly: true });
for (const r of d.prepare("select seq, kind, actor_role, actor_run, at from events where seq > ? order by seq").all(Number(after)))
console.log(`event ${r.seq} ${r.kind} actor ${r.actor_role ?? "null"}/${r.actor_run ?? "null"} at ${r.at}`);
console.log(`max ${d.prepare("select coalesce(max(seq), 0) as m from events").get().m}`);
@@ -0,0 +1,7 @@
// The Console stand-in: 200 on /healthz, 404 otherwise, on 127.0.0.1:<port>, and on 127.0.0.1:<tester port>
// too when the unit's ExecStart has --tester-port (merged window P3). Writes healthz-<pid> once every port listens.
const http=require('node:http'),fs=require('node:fs'),path=require('node:path');
const ports=process.argv.slice(2).filter(Boolean).map(Number);let up=0;
for(const p of ports)http.createServer((q,r)=>{r.writeHead(q.url==='/healthz'?200:404);r.end(q.url==='/healthz'?'ok\n':'');})
.listen(p,'127.0.0.1',()=>{if(++up===ports.length)fs.writeFileSync(path.join(process.env.SD_STATE,`healthz-${process.pid}`),'');});
process.on('SIGTERM',()=>process.exit(0));
@@ -0,0 +1,6 @@
// P3: prints one field of a JSON document on stdin, by a dotted path (a.b.c); "" when absent.
// usage: node jget.mjs PATH < doc.json
import { readFileSync } from 'node:fs';
let v = JSON.parse(readFileSync(0, 'utf8'));
for (const k of process.argv[2].split('.')) v = v == null ? undefined : v[k];
process.stdout.write(v === undefined ? '' : typeof v === 'string' ? v : JSON.stringify(v));
@@ -0,0 +1,10 @@
#!/usr/bin/node
// Stand-in `journalctl --user -u UNIT --since @EPOCH -o cat` over $SD_STATE/journal.jsonl (the test's).
const fs=require('node:fs'),path=require('node:path');
const sd=process.env.SD_STATE,a=process.argv.slice(2);let unit='',since='';
for(let i=0;i<a.length;i++){if(a[i]==='-u')unit=a[++i];else if(a[i]==='--since')since=a[++i]}
fs.appendFileSync(path.join(sd,'journal-since.log'),since+'\n');
if(!/^@\d+$/.test(since))process.exit(1);
const from=Number(since.slice(1));
let lines=[];try{lines=fs.readFileSync(path.join(sd,'journal.jsonl'),'utf8').split('\n').filter(Boolean).map((l)=>JSON.parse(l))}catch{}
for(const o of lines)if(o.unit===unit&&o.epoch>=from)console.log(o.text);
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Limits rerun driver (sage-1242, mos-0936): the live-store stat, prep, seed, lim.sh, teardown (and the two
# extra Console ports closed), the stat again, Docker network names before and after, then the outputs
# copied to $WIN/p3/lim-<stamp> with SHA256SUMS. Run 3's state was moved to ~/darkwing-scratch/w185/p3-run3.
set -uo pipefail
. /home/jwoltje/darkwing-scratch/w185/p3/b/env.sh
[ ! -e "$R/out" ] || { echo "lim-all.sh: $R/out exists; move it aside first" >&2; exit 1; }
mkdir -p "$R/out"
S=$R/out/lim-live-store-stat.txt
LIVE=(/home/jwoltje/.mosaic-dev/bus/bus.sqlite /home/jwoltje/.mosaic-dev/bus)
{ echo "# before prep, $(date -u +%FT%TZ)"; stat -c '%n %Y' "${LIVE[@]}"; } > "$S"
docker network ls --format '{{.Name}}' | sort > "$R/out/docker-networks-before.txt"
rc=0
"$B/prep.sh" && "$B/seed.sh" && "$B/lim.sh" || rc=$?
"$B/teardown.sh"
for p in 47398 47399; do if (exec 3<>/dev/tcp/127.0.0.1/$p) 2>/dev/null; then echo "port $p open"; rc=1; else echo "port $p closed"; fi; done | tee -a "$R/out/p3-teardown.txt"
docker network ls --format '{{.Name}}' | sort > "$R/out/docker-networks-after.txt"
{ echo "# after teardown, $(date -u +%FT%TZ)"; stat -c '%n %Y' "${LIVE[@]}"; } >> "$S"
cat "$S"
if [ "$(sed -n 2,3p "$S")" = "$(sed -n 5,6p "$S")" ]; then echo "live store stat: match"; else echo "live store stat: DIFFER"; rc=1; fi
if cmp -s "$R/out/docker-networks-before.txt" "$R/out/docker-networks-after.txt"; then echo "docker networks: unchanged"; else echo "docker networks: CHANGED"; diff "$R/out/docker-networks-before.txt" "$R/out/docker-networks-after.txt"; fi
echo "lim-all.sh: run exit $rc"
W=$WIN/p3/lim-$(date -u +%Y%m%dT%H%M%SZ)
mkdir -p "$W/b" && cp -p "$R"/out/*.txt "$R"/out/*.log "$W"/ 2>/dev/null
cp -p "$RB/data/launches/mosaic-stack.jsonl" "$W/launches-mosaic-stack.jsonl" 2>/dev/null
cp -p "$RB/sd/journal.jsonl" "$W/journal.jsonl" 2>/dev/null
cp -p "$B"/*.sh "$B"/*.mjs "$B"/*.cjs "$B"/systemctl "$B"/journalctl "$B"/systemd-analyze "$B"/claude "$W/b"/ 2>/dev/null
rm -f "$W/last.txt" "$W/get.body"
(cd "$W" && find . -type f ! -name SHA256SUMS | sort | xargs sha256sum > SHA256SUMS)
echo "outputs: $W ($(wc -l < "$W/SHA256SUMS") files, SHA256SUMS sha256 $(sha256sum < "$W/SHA256SUMS" | cut -c1-64))"
exit $rc
@@ -0,0 +1,119 @@
#!/usr/bin/env bash
# Limits rerun (sage-1242, mos-0934, mos-0936): on a fresh root seed.sh made at OLD and 3i, GET /api/bus/limits,
# decisions?list=recent and replies?id= on a real Console's tester port at three points: (A) OLD before NEW ever
# runs, (B) NEW after its activate --pm and a tester thread message, (C) OLD after a plain rollback. At each point
# also OLD's or NEW's `limits --tester --json` CLI read and limits-diag.mjs (the copy's view() with no bus), and
# the events rows each request adds (kind, actor, at only). Scratch paths only; Consoles on scratch ports.
set -uo pipefail
. /home/jwoltje/darkwing-scratch/w185/p3/b/env.sh
OUT=$R/out/lim.txt
NPORT=47398
NTPORT=47399
DB=$RB/data/bus/bus.sqlite
SR=(scripts/stack-release.sh)
CB=(--copies "$COPIES" --bin "$RB/bin")
C_OLD=(node "$COPIES/$OLD/packages/cli/src/cli.mjs")
C_NEW=(node "$COPIES/$NEW/packages/cli/src/cli.mjs")
BZ=(--business mosaic-stack)
[ ! -e "$OUT" ] || { echo "lim.sh: $OUT exists" >&2; exit 1; }
say() { echo "$*" >> "$OUT"; step "$*"; }
stop() { say "# STOP: $*"; exit 1; }
listens() { (exec 3<>/dev/tcp/127.0.0.1/$1) 2>/dev/null; }
pointer() { node "$B/jget.mjs" "$1" < "$RB/data/state/stack-active.json"; }
SEQ=0
ev() { # ev LABEL: the events rows written since the last ev call
local r; r="$(node "$B/ev.mjs" "$DB" "$SEQ" 2>&1)" || { say "# ev $1: could not read the store: $r"; return; }
local rows; rows="$(grep '^event ' <<< "$r")"
say "# events after $1: ${rows:-none new}"
SEQ="$(sed -n 's/^max //p' <<< "$r")"
}
part() { # part TITLE CMD...: one labeled call with its output and exit code; RC holds the exit code
local title="$1"; shift
{ echo; echo "## $title"; } >> "$OUT"; step "run: $*"
RC=0; (cd "$REPO" && "${E[@]}" "$@") > "$R/out/last.txt" 2>&1 || RC=$?
cat "$R/out/last.txt" >> "$OUT"; echo "# exit $RC" >> "$OUT"; step "exit $RC"
ev "$title"
}
get() { # get PORT PATH: the status, the body's bytes and the body (first 600 characters)
local code; code="$(curl -s -m 10 -o "$R/out/get.body" -w '%{http_code}' "http://127.0.0.1:$1$2")"
say "GET :$1$2 -> $code, $(wc -c < "$R/out/get.body") bytes: $(head -c 600 "$R/out/get.body")"
ev "GET $2"
}
CP=
console_up() { # console_up LABEL COPY PORT TPORT [EXTRA...]: a copy's real webui serve started by hand
local label="$1" copy="$2" port="$3" tport="$4"; shift 4
for p in "$port" "$tport"; do listens "$p" && stop "port $p is in use"; done
(cd "$REPO" && exec "${E[@]}" node "$COPIES/$copy/packages/webui/src/cli.mjs" serve --port "$port" --tester-port "$tport" --business mosaic-stack "$@") > "$R/out/console-$label.log" 2>&1 &
CP=$!; echo "console-$label $CP" >> "$RB/sd/pids.log"
for _ in $(seq 1 50); do listens "$tport" && break; sleep 0.2; done
listens "$tport" || { kill "$CP" 2>/dev/null; stop "$label: the Console did not listen on $tport"; }
say "# $label: $(git -C "$REPO" rev-parse --short=9 "$copy") Console up, pid $CP, ports $port and $tport${*:+, extra flags $*}"
ev "$label Console start"
}
console_down() { # console_down LABEL PORT TPORT
kill "$CP" 2>/dev/null; wait "$CP" 2>/dev/null
for p in "$2" "$3"; do listens "$p" && stop "$1: port $p still listens after the kill"; done
{ echo "# $1 Console log:"; sed 's/^/# /' "$R/out/console-$1.log"; } >> "$OUT"
ev "$1 Console stop"
}
diag() { part "$1 limits-diag.mjs on $(git -C "$REPO" rev-parse --short=9 "$2") (the copy's view(), no bus)" node "$B/limits-diag.mjs" "$COPIES/$2" "$RB"; }
point() { # point LABEL COPY PORT TPORT MID [EXTRA...]: the three reads on the Console's tester port, then the CLI read
local label="$1" copy="$2" port="$3" tport="$4" mid="$5"; shift 5
{ echo; echo "## $label. GETs on the tester port of the $(git -C "$REPO" rev-parse --short=9 "$copy") Console"; } >> "$OUT"
console_up "$label" "$copy" "$port" "$tport" "$@"
get "$tport" /api/bus/limits
get "$tport" "/api/bus/decisions?list=recent"
get "$tport" "/api/bus/replies?id=$mid"
console_down "$label" "$port" "$tport"
part "$label CLI limits --tester --json ($(git -C "$REPO" rev-parse --short=9 "$copy"))" node "$COPIES/$copy/packages/cli/src/cli.mjs" limits "${BZ[@]}" --tester --json
diag "$label" "$copy"
}
{
echo "# SCRATCH ROOT, NOT LIVE. Window rows 177-185, limits rerun (sage-1242, mos-0934, mos-0936), run by darkwing from $(date -u +%FT%TZ)."
echo "# Fresh root from prep.sh and seed.sh: OLD $OLD at 3i, NEW $NEW, tool blob $(git -C "$REPO" rev-parse HEAD:scripts/stack-release.sh)."
echo "# Real Consoles started by hand: OLD on $OPORT/$OTPORT, NEW on $NPORT/$NTPORT with --checkout $REPO. Events: kind, actor and at only."
} > "$OUT"
step "--- lim.sh"
[ "$(pointer copy)" = "$OLD" ] || stop "the seeded pointer is not on OLD"
ev "seed (rows before point A)"
# Before NEW ever runs no message exists, so (A) asks replies for a well-formed id that names nothing.
NOID=00000000-0000-4000-8000-000000000000
# (A) OLD before NEW
point A "$OLD" "$OPORT" "$OTPORT" "$NOID"
# NEW: activate --pm, then a tester thread message
part "W5. activate NEW --pm" "${SR[@]}" activate "$NEW" mosaic-stack --pm "${CB[@]}"
[ "$RC" = 0 ] && [ "$(pointer copy)" = "$NEW" ] || stop "W5 did not activate NEW"
part "W9e. project create window-p3 as the tester (NEW's CLI)" "${C_NEW[@]}" project create window-p3 "${BZ[@]}" --tester
[ "$RC" = 0 ] || stop "W9e exit $RC"
part "W9f. thread create window-p3 w9 as the tester (NEW's CLI)" "${C_NEW[@]}" thread create window-p3 w9 "${BZ[@]}" --tester
TID="$(sed -n 's/^created window-p3\/w9 id \([0-9a-f-]*\) instance .*$/\1/p' "$R/out/last.txt")"
[ -n "$TID" ] || stop "W9f: no thread id"
part "W9g. talk --thread as the tester, --wait 0 (NEW's CLI)" "${C_NEW[@]}" talk --thread "$TID" "[lim test] reply with one line, then stop" --wait 0 "${BZ[@]}" --tester
part "W9j. thread read --json as the tester (NEW's CLI)" "${C_NEW[@]}" thread read "$TID" --json "${BZ[@]}" --tester
MID="$(node -e 'const t=JSON.parse(require("fs").readFileSync(0,"utf8"));process.stdout.write(t[0]?.id??"")' < "$R/out/last.txt")"
[ -n "$MID" ] || stop "W9j: no message id"
say "# thread $TID, message $MID"
# (B) NEW after W5
point B "$NEW" "$NPORT" "$NTPORT" "$MID" --checkout "$REPO"
# (C) OLD after a plain rollback, with the (3) CLI reads first as in P3
part "R1b. rollback" "${SR[@]}" rollback mosaic-stack "${CB[@]}"
[ "$RC" = 0 ] && [ "$(pointer copy)" = "$OLD" ] || stop "R1b did not roll back to OLD"
part "O1. threads --tester --json (OLD's CLI)" "${C_OLD[@]}" threads --json "${BZ[@]}" --tester
part "O2. agents --tester --json (OLD's CLI)" "${C_OLD[@]}" agents "${BZ[@]}" --tester --json
part "O3. project list --tester --json (OLD's CLI)" "${C_OLD[@]}" project list --json "${BZ[@]}" --tester
point C "$OLD" "$OPORT" "$OTPORT" "$MID"
{ echo; echo "## the bus journal: lines naming limit, refused or no-route"; } >> "$OUT"
grep -iE 'limit|refus|no-route' "$RB/sd/journal.jsonl" >> "$OUT" || echo "(none)" >> "$OUT"
{ echo; echo "## sessions.json (run, instance, model, launchedBy only)"; } >> "$OUT"
node -e 'const d=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));for(const s of d.sessions)console.log(JSON.stringify({run:s.run,instance:s.instance,model:s.model,launchedBy:s.launchedBy}))' "$RB/data/sessions.json" >> "$OUT" 2>&1 || echo "(no sessions.json)" >> "$OUT"
{ echo; echo "## the launches log: thread start refusals (at, instance, code, why)"; } >> "$OUT"
node -e 'for(const l of require("fs").readFileSync(process.argv[1],"utf8").split("\n").filter(Boolean)){const j=JSON.parse(l);if(j.event==="refused")console.log(j.at,j.instance,j.code,j.why)}' "$RB/data/launches/mosaic-stack.jsonl" >> "$OUT" 2>&1 || echo "(no launches log)" >> "$OUT"
{ echo; echo "## the pointer moves (activation log: at, op, result, to)"; } >> "$OUT"
node -e 'for(const l of require("fs").readFileSync(process.argv[1],"utf8").split("\n").filter(Boolean)){const j=JSON.parse(l);console.log(j.at,j.op,j.result,"to",String(j.to).slice(0,9))}' "$RB/data/state/stack-activation-log.jsonl" >> "$OUT" 2>&1
say "# lim.sh: done"
@@ -0,0 +1,28 @@
// Limits diagnosis (mos-0934, sage-1242): call a copy's launchSettings view the way its bus runtime does, on a
// scratch root, once with readSessions and once with no sessions, and print the error's name and code or the
// instance list. Read only. usage: node limits-diag.mjs COPY ROOT
import { readFileSync } from "node:fs";
import { join } from "node:path";
const [copy, root] = process.argv.slice(2);
const { launchSettings } = await import(join(copy, "packages/business/src/launch-settings.mjs"));
const { systemVars, resolveInstance } = await import(join(copy, "packages/business/src/resolve.mjs"));
const { readSessions } = await import(join(copy, "packages/seat/src/session.mjs"));
const { loadBusiness } = await import(join(copy, "packages/business/src/index.mjs"));
const system = JSON.parse(readFileSync(join(root, "cfg/config.json"), "utf8"));
const vars = systemVars(system);
const org = { dir: join(root, "cfg"), rolesDir: join(copy, "roles"), system: vars };
const b = loadBusiness("mosaic-stack", { dir: org.dir, rolesDir: org.rolesDir });
const models = Object.keys(b.launch?.models ?? {});
console.log(`launch.models keys: ${JSON.stringify(models)}`);
for (const instance of Object.keys(b.roles)) {
const v = resolveInstance({ system: vars, business: b, instance }).vars;
console.log(`instance ${instance}: model ${JSON.stringify(v.model)}${v.model !== undefined && !models.includes(v.model) ? " NOT IN launch.models" : ""}`);
}
for (const [label, sessions] of [["readSessions", () => readSessions(system.dataRoot)], ["no sessions", () => []]]) {
try {
const r = launchSettings({ ...org, sessions, levels: () => null }).read("mosaic-stack");
console.log(`view with ${label}: ok, instances ${Object.keys(r.instances).length}`);
} catch (e) {
console.log(`view with ${label}: ${e.name} code ${JSON.stringify(e.code)} message ${JSON.stringify(String(e.message).slice(0, 200))}`);
}
}
@@ -0,0 +1,18 @@
// business-check-style load (repin-de352dfaf/business-check.mjs) of a scratch business file under COPY's own
// loader and roles. usage: node load-check.mjs COPY_DIR CONFIG_DIR. Prints counts, plug-in kinds, HOLDS lines, LOAD_OK or REFUSED.
const [copy, dir] = process.argv.slice(2);
const { loadBusiness } = await import(`${copy}/packages/business/src/business.mjs`);
const { resolveInstance } = await import(`${copy}/packages/business/src/resolve.mjs`);
try {
const b = loadBusiness('mosaic-stack', { dir, rolesDir: `${copy}/roles` });
const count = (o) => Object.keys(o ?? {}).length;
console.log(`copy ${copy.split('/').pop().slice(0, 9)}: roles ${count(b.roles)}, projects ${count(b.projects)}, arbiters ${count(b.arbiters)}, plugins ${JSON.stringify(b.plugins)}`);
for (const instance of ['chief', 'pm']) {
const { limits: { authority: a } } = resolveInstance({ system: {}, business: b, instance });
console.log(`HOLDS ${instance} instance.name ${[...a.withinRole, ...a.crossRole].includes('instance.name') ? 'yes' : 'no'}`);
}
console.log('LOAD_OK');
} catch (e) {
console.log(`REFUSED ${e.message}`);
process.exit(1);
}
@@ -0,0 +1,27 @@
// Scratch business file for P3: the committed example, made loadable, with the given plug-ins (mos-0847).
// Bot ids become distinct positive integers, date placeholders become 2027-01-01, every credential file
// points at a path under NOSECRET that does not exist, and both base URLs go to 127.0.0.1:9 (discard),
// so a scratch bus never reaches the live tracker or Gitea. As the live file (P1): `bus.tester` is true,
// every role's `harness` var is claude-code, and launch.instances includes assistant.
// usage: node mkbiz.mjs EXAMPLE NOSECRET PLUGINS_JSON
import { readFileSync } from 'node:fs';
import { basename } from 'node:path';
const [example, nosecret, plugins] = process.argv.slice(2);
const b = JSON.parse(readFileSync(example, 'utf8'));
let id = 0;
const walk = (o) => {
for (const [k, v] of Object.entries(o)) {
if (k === 'botId') o[k] = ++id;
else if (v === 'YYYY-MM-DD') o[k] = '2027-01-01';
else if (k === 'file' && typeof v === 'string') o[k] = `${nosecret}/${basename(v)}`;
else if (v && typeof v === 'object') walk(v);
}
};
walk(b);
b.vars['tracker.baseUrl'] = 'http://127.0.0.1:9';
b.vars['gitea.baseUrl'] = 'http://127.0.0.1:9';
b.vars['bus.tester'] = true;
for (const role of Object.values(b.roles)) role.vars = { ...(role.vars ?? {}), harness: 'claude-code' };
if (!b.launch.instances.includes('assistant')) b.launch.instances.push('assistant');
b.plugins = JSON.parse(plugins);
process.stdout.write(JSON.stringify(b, null, 2) + '\n');
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# P3, prep: the tool clone at NEW, the two copies and the scratch business file. Scratch paths only; the
# canonical checkout is read through git clone --shared. No patch is applied: the tool is NEW's own
# stack-release.sh, blob f8051d81cc18.
set -euo pipefail
. /home/jwoltje/darkwing-scratch/w185/p3/b/env.sh
[ ! -e "$REPO" ] && [ ! -e "$COPIES" ] && [ ! -e "$R/bfiles" ] || { echo "prep.sh: $R already has a repo, copies or bfiles" >&2; exit 1; }
mkdir -p "$R/out"
git clone -q --shared --no-checkout /mnt/storage/src/mosaic-stack "$REPO"
git -C "$REPO" checkout -q --detach "$NEW"
git -C "$REPO" branch -f refactor "$NEW"
step "tool clone $REPO at $(git -C "$REPO" rev-parse HEAD), refactor $(git -C "$REPO" rev-parse refactor); scripts/stack-release.sh blob $(git -C "$REPO" rev-parse HEAD:scripts/stack-release.sh)"
[ "$(git -C "$REPO" rev-parse HEAD:scripts/stack-release.sh)" = f8051d81cc18aedb7b6f2e743ec0c3f505e0e44b ] || { step "tool blob is not f8051d81cc18"; exit 1; }
mkdir -p "$COPIES"
for c in "$OLD" "$NEW"; do (cd "$REPO" && scripts/stack-copy.sh copy "$c" --copies "$COPIES") >> "$LOGF" 2>&1; step "copy $c: exit 0"; done
mkdir -p "$R/bfiles/B/businesses"
EX="$REPO/packages/business/examples/mosaic-stack.example.json"
step "example blob $(git -C "$REPO" hash-object "$EX")"
node "$B/mkbiz.mjs" "$EX" "$R/bfiles/nosecret" '{"usage-source":["transcript"],"channel":["cli","webui"],"harness":["claude-code"]}' > "$R/bfiles/B/businesses/mosaic-stack.json"
step "business file (the live plug-ins, assistant launched) $(sha256sum < "$R/bfiles/B/businesses/mosaic-stack.json" | cut -c1-64)"
@@ -0,0 +1,11 @@
// Each instance's resolved route vars on a scratch root (harness, model, thinking). usage: node routes.mjs COPY ROOT
import { readFileSync } from "node:fs";
const [copy, root] = process.argv.slice(2);
const { systemVars, resolveInstance } = await import(copy + "/packages/business/src/resolve.mjs");
const { loadBusiness } = await import(copy + "/packages/business/src/index.mjs");
const sys = JSON.parse(readFileSync(root + "/cfg/config.json", "utf8"));
const b = loadBusiness("mosaic-stack", { dir: root + "/cfg", rolesDir: copy + "/roles" });
for (const i of Object.keys(b.roles)) {
const v = resolveInstance({ system: systemVars(sys), business: b, instance: i }).vars;
console.log(i, JSON.stringify({ harness: v.harness, model: v.model, thinking: v.thinking ?? "(unset)" }));
}
@@ -0,0 +1,304 @@
#!/usr/bin/env bash
# Window rows 177-185, P3 (repin-rows-177-185.md at 2ea05700b, mos-0931, sage-1238): the rehearsal on the root
# seed.sh made at OLD 89af38a83 and 3i, on NEW 490e2771d's tool blob. Order: the load checks, W1 and S1, W2,
# W3 and W5 (one activate --pm, no --migrate), the W9 tester thread flow, S2 against the W5 backup, then the
# three rollbacks: (1) a plain rollback to OLD, (3) OLD's CLI and OLD's real Console reading NEW's thread rows,
# (2) activate NEW again, one tester project create after the stamp, D3, rollback --restore --plan, the run
# without --events (post-backup-events with the per-table counts), the rerun with --events N. Every
# stack-release call names the scratch --copies and --bin. Scratch paths only.
set -uo pipefail
. /home/jwoltje/darkwing-scratch/w185/p3/b/env.sh
OUT=$R/out/p3.txt
SN=$R/out/snap
[ ! -e "$OUT" ] || { echo "run.sh: $OUT exists" >&2; exit 1; }
[ -f "$RB/data/state/stack-active.json" ] || { echo "run.sh: no seeded root" >&2; exit 1; }
mkdir -p "$SN"
SR=(scripts/stack-release.sh)
CB=(--copies "$COPIES" --bin "$RB/bin")
C_OLD=(node "$COPIES/$OLD/packages/cli/src/cli.mjs")
C_NEW=(node "$COPIES/$NEW/packages/cli/src/cli.mjs")
BZ=(--business mosaic-stack)
DB=$RB/data/bus/bus.sqlite
UNIT_BUS=$RB/xcfg/systemd/user/[email protected]
UNIT_WEB=$RB/xcfg/systemd/user/mosaic-webui.service
HUMAN_THREAD=b3a7c1d2-0000-4000-8000-000000000001
part() { # part TITLE CMD... : one labeled part of the transcript, with its command and exit code
local title="$1"; shift
{ echo; echo "## $title"; echo "# command: (cd $REPO && <scratch env> $*)"; } >> "$OUT"
step "run: $*"
local rc=0; (cd "$REPO" && "${E[@]}" "$@") > "$R/out/last.txt" 2>&1 || rc=$?
cat "$R/out/last.txt" >> "$OUT"
echo "# exit $rc" >> "$OUT"; step "exit $rc"; RC=$rc
# mos-0902 (P19 391): a rollback in the same second as the activate before it can refuse capture-failed.
# Wait a second and retry once; both attempts stay in the transcript.
if [ "$rc" != 0 ] && [ -z "${RETRIED:-}" ] && grep -qF 'refusing capture-failed' "$R/out/last.txt"; then
note "capture-failed on the first attempt (mos-0902); one retry after 1 s"
sleep 1; RETRIED=1 part "$title (retry after capture-failed)" "$@"
fi
}
note() { echo "# $*" >> "$OUT"; step "$*"; }
stop() { note "STOP: $*"; exit 1; }
expect() { [ "$RC" = "$1" ] || stop "expected exit $1, got $RC"; }
# The last part's output, less a plan's commits: block (its subject lines name refusal codes), so has and
# lacks read only what the tool itself says.
body() { awk '/^commits:/{c=1;next} c && /^ /{next} {c=0;print}' "$R/out/last.txt"; }
has() { grep -qF -- "$1" <(body); } # the last part's output holds this text
lacks() { ! grep -qF -- "$1" <(body); }
ex_web() { sed -n 's/^ExecStart=//p' "$UNIT_WEB"; } # the unit's ExecStart only: NEW's unit has a comment naming --proof-header
ex_bus() { sed -n 's/^ExecStart=//p' "$UNIT_BUS"; }
lastlog() { tail -1 "$RB/data/state/stack-activation-log.jsonl"; }
pointer() { node "$B/jget.mjs" "$1" < "$RB/data/state/stack-active.json"; }
console_flags() { node -e 'const p=JSON.parse(require("fs").readFileSync(0,"utf8"));process.stdout.write((p.console??[]).join(" "))' < "$RB/data/state/stack-active.json"; }
listens() { (exec 3<>/dev/tcp/127.0.0.1/$1) 2>/dev/null; }
state() { # the pointer, the unit ExecStarts after a part
note "pointer: $(cat "$RB/data/state/stack-active.json")"
note "bus unit ExecStart: $(sed -n 's/^ExecStart=//p' "$UNIT_BUS")"
note "Console unit ExecStart: $(sed -n 's/^ExecStart=//p' "$UNIT_WEB")"
note "activation log, last line: $(lastlog)"
}
snap() { # snap NAME ARGS... : snapshot.mjs on the scratch store, the output kept in out/snap/NAME.json
local name="$1"; shift
local sha; sha="$(sha256sum "$SNAP" | cut -c1-64)"
note "snapshot.mjs sha256 before $name: $sha"
[ "$sha" = "$SNAP_SHA" ] || stop "snapshot.mjs is $sha, not the approved $SNAP_SHA; $name not run"
part "snapshot $name" node "$SNAP" --store "$DB" "$@"
cp "$R/out/last.txt" "$SN/$name.json"
}
same() { # same A B : the two snapshots match on every table
part "compare $1 with $2" node "$B/cmp.mjs" "$SN/$1.json" "$SN/$2.json"
}
markers() { # names only, never values: run markers in this shell's ancestors (the tester walk refuses them)
local p=$$ n=0
while [ "$p" -gt 1 ]; do
tr '\0' '\n' 2>/dev/null < "/proc/$p/environ" | grep -qE '^(MOSAIC_BUS_CAP|MOSAIC_RUN_ID|MOSAIC_AGENT_RUN)=.' && { echo "pid $p has a run marker"; n=$((n + 1)); }
p=$(awk '{print $4}' "/proc/$p/stat" 2>/dev/null) || break
done
echo "ancestors with a run marker: $n"
}
backup_of() { # backup_of ACT : check the activation's backup set; sets BDIR and STAMP
BDIR="$RB/data/backups/stack/$1"; STAMP="$(cat "$BDIR/STAMP" 2>/dev/null)"
note "backup $BDIR; STAMP $STAMP; SCHEMA $(cat "$BDIR/SCHEMA" 2>/dev/null); DATA_ROOT $(cat "$BDIR/DATA_ROOT" 2>/dev/null); files: $(ls "$BDIR" 2>/dev/null | paste -sd' ' -)"
[ -n "$STAMP" ] && [ "$(cat "$BDIR/SCHEMA")" = 3i ] && [ "$(cat "$BDIR/DATA_ROOT")" = "$RB/data" ] || stop "backup $1: no STAMP, or SCHEMA is not 3i, or DATA_ROOT is not $RB/data"
(cd "$BDIR/bus" && sha256sum -c --quiet ../SHA256SUMS) >> "$OUT" 2>&1 && note "backup SHA256SUMS: OK, file sha256 $(sha256sum < "$BDIR/SHA256SUMS" | cut -c1-64)" || stop "backup SHA256SUMS does not check"
}
activate_checks() { # activate_checks TITLE : the W5 checks on the last part; sets ACT
expect 0
has "gate passed on $NEW" || stop "$1: no 'gate passed on $NEW'"
has "backup: " || stop "$1: the run printed no backup line"
ACT="$(lastlog | node "$B/jget.mjs" activation)"
[ "$(lastlog | node "$B/jget.mjs" result)" = ok ] && [ -n "$ACT" ] && [ "$(lastlog | node "$B/jget.mjs" backup)" = "$ACT" ] || stop "$1: the last log line is not ok with this run's backup"
note "$1 activation $ACT"
backup_of "$ACT"
state
[ "$(pointer copy)" = "$NEW" ] && [ "$(pointer schema)" = 3i ] && [ "$(pointer pm)" = true ] && [ -z "$(pointer pmPending)" ] || stop "$1: pointer not on NEW, 3i, pm true, no pmPending"
[ "$(console_flags)" = "$OLD_FLAGS" ] || stop "$1: the pointer's Console flags are '$(console_flags)', not OLD's '$OLD_FLAGS'"
grep -qF -- "--tester-port $TPORT" <(ex_web) || stop "$1: the Console unit lacks --tester-port $TPORT"
grep -qE -- '--proof-(header|file)' <(ex_web) && stop "$1: the Console unit has a proof flag"
grep -qF -- ' --pm' <(ex_bus) || stop "$1: the bus unit lacks --pm"
listens "$TPORT" && note "tester port $TPORT listens (stand-in)" || stop "tester port $TPORT is closed"
[ "$(grep -c '"text":"launched chief' "$RB/sd/journal.jsonl")" -ge 1 ] && note "pm launch: $(grep -c '"text":"launched chief' "$RB/sd/journal.jsonl") emulated 'launched chief' journal line(s) so far" || stop "$1: no emulated 'launched chief' journal line"
}
{
echo "# SCRATCH ROOT, NOT LIVE. Window rows 177-185 P3, run by darkwing from $(date -u +%FT%TZ) (mos-0931, sage-1238)."
echo "# Plan: agents/sage/work/window-plan/repin-rows-177-185.md, sha256 $(sha256sum < /mnt/storage/src/mosaic-stack/agents/sage/work/window-plan/repin-rows-177-185.md | cut -c1-64)."
echo "# Tool: $REPO, a git clone --shared of the canonical checkout at $(git -C "$REPO" rev-parse HEAD), nothing applied;"
echo "# scripts/stack-release.sh blob $(git -C "$REPO" rev-parse HEAD:scripts/stack-release.sh). refactor in the clone: $(git -C "$REPO" rev-parse refactor)."
echo "# Copies (stack-copy.sh copy): OLD $OLD, NEW $NEW. Both at 3i: one activate --pm, no --migrate."
echo "# Root: $RB. Config $RB/cfg/config.json, data root $RB/data, units in $RB/xcfg/systemd/user."
echo "# Business file: the committed example (blob $(git -C "$REPO" hash-object packages/business/examples/mosaic-stack.example.json)) made loadable by mkbiz.mjs: plugins"
echo "# {\"usage-source\":[\"transcript\"],\"channel\":[\"cli\",\"webui\"],\"harness\":[\"claude-code\"]}, bus.tester true, every role's harness var"
echo "# claude-code, launch.instances plus assistant; sha256 $(sha256sum < "$R/bfiles/B/businesses/mosaic-stack.json" | cut -c1-64)."
echo "# Store: seeded at 3i by OLD's Store before the bus first started (store-seed.mjs): 5 usage_records, 2 instance_names,"
echo "# project p3-proj and one human thread $HUMAN_THREAD on the assistant instance."
echo "# snapshot.mjs: $SNAP, sha256 $(sha256sum < "$SNAP" | cut -c1-64) (expected $SNAP_SHA)."
echo "# Stand-ins ($B): systemctl runs the copy's real 'scripts/mosaic bus start mosaic-stack'. With pm-mode emulate a --pm"
echo "# start runs the bus WITHOUT --pm and EMULATES the PM launch: no claude runs; a waiting runner, one sessions.json entry"
echo "# and one 'launched chief' journal line stand in for it (no session credential goes to scratch). The Console is a"
echo "# /healthz stand-in on 127.0.0.1:$PORT and 127.0.0.1:$TPORT. (3) starts OLD's real webui serve by hand on $OPORT and"
echo "# $OTPORT. journalctl and systemd-analyze are the test's; claude is a fixture that prints a version. Credential files are"
echo "# fake placeholders."
echo "# Environment for every call: env -i with HOME, PATH, TMPDIR, LANG, MOSAIC_CONFIG, XDG_* and SD_* only (names: $(printf '%s\n' "${E[@]:2}" | cut -d= -f1 | paste -sd' ' -))."
} > "$OUT"
step "--- run.sh"
[ "$(sha256sum < "$SNAP" | cut -c1-64)" = "$SNAP_SHA" ] || stop "snapshot.mjs is not the copy Mos read"
state
OLD_FLAGS="$(console_flags)"
note "OLD's Console flags in the pointer: $OLD_FLAGS"
grep -qF -- "--tester-port $TPORT" <<< "$OLD_FLAGS" || stop "the seeded pointer's Console flags lack --tester-port $TPORT"
# --- the load checks (P1 shape, on the scratch file)
part "L1. OLD on the rehearsal file" node "$B/load-check.mjs" "$COPIES/$OLD" "$RB/cfg"; expect 0
part "L2. NEW on the rehearsal file" node "$B/load-check.mjs" "$COPIES/$NEW" "$RB/cfg"; expect 0
part "L3. the P1 values of the rehearsal file" node -e '
const b=require(process.argv[1]);const r=Object.entries(b.roles);
console.log(JSON.stringify({busTester:b.vars["bus.tester"],pluginKinds:Object.keys(b.plugins??{}).sort(),launchBy:b.launch?.by,
assistantLaunchable:(b.launch?.instances??[]).includes("assistant"),piRoles:r.filter(([,x])=>[x.vars?.harness,x.vars?.["fallback.harness"]].includes("pi")).map(([k])=>k),
harnessByRole:Object.fromEntries(r.map(([k,x])=>[k,x.vars?.harness??null]))}))' "$RB/cfg/businesses/mosaic-stack.json"
expect 0; has '"busTester":true' && has '"pluginKinds":["channel","harness","usage-source"]' && has '"launchBy":"chief","assistantLaunchable":true,"piRoles":[]' || stop "P1 values differ"
{ echo; echo "## L4. run markers in this shell's ancestors (names only; the tester walk refuses one)"; } >> "$OUT"
markers > "$R/out/last.txt"; cat "$R/out/last.txt" >> "$OUT"
has 'ancestors with a run marker: 0' || stop "a run marker above this shell; the tester proof would refuse"
# --- W1, S1, W2
part "W1a. agents as the tester (OLD's CLI; the chief must not be working)" "${C_OLD[@]}" agents "${BZ[@]}" --tester --json
expect 0
lacks '"working"' || stop "W1: an agent is working"
part "W1b. launches list (OLD's CLI)" "${C_OLD[@]}" launches list --json; expect 0
note "W1 start stamp: $(date -u +%FT%TZ)"
snap S1 --held; expect 0
has '"schema": "3i"' || stop "S1 is not at 3i"
part "W2. status" "${SR[@]}" status mosaic-stack "${CB[@]}"; expect 0
[ "$(pointer copy)" = "$OLD" ] && [ "$(pointer schema)" = 3i ] && [ "$(pointer pm)" = true ] && [ -z "$(pointer pmPending)" ] || stop "W2: pointer not on OLD, 3i, pm true, no pending"
# --- W3, W5
part "W3. activate NEW --pm --plan" "${SR[@]}" activate "$NEW" mosaic-stack --pm --plan "${CB[@]}"
expect 0
cp "$R/out/last.txt" "$R/out/W3-plan.txt"
for c in tester-port-invalid tester-port-target 'refuses ' '--proof-header' '--proof-file' 'drop '; do lacks "$c" || stop "W3 names $c"; done
has "from: $OLD" && has "to: $NEW" || stop "W3: from or to differs"
has "migration: none; the store is at the target's version" || stop "W3: no 'migration: none' line"
has "backup: the run backs up the stopped store to $RB/data/backups/stack/<activation>/ before any migration" || stop "W3: no backup line"
NCOMMITS="$(git -C "$REPO" rev-list --count --no-merges "$OLD..$NEW")"
grep -qxF "commits: the run adds $NCOMMITS (newest first, at most 200):" "$R/out/last.txt" || stop "W3: the commit count is not $NCOMMITS"
has "Console: scripts/webui-service.sh install --copy $NEW --copies $COPIES $OLD_FLAGS" || stop "W3: the Console line's flags are not OLD's ($OLD_FLAGS)"
has "bus: scripts/bus-service.sh install --copy $NEW --copies $COPIES --pm " || stop "W3: the bus line lacks --pm"
note "W3: from OLD, to NEW, migration none, the backup line, $NCOMMITS commits, Console flags identical to OLD's, no refusal"
W5EPOCH="$(date +%s)"
part "W5. activate NEW --pm" "${SR[@]}" activate "$NEW" mosaic-stack --pm "${CB[@]}"
activate_checks W5
W5ACT=$ACT W5BDIR=$BDIR W5STAMP=$STAMP
part "W5b. status" "${SR[@]}" status mosaic-stack "${CB[@]}"; expect 0
snap W5 --held; expect 0
has '"schema": "3i"' || stop "the store is not at 3i after W5"
same S1 W5
note "S1 against W5 exit $RC (the bus restart on NEW may add rows; S2 below checks the prior rows against the backup)"
# --- W9, the tester thread flow (the chief and assistant turns cannot run in scratch)
part "W9a. status" "${SR[@]}" status mosaic-stack "${CB[@]}"; expect 0
part "W9b. launches list (NEW's CLI)" "${C_NEW[@]}" launches list --json; expect 0
part "W9c. agents as the tester (NEW's CLI)" "${C_NEW[@]}" agents "${BZ[@]}" --tester --json; expect 0
part "W9d. talk chief as the tester, --wait 0 (scratch: the emulated chief holds no claim, so tester-target-not-running is expected)" \
timeout 60 "${C_NEW[@]}" talk chief "[p3 test] re-pin $NEW: reply with one line, no staffing request" --wait 0 "${BZ[@]}" --tester
if [ "$RC" = 3 ] && has "isn't running, so the tester's message was refused and nothing was stored"; then note "W9d: refused tester-target-not-running, as expected with the PM launch emulated"
elif [ "$RC" = 0 ]; then note "W9d: sent (exit 0)"
else stop "W9d: exit $RC"; fi
part "W9e. project create window-p3 as the tester" "${C_NEW[@]}" project create window-p3 "${BZ[@]}" --tester; expect 0
part "W9f. thread create window-p3 w9 as the tester" "${C_NEW[@]}" thread create window-p3 w9 "${BZ[@]}" --tester; expect 0
TID="$(sed -n 's/^created window-p3\/w9 id \([0-9a-f-]*\) instance \(.*\)$/\1/p' "$R/out/last.txt")"
TINST="$(sed -n 's/^created window-p3\/w9 id [0-9a-f-]* instance \(.*\)$/\1/p' "$R/out/last.txt")"
[ -n "$TID" ] && [ "$TINST" = assistant ] || stop "W9f: no thread id, or the instance is not assistant"
note "W9f: thread $TID on instance $TINST"
part "W9g. talk --thread as the tester, --wait 20 (scratch: no assistant reply can come; claude is a fixture)" \
timeout 60 "${C_NEW[@]}" talk --thread "$TID" "[p3 test] reply with one line, then stop" --wait 20 "${BZ[@]}" --tester
note "W9g: exit $RC$([ "$RC" = 1 ] && has "no reply from" && echo ', no reply within 20 s, as expected in scratch')"
[ "$RC" = 0 ] || [ "$RC" = 1 ] || stop "W9g: exit $RC"
part "W9h. the bus journal since W5 (the assistant thread start)" journalctl --user -u [email protected] --since "@$W5EPOCH" -o cat
part "W9i. launches list after the thread message (NEW's CLI)" "${C_NEW[@]}" launches list --json; expect 0
part "W9j. thread read --json as the tester" "${C_NEW[@]}" thread read "$TID" --json "${BZ[@]}" --tester; expect 0
has "[p3 test] reply with one line, then stop" || stop "W9j: the tester message is not in the thread"
part "W9k. threads --json as the tester" "${C_NEW[@]}" threads --json "${BZ[@]}" --tester; expect 0
node -e 'const t=JSON.parse(require("fs").readFileSync(0,"utf8"));process.exit(t.length===1&&t[0].id===process.argv[1]?0:1)' "$TID" < "$R/out/last.txt" \
&& note "W9k: lists exactly the tester's thread $TID, not the human thread" || stop "W9k: threads --tester does not list exactly $TID"
part "W9l. threads as the tester (text)" "${C_NEW[@]}" threads "${BZ[@]}" --tester; expect 0
part "W9m. thread read of the seeded human thread as the tester (expect thread-not-owned)" "${C_NEW[@]}" thread read "$HUMAN_THREAD" "${BZ[@]}" --tester
[ "$RC" != 0 ] && has thread-not-owned || stop "W9m: no thread-not-owned refusal"
part "W9n. names as the tester" "${C_NEW[@]}" names "${BZ[@]}" --tester --json; expect 0
part "W9o. usage as the tester" "${C_NEW[@]}" usage "${BZ[@]}" --tester --json; expect 0
snap S2 --held --after "$W5STAMP" --backup "$W5BDIR/bus/bus.sqlite"; expect 0
part "S2 check against the W5 backup" node "$B/s2check.mjs" "$SN/S2.json"; expect 0
part "S2 D3-style report" node "$B/d3report.mjs" "$SN/S2.json"
[ "$RC" = 0 ] || [ "$RC" = 3 ] || stop "S2 report could not read the snapshot"
# --- (1) the plain rollback
part "R1a. rollback --plan" "${SR[@]}" rollback mosaic-stack --plan "${CB[@]}"; expect 0
lacks 'refuses ' || stop "R1a: the plan refuses"
has "to: $OLD" || stop "R1a: to is not OLD"
snap R1pre --held; expect 0
part "R1b. rollback" "${SR[@]}" rollback mosaic-stack "${CB[@]}"; expect 0
state
[ "$(pointer copy)" = "$OLD" ] && [ "$(pointer schema)" = 3i ] && [ "$(pointer pm)" = true ] && [ -z "$(pointer pmPending)" ] || stop "R1: pointer not on OLD, 3i, pm true"
[ "$(console_flags)" = "$OLD_FLAGS" ] || stop "R1: the pointer's Console flags are not OLD's"
grep -qF -- "--tester-port $TPORT" <(ex_web) || stop "R1: the Console unit lacks --tester-port $TPORT"
grep -qF -- "/copies/$OLD/" <(ex_web) && grep -qF -- "/copies/$OLD/" <(ex_bus) || stop "R1: a unit does not name the OLD copy"
listens "$TPORT" && note "R1: tester port $TPORT listens" || stop "R1: tester port $TPORT is closed"
ls -d "$RB/data"/bus.post-attempt-* 2>/dev/null && stop "R1: a post-attempt store appeared"
note "R1: pointer on OLD at 3i, pm true, Console flags kept with --tester-port $TPORT, no post-attempt store"
snap R1 --held; expect 0
same R1pre R1
[ "$RC" = 0 ] && note "R1: the store matches the snapshot before the rollback" || note "FINDING: the store after the plain rollback differs from the one before it (the compare above lists the tables)"
part "R1c. status" "${SR[@]}" status mosaic-stack "${CB[@]}"; expect 0
# --- (3) OLD reads the rows NEW's tester thread wrote
part "O1. threads --tester --json (OLD's CLI; before row 177 it lists the human's threads)" "${C_OLD[@]}" threads --json "${BZ[@]}" --tester; expect 0
part "O2. agents --tester --json (OLD's CLI)" "${C_OLD[@]}" agents "${BZ[@]}" --tester --json; expect 0
part "O3. project list --tester --json (OLD's CLI)" "${C_OLD[@]}" project list --json "${BZ[@]}" --tester; expect 0
has '"window-p3"' && note "O3: OLD lists NEW's project window-p3" || note "FINDING: OLD's project list does not show window-p3"
note "O4: OLD's real Console, started by hand: webui serve --port $OPORT --tester-port $OTPORT --business mosaic-stack"
(cd "$REPO" && exec "${E[@]}" node "$COPIES/$OLD/packages/webui/src/cli.mjs" serve --port "$OPORT" --tester-port "$OTPORT" --business mosaic-stack) > "$R/out/old-console.log" 2>&1 &
OCP=$!; echo "old-console $OCP" >> "$RB/sd/pids.log"
for _ in $(seq 1 50); do listens "$OTPORT" && break; sleep 0.2; done
listens "$OTPORT" || { kill "$OCP" 2>/dev/null; stop "O4: OLD's Console did not listen on $OTPORT"; }
{ echo; echo "## O4. GET on OLD's Console tester port $OTPORT (status, then the body's length and any error)"; } >> "$OUT"
O4FAIL=0
for p in /healthz /api/config /api/instances /api/bus/inbox /api/bus/tasks /api/bus/agents /api/bus/decisions /api/bus/messages /api/bus/usage /api/bus/names /api/bus/instances /api/bus/routes /api/bus/limits /api/bus/credentials /api/bus/replies; do
code="$(curl -s -o "$R/out/o4.body" -w '%{http_code}' "http://127.0.0.1:$OTPORT$p")"
err="$(node -e 'try{const o=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));process.stdout.write(o&&o.error?JSON.stringify(o.error):"")}catch{}' "$R/out/o4.body")"
echo "GET $p $code bytes $(wc -c < "$R/out/o4.body")${err:+ error $err}" >> "$OUT"
[ "$code" = 200 ] || O4FAIL=1
done
rm -f "$R/out/o4.body"
{ echo "# OLD's Console log:"; sed 's/^/# /' "$R/out/old-console.log"; } >> "$OUT"
kill "$OCP" 2>/dev/null; wait "$OCP" 2>/dev/null
listens "$OTPORT" && stop "O4: $OTPORT still listens after the kill"
[ "$O4FAIL" = 0 ] && note "O4: every read on OLD's tester port returned 200" || note "FINDING: a read on OLD's tester port did not return 200 (listed above)"
snap R1post --held; expect 0
same R1 R1post
note "(3) store after OLD's reads against R1: exit $RC"
# --- (2) activate NEW again, one tester write after the stamp, the restore
part "A2. activate NEW --pm again" "${SR[@]}" activate "$NEW" mosaic-stack --pm "${CB[@]}"
activate_checks A2
A2ACT=$ACT A2BDIR=$BDIR A2STAMP=$STAMP
part "X1. project create p3-after-stamp as the tester (after the A2 stamp)" "${C_NEW[@]}" project create p3-after-stamp "${BZ[@]}" --tester; expect 0
snap D3 --held --after "$A2STAMP" --backup "$A2BDIR/bus/bus.sqlite"; expect 0
part "D3 report" node "$B/d3report.mjs" "$SN/D3.json"
[ "$RC" = 0 ] || [ "$RC" = 3 ] || stop "D3 report could not read the snapshot"
grep -qF '"projects"' <<< "$(node "$B/jget.mjs" stopTables < "$SN/D3.json")" || stop "D3 stopTables does not name projects, which X1 wrote"
part "R2a. rollback --restore $A2ACT --plan" "${SR[@]}" rollback mosaic-stack --restore "$A2ACT" --plan "${CB[@]}"; expect 0
lacks 'refuses ' || stop "R2a: the plan refuses"
has "to: $OLD" || stop "R2a: to is not OLD"
part "R2b. rollback --restore $A2ACT without --events (expect post-backup-events with per-table counts)" "${SR[@]}" rollback mosaic-stack --restore "$A2ACT" "${CB[@]}"
[ "$RC" != 0 ] && has post-backup-events || stop "R2b did not refuse post-backup-events"
has 'projects' || stop "R2b: the refusal does not count projects"
N="$(sed -n 's/.*then --events \([0-9][0-9]*\).*/\1/p' "$R/out/last.txt" | head -1)"
[ -n "$N" ] || stop "R2b: no 'then --events N' in the refusal"
note "R2b refused post-backup-events; the refusal names --events $N"
[ "$(pointer copy)" = "$NEW" ] && [ "$(pointer pm)" = true ] || stop "R2b's refusal changed the pointer"
part "R2c. rollback --restore $A2ACT --events $N" "${SR[@]}" rollback mosaic-stack --restore "$A2ACT" --events "$N" "${CB[@]}"
if [ "$RC" != 0 ] && has post-backup-events; then
N2="$(sed -n 's/.*then --events \([0-9][0-9]*\).*/\1/p' "$R/out/last.txt" | head -1)"
note "FINDING: the count moved between R2b ($N) and R2c ($N2); one rerun with --events $N2"
part "R2d. rollback --restore $A2ACT --events $N2" "${SR[@]}" rollback mosaic-stack --restore "$A2ACT" --events "$N2" "${CB[@]}"
fi
expect 0
POST="$(sed -n 's/^restore: .*moves to \(.*\) and is kept;.*/\1/p' "$R/out/last.txt" | head -1)"
note "post-attempt store: ${POST:-(none named)}$([ -n "$POST" ] && [ -e "$POST" ] && echo ', present')"
state
# --- the end state
part "F1. status at the end" "${SR[@]}" status mosaic-stack "${CB[@]}"; expect 0
[ "$(pointer copy)" = "$OLD" ] && [ "$(pointer schema)" = 3i ] && [ "$(pointer pm)" = true ] && [ -z "$(pointer pmPending)" ] || stop "F1: pointer not on OLD, 3i, pm true"
[ "$(console_flags)" = "$OLD_FLAGS" ] || stop "F1: the pointer's Console flags are not OLD's"
grep -qF -- "--tester-port $TPORT" <(ex_web) || stop "F1: the Console unit lacks --tester-port $TPORT"
note "F1: pointer on OLD at 3i, pm true, Console flags OLD's with --tester-port $TPORT"
snap F --held --after "$A2STAMP" --backup "$A2BDIR/bus/bus.sqlite"; expect 0
part "F report against the A2 backup" node "$B/d3report.mjs" "$SN/F.json"
[ "$RC" = 0 ] || [ "$RC" = 3 ] || stop "F report could not read the snapshot"
part "F2. project list --tester --json (OLD's CLI; p3-after-stamp must be gone, window-p3 kept)" "${C_OLD[@]}" project list --json "${BZ[@]}" --tester; expect 0
lacks '"p3-after-stamp"' || stop "F2: the restore kept p3-after-stamp"
has '"window-p3"' || stop "F2: the restore lost window-p3, which was in the A2 backup"
part "F3. agents as the tester (OLD's CLI)" "${C_OLD[@]}" agents "${BZ[@]}" --tester --json; expect 0
part "F4. launches list (OLD's CLI)" "${C_OLD[@]}" launches list --json; expect 0
note "P3: PASS"
echo "done: $OUT"
@@ -0,0 +1,21 @@
// P3, S2 (repin-rows-177-185.md W9 "What passes"): from one snapshot.mjs --after --backup output, every
// table's rows at the backup are unchanged (priorSame true), instance_names is unchanged, and the tables
// with rows past the backup are listed with their counts. Counts and table names only.
// usage: node s2check.mjs S2.json. Exits 0 when both hold, 3 when one does not, 1 on a bad document.
import { readFileSync } from 'node:fs';
const s = JSON.parse(readFileSync(process.argv[2], 'utf8'));
if (!s.tables || !Array.isArray(s.stopTables)) { console.log('S2 check: not a --backup snapshot'); process.exit(1); }
const t = Object.entries(s.tables);
const prior = t.filter(([, x]) => x.vsBackup && x.vsBackup.priorSame === false).map(([n]) => n);
const grown = t.filter(([, x]) => x.vsBackup && x.rows !== x.vsBackup.backupRows).map(([n, x]) => `${n} ${x.vsBackup.backupRows}->${x.rows}`);
const after = t.filter(([, x]) => (x.rowsAfter ?? 0) > 0).map(([n, x]) => `${n} ${x.rowsAfter}`);
const names = s.tables.instance_names;
const namesSame = !!names && names.vsBackup?.priorSame === true && names.rows === names.vsBackup.backupRows && (names.rowsAfter ?? 0) === 0;
console.log(`S2 priorSame false: ${prior.join(', ') || 'none'}`);
console.log(`S2 row counts backup->now, changed only: ${grown.join(', ') || 'none'}`);
console.log(`S2 rowsAfter nonzero: ${after.join(', ') || 'none'}`);
console.log(`S2 instance_names unchanged: ${namesSame}`);
console.log(`S2 stopTables ${JSON.stringify(s.stopTables)}; eventsAfter ${s.eventsAfter}`);
const ok = prior.length === 0 && namesSame;
console.log(ok ? 'S2 CHECK OK' : 'S2 CHECK FAILED');
process.exit(ok ? 0 : 3);
@@ -0,0 +1,42 @@
#!/usr/bin/env bash
# P3, seed: a scratch root at OLD 89af38a83 and 3i on stand-in units, pm true, with synthetic store rows
# written by OLD's Store before the bus first starts, then adopt. The Console unit carries the live flag
# shape (preview set, --send, --tester-port) with scratch values: no Jason id, no live host. Real OLD bus
# host (PM launch emulated), healthz stand-in for the Console. Scratch paths only.
set -euo pipefail
. /home/jwoltje/darkwing-scratch/w185/p3/b/env.sh
[ ! -e "$RB" ] || { echo "seed.sh: $RB exists; remove it first" >&2; exit 1; }
for p in $PORT $TPORT $OPORT $OTPORT; do ! (exec 3<>/dev/tcp/127.0.0.1/$p) 2>/dev/null || { echo "seed.sh: port $p is in use" >&2; exit 1; }; done
mkdir -p "$RB"/{home,cfg/businesses,xcfg,xdata,xstate,run,sd,bin,tmp,data/notify/mosaic-stack}
chmod 700 "$RB/run"
echo emulate > "$RB/sd/pm-mode"
cat > "$RB/cfg/config.json" <<EOC
{"configVersion":1,"environment":"development","dataRoot":"$RB/data","execution":{"backend":"docker","provider":"zai","model":"glm-5.3-flash","adapter":"mock"}}
EOC
cp "$R/bfiles/B/businesses/mosaic-stack.json" "$RB/cfg/businesses/mosaic-stack.json"
printf '{"notifyVersion":1,"binding":null}' > "$RB/data/notify/mosaic-stack/notify.json"; chmod 600 "$RB/data/notify/mosaic-stack/notify.json"
step "root: $RB; business file $(sha256sum < "$RB/cfg/businesses/mosaic-stack.json" | cut -c1-64) (the example with the live plug-ins usage-source transcript, channel cli,webui, harness claude-code; bus.tester true; every role's harness var claude-code; launch.instances plus assistant)"
# The broker refuses to boot unless every credential file in the business file exists, mode 0600. These are
# FAKE placeholder strings, not tokens; both base URLs in the file are 127.0.0.1:9.
NS="$R/bfiles/nosecret"; [ ! -e "$NS" ] || { echo "seed.sh: $NS exists; remove it first" >&2; exit 1; }
mkdir -m 700 "$NS"
for f in $(node -e 'const b=require(process.argv[1]);const o=[];(function w(x){if(x&&typeof x==="object")for(const[k,v]of Object.entries(x)){if(k==="file"&&typeof v==="string")o.push(v);w(v)}})(b);console.log(o.join(" "))' "$RB/cfg/businesses/mosaic-stack.json"); do
case "$f" in "$NS"/*) ;; *) echo "seed.sh: credential path outside $NS: $f" >&2; exit 1;; esac
printf 'scratch-fake-placeholder-%s\n' "$(basename "$f" .token)" > "$f"; chmod 600 "$f"
done
step "fake credential files: $(ls "$NS" | wc -l) under $NS, each 'scratch-fake-placeholder-<name>', mode 0600"
HUMAN="$(node -e 'console.log(require(process.argv[1]).human)' "$RB/cfg/businesses/mosaic-stack.json")"
step "store seed: $(node "$B/store-seed.mjs" "$COPIES/$OLD" "$RB/data" "$HUMAN" 2>&1)"
printf '#!/usr/bin/env bash\n# mosaic-cli wrapper (Q81, agents/sage/work/q81/mosaic-cli.sh)\n[ "${1:-}" = __lease-capability ] && exec "%s/mosaic-old" "$@"\nexit 1\n' "$RB/bin" > "$RB/bin/mosaic"; chmod 755 "$RB/bin/mosaic"
run() { step "run: $*"; local rc=0; (cd "$REPO" && "${E[@]}" "$@") >> "$LOGF" 2>&1 || rc=$?; step "exit $rc"; return $rc; }
run scripts/bus-service.sh install --copy $OLD --copies "$COPIES" --pm --claude "$B/claude"
run systemctl --user start [email protected]
run scripts/webui-service.sh install --copy $OLD --copies "$COPIES" --port $PORT --business mosaic-stack \
--public-host p3.scratch.invalid --auth-header X-authentik-username --allow-user p3-scratch-user --send --tester-port $TPORT
run systemctl --user start mosaic-webui.service
run scripts/stack-copy.sh wrapper $OLD --copies "$COPIES" --bin "$RB/bin"
run "$COPIES/$OLD/scripts/mosaic" bus status --json
run "$COPIES/$OLD/scripts/mosaic" launches list --json
run scripts/stack-release.sh adopt mosaic-stack --copies "$COPIES" --bin "$RB/bin"
run scripts/stack-release.sh status mosaic-stack --copies "$COPIES" --bin "$RB/bin" || true
step "pointer: $(cat "$RB/data/state/stack-active.json")"
@@ -0,0 +1,25 @@
// P3: synthetic rows in the scratch store before the OLD bus first starts, so S1, D3 and the restore have
// something to hash: usage records (a known end, a begin only, an unknown end), two instance names, a
// project and one human thread on the assistant, so W9's thread-not-owned read has a target. OLD's own
// Store writes them at 3i. Synthetic values only; HUMAN is the scratch business file's `human`.
// usage: node store-seed.mjs OLD_COPY DATA_ROOT HUMAN
import { pathToFileURL } from 'node:url';
const [old, root, human] = process.argv.slice(2);
const { Store, SCHEMA_VERSION } = await import(pathToFileURL(`${old}/packages/bus/src/store.mjs`).href);
if (SCHEMA_VERSION !== '3i') throw Error(`OLD is ${SCHEMA_VERSION}, not 3i`);
const s = new Store(root);
const u = (at, run, turn, phase, status, n, cursor) =>
s.run(`INSERT INTO usage_records(at,business,run,instance,provider,model,account,turn,source,phase,status,code,cursor,input,output,cache_write,cache_read)
VALUES (?,?,?,?,?,?,NULL,?,?,?,?,?,?,?,?,?,?)`, at, 'mosaic-stack', run, 'chief', 'anthropic', 'scratch-model', turn, 'transcript', phase, status,
status === 'unknown' ? 'no-new-usage' : null, cursor, ...(status === 'known' ? [n, n * 2, 0, n * 3] : [null, null, null, null]));
u('2026-10-19T10:00:00.000Z', 'p3-run-a', 't1', 'begin', 'known', 0, '{"offset":1}');
u('2026-10-19T10:00:05.000Z', 'p3-run-a', 't1', 'end', 'known', 1200, null);
u('2026-10-19T10:01:00.000Z', 'p3-run-a', 't2', 'begin', 'known', 0, '{"offset":2}');
u('2026-10-19T13:00:00.000Z', 'p3-run-b', 't1', 'begin', 'known', 0, '{"offset":3}');
u('2026-10-19T13:00:09.000Z', 'p3-run-b', 't1', 'end', 'unknown', 0, null);
s.run(`INSERT INTO instance_names(at,business,instance,name,by) VALUES (?,?,?,?,?)`, '2026-10-19T09:00:00.000Z', 'mosaic-stack', 'chief', 'p3-name-1', 'tester');
s.run(`INSERT INTO instance_names(at,business,instance,name,by) VALUES (?,?,?,?,?)`, '2026-10-19T14:00:00.000Z', 'mosaic-stack', 'chief', 'p3-name-2', 'tester');
s.run(`INSERT INTO projects(id,at,business,name,repo,created_by) VALUES (?,?,?,?,?,?)`, 'p3-proj', '2026-10-19T09:00:00.000Z', 'mosaic-stack', 'p3-project', null, human);
s.run(`INSERT INTO threads(id,at,business,project,instance,owner,name) VALUES (?,?,?,?,?,?,?)`, 'b3a7c1d2-0000-4000-8000-000000000001', '2026-10-19T09:30:00.000Z', 'mosaic-stack', 'p3-proj', 'assistant', human, 'p3-human-thread');
s.close();
console.log(`seeded 5 usage_records, 2 instance_names, 1 project, 1 human thread (owner ${human}, instance assistant) at 3i`);
@@ -0,0 +1,136 @@
#!/usr/bin/node
// sage-1004/1006 Window B rehearsal: a stand-in `systemctl --user`, based on the one in
// packages/cli/tests/stack-release.test.mjs. Unit state lives in $SD_STATE/units.json and every call
// goes to calls.log. Unlike the test's holder, a bus start runs the copy's real
// `scripts/mosaic bus start <business>` from the unit's WorkingDirectory, without --pm and under this
// scratch environment. Its output becomes journal lines. With --pm in ExecStart, the PM launch is
// EMULATED: no claude runs. A scratch runner process stands in for the session, with one
// sessions.json entry and one `launched` journal line. A Console start runs a scratch /healthz
// server on the unit's --port. Nothing here reads or writes a live unit, store or journal.
// Row 169 rehearsal: when $SD_STATE/pm-mode holds "real", a bus start with --pm runs the unit's argv as
// it is, --pm included, so the copy's own launchPm runs (step 5). The start then waits up to 30 s more
// for the `launched` line or the bus exiting, and journals all of it. A bus that exited is "failed",
// as systemd shows a Type=simple unit whose main process exited 3 (RestartPreventExitStatus=2 3 4,
// so no restart). A `launched` line in real mode writes REAL-LAUNCH to pids.log; run.sh stops on it.
const fs = require('node:fs'), path = require('node:path'), cp = require('node:child_process');
const sd = process.env.SD_STATE, data = process.env.SD_DATA, unitDir = path.join(process.env.XDG_CONFIG_HOME, 'systemd', 'user');
const args = process.argv.slice(2).filter((a) => a !== '--user'), [verb, ...rest] = args;
fs.appendFileSync(path.join(sd, 'calls.log'), `${new Date().toISOString()} ${args.join(' ')}\n`);
const stFile = path.join(sd, 'units.json');
const load = () => { try { return JSON.parse(fs.readFileSync(stFile, 'utf8')); } catch { return {}; } };
const save = (s) => { fs.writeFileSync(stFile + '.tmp', JSON.stringify(s)); fs.renameSync(stFile + '.tmp', stFile); };
const sleep = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
const alive = (pid) => { try { const s = fs.readFileSync(`/proc/${pid}/stat`, 'utf8'); return s[s.lastIndexOf(')') + 2] !== 'Z'; } catch { return false; } };
const startTime = (pid) => { const s = fs.readFileSync(`/proc/${pid}/stat`, 'utf8'); return s.slice(s.lastIndexOf(')') + 2).split(' ')[19]; };
const isBus = (u) => /^mosaic-bus@[a-z0-9-]+\.service$/.test(u), inst = (u) => u.slice('mosaic-bus@'.length, -'.service'.length);
const journal = (unit, text) => fs.appendFileSync(path.join(sd, 'journal.jsonl'), JSON.stringify({ epoch: Math.floor(Date.now() / 1000), unit, text }) + '\n');
const unitFile = (u) => {
const main = isBus(u) ? '[email protected]' : u, file = path.join(unitDir, main);
if (!fs.existsSync(file)) return null;
const drops = [];
for (const d of isBus(u) ? [main + '.d', u + '.d'] : [u + '.d']) { const dir = path.join(unitDir, d); if (fs.existsSync(dir)) for (const f of fs.readdirSync(dir).sort()) if (f.endsWith('.conf')) drops.push(path.join(dir, f)); }
const r = { wd: '', exec: '', env: [], drops };
for (const f of [file, ...drops]) for (let line of fs.readFileSync(f, 'utf8').split('\n')) {
line = line.replace(/%i/g, isBus(u) ? inst(u) : '').replace(/%h/g, process.env.HOME);
const m = /^(WorkingDirectory|ExecStart|Environment)=(.*)$/.exec(line); if (!m) continue;
if (m[1] === 'WorkingDirectory') r.wd = m[2]; else if (m[1] === 'ExecStart') { if (m[2]) r.exec = m[2]; } else r.env.push(m[2]);
}
return r;
};
// Every fd above 2 is closed first, so no child holds stack-release.sh's lock (fd 9).
const CLOSE = 'for fd in /proc/self/fd/*; do n=${fd##*/}; [ "$n" -gt 2 ] && eval "exec $n>&-" 2>/dev/null; done; exec "$@"';
const spawnDetached = (argv, opts) => { const ch = cp.spawn('bash', ['-c', CLOSE, 'standin', ...argv], { detached: true, ...opts }); ch.unref(); return ch; };
const sessionsFile = path.join(data, 'bus-host', 'sessions.json');
const readSessions = () => { try { return JSON.parse(fs.readFileSync(sessionsFile, 'utf8')); } catch { return { sessionsVersion: 1, sessions: [] }; } };
const writeSessions = (doc) => { fs.mkdirSync(path.dirname(sessionsFile), { recursive: true, mode: 0o700 }); fs.writeFileSync(sessionsFile + '.tmp', JSON.stringify(doc, null, 2) + '\n', { mode: 0o600 }); fs.renameSync(sessionsFile + '.tmp', sessionsFile); };
if (verb === 'show') {
const u = rest[0], props = []; for (let i = 1; i < rest.length; i++) if (rest[i] === '-p') props.push(rest[++i]);
const f = unitFile(u), st = load()[u] || {};
for (const p of props) {
if (p === 'LoadState') console.log(`LoadState=${f ? 'loaded' : 'not-found'}`);
else if (p === 'WorkingDirectory') console.log(`WorkingDirectory=${f ? f.wd : ''}`);
else if (p === 'ExecStart') console.log(f && f.exec ? `ExecStart={ path=${f.exec.split(' ')[0]} ; argv[]=${f.exec} ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }` : 'ExecStart=');
else if (p === 'Environment') console.log(`Environment=${f ? f.env.join(' ') : ''}`);
else if (p === 'MainPID') console.log(`MainPID=${st.state === 'active' && st.pid ? st.pid : 0}`);
else if (p === 'DropInPaths') console.log(`DropInPaths=${f ? f.drops.join(' ') : ''}`);
}
process.exit(0);
}
if (verb === 'is-active') { const q = rest[0] === '--quiet', u = q ? rest[1] : rest[0], st = load()[u] || {}; let s = st.state || 'inactive'; if (s === 'active' && st.pid && !alive(st.pid)) s = 'failed'; if (!q) console.log(s); process.exit(s === 'active' ? 0 : 3); }
if (verb === 'is-enabled') { console.log('disabled'); process.exit(1); }
if (verb === 'daemon-reload') process.exit(0);
if (verb === 'list-units') {
const pats = rest.filter((a) => !a.startsWith('--')).map((p) => new RegExp('^' + p.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$'));
for (const [u, s] of Object.entries(load())) if (pats.some((re) => re.test(u))) console.log(`${u} loaded ${s.state} ${s.state === 'active' ? 'running' : 'dead'} standin`);
process.exit(0);
}
if (verb === 'start') {
const u = rest[0], f = unitFile(u); if (!f) { console.error(`Unit ${u} not found.`); process.exit(5); }
const s = load();
if (!isBus(u)) {
const argv = f.exec.split(' '), i = argv.indexOf('--port'), port = i > 0 ? argv[i + 1] : '7332';
const t = argv.indexOf('--tester-port'), tport = t > 0 ? argv[t + 1] : '';
const ch = spawnDetached([process.execPath, path.join(__dirname, 'healthz.cjs'), port, tport], { stdio: 'ignore', cwd: sd });
fs.appendFileSync(path.join(sd, 'pids.log'), `healthz ${ch.pid}\n`);
const end = Date.now() + 10000;
while (!fs.existsSync(path.join(sd, `healthz-${ch.pid}`))) { if (Date.now() > end) { console.error('healthz stand-in did not start'); process.exit(1); } sleep(20); }
s[u] = { state: 'active', pid: ch.pid, wd: f.wd }; save(s); process.exit(0);
}
const argv = f.exec.split(' '), pm = argv.slice(1).includes('--pm');
let mode = 'emulate'; try { mode = fs.readFileSync(path.join(sd, 'pm-mode'), 'utf8').trim(); } catch {}
const realPm = pm && mode === 'real', real = realPm ? argv : argv.filter((a) => a !== '--pm');
const env = { ...process.env }; for (const e of f.env) { const k = e.slice(0, e.indexOf('=')); if (k === 'PATH' || k === 'MOSAIC_CLAUDE_BIN') env[k] = e.slice(k.length + 1); }
const out = path.join(sd, `bus-${Date.now()}.out`), fd = fs.openSync(out, 'a');
const ch = spawnDetached(real, { stdio: ['ignore', fd, fd], cwd: f.wd, env });
fs.closeSync(fd);
fs.appendFileSync(path.join(sd, 'pids.log'), `bus ${ch.pid} ${real.join(' ')} > ${out}\n`);
const end = Date.now() + 60000; let text = '';
while (!/^bus host up/m.test(text)) {
if (!alive(ch.pid) && realPm) { text = fs.readFileSync(out, 'utf8'); if (/^bus host up/m.test(text)) break; }
if (!alive(ch.pid)) { for (const l of text.split('\n').filter(Boolean)) journal(u, l); s[u] = { state: 'failed', pid: null, wd: f.wd }; save(s); console.error(`bus exited before host up; see ${out}`); process.exit(1); }
if (Date.now() > end) { console.error(`bus host up not seen in 60 s; see ${out}`); process.exit(1); }
sleep(100); text = fs.readFileSync(out, 'utf8');
}
if (realPm) {
const end3 = Date.now() + 30000;
while (!/^launched /m.test(text) && alive(ch.pid) && Date.now() < end3) { sleep(100); text = fs.readFileSync(out, 'utf8'); }
sleep(200); text = fs.readFileSync(out, 'utf8');
if (/^launched /m.test(text)) fs.appendFileSync(path.join(sd, 'pids.log'), `REAL-LAUNCH ${ch.pid}\n`);
}
for (const l of text.split('\n').filter(Boolean)) journal(u, l);
if (realPm) {
const up = alive(ch.pid);
fs.appendFileSync(path.join(sd, 'pids.log'), `real-pm ${ch.pid} ${up ? 'running' : 'exited'}\n`);
s[u] = up ? { state: 'active', pid: ch.pid, wd: f.wd, out, journaled: text.length } : { state: 'failed', pid: null, wd: f.wd };
save(s); process.exit(0);
}
s[u] = { state: 'active', pid: ch.pid, wd: f.wd, out, journaled: text.length };
if (pm) {
// The emulated PM launch: a runner that only waits, one sessions.json entry, one journal line.
const runner = spawnDetached([process.execPath, '-e', 'setInterval(()=>{},1<<30)'], { stdio: 'ignore', cwd: sd });
const end2 = Date.now() + 5000; while (!fs.existsSync(`/proc/${runner.pid}/stat`) && Date.now() < end2) sleep(10);
const run = `remul${runner.pid}`, b = inst(u);
const doc = readSessions(); doc.sessions.push({ run, business: b, instance: 'chief', harness: 'claude-code', model: 'emulated', launchedBy: 'standin-pm-emulation', runnerPid: runner.pid, runnerStartTime: startTime(runner.pid), startedAt: new Date().toISOString() }); writeSessions(doc);
journal(u, `launched chief: run ${run}, claude-code emulated`);
fs.appendFileSync(path.join(sd, 'pids.log'), `runner ${runner.pid} ${run}\n`);
s[u].runner = runner.pid; s[u].run = run;
}
save(s); process.exit(0);
}
if (verb === 'stop') {
const u = rest[0], s = load(), st = s[u];
if (st && st.pid) {
try { process.kill(st.pid, 'SIGTERM'); } catch {}
const end = Date.now() + 20000; while (alive(st.pid) && Date.now() < end) sleep(50);
if (alive(st.pid)) { fs.appendFileSync(path.join(sd, 'pids.log'), `SIGKILL ${st.pid}\n`); try { process.kill(st.pid, 'SIGKILL'); } catch {} }
if (st.out) { const text = fs.readFileSync(st.out, 'utf8'); for (const l of text.slice(st.journaled || 0).split('\n').filter(Boolean)) journal(u, l); }
}
if (st && st.runner) {
try { process.kill(st.runner, 'SIGTERM'); } catch {}
const doc = readSessions(); doc.sessions = doc.sessions.filter((x) => x.run !== st.run); writeSessions(doc);
}
if (st) { s[u] = { wd: st.wd, state: 'inactive', pid: null }; save(s); }
process.exit(0);
}
console.error(`stand-in systemctl: unsupported: ${args.join(' ')}`); process.exit(1);
@@ -0,0 +1,4 @@
#!/usr/bin/node
// Stand-in `systemd-analyze --user unit-paths` (the test's): only the scratch unit dir.
if(process.argv[2]!=='--user'||process.argv[3]!=='unit-paths')process.exit(1);
console.log(require('node:path').join(process.env.XDG_CONFIG_HOME,'systemd','user'));
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# P3: stops the scratch units through the stand-in, then confirms every PID it started is gone and the port is closed.
set -uo pipefail
. /home/jwoltje/darkwing-scratch/w185/p3/b/env.sh
T=$R/out/p3-teardown.txt
{
echo "# teardown $(date -u +%FT%TZ)"
for u in mosaic-webui.service [email protected]; do
(cd "$REPO" && "${E[@]}" systemctl --user stop "$u"); echo "stop $u exit $?"
done
left=0
while read -r kind pid _; do
if [ -e "/proc/$pid" ] && grep -q "$R" "/proc/$pid/cmdline" 2>/dev/null; then echo "ALIVE $kind $pid"; left=1; fi
done < <(grep -v "^REAL-LAUNCH\|^SIGKILL\|^real-pm" "$RB/sd/pids.log")
for p in /proc/[0-9]*; do c=$(readlink "$p/cwd" 2>/dev/null) || continue; case $c in "$R"*) case "$(tr "\0" " " < "$p/cmdline" 2>/dev/null)" in *b/all.sh*|*b/teardown.sh*) continue;; esac; echo "cwd-in-root ${p#/proc/} $c"; left=1;; esac; done
for p in $PORT $TPORT $OPORT $OTPORT; do if (exec 3<>/dev/tcp/127.0.0.1/$p) 2>/dev/null; then echo "port $p open"; left=1; else echo "port $p closed"; fi; done
echo "left: $left"
} 2>&1 | tee "$T"
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Waits until busy.sh passes twice, 60 s apart, then runs all.sh, teed. Gives up after 90 minutes.
set -uo pipefail
end=$((SECONDS + 5400))
while :; do
if ~/darkwing-scratch/busy.sh > /dev/null; then
sleep 60
if ~/darkwing-scratch/busy.sh; then break; fi
fi
[ "$SECONDS" -lt "$end" ] || { echo "wait-run: still busy after 90 min"; exit 9; }
sleep 30
done
echo "wait-run: clear at $(date -u +%FT%TZ)"
/home/jwoltje/darkwing-scratch/mbc/p3/b/all.sh 2>&1 | tee /home/jwoltje/darkwing-scratch/mbc/p3/all-$(date -u +%Y%m%dT%H%M%SZ).log
exit "${PIPESTATUS[0]}"
@@ -0,0 +1,15 @@
addy-gen_default
bridge
compose-audit_default
host
jarvis-crypto_default
mage-ai_mage-network
mosaic-dev_default
mosaic-internal
mosaic-mono-v1_default
mosaic-public
neni-content-engine-dashboard_default
none
sage-phr-ng_sage-network
t205-directus_dd-internal
t205-payload_pl-internal
@@ -0,0 +1,15 @@
addy-gen_default
bridge
compose-audit_default
host
jarvis-crypto_default
mage-ai_mage-network
mosaic-dev_default
mosaic-internal
mosaic-mono-v1_default
mosaic-public
neni-content-engine-dashboard_default
none
sage-phr-ng_sage-network
t205-directus_dd-internal
t205-payload_pl-internal
@@ -0,0 +1,4 @@
{"epoch":1792482363,"unit":"[email protected]","text":"tasks mosaic-stack startup tracker-unavailable"}
{"epoch":1792482363,"unit":"[email protected]","text":"bus host up: business mosaic-stack, socket /home/jwoltje/darkwing-scratch/w185/p3/root/data/bus/broker.sock, notifier off"}
{"epoch":1792482363,"unit":"[email protected]","text":"launched chief: run remul428531, claude-code emulated"}
{"epoch":1792482364,"unit":"[email protected]","text":"bus host stopped (0)"}
@@ -0,0 +1,6 @@
# before prep, 2026-10-20T07:45:59Z
/home/jwoltje/.mosaic-dev/bus/bus.sqlite 1792460229
/home/jwoltje/.mosaic-dev/bus 1792460463
# after teardown, 2026-10-20T07:46:05Z
/home/jwoltje/.mosaic-dev/bus/bus.sqlite 1792460229
/home/jwoltje/.mosaic-dev/bus 1792460463
@@ -0,0 +1,126 @@
2026-10-20T07:46:00Z tool clone /home/jwoltje/darkwing-scratch/w185/p3/repo at 490e2771d3d057ab76991e2c7859cd251857e64c, refactor 490e2771d3d057ab76991e2c7859cd251857e64c; scripts/stack-release.sh blob f8051d81cc18aedb7b6f2e743ec0c3f505e0e44b
npm warn deprecated [email protected]: Use your platform's native DOMException instead
added 127 packages in 1s
npm warn install-scripts 3 packages had install scripts blocked because they are not covered by allowScripts:
npm warn install-scripts @google/[email protected] (preinstall: echo 'preinstall: no-op')
npm warn install-scripts [email protected] (postinstall: node install.js)
npm warn install-scripts [email protected] (postinstall: node scripts/postinstall)
npm warn install-scripts
npm warn install-scripts Run `npm install-scripts ls` to review, or `npm install-scripts approve <pkg>` to allow.
copied: /home/jwoltje/darkwing-scratch/w185/p3/copies/89af38a83afa22a8a4f0d368a0a4030ad15a0efa
2026-10-20T07:46:01Z copy 89af38a83afa22a8a4f0d368a0a4030ad15a0efa: exit 0
npm warn deprecated [email protected]: Use your platform's native DOMException instead
added 127 packages in 1s
npm warn install-scripts 3 packages had install scripts blocked because they are not covered by allowScripts:
npm warn install-scripts @google/[email protected] (preinstall: echo 'preinstall: no-op')
npm warn install-scripts [email protected] (postinstall: node install.js)
npm warn install-scripts [email protected] (postinstall: node scripts/postinstall)
npm warn install-scripts
npm warn install-scripts Run `npm install-scripts ls` to review, or `npm install-scripts approve <pkg>` to allow.
copied: /home/jwoltje/darkwing-scratch/w185/p3/copies/490e2771d3d057ab76991e2c7859cd251857e64c
2026-10-20T07:46:03Z copy 490e2771d3d057ab76991e2c7859cd251857e64c: exit 0
2026-10-20T07:46:03Z example blob 3a81ca0faea6d3dc3b29ae513c799497ed53f9d7
2026-10-20T07:46:03Z business file (the live plug-ins, assistant launched) 0e14dee45ca5655f2a22d0e3381ddda84598e2f9c685f7d4b3de095e71fe00be
2026-10-20T07:46:03Z root: /home/jwoltje/darkwing-scratch/w185/p3/root; business file 0e14dee45ca5655f2a22d0e3381ddda84598e2f9c685f7d4b3de095e71fe00be (the example with the live plug-ins usage-source transcript, channel cli,webui, harness claude-code; bus.tester true; every role's harness var claude-code; launch.instances plus assistant)
2026-10-20T07:46:03Z fake credential files: 9 under /home/jwoltje/darkwing-scratch/w185/p3/bfiles/nosecret, each 'scratch-fake-placeholder-<name>', mode 0600
2026-10-20T07:46:03Z store seed: seeded 5 usage_records, 2 instance_names, 1 project, 1 human thread (owner jason, instance assistant) at 3i
2026-10-20T07:46:03Z run: scripts/bus-service.sh install --copy 89af38a83afa22a8a4f0d368a0a4030ad15a0efa --copies /home/jwoltje/darkwing-scratch/w185/p3/copies --pm --claude /home/jwoltje/darkwing-scratch/w185/p3/b/claude
written: /home/jwoltje/darkwing-scratch/w185/p3/root/xcfg/systemd/user/[email protected]
claude: /home/jwoltje/darkwing-scratch/w185/p3/b/claude (9.9.9 (Claude Code fixture))
daemon-reload done
next, for one business (one per data root):
mkdir -m 0700 -p <dataRoot>/notify/<business> the notifier refuses a looser directory
write <dataRoot>/notify/<business>/notify.json, mode 0600:
{"notifyVersion": 1, "binding": "<discord binding>"} or "binding": null for no DMs
systemctl --user enable --now mosaic-bus@<business> start now and at login
systemctl --user status mosaic-bus@<business>
journalctl --user -u mosaic-bus@<business> -f the host's log
systemctl --user stop mosaic-bus@<business> SIGTERM; restartable
survive logout and reboot only with lingering on: loginctl enable-linger jwoltje
2026-10-20T07:46:03Z exit 0
2026-10-20T07:46:03Z run: systemctl --user start [email protected]
2026-10-20T07:46:03Z exit 0
2026-10-20T07:46:03Z run: scripts/webui-service.sh install --copy 89af38a83afa22a8a4f0d368a0a4030ad15a0efa --copies /home/jwoltje/darkwing-scratch/w185/p3/copies --port 47394 --business mosaic-stack --public-host p3.scratch.invalid --auth-header X-authentik-username --allow-user p3-scratch-user --send --tester-port 47395
written: /home/jwoltje/darkwing-scratch/w185/p3/root/xcfg/systemd/user/mosaic-webui.service
serve: /usr/bin/node /home/jwoltje/darkwing-scratch/w185/p3/copies/89af38a83afa22a8a4f0d368a0a4030ad15a0efa/packages/webui/src/cli.mjs serve --checkout /home/jwoltje/darkwing-scratch/w185/p3/repo --port 47394 --business mosaic-stack --public-host p3.scratch.invalid --auth-header X-authentik-username --allow-user p3-scratch-user --send --tester-port 47395
daemon-reload done
this script starts nothing: the Console holds the human's capability. Jason
starts and restarts it, or Mos under lead decision 102 (sends then enter the
trail as Jason); no other agent does:
systemctl --user enable --now mosaic-webui start now and at login
systemctl --user status mosaic-webui
journalctl --user -u mosaic-webui -f the Console's log
systemctl --user stop mosaic-webui SIGTERM; restartable
scripts/webui-service.sh status unit state and /healthz
survive logout and reboot only with lingering on: loginctl enable-linger jwoltje
2026-10-20T07:46:03Z exit 0
2026-10-20T07:46:03Z run: systemctl --user start mosaic-webui.service
2026-10-20T07:46:03Z exit 0
2026-10-20T07:46:03Z run: scripts/stack-copy.sh wrapper 89af38a83afa22a8a4f0d368a0a4030ad15a0efa --copies /home/jwoltje/darkwing-scratch/w185/p3/copies --bin /home/jwoltje/darkwing-scratch/w185/p3/root/bin
wrapper: /home/jwoltje/darkwing-scratch/w185/p3/copies/89af38a83afa22a8a4f0d368a0a4030ad15a0efa
2026-10-20T07:46:03Z exit 0
2026-10-20T07:46:03Z run: /home/jwoltje/darkwing-scratch/w185/p3/copies/89af38a83afa22a8a4f0d368a0a4030ad15a0efa/scripts/mosaic bus status --json
{
"host": {
"business": "mosaic-stack",
"pid": 428484,
"startedAt": "2026-10-20T07:46:03.710Z",
"notifier": null,
"live": true
},
"socket": true,
"writerLock": {
"pid": 428501,
"at": "2026-10-20T07:46:03.774Z",
"live": true
},
"boot": {
"ok": true,
"at": "2026-10-20T07:46:03.709Z",
"business": "mosaic-stack",
"file": "/home/jwoltje/darkwing-scratch/w185/p3/root/cfg/businesses/mosaic-stack.json",
"reason": null,
"lastGood": "2026-10-20T07:46:03.709Z"
}
}
2026-10-20T07:46:04Z exit 0
2026-10-20T07:46:04Z run: /home/jwoltje/darkwing-scratch/w185/p3/copies/89af38a83afa22a8a4f0d368a0a4030ad15a0efa/scripts/mosaic launches list --json
[
{
"run": "remul428531",
"business": "mosaic-stack",
"instance": "chief",
"harness": "claude-code",
"model": "emulated",
"launchedBy": "standin-pm-emulation",
"runnerPid": 428531,
"runnerStartTime": "58355403",
"startedAt": "2026-10-20T07:46:03.814Z",
"live": true
}
]
2026-10-20T07:46:04Z exit 0
2026-10-20T07:46:04Z run: scripts/stack-release.sh adopt mosaic-stack --copies /home/jwoltje/darkwing-scratch/w185/p3/copies --bin /home/jwoltje/darkwing-scratch/w185/p3/root/bin
adopted 89af38a83afa22a8a4f0d368a0a4030ad15a0efa for mosaic-stack (pm 1, schema 3i)
2026-10-20T07:46:04Z exit 0
2026-10-20T07:46:04Z run: scripts/stack-release.sh status mosaic-stack --copies /home/jwoltje/darkwing-scratch/w185/p3/copies --bin /home/jwoltje/darkwing-scratch/w185/p3/root/bin
pointer: /home/jwoltje/darkwing-scratch/w185/p3/root/data/state/stack-active.json
{"copy":"89af38a83afa22a8a4f0d368a0a4030ad15a0efa","business":"mosaic-stack","pm":true,"claude":"/home/jwoltje/darkwing-scratch/w185/p3/b/claude","console":["--port","47394","--business","mosaic-stack","--public-host","p3.scratch.invalid","--auth-header","X-authentik-username","--allow-user","p3-scratch-user","--send","--tester-port","47395"],"schema":"3i","activation":"20261020T074604Z-89af38a83afa","at":"2026-10-20T07:46:04Z"}
log (last five lines):
{"at":"2026-10-20T07:46:04Z","op":"adopt","from":null,"to":"89af38a83afa22a8a4f0d368a0a4030ad15a0efa","backup":null,"activation":"20261020T074604Z-89af38a83afa","result":"ok"}
check:
bus: /home/jwoltje/darkwing-scratch/w185/p3/copies/89af38a83afa22a8a4f0d368a0a4030ad15a0efa
Console: /home/jwoltje/darkwing-scratch/w185/p3/copies/89af38a83afa22a8a4f0d368a0a4030ad15a0efa
CLI: /home/jwoltje/darkwing-scratch/w185/p3/copies/89af38a83afa22a8a4f0d368a0a4030ad15a0efa
result: pass
bus status:
host: mosaic-stack, pid 428484, running, since 2026-10-20T07:46:03.710Z, notifier off
socket: present
writer.lock: pid 428501
boot: organization file loaded at 2026-10-20T07:46:03.709Z
unit flags: match the pointer
drift: 52 commit(s) on refactor since the pointer's copy
2026-10-20T07:46:04Z exit 0
2026-10-20T07:46:04Z pointer: {"copy":"89af38a83afa22a8a4f0d368a0a4030ad15a0efa","business":"mosaic-stack","pm":true,"claude":"/home/jwoltje/darkwing-scratch/w185/p3/b/claude","console":["--port","47394","--business","mosaic-stack","--public-host","p3.scratch.invalid","--auth-header","X-authentik-username","--allow-user","p3-scratch-user","--send","--tester-port","47395"],"schema":"3i","activation":"20261020T074604Z-89af38a83afa","at":"2026-10-20T07:46:04Z"}
@@ -0,0 +1,10 @@
# teardown 2026-10-20T07:46:04Z
stop mosaic-webui.service exit 0
stop [email protected] exit 0
port 47394 closed
port 47395 closed
port 47396 closed
port 47397 closed
left: 0
port 47398 closed
port 47399 closed
@@ -0,0 +1,35 @@
1860a026ec30d685df2f21d8aee6922148a13979b1c3c1521f350a4cc4380af9 ./b/all.sh
6b25b038e3d7f4d12d169613a6f7e214d2c4f69e34fb98d446f4adad3876e44d ./b/claude
bbb6dbe133f9974db5e88def4cc2859d9d6a14c0cb58c4c3806a99f382ca3183 ./b/cmp.mjs
90e464119c5991480f097ad2904b8054bf1ef40297ac16e4a179ed4c4e83f5cb ./b/d3report.mjs
442470138e1a3d67fc8ab332c0319e9ad8d9eae0f8ee2ecf14c53f6a28519f69 ./b/env.sh
f41c5eb72f182ba052d5e2f2954bed223c10984e512afd68b405a2bb8790d944 ./b/ev.mjs
89c201e59d7bb6427041734757d170df7c9b39d96d8cb680a57fbb9eff5ab5aa ./b/healthz.cjs
fb1f19bb5a464d575ffdeb133a9a2df7fdf507218e683fddaaa170280d5c9fed ./b/jget.mjs
e1dcabdd3e43cae6f885a44fc1fe357406c00d896c03fff889f8b61dd4e15683 ./b/journalctl
f6b139e855575def6b4485f6a4737036773699e631a5956178740f29ddc0d34e ./b/lim-all.sh
2edffb03582975d530c7582f59f841bb3d85e6a43d97d5c413afa8792fae8c7b ./b/limits-diag.mjs
03776823419943dab8dbd7223c26957dcb642ed83e22e3cd8324f2dae65ca62c ./b/lim.sh
2aed6b4762c6f48cd338f15134822118dddac9ce6246ef5c4fc9faff6f80178e ./b/load-check.mjs
fb3036039d835dd93c22f2e0be93f3bac601b4e283e52fba95c3da5f363849c7 ./b/mkbiz.mjs
ee146f9feebcbc03a5b7582cc189321588c3e5cc12cd919df2062489e42eb5f1 ./b/prep.sh
75b88cbd68752a347e7e824f2464b4bf9d4bbd29267f76a045c2527c67bd8189 ./b/routes.mjs
493ca4ecd26d2a21de2bf24830241fba6275790de44d8c88a55770e78b30614e ./b/run.sh
d6ee41b67f97469f6dbabc95ee8a91b4724fd955b43bead0b65c4453eff287d2 ./b/s2check.mjs
84859226712d3d2858a3f30fb9b397c8ca01029a829829df4c06afb5a9262977 ./b/seed.sh
699029215ac022768dcd5c91877cd5f68bd5e2c37284a55ce6187ff44a68d3bf ./b/store-seed.mjs
e3669292be33f8b8298d9d1a00ba19e5ca42e3b50fc995007c9fc4587d0df6cd ./b/systemctl
30908331d33adead6e32b9da5b77ca29283f42446636527946541618331dc8a2 ./b/systemd-analyze
6e1a3fe0d11192fd62dd5fc0ff17afff02a41d9d4b2a64c8b854a25f7119ab3f ./b/teardown.sh
0a4884269bd5914075294e803262078bd7aa12c8b8e119b1aa7b8d58fb8c5f72 ./b/wait-run.sh
585f173eac0e39990aa187c5e5cfd4494570245729708a1a9961c1a35dc16ce8 ./console-A.log
35b201fe88e84e029fec95f78a702a0aa5d6366abaa6c0c0a25936e843a39ec9 ./console-B.log
585f173eac0e39990aa187c5e5cfd4494570245729708a1a9961c1a35dc16ce8 ./console-C.log
b3a59cabad922206911f53bbbd664c2409f1356947e580e97a990d60220d6f09 ./docker-networks-after.txt
b3a59cabad922206911f53bbbd664c2409f1356947e580e97a990d60220d6f09 ./docker-networks-before.txt
4359f780f74fc456d2abaeee0c72efe96dd531565a2e5b00c0433f703cfacc24 ./journal.jsonl
1c3b3dac4507a31c150921e13b905144545e351a5397bebd823f0ace4567237d ./launches-mosaic-stack.jsonl
8ca12fc88c33f194e7e7784c1fdcb58f107ee2ffadfd7cf801f043c2fd3aedb3 ./lim-live-store-stat.txt
36000ab1582e193791c11033f08f12b460ce4a05774f14453258d36c98f631d1 ./lim.txt
76ff889a3e1df796c23a2e7cc9247b6a931ed08be27da0070361d4c78e5ecffb ./p3-steps.log
8d598a265543abf5b606452115e8dfdc766b0e33287318439ddd71b8f459f122 ./p3-teardown.txt
@@ -0,0 +1,28 @@
#!/usr/bin/env bash
# P3 driver: the live-store stat (mos-0878), prep, seed, run, teardown, the stat again, the Docker network
# names before and after, then the outputs copied to $WIN/p3 with SHA256SUMS. Started only behind
# wait-run.sh (busy.sh) on Sage's go. NEW=<sha> TP=1 for the run on the row 176 tool blob.
set -uo pipefail
. /home/jwoltje/darkwing-scratch/w185/p3/b/env.sh
[ ! -e "$R/out" ] || { echo "all.sh: $R/out exists; move it aside first" >&2; exit 1; }
mkdir -p "$R/out"
S=$R/out/p3-live-store-stat.txt
LIVE=(/home/jwoltje/.mosaic-dev/bus/bus.sqlite /home/jwoltje/.mosaic-dev/bus)
{ echo "# before prep, $(date -u +%FT%TZ)"; stat -c '%n %Y' "${LIVE[@]}"; } > "$S"
docker network ls --format '{{.Name}}' | sort > "$R/out/docker-networks-before.txt"
rc=0
"$B/prep.sh" && "$B/seed.sh" && "$B/run.sh" || rc=$?
"$B/teardown.sh"
docker network ls --format '{{.Name}}' | sort > "$R/out/docker-networks-after.txt"
{ echo "# after teardown, $(date -u +%FT%TZ)"; stat -c '%n %Y' "${LIVE[@]}"; } >> "$S"
cat "$S"
if [ "$(sed -n 2,3p "$S")" = "$(sed -n 5,6p "$S")" ]; then echo "live store stat: match"; else echo "live store stat: DIFFER"; rc=1; fi
if cmp -s "$R/out/docker-networks-before.txt" "$R/out/docker-networks-after.txt"; then echo "docker networks: unchanged"; else echo "docker networks: CHANGED"; diff "$R/out/docker-networks-before.txt" "$R/out/docker-networks-after.txt"; fi
echo "all.sh: run exit $rc"
W=$WIN/p3/run-$(date -u +%Y%m%dT%H%M%SZ)
mkdir -p "$W" && cp -p "$R"/out/*.txt "$R"/out/*.json "$R/out/p3-steps.log" "$W"/ 2>/dev/null; cp -pr "$R/out/snap" "$W"/
cp -p "$B"/*.sh "$B"/*.mjs "$B"/*.cjs "$B"/systemctl "$B"/journalctl "$B"/systemd-analyze "$B"/claude "$W"/ 2>/dev/null; mkdir -p "$W/b" && mv "$W"/*.sh "$W"/*.mjs "$W"/*.cjs "$W"/systemctl "$W"/journalctl "$W"/systemd-analyze "$W"/claude "$W/b"/ 2>/dev/null
rm -f "$W/last.txt"
(cd "$W" && find . -type f ! -name SHA256SUMS | sort | xargs sha256sum > SHA256SUMS)
echo "outputs: $W ($(wc -l < "$W/SHA256SUMS") files, SHA256SUMS sha256 $(sha256sum < "$W/SHA256SUMS" | cut -c1-64))"
exit $rc
@@ -0,0 +1,3 @@
#!/bin/sh
# Fake claude for the scratch rehearsal. It never runs a model; the PM launch is emulated.
echo "9.9.9 (Claude Code fixture)"
@@ -0,0 +1,18 @@
// P3: compares two snapshot.mjs outputs table by table, over every table either one lists (rows,
// maxSeq, sha256 where printed, and byRun for usage_records). Across a schema change meta compares
// on rows only, since the migration sets schema_version and schema_digest.
// usage: node cmp.mjs A.json B.json. Prints one line per table and exits 1 if any differ.
import { readFileSync } from 'node:fs';
const [a, b] = process.argv.slice(2).map((p) => JSON.parse(readFileSync(p, 'utf8')));
const short = (h) => (h === undefined ? 'counts-only' : h.slice(0, 12));
let differ = 0;
for (const t of [...new Set([...Object.keys(a.tables), ...Object.keys(b.tables)])].sort()) {
const x = a.tables[t], y = b.tables[t];
if (!x || !y) { console.log(`DIFFER ${t}: only in ${x ? 'A' : 'B'}`); differ = 1; continue; }
const keys = t === 'meta' && a.schema !== b.schema ? ['rows'] : ['rows', 'maxSeq', 'sha256'];
const same = keys.every((k) => x[k] === y[k]) && JSON.stringify(x.byRun ?? null) === JSON.stringify(y.byRun ?? null);
console.log(`${same ? 'EQUAL' : 'DIFFER'} ${t}: rows ${x.rows}/${y.rows}, maxSeq ${x.maxSeq}/${y.maxSeq}, sha256 ${short(x.sha256)}/${short(y.sha256)}${keys.length === 1 ? ' (rows only across the schema change)' : ''}`);
if (!same) differ = 1;
}
console.log(`schema ${a.schema}/${b.schema}`);
process.exit(differ);
@@ -0,0 +1,22 @@
// P3, D3 (plan draft 7, mos-0899 A1): the stop decision from one snapshot.mjs --after --backup output.
// Prints stopTables, every table with priorSame or same false, every nonzero rowsAfter, every
// untimed table whose rows differ from the backup's, and eventsAfter. Counts and table names only.
// usage: node d3report.mjs D3.json (or the document on stdin). Exits 0 when stopTables is empty, 3 when it is not, 1 on a bad document.
import { readFileSync } from 'node:fs';
const s = JSON.parse(readFileSync(process.argv[2] ?? 0, "utf8"));
if (!Array.isArray(s.stopTables) || !s.tables || !Array.isArray(s.untimed)) {
console.log('D3 report: not a --backup snapshot');
process.exit(1);
}
const t = Object.entries(s.tables);
const changed = t.filter(([, x]) => x.vsBackup && (x.vsBackup.priorSame === false || x.vsBackup.same === false)).map(([n]) => n);
const after = t.filter(([, x]) => (x.rowsAfter ?? 0) > 0).map(([n, x]) => `${n} ${x.rowsAfter}`);
const untimed = s.untimed.filter((n) => s.tables[n].rows !== s.tables[n].vsBackup?.backupRows)
.map((n) => `${n} ${s.tables[n].vsBackup?.backupRows ?? 'absent'}->${s.tables[n].rows}`);
console.log(`D3 stopTables ${JSON.stringify(s.stopTables)}${s.onlyInBackup?.length ? `; only in the backup ${JSON.stringify(s.onlyInBackup)}` : ''}`);
console.log(`D3 priorSame or same false: ${changed.join(', ') || 'none'}`);
console.log(`D3 rowsAfter nonzero: ${after.join(', ') || 'none'}`);
console.log(`D3 untimed count differences (backup->now): ${untimed.join(', ') || 'none'}`);
console.log(`D3 eventsAfter ${s.eventsAfter}`);
console.log(s.stopTables.length ? 'D3 STOP (mos-0899 A1): stopTables is not empty; a real window stops here before the restore' : 'D3 stopTables empty: only events can have moved');
process.exit(s.stopTables.length ? 3 : 0);
@@ -0,0 +1,23 @@
# Sourced by every P3 script (window rows 177-185, repin-rows-177-185.md P3): the scratch root and a clean env
# for every call. OLD is the live copy 89af38a83 at 3i, NEW is row 185's landing 490e2771d.
R=/home/jwoltje/darkwing-scratch/w185/p3
B=$R/b
RB=$R/root
REPO=$R/repo
COPIES=$R/copies
OLD=89af38a83afa22a8a4f0d368a0a4030ad15a0efa
NEW=490e2771d3d057ab76991e2c7859cd251857e64c
PORT=47394
TPORT=47395
# (3): OLD's real Console, started by hand after the plain rollback, on two more scratch ports.
OPORT=47396
OTPORT=47397
WIN=/mnt/storage/src/mosaic-stack/agents/darkwing/work/window-rows-177-185
SNAP=$WIN/snapshot.mjs
SNAP_SHA=639fd28a2674aaa03ce824e3394b4052f21cdef7d6fa49bae61a9927ed341e67
LOGF=$R/out/p3-steps.log
# env -i keeps every live MOSAIC_*, XDG_*, credential and pi variable out; HOME is scratch too.
E=(env -i HOME=$RB/home PATH=$B:/usr/local/bin:/usr/bin:/bin TMPDIR=$RB/tmp LANG=C.UTF-8
MOSAIC_CONFIG=$RB/cfg/config.json XDG_CONFIG_HOME=$RB/xcfg XDG_DATA_HOME=$RB/xdata
XDG_STATE_HOME=$RB/xstate XDG_RUNTIME_DIR=$RB/run SD_STATE=$RB/sd SD_DATA=$RB/data)
step() { echo "$(date -u +%FT%TZ) $*" >> "$LOGF"; }
@@ -0,0 +1,8 @@
// Limits rerun (mos-0934 item 2): the events rows after seq N on a scratch store, kind, actor and at only, never
// the body or subject. The last line is "max <seq>". usage: node ev.mjs DB N
import { DatabaseSync } from "node:sqlite";
const [db, after] = process.argv.slice(2);
const d = new DatabaseSync(db, { readOnly: true });
for (const r of d.prepare("select seq, kind, actor_role, actor_run, at from events where seq > ? order by seq").all(Number(after)))
console.log(`event ${r.seq} ${r.kind} actor ${r.actor_role ?? "null"}/${r.actor_run ?? "null"} at ${r.at}`);
console.log(`max ${d.prepare("select coalesce(max(seq), 0) as m from events").get().m}`);
@@ -0,0 +1,7 @@
// The Console stand-in: 200 on /healthz, 404 otherwise, on 127.0.0.1:<port>, and on 127.0.0.1:<tester port>
// too when the unit's ExecStart has --tester-port (merged window P3). Writes healthz-<pid> once every port listens.
const http=require('node:http'),fs=require('node:fs'),path=require('node:path');
const ports=process.argv.slice(2).filter(Boolean).map(Number);let up=0;
for(const p of ports)http.createServer((q,r)=>{r.writeHead(q.url==='/healthz'?200:404);r.end(q.url==='/healthz'?'ok\n':'');})
.listen(p,'127.0.0.1',()=>{if(++up===ports.length)fs.writeFileSync(path.join(process.env.SD_STATE,`healthz-${process.pid}`),'');});
process.on('SIGTERM',()=>process.exit(0));
@@ -0,0 +1,6 @@
// P3: prints one field of a JSON document on stdin, by a dotted path (a.b.c); "" when absent.
// usage: node jget.mjs PATH < doc.json
import { readFileSync } from 'node:fs';
let v = JSON.parse(readFileSync(0, 'utf8'));
for (const k of process.argv[2].split('.')) v = v == null ? undefined : v[k];
process.stdout.write(v === undefined ? '' : typeof v === 'string' ? v : JSON.stringify(v));
@@ -0,0 +1,10 @@
#!/usr/bin/node
// Stand-in `journalctl --user -u UNIT --since @EPOCH -o cat` over $SD_STATE/journal.jsonl (the test's).
const fs=require('node:fs'),path=require('node:path');
const sd=process.env.SD_STATE,a=process.argv.slice(2);let unit='',since='';
for(let i=0;i<a.length;i++){if(a[i]==='-u')unit=a[++i];else if(a[i]==='--since')since=a[++i]}
fs.appendFileSync(path.join(sd,'journal-since.log'),since+'\n');
if(!/^@\d+$/.test(since))process.exit(1);
const from=Number(since.slice(1));
let lines=[];try{lines=fs.readFileSync(path.join(sd,'journal.jsonl'),'utf8').split('\n').filter(Boolean).map((l)=>JSON.parse(l))}catch{}
for(const o of lines)if(o.unit===unit&&o.epoch>=from)console.log(o.text);
@@ -0,0 +1,31 @@
#!/usr/bin/env bash
# Limits rerun driver (sage-1242, mos-0936): the live-store stat, prep, seed, lim.sh, teardown (and the two
# extra Console ports closed), the stat again, Docker network names before and after, then the outputs
# copied to $WIN/p3/lim-<stamp> with SHA256SUMS. Run 3's state was moved to ~/darkwing-scratch/w185/p3-run3.
set -uo pipefail
. /home/jwoltje/darkwing-scratch/w185/p3/b/env.sh
[ ! -e "$R/out" ] || { echo "lim-all.sh: $R/out exists; move it aside first" >&2; exit 1; }
mkdir -p "$R/out"
S=$R/out/lim-live-store-stat.txt
LIVE=(/home/jwoltje/.mosaic-dev/bus/bus.sqlite /home/jwoltje/.mosaic-dev/bus)
{ echo "# before prep, $(date -u +%FT%TZ)"; stat -c '%n %Y' "${LIVE[@]}"; } > "$S"
docker network ls --format '{{.Name}}' | sort > "$R/out/docker-networks-before.txt"
rc=0
"$B/prep.sh" && "$B/seed.sh" && "$B/lim.sh" || rc=$?
"$B/teardown.sh"
for p in 47398 47399; do if (exec 3<>/dev/tcp/127.0.0.1/$p) 2>/dev/null; then echo "port $p open"; rc=1; else echo "port $p closed"; fi; done | tee -a "$R/out/p3-teardown.txt"
docker network ls --format '{{.Name}}' | sort > "$R/out/docker-networks-after.txt"
{ echo "# after teardown, $(date -u +%FT%TZ)"; stat -c '%n %Y' "${LIVE[@]}"; } >> "$S"
cat "$S"
if [ "$(sed -n 2,3p "$S")" = "$(sed -n 5,6p "$S")" ]; then echo "live store stat: match"; else echo "live store stat: DIFFER"; rc=1; fi
if cmp -s "$R/out/docker-networks-before.txt" "$R/out/docker-networks-after.txt"; then echo "docker networks: unchanged"; else echo "docker networks: CHANGED"; diff "$R/out/docker-networks-before.txt" "$R/out/docker-networks-after.txt"; fi
echo "lim-all.sh: run exit $rc"
W=$WIN/p3/lim-$(date -u +%Y%m%dT%H%M%SZ)
mkdir -p "$W/b" && cp -p "$R"/out/*.txt "$R"/out/*.log "$W"/ 2>/dev/null
cp -p "$RB/data/launches/mosaic-stack.jsonl" "$W/launches-mosaic-stack.jsonl" 2>/dev/null
cp -p "$RB/sd/journal.jsonl" "$W/journal.jsonl" 2>/dev/null
cp -p "$B"/*.sh "$B"/*.mjs "$B"/*.cjs "$B"/systemctl "$B"/journalctl "$B"/systemd-analyze "$B"/claude "$W/b"/ 2>/dev/null
rm -f "$W/last.txt" "$W/get.body"
(cd "$W" && find . -type f ! -name SHA256SUMS | sort | xargs sha256sum > SHA256SUMS)
echo "outputs: $W ($(wc -l < "$W/SHA256SUMS") files, SHA256SUMS sha256 $(sha256sum < "$W/SHA256SUMS" | cut -c1-64))"
exit $rc
@@ -0,0 +1,119 @@
#!/usr/bin/env bash
# Limits rerun (sage-1242, mos-0934, mos-0936): on a fresh root seed.sh made at OLD and 3i, GET /api/bus/limits,
# decisions?list=recent and replies?id= on a real Console's tester port at three points: (A) OLD before NEW ever
# runs, (B) NEW after its activate --pm and a tester thread message, (C) OLD after a plain rollback. At each point
# also OLD's or NEW's `limits --tester --json` CLI read and limits-diag.mjs (the copy's view() with no bus), and
# the events rows each request adds (kind, actor, at only). Scratch paths only; Consoles on scratch ports.
set -uo pipefail
. /home/jwoltje/darkwing-scratch/w185/p3/b/env.sh
OUT=$R/out/lim.txt
NPORT=47398
NTPORT=47399
DB=$RB/data/bus/bus.sqlite
SR=(scripts/stack-release.sh)
CB=(--copies "$COPIES" --bin "$RB/bin")
C_OLD=(node "$COPIES/$OLD/packages/cli/src/cli.mjs")
C_NEW=(node "$COPIES/$NEW/packages/cli/src/cli.mjs")
BZ=(--business mosaic-stack)
[ ! -e "$OUT" ] || { echo "lim.sh: $OUT exists" >&2; exit 1; }
say() { echo "$*" >> "$OUT"; step "$*"; }
stop() { say "# STOP: $*"; exit 1; }
listens() { (exec 3<>/dev/tcp/127.0.0.1/$1) 2>/dev/null; }
pointer() { node "$B/jget.mjs" "$1" < "$RB/data/state/stack-active.json"; }
SEQ=0
ev() { # ev LABEL: the events rows written since the last ev call
local r; r="$(node "$B/ev.mjs" "$DB" "$SEQ" 2>&1)" || { say "# ev $1: could not read the store: $r"; return; }
local rows; rows="$(grep '^event ' <<< "$r")"
say "# events after $1: ${rows:-none new}"
SEQ="$(sed -n 's/^max //p' <<< "$r")"
}
part() { # part TITLE CMD...: one labeled call with its output and exit code; RC holds the exit code
local title="$1"; shift
{ echo; echo "## $title"; } >> "$OUT"; step "run: $*"
RC=0; (cd "$REPO" && "${E[@]}" "$@") > "$R/out/last.txt" 2>&1 || RC=$?
cat "$R/out/last.txt" >> "$OUT"; echo "# exit $RC" >> "$OUT"; step "exit $RC"
ev "$title"
}
get() { # get PORT PATH: the status, the body's bytes and the body (first 600 characters)
local code; code="$(curl -s -m 10 -o "$R/out/get.body" -w '%{http_code}' "http://127.0.0.1:$1$2")"
say "GET :$1$2 -> $code, $(wc -c < "$R/out/get.body") bytes: $(head -c 600 "$R/out/get.body")"
ev "GET $2"
}
CP=
console_up() { # console_up LABEL COPY PORT TPORT [EXTRA...]: a copy's real webui serve started by hand
local label="$1" copy="$2" port="$3" tport="$4"; shift 4
for p in "$port" "$tport"; do listens "$p" && stop "port $p is in use"; done
(cd "$REPO" && exec "${E[@]}" node "$COPIES/$copy/packages/webui/src/cli.mjs" serve --port "$port" --tester-port "$tport" --business mosaic-stack "$@") > "$R/out/console-$label.log" 2>&1 &
CP=$!; echo "console-$label $CP" >> "$RB/sd/pids.log"
for _ in $(seq 1 50); do listens "$tport" && break; sleep 0.2; done
listens "$tport" || { kill "$CP" 2>/dev/null; stop "$label: the Console did not listen on $tport"; }
say "# $label: $(git -C "$REPO" rev-parse --short=9 "$copy") Console up, pid $CP, ports $port and $tport${*:+, extra flags $*}"
ev "$label Console start"
}
console_down() { # console_down LABEL PORT TPORT
kill "$CP" 2>/dev/null; wait "$CP" 2>/dev/null
for p in "$2" "$3"; do listens "$p" && stop "$1: port $p still listens after the kill"; done
{ echo "# $1 Console log:"; sed 's/^/# /' "$R/out/console-$1.log"; } >> "$OUT"
ev "$1 Console stop"
}
diag() { part "$1 limits-diag.mjs on $(git -C "$REPO" rev-parse --short=9 "$2") (the copy's view(), no bus)" node "$B/limits-diag.mjs" "$COPIES/$2" "$RB"; }
point() { # point LABEL COPY PORT TPORT MID [EXTRA...]: the three reads on the Console's tester port, then the CLI read
local label="$1" copy="$2" port="$3" tport="$4" mid="$5"; shift 5
{ echo; echo "## $label. GETs on the tester port of the $(git -C "$REPO" rev-parse --short=9 "$copy") Console"; } >> "$OUT"
console_up "$label" "$copy" "$port" "$tport" "$@"
get "$tport" /api/bus/limits
get "$tport" "/api/bus/decisions?list=recent"
get "$tport" "/api/bus/replies?id=$mid"
console_down "$label" "$port" "$tport"
part "$label CLI limits --tester --json ($(git -C "$REPO" rev-parse --short=9 "$copy"))" node "$COPIES/$copy/packages/cli/src/cli.mjs" limits "${BZ[@]}" --tester --json
diag "$label" "$copy"
}
{
echo "# SCRATCH ROOT, NOT LIVE. Window rows 177-185, limits rerun (sage-1242, mos-0934, mos-0936), run by darkwing from $(date -u +%FT%TZ)."
echo "# Fresh root from prep.sh and seed.sh: OLD $OLD at 3i, NEW $NEW, tool blob $(git -C "$REPO" rev-parse HEAD:scripts/stack-release.sh)."
echo "# Real Consoles started by hand: OLD on $OPORT/$OTPORT, NEW on $NPORT/$NTPORT with --checkout $REPO. Events: kind, actor and at only."
} > "$OUT"
step "--- lim.sh"
[ "$(pointer copy)" = "$OLD" ] || stop "the seeded pointer is not on OLD"
ev "seed (rows before point A)"
# Before NEW ever runs no message exists, so (A) asks replies for a well-formed id that names nothing.
NOID=00000000-0000-4000-8000-000000000000
# (A) OLD before NEW
point A "$OLD" "$OPORT" "$OTPORT" "$NOID"
# NEW: activate --pm, then a tester thread message
part "W5. activate NEW --pm" "${SR[@]}" activate "$NEW" mosaic-stack --pm "${CB[@]}"
[ "$RC" = 0 ] && [ "$(pointer copy)" = "$NEW" ] || stop "W5 did not activate NEW"
part "W9e. project create window-p3 as the tester (NEW's CLI)" "${C_NEW[@]}" project create window-p3 "${BZ[@]}" --tester
[ "$RC" = 0 ] || stop "W9e exit $RC"
part "W9f. thread create window-p3 w9 as the tester (NEW's CLI)" "${C_NEW[@]}" thread create window-p3 w9 "${BZ[@]}" --tester
TID="$(sed -n 's/^created window-p3\/w9 id \([0-9a-f-]*\) instance .*$/\1/p' "$R/out/last.txt")"
[ -n "$TID" ] || stop "W9f: no thread id"
part "W9g. talk --thread as the tester, --wait 0 (NEW's CLI)" "${C_NEW[@]}" talk --thread "$TID" "[lim test] reply with one line, then stop" --wait 0 "${BZ[@]}" --tester
part "W9j. thread read --json as the tester (NEW's CLI)" "${C_NEW[@]}" thread read "$TID" --json "${BZ[@]}" --tester
MID="$(node -e 'const t=JSON.parse(require("fs").readFileSync(0,"utf8"));process.stdout.write(t[0]?.id??"")' < "$R/out/last.txt")"
[ -n "$MID" ] || stop "W9j: no message id"
say "# thread $TID, message $MID"
# (B) NEW after W5
point B "$NEW" "$NPORT" "$NTPORT" "$MID" --checkout "$REPO"
# (C) OLD after a plain rollback, with the (3) CLI reads first as in P3
part "R1b. rollback" "${SR[@]}" rollback mosaic-stack "${CB[@]}"
[ "$RC" = 0 ] && [ "$(pointer copy)" = "$OLD" ] || stop "R1b did not roll back to OLD"
part "O1. threads --tester --json (OLD's CLI)" "${C_OLD[@]}" threads --json "${BZ[@]}" --tester
part "O2. agents --tester --json (OLD's CLI)" "${C_OLD[@]}" agents "${BZ[@]}" --tester --json
part "O3. project list --tester --json (OLD's CLI)" "${C_OLD[@]}" project list --json "${BZ[@]}" --tester
point C "$OLD" "$OPORT" "$OTPORT" "$MID"
{ echo; echo "## the bus journal: lines naming limit, refused or no-route"; } >> "$OUT"
grep -iE 'limit|refus|no-route' "$RB/sd/journal.jsonl" >> "$OUT" || echo "(none)" >> "$OUT"
{ echo; echo "## sessions.json (run, instance, model, launchedBy only)"; } >> "$OUT"
node -e 'const d=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));for(const s of d.sessions)console.log(JSON.stringify({run:s.run,instance:s.instance,model:s.model,launchedBy:s.launchedBy}))' "$RB/data/sessions.json" >> "$OUT" 2>&1 || echo "(no sessions.json)" >> "$OUT"
{ echo; echo "## the launches log: thread start refusals (at, instance, code, why)"; } >> "$OUT"
node -e 'for(const l of require("fs").readFileSync(process.argv[1],"utf8").split("\n").filter(Boolean)){const j=JSON.parse(l);if(j.event==="refused")console.log(j.at,j.instance,j.code,j.why)}' "$RB/data/launches/mosaic-stack.jsonl" >> "$OUT" 2>&1 || echo "(no launches log)" >> "$OUT"
{ echo; echo "## the pointer moves (activation log: at, op, result, to)"; } >> "$OUT"
node -e 'for(const l of require("fs").readFileSync(process.argv[1],"utf8").split("\n").filter(Boolean)){const j=JSON.parse(l);console.log(j.at,j.op,j.result,"to",String(j.to).slice(0,9))}' "$RB/data/state/stack-activation-log.jsonl" >> "$OUT" 2>&1
say "# lim.sh: done"
@@ -0,0 +1,28 @@
// Limits diagnosis (mos-0934, sage-1242): call a copy's launchSettings view the way its bus runtime does, on a
// scratch root, once with readSessions and once with no sessions, and print the error's name and code or the
// instance list. Read only. usage: node limits-diag.mjs COPY ROOT
import { readFileSync } from "node:fs";
import { join } from "node:path";
const [copy, root] = process.argv.slice(2);
const { launchSettings } = await import(join(copy, "packages/business/src/launch-settings.mjs"));
const { systemVars, resolveInstance } = await import(join(copy, "packages/business/src/resolve.mjs"));
const { readSessions } = await import(join(copy, "packages/seat/src/session.mjs"));
const { loadBusiness } = await import(join(copy, "packages/business/src/index.mjs"));
const system = JSON.parse(readFileSync(join(root, "cfg/config.json"), "utf8"));
const vars = systemVars(system);
const org = { dir: join(root, "cfg"), rolesDir: join(copy, "roles"), system: vars };
const b = loadBusiness("mosaic-stack", { dir: org.dir, rolesDir: org.rolesDir });
const models = Object.keys(b.launch?.models ?? {});
console.log(`launch.models keys: ${JSON.stringify(models)}`);
for (const instance of Object.keys(b.roles)) {
const v = resolveInstance({ system: vars, business: b, instance }).vars;
console.log(`instance ${instance}: model ${JSON.stringify(v.model)}${v.model !== undefined && !models.includes(v.model) ? " NOT IN launch.models" : ""}`);
}
for (const [label, sessions] of [["readSessions", () => readSessions(system.dataRoot)], ["no sessions", () => []]]) {
try {
const r = launchSettings({ ...org, sessions, levels: () => null }).read("mosaic-stack");
console.log(`view with ${label}: ok, instances ${Object.keys(r.instances).length}`);
} catch (e) {
console.log(`view with ${label}: ${e.name} code ${JSON.stringify(e.code)} message ${JSON.stringify(String(e.message).slice(0, 200))}`);
}
}
@@ -0,0 +1,18 @@
// business-check-style load (repin-de352dfaf/business-check.mjs) of a scratch business file under COPY's own
// loader and roles. usage: node load-check.mjs COPY_DIR CONFIG_DIR. Prints counts, plug-in kinds, HOLDS lines, LOAD_OK or REFUSED.
const [copy, dir] = process.argv.slice(2);
const { loadBusiness } = await import(`${copy}/packages/business/src/business.mjs`);
const { resolveInstance } = await import(`${copy}/packages/business/src/resolve.mjs`);
try {
const b = loadBusiness('mosaic-stack', { dir, rolesDir: `${copy}/roles` });
const count = (o) => Object.keys(o ?? {}).length;
console.log(`copy ${copy.split('/').pop().slice(0, 9)}: roles ${count(b.roles)}, projects ${count(b.projects)}, arbiters ${count(b.arbiters)}, plugins ${JSON.stringify(b.plugins)}`);
for (const instance of ['chief', 'pm']) {
const { limits: { authority: a } } = resolveInstance({ system: {}, business: b, instance });
console.log(`HOLDS ${instance} instance.name ${[...a.withinRole, ...a.crossRole].includes('instance.name') ? 'yes' : 'no'}`);
}
console.log('LOAD_OK');
} catch (e) {
console.log(`REFUSED ${e.message}`);
process.exit(1);
}
@@ -0,0 +1,27 @@
// Scratch business file for P3: the committed example, made loadable, with the given plug-ins (mos-0847).
// Bot ids become distinct positive integers, date placeholders become 2027-01-01, every credential file
// points at a path under NOSECRET that does not exist, and both base URLs go to 127.0.0.1:9 (discard),
// so a scratch bus never reaches the live tracker or Gitea. As the live file (P1): `bus.tester` is true,
// every role's `harness` var is claude-code, and launch.instances includes assistant.
// usage: node mkbiz.mjs EXAMPLE NOSECRET PLUGINS_JSON
import { readFileSync } from 'node:fs';
import { basename } from 'node:path';
const [example, nosecret, plugins] = process.argv.slice(2);
const b = JSON.parse(readFileSync(example, 'utf8'));
let id = 0;
const walk = (o) => {
for (const [k, v] of Object.entries(o)) {
if (k === 'botId') o[k] = ++id;
else if (v === 'YYYY-MM-DD') o[k] = '2027-01-01';
else if (k === 'file' && typeof v === 'string') o[k] = `${nosecret}/${basename(v)}`;
else if (v && typeof v === 'object') walk(v);
}
};
walk(b);
b.vars['tracker.baseUrl'] = 'http://127.0.0.1:9';
b.vars['gitea.baseUrl'] = 'http://127.0.0.1:9';
b.vars['bus.tester'] = true;
for (const role of Object.values(b.roles)) role.vars = { ...(role.vars ?? {}), harness: 'claude-code' };
if (!b.launch.instances.includes('assistant')) b.launch.instances.push('assistant');
b.plugins = JSON.parse(plugins);
process.stdout.write(JSON.stringify(b, null, 2) + '\n');
@@ -0,0 +1,20 @@
#!/usr/bin/env bash
# P3, prep: the tool clone at NEW, the two copies and the scratch business file. Scratch paths only; the
# canonical checkout is read through git clone --shared. No patch is applied: the tool is NEW's own
# stack-release.sh, blob f8051d81cc18.
set -euo pipefail
. /home/jwoltje/darkwing-scratch/w185/p3/b/env.sh
[ ! -e "$REPO" ] && [ ! -e "$COPIES" ] && [ ! -e "$R/bfiles" ] || { echo "prep.sh: $R already has a repo, copies or bfiles" >&2; exit 1; }
mkdir -p "$R/out"
git clone -q --shared --no-checkout /mnt/storage/src/mosaic-stack "$REPO"
git -C "$REPO" checkout -q --detach "$NEW"
git -C "$REPO" branch -f refactor "$NEW"
step "tool clone $REPO at $(git -C "$REPO" rev-parse HEAD), refactor $(git -C "$REPO" rev-parse refactor); scripts/stack-release.sh blob $(git -C "$REPO" rev-parse HEAD:scripts/stack-release.sh)"
[ "$(git -C "$REPO" rev-parse HEAD:scripts/stack-release.sh)" = f8051d81cc18aedb7b6f2e743ec0c3f505e0e44b ] || { step "tool blob is not f8051d81cc18"; exit 1; }
mkdir -p "$COPIES"
for c in "$OLD" "$NEW"; do (cd "$REPO" && scripts/stack-copy.sh copy "$c" --copies "$COPIES") >> "$LOGF" 2>&1; step "copy $c: exit 0"; done
mkdir -p "$R/bfiles/B/businesses"
EX="$REPO/packages/business/examples/mosaic-stack.example.json"
step "example blob $(git -C "$REPO" hash-object "$EX")"
node "$B/mkbiz.mjs" "$EX" "$R/bfiles/nosecret" '{"usage-source":["transcript"],"channel":["cli","webui"],"harness":["claude-code"]}' > "$R/bfiles/B/businesses/mosaic-stack.json"
step "business file (the live plug-ins, assistant launched) $(sha256sum < "$R/bfiles/B/businesses/mosaic-stack.json" | cut -c1-64)"
@@ -0,0 +1,11 @@
// Each instance's resolved route vars on a scratch root (harness, model, thinking). usage: node routes.mjs COPY ROOT
import { readFileSync } from "node:fs";
const [copy, root] = process.argv.slice(2);
const { systemVars, resolveInstance } = await import(copy + "/packages/business/src/resolve.mjs");
const { loadBusiness } = await import(copy + "/packages/business/src/index.mjs");
const sys = JSON.parse(readFileSync(root + "/cfg/config.json", "utf8"));
const b = loadBusiness("mosaic-stack", { dir: root + "/cfg", rolesDir: copy + "/roles" });
for (const i of Object.keys(b.roles)) {
const v = resolveInstance({ system: systemVars(sys), business: b, instance: i }).vars;
console.log(i, JSON.stringify({ harness: v.harness, model: v.model, thinking: v.thinking ?? "(unset)" }));
}
@@ -0,0 +1,304 @@
#!/usr/bin/env bash
# Window rows 177-185, P3 (repin-rows-177-185.md at 2ea05700b, mos-0931, sage-1238): the rehearsal on the root
# seed.sh made at OLD 89af38a83 and 3i, on NEW 490e2771d's tool blob. Order: the load checks, W1 and S1, W2,
# W3 and W5 (one activate --pm, no --migrate), the W9 tester thread flow, S2 against the W5 backup, then the
# three rollbacks: (1) a plain rollback to OLD, (3) OLD's CLI and OLD's real Console reading NEW's thread rows,
# (2) activate NEW again, one tester project create after the stamp, D3, rollback --restore --plan, the run
# without --events (post-backup-events with the per-table counts), the rerun with --events N. Every
# stack-release call names the scratch --copies and --bin. Scratch paths only.
set -uo pipefail
. /home/jwoltje/darkwing-scratch/w185/p3/b/env.sh
OUT=$R/out/p3.txt
SN=$R/out/snap
[ ! -e "$OUT" ] || { echo "run.sh: $OUT exists" >&2; exit 1; }
[ -f "$RB/data/state/stack-active.json" ] || { echo "run.sh: no seeded root" >&2; exit 1; }
mkdir -p "$SN"
SR=(scripts/stack-release.sh)
CB=(--copies "$COPIES" --bin "$RB/bin")
C_OLD=(node "$COPIES/$OLD/packages/cli/src/cli.mjs")
C_NEW=(node "$COPIES/$NEW/packages/cli/src/cli.mjs")
BZ=(--business mosaic-stack)
DB=$RB/data/bus/bus.sqlite
UNIT_BUS=$RB/xcfg/systemd/user/[email protected]
UNIT_WEB=$RB/xcfg/systemd/user/mosaic-webui.service
HUMAN_THREAD=b3a7c1d2-0000-4000-8000-000000000001
part() { # part TITLE CMD... : one labeled part of the transcript, with its command and exit code
local title="$1"; shift
{ echo; echo "## $title"; echo "# command: (cd $REPO && <scratch env> $*)"; } >> "$OUT"
step "run: $*"
local rc=0; (cd "$REPO" && "${E[@]}" "$@") > "$R/out/last.txt" 2>&1 || rc=$?
cat "$R/out/last.txt" >> "$OUT"
echo "# exit $rc" >> "$OUT"; step "exit $rc"; RC=$rc
# mos-0902 (P19 391): a rollback in the same second as the activate before it can refuse capture-failed.
# Wait a second and retry once; both attempts stay in the transcript.
if [ "$rc" != 0 ] && [ -z "${RETRIED:-}" ] && grep -qF 'refusing capture-failed' "$R/out/last.txt"; then
note "capture-failed on the first attempt (mos-0902); one retry after 1 s"
sleep 1; RETRIED=1 part "$title (retry after capture-failed)" "$@"
fi
}
note() { echo "# $*" >> "$OUT"; step "$*"; }
stop() { note "STOP: $*"; exit 1; }
expect() { [ "$RC" = "$1" ] || stop "expected exit $1, got $RC"; }
# The last part's output, less a plan's commits: block (its subject lines name refusal codes), so has and
# lacks read only what the tool itself says.
body() { awk '/^commits:/{c=1;next} c && /^ /{next} {c=0;print}' "$R/out/last.txt"; }
has() { grep -qF -- "$1" <(body); } # the last part's output holds this text
lacks() { ! grep -qF -- "$1" <(body); }
ex_web() { sed -n 's/^ExecStart=//p' "$UNIT_WEB"; } # the unit's ExecStart only: NEW's unit has a comment naming --proof-header
ex_bus() { sed -n 's/^ExecStart=//p' "$UNIT_BUS"; }
lastlog() { tail -1 "$RB/data/state/stack-activation-log.jsonl"; }
pointer() { node "$B/jget.mjs" "$1" < "$RB/data/state/stack-active.json"; }
console_flags() { node -e 'const p=JSON.parse(require("fs").readFileSync(0,"utf8"));process.stdout.write((p.console??[]).join(" "))' < "$RB/data/state/stack-active.json"; }
listens() { (exec 3<>/dev/tcp/127.0.0.1/$1) 2>/dev/null; }
state() { # the pointer, the unit ExecStarts after a part
note "pointer: $(cat "$RB/data/state/stack-active.json")"
note "bus unit ExecStart: $(sed -n 's/^ExecStart=//p' "$UNIT_BUS")"
note "Console unit ExecStart: $(sed -n 's/^ExecStart=//p' "$UNIT_WEB")"
note "activation log, last line: $(lastlog)"
}
snap() { # snap NAME ARGS... : snapshot.mjs on the scratch store, the output kept in out/snap/NAME.json
local name="$1"; shift
local sha; sha="$(sha256sum "$SNAP" | cut -c1-64)"
note "snapshot.mjs sha256 before $name: $sha"
[ "$sha" = "$SNAP_SHA" ] || stop "snapshot.mjs is $sha, not the approved $SNAP_SHA; $name not run"
part "snapshot $name" node "$SNAP" --store "$DB" "$@"
cp "$R/out/last.txt" "$SN/$name.json"
}
same() { # same A B : the two snapshots match on every table
part "compare $1 with $2" node "$B/cmp.mjs" "$SN/$1.json" "$SN/$2.json"
}
markers() { # names only, never values: run markers in this shell's ancestors (the tester walk refuses them)
local p=$$ n=0
while [ "$p" -gt 1 ]; do
tr '\0' '\n' 2>/dev/null < "/proc/$p/environ" | grep -qE '^(MOSAIC_BUS_CAP|MOSAIC_RUN_ID|MOSAIC_AGENT_RUN)=.' && { echo "pid $p has a run marker"; n=$((n + 1)); }
p=$(awk '{print $4}' "/proc/$p/stat" 2>/dev/null) || break
done
echo "ancestors with a run marker: $n"
}
backup_of() { # backup_of ACT : check the activation's backup set; sets BDIR and STAMP
BDIR="$RB/data/backups/stack/$1"; STAMP="$(cat "$BDIR/STAMP" 2>/dev/null)"
note "backup $BDIR; STAMP $STAMP; SCHEMA $(cat "$BDIR/SCHEMA" 2>/dev/null); DATA_ROOT $(cat "$BDIR/DATA_ROOT" 2>/dev/null); files: $(ls "$BDIR" 2>/dev/null | paste -sd' ' -)"
[ -n "$STAMP" ] && [ "$(cat "$BDIR/SCHEMA")" = 3i ] && [ "$(cat "$BDIR/DATA_ROOT")" = "$RB/data" ] || stop "backup $1: no STAMP, or SCHEMA is not 3i, or DATA_ROOT is not $RB/data"
(cd "$BDIR/bus" && sha256sum -c --quiet ../SHA256SUMS) >> "$OUT" 2>&1 && note "backup SHA256SUMS: OK, file sha256 $(sha256sum < "$BDIR/SHA256SUMS" | cut -c1-64)" || stop "backup SHA256SUMS does not check"
}
activate_checks() { # activate_checks TITLE : the W5 checks on the last part; sets ACT
expect 0
has "gate passed on $NEW" || stop "$1: no 'gate passed on $NEW'"
has "backup: " || stop "$1: the run printed no backup line"
ACT="$(lastlog | node "$B/jget.mjs" activation)"
[ "$(lastlog | node "$B/jget.mjs" result)" = ok ] && [ -n "$ACT" ] && [ "$(lastlog | node "$B/jget.mjs" backup)" = "$ACT" ] || stop "$1: the last log line is not ok with this run's backup"
note "$1 activation $ACT"
backup_of "$ACT"
state
[ "$(pointer copy)" = "$NEW" ] && [ "$(pointer schema)" = 3i ] && [ "$(pointer pm)" = true ] && [ -z "$(pointer pmPending)" ] || stop "$1: pointer not on NEW, 3i, pm true, no pmPending"
[ "$(console_flags)" = "$OLD_FLAGS" ] || stop "$1: the pointer's Console flags are '$(console_flags)', not OLD's '$OLD_FLAGS'"
grep -qF -- "--tester-port $TPORT" <(ex_web) || stop "$1: the Console unit lacks --tester-port $TPORT"
grep -qE -- '--proof-(header|file)' <(ex_web) && stop "$1: the Console unit has a proof flag"
grep -qF -- ' --pm' <(ex_bus) || stop "$1: the bus unit lacks --pm"
listens "$TPORT" && note "tester port $TPORT listens (stand-in)" || stop "tester port $TPORT is closed"
[ "$(grep -c '"text":"launched chief' "$RB/sd/journal.jsonl")" -ge 1 ] && note "pm launch: $(grep -c '"text":"launched chief' "$RB/sd/journal.jsonl") emulated 'launched chief' journal line(s) so far" || stop "$1: no emulated 'launched chief' journal line"
}
{
echo "# SCRATCH ROOT, NOT LIVE. Window rows 177-185 P3, run by darkwing from $(date -u +%FT%TZ) (mos-0931, sage-1238)."
echo "# Plan: agents/sage/work/window-plan/repin-rows-177-185.md, sha256 $(sha256sum < /mnt/storage/src/mosaic-stack/agents/sage/work/window-plan/repin-rows-177-185.md | cut -c1-64)."
echo "# Tool: $REPO, a git clone --shared of the canonical checkout at $(git -C "$REPO" rev-parse HEAD), nothing applied;"
echo "# scripts/stack-release.sh blob $(git -C "$REPO" rev-parse HEAD:scripts/stack-release.sh). refactor in the clone: $(git -C "$REPO" rev-parse refactor)."
echo "# Copies (stack-copy.sh copy): OLD $OLD, NEW $NEW. Both at 3i: one activate --pm, no --migrate."
echo "# Root: $RB. Config $RB/cfg/config.json, data root $RB/data, units in $RB/xcfg/systemd/user."
echo "# Business file: the committed example (blob $(git -C "$REPO" hash-object packages/business/examples/mosaic-stack.example.json)) made loadable by mkbiz.mjs: plugins"
echo "# {\"usage-source\":[\"transcript\"],\"channel\":[\"cli\",\"webui\"],\"harness\":[\"claude-code\"]}, bus.tester true, every role's harness var"
echo "# claude-code, launch.instances plus assistant; sha256 $(sha256sum < "$R/bfiles/B/businesses/mosaic-stack.json" | cut -c1-64)."
echo "# Store: seeded at 3i by OLD's Store before the bus first started (store-seed.mjs): 5 usage_records, 2 instance_names,"
echo "# project p3-proj and one human thread $HUMAN_THREAD on the assistant instance."
echo "# snapshot.mjs: $SNAP, sha256 $(sha256sum < "$SNAP" | cut -c1-64) (expected $SNAP_SHA)."
echo "# Stand-ins ($B): systemctl runs the copy's real 'scripts/mosaic bus start mosaic-stack'. With pm-mode emulate a --pm"
echo "# start runs the bus WITHOUT --pm and EMULATES the PM launch: no claude runs; a waiting runner, one sessions.json entry"
echo "# and one 'launched chief' journal line stand in for it (no session credential goes to scratch). The Console is a"
echo "# /healthz stand-in on 127.0.0.1:$PORT and 127.0.0.1:$TPORT. (3) starts OLD's real webui serve by hand on $OPORT and"
echo "# $OTPORT. journalctl and systemd-analyze are the test's; claude is a fixture that prints a version. Credential files are"
echo "# fake placeholders."
echo "# Environment for every call: env -i with HOME, PATH, TMPDIR, LANG, MOSAIC_CONFIG, XDG_* and SD_* only (names: $(printf '%s\n' "${E[@]:2}" | cut -d= -f1 | paste -sd' ' -))."
} > "$OUT"
step "--- run.sh"
[ "$(sha256sum < "$SNAP" | cut -c1-64)" = "$SNAP_SHA" ] || stop "snapshot.mjs is not the copy Mos read"
state
OLD_FLAGS="$(console_flags)"
note "OLD's Console flags in the pointer: $OLD_FLAGS"
grep -qF -- "--tester-port $TPORT" <<< "$OLD_FLAGS" || stop "the seeded pointer's Console flags lack --tester-port $TPORT"
# --- the load checks (P1 shape, on the scratch file)
part "L1. OLD on the rehearsal file" node "$B/load-check.mjs" "$COPIES/$OLD" "$RB/cfg"; expect 0
part "L2. NEW on the rehearsal file" node "$B/load-check.mjs" "$COPIES/$NEW" "$RB/cfg"; expect 0
part "L3. the P1 values of the rehearsal file" node -e '
const b=require(process.argv[1]);const r=Object.entries(b.roles);
console.log(JSON.stringify({busTester:b.vars["bus.tester"],pluginKinds:Object.keys(b.plugins??{}).sort(),launchBy:b.launch?.by,
assistantLaunchable:(b.launch?.instances??[]).includes("assistant"),piRoles:r.filter(([,x])=>[x.vars?.harness,x.vars?.["fallback.harness"]].includes("pi")).map(([k])=>k),
harnessByRole:Object.fromEntries(r.map(([k,x])=>[k,x.vars?.harness??null]))}))' "$RB/cfg/businesses/mosaic-stack.json"
expect 0; has '"busTester":true' && has '"pluginKinds":["channel","harness","usage-source"]' && has '"launchBy":"chief","assistantLaunchable":true,"piRoles":[]' || stop "P1 values differ"
{ echo; echo "## L4. run markers in this shell's ancestors (names only; the tester walk refuses one)"; } >> "$OUT"
markers > "$R/out/last.txt"; cat "$R/out/last.txt" >> "$OUT"
has 'ancestors with a run marker: 0' || stop "a run marker above this shell; the tester proof would refuse"
# --- W1, S1, W2
part "W1a. agents as the tester (OLD's CLI; the chief must not be working)" "${C_OLD[@]}" agents "${BZ[@]}" --tester --json
expect 0
lacks '"working"' || stop "W1: an agent is working"
part "W1b. launches list (OLD's CLI)" "${C_OLD[@]}" launches list --json; expect 0
note "W1 start stamp: $(date -u +%FT%TZ)"
snap S1 --held; expect 0
has '"schema": "3i"' || stop "S1 is not at 3i"
part "W2. status" "${SR[@]}" status mosaic-stack "${CB[@]}"; expect 0
[ "$(pointer copy)" = "$OLD" ] && [ "$(pointer schema)" = 3i ] && [ "$(pointer pm)" = true ] && [ -z "$(pointer pmPending)" ] || stop "W2: pointer not on OLD, 3i, pm true, no pending"
# --- W3, W5
part "W3. activate NEW --pm --plan" "${SR[@]}" activate "$NEW" mosaic-stack --pm --plan "${CB[@]}"
expect 0
cp "$R/out/last.txt" "$R/out/W3-plan.txt"
for c in tester-port-invalid tester-port-target 'refuses ' '--proof-header' '--proof-file' 'drop '; do lacks "$c" || stop "W3 names $c"; done
has "from: $OLD" && has "to: $NEW" || stop "W3: from or to differs"
has "migration: none; the store is at the target's version" || stop "W3: no 'migration: none' line"
has "backup: the run backs up the stopped store to $RB/data/backups/stack/<activation>/ before any migration" || stop "W3: no backup line"
NCOMMITS="$(git -C "$REPO" rev-list --count --no-merges "$OLD..$NEW")"
grep -qxF "commits: the run adds $NCOMMITS (newest first, at most 200):" "$R/out/last.txt" || stop "W3: the commit count is not $NCOMMITS"
has "Console: scripts/webui-service.sh install --copy $NEW --copies $COPIES $OLD_FLAGS" || stop "W3: the Console line's flags are not OLD's ($OLD_FLAGS)"
has "bus: scripts/bus-service.sh install --copy $NEW --copies $COPIES --pm " || stop "W3: the bus line lacks --pm"
note "W3: from OLD, to NEW, migration none, the backup line, $NCOMMITS commits, Console flags identical to OLD's, no refusal"
W5EPOCH="$(date +%s)"
part "W5. activate NEW --pm" "${SR[@]}" activate "$NEW" mosaic-stack --pm "${CB[@]}"
activate_checks W5
W5ACT=$ACT W5BDIR=$BDIR W5STAMP=$STAMP
part "W5b. status" "${SR[@]}" status mosaic-stack "${CB[@]}"; expect 0
snap W5 --held; expect 0
has '"schema": "3i"' || stop "the store is not at 3i after W5"
same S1 W5
note "S1 against W5 exit $RC (the bus restart on NEW may add rows; S2 below checks the prior rows against the backup)"
# --- W9, the tester thread flow (the chief and assistant turns cannot run in scratch)
part "W9a. status" "${SR[@]}" status mosaic-stack "${CB[@]}"; expect 0
part "W9b. launches list (NEW's CLI)" "${C_NEW[@]}" launches list --json; expect 0
part "W9c. agents as the tester (NEW's CLI)" "${C_NEW[@]}" agents "${BZ[@]}" --tester --json; expect 0
part "W9d. talk chief as the tester, --wait 0 (scratch: the emulated chief holds no claim, so tester-target-not-running is expected)" \
timeout 60 "${C_NEW[@]}" talk chief "[p3 test] re-pin $NEW: reply with one line, no staffing request" --wait 0 "${BZ[@]}" --tester
if [ "$RC" = 3 ] && has "isn't running, so the tester's message was refused and nothing was stored"; then note "W9d: refused tester-target-not-running, as expected with the PM launch emulated"
elif [ "$RC" = 0 ]; then note "W9d: sent (exit 0)"
else stop "W9d: exit $RC"; fi
part "W9e. project create window-p3 as the tester" "${C_NEW[@]}" project create window-p3 "${BZ[@]}" --tester; expect 0
part "W9f. thread create window-p3 w9 as the tester" "${C_NEW[@]}" thread create window-p3 w9 "${BZ[@]}" --tester; expect 0
TID="$(sed -n 's/^created window-p3\/w9 id \([0-9a-f-]*\) instance \(.*\)$/\1/p' "$R/out/last.txt")"
TINST="$(sed -n 's/^created window-p3\/w9 id [0-9a-f-]* instance \(.*\)$/\1/p' "$R/out/last.txt")"
[ -n "$TID" ] && [ "$TINST" = assistant ] || stop "W9f: no thread id, or the instance is not assistant"
note "W9f: thread $TID on instance $TINST"
part "W9g. talk --thread as the tester, --wait 20 (scratch: no assistant reply can come; claude is a fixture)" \
timeout 60 "${C_NEW[@]}" talk --thread "$TID" "[p3 test] reply with one line, then stop" --wait 20 "${BZ[@]}" --tester
note "W9g: exit $RC$([ "$RC" = 1 ] && has "no reply from" && echo ', no reply within 20 s, as expected in scratch')"
[ "$RC" = 0 ] || [ "$RC" = 1 ] || stop "W9g: exit $RC"
part "W9h. the bus journal since W5 (the assistant thread start)" journalctl --user -u [email protected] --since "@$W5EPOCH" -o cat
part "W9i. launches list after the thread message (NEW's CLI)" "${C_NEW[@]}" launches list --json; expect 0
part "W9j. thread read --json as the tester" "${C_NEW[@]}" thread read "$TID" --json "${BZ[@]}" --tester; expect 0
has "[p3 test] reply with one line, then stop" || stop "W9j: the tester message is not in the thread"
part "W9k. threads --json as the tester" "${C_NEW[@]}" threads --json "${BZ[@]}" --tester; expect 0
node -e 'const t=JSON.parse(require("fs").readFileSync(0,"utf8"));process.exit(t.length===1&&t[0].id===process.argv[1]?0:1)' "$TID" < "$R/out/last.txt" \
&& note "W9k: lists exactly the tester's thread $TID, not the human thread" || stop "W9k: threads --tester does not list exactly $TID"
part "W9l. threads as the tester (text)" "${C_NEW[@]}" threads "${BZ[@]}" --tester; expect 0
part "W9m. thread read of the seeded human thread as the tester (expect thread-not-owned)" "${C_NEW[@]}" thread read "$HUMAN_THREAD" "${BZ[@]}" --tester
[ "$RC" != 0 ] && has thread-not-owned || stop "W9m: no thread-not-owned refusal"
part "W9n. names as the tester" "${C_NEW[@]}" names "${BZ[@]}" --tester --json; expect 0
part "W9o. usage as the tester" "${C_NEW[@]}" usage "${BZ[@]}" --tester --json; expect 0
snap S2 --held --after "$W5STAMP" --backup "$W5BDIR/bus/bus.sqlite"; expect 0
part "S2 check against the W5 backup" node "$B/s2check.mjs" "$SN/S2.json"; expect 0
part "S2 D3-style report" node "$B/d3report.mjs" "$SN/S2.json"
[ "$RC" = 0 ] || [ "$RC" = 3 ] || stop "S2 report could not read the snapshot"
# --- (1) the plain rollback
part "R1a. rollback --plan" "${SR[@]}" rollback mosaic-stack --plan "${CB[@]}"; expect 0
lacks 'refuses ' || stop "R1a: the plan refuses"
has "to: $OLD" || stop "R1a: to is not OLD"
snap R1pre --held; expect 0
part "R1b. rollback" "${SR[@]}" rollback mosaic-stack "${CB[@]}"; expect 0
state
[ "$(pointer copy)" = "$OLD" ] && [ "$(pointer schema)" = 3i ] && [ "$(pointer pm)" = true ] && [ -z "$(pointer pmPending)" ] || stop "R1: pointer not on OLD, 3i, pm true"
[ "$(console_flags)" = "$OLD_FLAGS" ] || stop "R1: the pointer's Console flags are not OLD's"
grep -qF -- "--tester-port $TPORT" <(ex_web) || stop "R1: the Console unit lacks --tester-port $TPORT"
grep -qF -- "/copies/$OLD/" <(ex_web) && grep -qF -- "/copies/$OLD/" <(ex_bus) || stop "R1: a unit does not name the OLD copy"
listens "$TPORT" && note "R1: tester port $TPORT listens" || stop "R1: tester port $TPORT is closed"
ls -d "$RB/data"/bus.post-attempt-* 2>/dev/null && stop "R1: a post-attempt store appeared"
note "R1: pointer on OLD at 3i, pm true, Console flags kept with --tester-port $TPORT, no post-attempt store"
snap R1 --held; expect 0
same R1pre R1
[ "$RC" = 0 ] && note "R1: the store matches the snapshot before the rollback" || note "FINDING: the store after the plain rollback differs from the one before it (the compare above lists the tables)"
part "R1c. status" "${SR[@]}" status mosaic-stack "${CB[@]}"; expect 0
# --- (3) OLD reads the rows NEW's tester thread wrote
part "O1. threads --tester --json (OLD's CLI; before row 177 it lists the human's threads)" "${C_OLD[@]}" threads --json "${BZ[@]}" --tester; expect 0
part "O2. agents --tester --json (OLD's CLI)" "${C_OLD[@]}" agents "${BZ[@]}" --tester --json; expect 0
part "O3. project list --tester --json (OLD's CLI)" "${C_OLD[@]}" project list --json "${BZ[@]}" --tester; expect 0
has '"window-p3"' && note "O3: OLD lists NEW's project window-p3" || note "FINDING: OLD's project list does not show window-p3"
note "O4: OLD's real Console, started by hand: webui serve --port $OPORT --tester-port $OTPORT --business mosaic-stack"
(cd "$REPO" && exec "${E[@]}" node "$COPIES/$OLD/packages/webui/src/cli.mjs" serve --port "$OPORT" --tester-port "$OTPORT" --business mosaic-stack) > "$R/out/old-console.log" 2>&1 &
OCP=$!; echo "old-console $OCP" >> "$RB/sd/pids.log"
for _ in $(seq 1 50); do listens "$OTPORT" && break; sleep 0.2; done
listens "$OTPORT" || { kill "$OCP" 2>/dev/null; stop "O4: OLD's Console did not listen on $OTPORT"; }
{ echo; echo "## O4. GET on OLD's Console tester port $OTPORT (status, then the body's length and any error)"; } >> "$OUT"
O4FAIL=0
for p in /healthz /api/config /api/instances /api/bus/inbox /api/bus/tasks /api/bus/agents /api/bus/decisions /api/bus/messages /api/bus/usage /api/bus/names /api/bus/instances /api/bus/routes /api/bus/limits /api/bus/credentials /api/bus/replies; do
code="$(curl -s -o "$R/out/o4.body" -w '%{http_code}' "http://127.0.0.1:$OTPORT$p")"
err="$(node -e 'try{const o=JSON.parse(require("fs").readFileSync(process.argv[1],"utf8"));process.stdout.write(o&&o.error?JSON.stringify(o.error):"")}catch{}' "$R/out/o4.body")"
echo "GET $p $code bytes $(wc -c < "$R/out/o4.body")${err:+ error $err}" >> "$OUT"
[ "$code" = 200 ] || O4FAIL=1
done
rm -f "$R/out/o4.body"
{ echo "# OLD's Console log:"; sed 's/^/# /' "$R/out/old-console.log"; } >> "$OUT"
kill "$OCP" 2>/dev/null; wait "$OCP" 2>/dev/null
listens "$OTPORT" && stop "O4: $OTPORT still listens after the kill"
[ "$O4FAIL" = 0 ] && note "O4: every read on OLD's tester port returned 200" || note "FINDING: a read on OLD's tester port did not return 200 (listed above)"
snap R1post --held; expect 0
same R1 R1post
note "(3) store after OLD's reads against R1: exit $RC"
# --- (2) activate NEW again, one tester write after the stamp, the restore
part "A2. activate NEW --pm again" "${SR[@]}" activate "$NEW" mosaic-stack --pm "${CB[@]}"
activate_checks A2
A2ACT=$ACT A2BDIR=$BDIR A2STAMP=$STAMP
part "X1. project create p3-after-stamp as the tester (after the A2 stamp)" "${C_NEW[@]}" project create p3-after-stamp "${BZ[@]}" --tester; expect 0
snap D3 --held --after "$A2STAMP" --backup "$A2BDIR/bus/bus.sqlite"; expect 0
part "D3 report" node "$B/d3report.mjs" "$SN/D3.json"
[ "$RC" = 0 ] || [ "$RC" = 3 ] || stop "D3 report could not read the snapshot"
grep -qF '"projects"' <<< "$(node "$B/jget.mjs" stopTables < "$SN/D3.json")" || stop "D3 stopTables does not name projects, which X1 wrote"
part "R2a. rollback --restore $A2ACT --plan" "${SR[@]}" rollback mosaic-stack --restore "$A2ACT" --plan "${CB[@]}"; expect 0
lacks 'refuses ' || stop "R2a: the plan refuses"
has "to: $OLD" || stop "R2a: to is not OLD"
part "R2b. rollback --restore $A2ACT without --events (expect post-backup-events with per-table counts)" "${SR[@]}" rollback mosaic-stack --restore "$A2ACT" "${CB[@]}"
[ "$RC" != 0 ] && has post-backup-events || stop "R2b did not refuse post-backup-events"
has 'projects' || stop "R2b: the refusal does not count projects"
N="$(sed -n 's/.*then --events \([0-9][0-9]*\).*/\1/p' "$R/out/last.txt" | head -1)"
[ -n "$N" ] || stop "R2b: no 'then --events N' in the refusal"
note "R2b refused post-backup-events; the refusal names --events $N"
[ "$(pointer copy)" = "$NEW" ] && [ "$(pointer pm)" = true ] || stop "R2b's refusal changed the pointer"
part "R2c. rollback --restore $A2ACT --events $N" "${SR[@]}" rollback mosaic-stack --restore "$A2ACT" --events "$N" "${CB[@]}"
if [ "$RC" != 0 ] && has post-backup-events; then
N2="$(sed -n 's/.*then --events \([0-9][0-9]*\).*/\1/p' "$R/out/last.txt" | head -1)"
note "FINDING: the count moved between R2b ($N) and R2c ($N2); one rerun with --events $N2"
part "R2d. rollback --restore $A2ACT --events $N2" "${SR[@]}" rollback mosaic-stack --restore "$A2ACT" --events "$N2" "${CB[@]}"
fi
expect 0
POST="$(sed -n 's/^restore: .*moves to \(.*\) and is kept;.*/\1/p' "$R/out/last.txt" | head -1)"
note "post-attempt store: ${POST:-(none named)}$([ -n "$POST" ] && [ -e "$POST" ] && echo ', present')"
state
# --- the end state
part "F1. status at the end" "${SR[@]}" status mosaic-stack "${CB[@]}"; expect 0
[ "$(pointer copy)" = "$OLD" ] && [ "$(pointer schema)" = 3i ] && [ "$(pointer pm)" = true ] && [ -z "$(pointer pmPending)" ] || stop "F1: pointer not on OLD, 3i, pm true"
[ "$(console_flags)" = "$OLD_FLAGS" ] || stop "F1: the pointer's Console flags are not OLD's"
grep -qF -- "--tester-port $TPORT" <(ex_web) || stop "F1: the Console unit lacks --tester-port $TPORT"
note "F1: pointer on OLD at 3i, pm true, Console flags OLD's with --tester-port $TPORT"
snap F --held --after "$A2STAMP" --backup "$A2BDIR/bus/bus.sqlite"; expect 0
part "F report against the A2 backup" node "$B/d3report.mjs" "$SN/F.json"
[ "$RC" = 0 ] || [ "$RC" = 3 ] || stop "F report could not read the snapshot"
part "F2. project list --tester --json (OLD's CLI; p3-after-stamp must be gone, window-p3 kept)" "${C_OLD[@]}" project list --json "${BZ[@]}" --tester; expect 0
lacks '"p3-after-stamp"' || stop "F2: the restore kept p3-after-stamp"
has '"window-p3"' || stop "F2: the restore lost window-p3, which was in the A2 backup"
part "F3. agents as the tester (OLD's CLI)" "${C_OLD[@]}" agents "${BZ[@]}" --tester --json; expect 0
part "F4. launches list (OLD's CLI)" "${C_OLD[@]}" launches list --json; expect 0
note "P3: PASS"
echo "done: $OUT"
@@ -0,0 +1,21 @@
// P3, S2 (repin-rows-177-185.md W9 "What passes"): from one snapshot.mjs --after --backup output, every
// table's rows at the backup are unchanged (priorSame true), instance_names is unchanged, and the tables
// with rows past the backup are listed with their counts. Counts and table names only.
// usage: node s2check.mjs S2.json. Exits 0 when both hold, 3 when one does not, 1 on a bad document.
import { readFileSync } from 'node:fs';
const s = JSON.parse(readFileSync(process.argv[2], 'utf8'));
if (!s.tables || !Array.isArray(s.stopTables)) { console.log('S2 check: not a --backup snapshot'); process.exit(1); }
const t = Object.entries(s.tables);
const prior = t.filter(([, x]) => x.vsBackup && x.vsBackup.priorSame === false).map(([n]) => n);
const grown = t.filter(([, x]) => x.vsBackup && x.rows !== x.vsBackup.backupRows).map(([n, x]) => `${n} ${x.vsBackup.backupRows}->${x.rows}`);
const after = t.filter(([, x]) => (x.rowsAfter ?? 0) > 0).map(([n, x]) => `${n} ${x.rowsAfter}`);
const names = s.tables.instance_names;
const namesSame = !!names && names.vsBackup?.priorSame === true && names.rows === names.vsBackup.backupRows && (names.rowsAfter ?? 0) === 0;
console.log(`S2 priorSame false: ${prior.join(', ') || 'none'}`);
console.log(`S2 row counts backup->now, changed only: ${grown.join(', ') || 'none'}`);
console.log(`S2 rowsAfter nonzero: ${after.join(', ') || 'none'}`);
console.log(`S2 instance_names unchanged: ${namesSame}`);
console.log(`S2 stopTables ${JSON.stringify(s.stopTables)}; eventsAfter ${s.eventsAfter}`);
const ok = prior.length === 0 && namesSame;
console.log(ok ? 'S2 CHECK OK' : 'S2 CHECK FAILED');
process.exit(ok ? 0 : 3);
@@ -0,0 +1,42 @@
#!/usr/bin/env bash
# P3, seed: a scratch root at OLD 89af38a83 and 3i on stand-in units, pm true, with synthetic store rows
# written by OLD's Store before the bus first starts, then adopt. The Console unit carries the live flag
# shape (preview set, --send, --tester-port) with scratch values: no Jason id, no live host. Real OLD bus
# host (PM launch emulated), healthz stand-in for the Console. Scratch paths only.
set -euo pipefail
. /home/jwoltje/darkwing-scratch/w185/p3/b/env.sh
[ ! -e "$RB" ] || { echo "seed.sh: $RB exists; remove it first" >&2; exit 1; }
for p in $PORT $TPORT $OPORT $OTPORT; do ! (exec 3<>/dev/tcp/127.0.0.1/$p) 2>/dev/null || { echo "seed.sh: port $p is in use" >&2; exit 1; }; done
mkdir -p "$RB"/{home,cfg/businesses,xcfg,xdata,xstate,run,sd,bin,tmp,data/notify/mosaic-stack}
chmod 700 "$RB/run"
echo emulate > "$RB/sd/pm-mode"
cat > "$RB/cfg/config.json" <<EOC
{"configVersion":1,"environment":"development","dataRoot":"$RB/data","execution":{"backend":"docker","provider":"zai","model":"glm-5.3-flash","adapter":"mock"}}
EOC
cp "$R/bfiles/B/businesses/mosaic-stack.json" "$RB/cfg/businesses/mosaic-stack.json"
printf '{"notifyVersion":1,"binding":null}' > "$RB/data/notify/mosaic-stack/notify.json"; chmod 600 "$RB/data/notify/mosaic-stack/notify.json"
step "root: $RB; business file $(sha256sum < "$RB/cfg/businesses/mosaic-stack.json" | cut -c1-64) (the example with the live plug-ins usage-source transcript, channel cli,webui, harness claude-code; bus.tester true; every role's harness var claude-code; launch.instances plus assistant)"
# The broker refuses to boot unless every credential file in the business file exists, mode 0600. These are
# FAKE placeholder strings, not tokens; both base URLs in the file are 127.0.0.1:9.
NS="$R/bfiles/nosecret"; [ ! -e "$NS" ] || { echo "seed.sh: $NS exists; remove it first" >&2; exit 1; }
mkdir -m 700 "$NS"
for f in $(node -e 'const b=require(process.argv[1]);const o=[];(function w(x){if(x&&typeof x==="object")for(const[k,v]of Object.entries(x)){if(k==="file"&&typeof v==="string")o.push(v);w(v)}})(b);console.log(o.join(" "))' "$RB/cfg/businesses/mosaic-stack.json"); do
case "$f" in "$NS"/*) ;; *) echo "seed.sh: credential path outside $NS: $f" >&2; exit 1;; esac
printf 'scratch-fake-placeholder-%s\n' "$(basename "$f" .token)" > "$f"; chmod 600 "$f"
done
step "fake credential files: $(ls "$NS" | wc -l) under $NS, each 'scratch-fake-placeholder-<name>', mode 0600"
HUMAN="$(node -e 'console.log(require(process.argv[1]).human)' "$RB/cfg/businesses/mosaic-stack.json")"
step "store seed: $(node "$B/store-seed.mjs" "$COPIES/$OLD" "$RB/data" "$HUMAN" 2>&1)"
printf '#!/usr/bin/env bash\n# mosaic-cli wrapper (Q81, agents/sage/work/q81/mosaic-cli.sh)\n[ "${1:-}" = __lease-capability ] && exec "%s/mosaic-old" "$@"\nexit 1\n' "$RB/bin" > "$RB/bin/mosaic"; chmod 755 "$RB/bin/mosaic"
run() { step "run: $*"; local rc=0; (cd "$REPO" && "${E[@]}" "$@") >> "$LOGF" 2>&1 || rc=$?; step "exit $rc"; return $rc; }
run scripts/bus-service.sh install --copy $OLD --copies "$COPIES" --pm --claude "$B/claude"
run systemctl --user start [email protected]
run scripts/webui-service.sh install --copy $OLD --copies "$COPIES" --port $PORT --business mosaic-stack \
--public-host p3.scratch.invalid --auth-header X-authentik-username --allow-user p3-scratch-user --send --tester-port $TPORT
run systemctl --user start mosaic-webui.service
run scripts/stack-copy.sh wrapper $OLD --copies "$COPIES" --bin "$RB/bin"
run "$COPIES/$OLD/scripts/mosaic" bus status --json
run "$COPIES/$OLD/scripts/mosaic" launches list --json
run scripts/stack-release.sh adopt mosaic-stack --copies "$COPIES" --bin "$RB/bin"
run scripts/stack-release.sh status mosaic-stack --copies "$COPIES" --bin "$RB/bin" || true
step "pointer: $(cat "$RB/data/state/stack-active.json")"
@@ -0,0 +1,25 @@
// P3: synthetic rows in the scratch store before the OLD bus first starts, so S1, D3 and the restore have
// something to hash: usage records (a known end, a begin only, an unknown end), two instance names, a
// project and one human thread on the assistant, so W9's thread-not-owned read has a target. OLD's own
// Store writes them at 3i. Synthetic values only; HUMAN is the scratch business file's `human`.
// usage: node store-seed.mjs OLD_COPY DATA_ROOT HUMAN
import { pathToFileURL } from 'node:url';
const [old, root, human] = process.argv.slice(2);
const { Store, SCHEMA_VERSION } = await import(pathToFileURL(`${old}/packages/bus/src/store.mjs`).href);
if (SCHEMA_VERSION !== '3i') throw Error(`OLD is ${SCHEMA_VERSION}, not 3i`);
const s = new Store(root);
const u = (at, run, turn, phase, status, n, cursor) =>
s.run(`INSERT INTO usage_records(at,business,run,instance,provider,model,account,turn,source,phase,status,code,cursor,input,output,cache_write,cache_read)
VALUES (?,?,?,?,?,?,NULL,?,?,?,?,?,?,?,?,?,?)`, at, 'mosaic-stack', run, 'chief', 'anthropic', 'scratch-model', turn, 'transcript', phase, status,
status === 'unknown' ? 'no-new-usage' : null, cursor, ...(status === 'known' ? [n, n * 2, 0, n * 3] : [null, null, null, null]));
u('2026-10-19T10:00:00.000Z', 'p3-run-a', 't1', 'begin', 'known', 0, '{"offset":1}');
u('2026-10-19T10:00:05.000Z', 'p3-run-a', 't1', 'end', 'known', 1200, null);
u('2026-10-19T10:01:00.000Z', 'p3-run-a', 't2', 'begin', 'known', 0, '{"offset":2}');
u('2026-10-19T13:00:00.000Z', 'p3-run-b', 't1', 'begin', 'known', 0, '{"offset":3}');
u('2026-10-19T13:00:09.000Z', 'p3-run-b', 't1', 'end', 'unknown', 0, null);
s.run(`INSERT INTO instance_names(at,business,instance,name,by) VALUES (?,?,?,?,?)`, '2026-10-19T09:00:00.000Z', 'mosaic-stack', 'chief', 'p3-name-1', 'tester');
s.run(`INSERT INTO instance_names(at,business,instance,name,by) VALUES (?,?,?,?,?)`, '2026-10-19T14:00:00.000Z', 'mosaic-stack', 'chief', 'p3-name-2', 'tester');
s.run(`INSERT INTO projects(id,at,business,name,repo,created_by) VALUES (?,?,?,?,?,?)`, 'p3-proj', '2026-10-19T09:00:00.000Z', 'mosaic-stack', 'p3-project', null, human);
s.run(`INSERT INTO threads(id,at,business,project,instance,owner,name) VALUES (?,?,?,?,?,?,?)`, 'b3a7c1d2-0000-4000-8000-000000000001', '2026-10-19T09:30:00.000Z', 'mosaic-stack', 'p3-proj', 'assistant', human, 'p3-human-thread');
s.close();
console.log(`seeded 5 usage_records, 2 instance_names, 1 project, 1 human thread (owner ${human}, instance assistant) at 3i`);
@@ -0,0 +1,136 @@
#!/usr/bin/node
// sage-1004/1006 Window B rehearsal: a stand-in `systemctl --user`, based on the one in
// packages/cli/tests/stack-release.test.mjs. Unit state lives in $SD_STATE/units.json and every call
// goes to calls.log. Unlike the test's holder, a bus start runs the copy's real
// `scripts/mosaic bus start <business>` from the unit's WorkingDirectory, without --pm and under this
// scratch environment. Its output becomes journal lines. With --pm in ExecStart, the PM launch is
// EMULATED: no claude runs. A scratch runner process stands in for the session, with one
// sessions.json entry and one `launched` journal line. A Console start runs a scratch /healthz
// server on the unit's --port. Nothing here reads or writes a live unit, store or journal.
// Row 169 rehearsal: when $SD_STATE/pm-mode holds "real", a bus start with --pm runs the unit's argv as
// it is, --pm included, so the copy's own launchPm runs (step 5). The start then waits up to 30 s more
// for the `launched` line or the bus exiting, and journals all of it. A bus that exited is "failed",
// as systemd shows a Type=simple unit whose main process exited 3 (RestartPreventExitStatus=2 3 4,
// so no restart). A `launched` line in real mode writes REAL-LAUNCH to pids.log; run.sh stops on it.
const fs = require('node:fs'), path = require('node:path'), cp = require('node:child_process');
const sd = process.env.SD_STATE, data = process.env.SD_DATA, unitDir = path.join(process.env.XDG_CONFIG_HOME, 'systemd', 'user');
const args = process.argv.slice(2).filter((a) => a !== '--user'), [verb, ...rest] = args;
fs.appendFileSync(path.join(sd, 'calls.log'), `${new Date().toISOString()} ${args.join(' ')}\n`);
const stFile = path.join(sd, 'units.json');
const load = () => { try { return JSON.parse(fs.readFileSync(stFile, 'utf8')); } catch { return {}; } };
const save = (s) => { fs.writeFileSync(stFile + '.tmp', JSON.stringify(s)); fs.renameSync(stFile + '.tmp', stFile); };
const sleep = (ms) => Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, ms);
const alive = (pid) => { try { const s = fs.readFileSync(`/proc/${pid}/stat`, 'utf8'); return s[s.lastIndexOf(')') + 2] !== 'Z'; } catch { return false; } };
const startTime = (pid) => { const s = fs.readFileSync(`/proc/${pid}/stat`, 'utf8'); return s.slice(s.lastIndexOf(')') + 2).split(' ')[19]; };
const isBus = (u) => /^mosaic-bus@[a-z0-9-]+\.service$/.test(u), inst = (u) => u.slice('mosaic-bus@'.length, -'.service'.length);
const journal = (unit, text) => fs.appendFileSync(path.join(sd, 'journal.jsonl'), JSON.stringify({ epoch: Math.floor(Date.now() / 1000), unit, text }) + '\n');
const unitFile = (u) => {
const main = isBus(u) ? '[email protected]' : u, file = path.join(unitDir, main);
if (!fs.existsSync(file)) return null;
const drops = [];
for (const d of isBus(u) ? [main + '.d', u + '.d'] : [u + '.d']) { const dir = path.join(unitDir, d); if (fs.existsSync(dir)) for (const f of fs.readdirSync(dir).sort()) if (f.endsWith('.conf')) drops.push(path.join(dir, f)); }
const r = { wd: '', exec: '', env: [], drops };
for (const f of [file, ...drops]) for (let line of fs.readFileSync(f, 'utf8').split('\n')) {
line = line.replace(/%i/g, isBus(u) ? inst(u) : '').replace(/%h/g, process.env.HOME);
const m = /^(WorkingDirectory|ExecStart|Environment)=(.*)$/.exec(line); if (!m) continue;
if (m[1] === 'WorkingDirectory') r.wd = m[2]; else if (m[1] === 'ExecStart') { if (m[2]) r.exec = m[2]; } else r.env.push(m[2]);
}
return r;
};
// Every fd above 2 is closed first, so no child holds stack-release.sh's lock (fd 9).
const CLOSE = 'for fd in /proc/self/fd/*; do n=${fd##*/}; [ "$n" -gt 2 ] && eval "exec $n>&-" 2>/dev/null; done; exec "$@"';
const spawnDetached = (argv, opts) => { const ch = cp.spawn('bash', ['-c', CLOSE, 'standin', ...argv], { detached: true, ...opts }); ch.unref(); return ch; };
const sessionsFile = path.join(data, 'bus-host', 'sessions.json');
const readSessions = () => { try { return JSON.parse(fs.readFileSync(sessionsFile, 'utf8')); } catch { return { sessionsVersion: 1, sessions: [] }; } };
const writeSessions = (doc) => { fs.mkdirSync(path.dirname(sessionsFile), { recursive: true, mode: 0o700 }); fs.writeFileSync(sessionsFile + '.tmp', JSON.stringify(doc, null, 2) + '\n', { mode: 0o600 }); fs.renameSync(sessionsFile + '.tmp', sessionsFile); };
if (verb === 'show') {
const u = rest[0], props = []; for (let i = 1; i < rest.length; i++) if (rest[i] === '-p') props.push(rest[++i]);
const f = unitFile(u), st = load()[u] || {};
for (const p of props) {
if (p === 'LoadState') console.log(`LoadState=${f ? 'loaded' : 'not-found'}`);
else if (p === 'WorkingDirectory') console.log(`WorkingDirectory=${f ? f.wd : ''}`);
else if (p === 'ExecStart') console.log(f && f.exec ? `ExecStart={ path=${f.exec.split(' ')[0]} ; argv[]=${f.exec} ; ignore_errors=no ; start_time=[n/a] ; stop_time=[n/a] ; pid=0 ; code=(null) ; status=0/0 }` : 'ExecStart=');
else if (p === 'Environment') console.log(`Environment=${f ? f.env.join(' ') : ''}`);
else if (p === 'MainPID') console.log(`MainPID=${st.state === 'active' && st.pid ? st.pid : 0}`);
else if (p === 'DropInPaths') console.log(`DropInPaths=${f ? f.drops.join(' ') : ''}`);
}
process.exit(0);
}
if (verb === 'is-active') { const q = rest[0] === '--quiet', u = q ? rest[1] : rest[0], st = load()[u] || {}; let s = st.state || 'inactive'; if (s === 'active' && st.pid && !alive(st.pid)) s = 'failed'; if (!q) console.log(s); process.exit(s === 'active' ? 0 : 3); }
if (verb === 'is-enabled') { console.log('disabled'); process.exit(1); }
if (verb === 'daemon-reload') process.exit(0);
if (verb === 'list-units') {
const pats = rest.filter((a) => !a.startsWith('--')).map((p) => new RegExp('^' + p.replace(/\./g, '\\.').replace(/\*/g, '.*') + '$'));
for (const [u, s] of Object.entries(load())) if (pats.some((re) => re.test(u))) console.log(`${u} loaded ${s.state} ${s.state === 'active' ? 'running' : 'dead'} standin`);
process.exit(0);
}
if (verb === 'start') {
const u = rest[0], f = unitFile(u); if (!f) { console.error(`Unit ${u} not found.`); process.exit(5); }
const s = load();
if (!isBus(u)) {
const argv = f.exec.split(' '), i = argv.indexOf('--port'), port = i > 0 ? argv[i + 1] : '7332';
const t = argv.indexOf('--tester-port'), tport = t > 0 ? argv[t + 1] : '';
const ch = spawnDetached([process.execPath, path.join(__dirname, 'healthz.cjs'), port, tport], { stdio: 'ignore', cwd: sd });
fs.appendFileSync(path.join(sd, 'pids.log'), `healthz ${ch.pid}\n`);
const end = Date.now() + 10000;
while (!fs.existsSync(path.join(sd, `healthz-${ch.pid}`))) { if (Date.now() > end) { console.error('healthz stand-in did not start'); process.exit(1); } sleep(20); }
s[u] = { state: 'active', pid: ch.pid, wd: f.wd }; save(s); process.exit(0);
}
const argv = f.exec.split(' '), pm = argv.slice(1).includes('--pm');
let mode = 'emulate'; try { mode = fs.readFileSync(path.join(sd, 'pm-mode'), 'utf8').trim(); } catch {}
const realPm = pm && mode === 'real', real = realPm ? argv : argv.filter((a) => a !== '--pm');
const env = { ...process.env }; for (const e of f.env) { const k = e.slice(0, e.indexOf('=')); if (k === 'PATH' || k === 'MOSAIC_CLAUDE_BIN') env[k] = e.slice(k.length + 1); }
const out = path.join(sd, `bus-${Date.now()}.out`), fd = fs.openSync(out, 'a');
const ch = spawnDetached(real, { stdio: ['ignore', fd, fd], cwd: f.wd, env });
fs.closeSync(fd);
fs.appendFileSync(path.join(sd, 'pids.log'), `bus ${ch.pid} ${real.join(' ')} > ${out}\n`);
const end = Date.now() + 60000; let text = '';
while (!/^bus host up/m.test(text)) {
if (!alive(ch.pid) && realPm) { text = fs.readFileSync(out, 'utf8'); if (/^bus host up/m.test(text)) break; }
if (!alive(ch.pid)) { for (const l of text.split('\n').filter(Boolean)) journal(u, l); s[u] = { state: 'failed', pid: null, wd: f.wd }; save(s); console.error(`bus exited before host up; see ${out}`); process.exit(1); }
if (Date.now() > end) { console.error(`bus host up not seen in 60 s; see ${out}`); process.exit(1); }
sleep(100); text = fs.readFileSync(out, 'utf8');
}
if (realPm) {
const end3 = Date.now() + 30000;
while (!/^launched /m.test(text) && alive(ch.pid) && Date.now() < end3) { sleep(100); text = fs.readFileSync(out, 'utf8'); }
sleep(200); text = fs.readFileSync(out, 'utf8');
if (/^launched /m.test(text)) fs.appendFileSync(path.join(sd, 'pids.log'), `REAL-LAUNCH ${ch.pid}\n`);
}
for (const l of text.split('\n').filter(Boolean)) journal(u, l);
if (realPm) {
const up = alive(ch.pid);
fs.appendFileSync(path.join(sd, 'pids.log'), `real-pm ${ch.pid} ${up ? 'running' : 'exited'}\n`);
s[u] = up ? { state: 'active', pid: ch.pid, wd: f.wd, out, journaled: text.length } : { state: 'failed', pid: null, wd: f.wd };
save(s); process.exit(0);
}
s[u] = { state: 'active', pid: ch.pid, wd: f.wd, out, journaled: text.length };
if (pm) {
// The emulated PM launch: a runner that only waits, one sessions.json entry, one journal line.
const runner = spawnDetached([process.execPath, '-e', 'setInterval(()=>{},1<<30)'], { stdio: 'ignore', cwd: sd });
const end2 = Date.now() + 5000; while (!fs.existsSync(`/proc/${runner.pid}/stat`) && Date.now() < end2) sleep(10);
const run = `remul${runner.pid}`, b = inst(u);
const doc = readSessions(); doc.sessions.push({ run, business: b, instance: 'chief', harness: 'claude-code', model: 'emulated', launchedBy: 'standin-pm-emulation', runnerPid: runner.pid, runnerStartTime: startTime(runner.pid), startedAt: new Date().toISOString() }); writeSessions(doc);
journal(u, `launched chief: run ${run}, claude-code emulated`);
fs.appendFileSync(path.join(sd, 'pids.log'), `runner ${runner.pid} ${run}\n`);
s[u].runner = runner.pid; s[u].run = run;
}
save(s); process.exit(0);
}
if (verb === 'stop') {
const u = rest[0], s = load(), st = s[u];
if (st && st.pid) {
try { process.kill(st.pid, 'SIGTERM'); } catch {}
const end = Date.now() + 20000; while (alive(st.pid) && Date.now() < end) sleep(50);
if (alive(st.pid)) { fs.appendFileSync(path.join(sd, 'pids.log'), `SIGKILL ${st.pid}\n`); try { process.kill(st.pid, 'SIGKILL'); } catch {} }
if (st.out) { const text = fs.readFileSync(st.out, 'utf8'); for (const l of text.slice(st.journaled || 0).split('\n').filter(Boolean)) journal(u, l); }
}
if (st && st.runner) {
try { process.kill(st.runner, 'SIGTERM'); } catch {}
const doc = readSessions(); doc.sessions = doc.sessions.filter((x) => x.run !== st.run); writeSessions(doc);
}
if (st) { s[u] = { wd: st.wd, state: 'inactive', pid: null }; save(s); }
process.exit(0);
}
console.error(`stand-in systemctl: unsupported: ${args.join(' ')}`); process.exit(1);
@@ -0,0 +1,4 @@
#!/usr/bin/node
// Stand-in `systemd-analyze --user unit-paths` (the test's): only the scratch unit dir.
if(process.argv[2]!=='--user'||process.argv[3]!=='unit-paths')process.exit(1);
console.log(require('node:path').join(process.env.XDG_CONFIG_HOME,'systemd','user'));
@@ -0,0 +1,18 @@
#!/usr/bin/env bash
# P3: stops the scratch units through the stand-in, then confirms every PID it started is gone and the port is closed.
set -uo pipefail
. /home/jwoltje/darkwing-scratch/w185/p3/b/env.sh
T=$R/out/p3-teardown.txt
{
echo "# teardown $(date -u +%FT%TZ)"
for u in mosaic-webui.service [email protected]; do
(cd "$REPO" && "${E[@]}" systemctl --user stop "$u"); echo "stop $u exit $?"
done
left=0
while read -r kind pid _; do
if [ -e "/proc/$pid" ] && grep -q "$R" "/proc/$pid/cmdline" 2>/dev/null; then echo "ALIVE $kind $pid"; left=1; fi
done < <(grep -v "^REAL-LAUNCH\|^SIGKILL\|^real-pm" "$RB/sd/pids.log")
for p in /proc/[0-9]*; do c=$(readlink "$p/cwd" 2>/dev/null) || continue; case $c in "$R"*) case "$(tr "\0" " " < "$p/cmdline" 2>/dev/null)" in *b/all.sh*|*b/teardown.sh*) continue;; esac; echo "cwd-in-root ${p#/proc/} $c"; left=1;; esac; done
for p in $PORT $TPORT $OPORT $OTPORT; do if (exec 3<>/dev/tcp/127.0.0.1/$p) 2>/dev/null; then echo "port $p open"; left=1; else echo "port $p closed"; fi; done
echo "left: $left"
} 2>&1 | tee "$T"
@@ -0,0 +1,15 @@
#!/usr/bin/env bash
# Waits until busy.sh passes twice, 60 s apart, then runs all.sh, teed. Gives up after 90 minutes.
set -uo pipefail
end=$((SECONDS + 5400))
while :; do
if ~/darkwing-scratch/busy.sh > /dev/null; then
sleep 60
if ~/darkwing-scratch/busy.sh; then break; fi
fi
[ "$SECONDS" -lt "$end" ] || { echo "wait-run: still busy after 90 min"; exit 9; }
sleep 30
done
echo "wait-run: clear at $(date -u +%FT%TZ)"
/home/jwoltje/darkwing-scratch/mbc/p3/b/all.sh 2>&1 | tee /home/jwoltje/darkwing-scratch/mbc/p3/all-$(date -u +%Y%m%dT%H%M%SZ).log
exit "${PIPESTATUS[0]}"

Some files were not shown because too many files have changed in this diff Show More