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:
@@ -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
@@ -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));
|
||||
+10
@@ -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);
|
||||
+31
@@ -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`);
|
||||
+136
@@ -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);
|
||||
+4
@@ -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'));
|
||||
+18
@@ -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"
|
||||
+15
@@ -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]}"
|
||||
+15
@@ -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
|
||||
+15
@@ -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)"}
|
||||
+6
@@ -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));
|
||||
+10
@@ -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);
|
||||
+31
@@ -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`);
|
||||
+136
@@ -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);
|
||||
+4
@@ -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'));
|
||||
+18
@@ -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"
|
||||
+15
@@ -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
Reference in New Issue
Block a user