Compare commits
10
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2da63d5e30 | ||
|
|
a14fdc4436 | ||
|
|
ec86f2c847 | ||
|
|
4ec279dc43 | ||
|
|
3d2569ce25 | ||
|
|
3a20f29d66 | ||
|
|
c5f96836d5 | ||
|
|
99930362e4 | ||
|
|
579720e84a | ||
|
|
c25799928d |
No files matched your search
@@ -5453,3 +5453,17 @@ Row 299 (573, #1806, Gander, Filbert): before, the P10 band test timed a fault r
|
||||
2026-10-24 | Goldie | Row 323 / #1819 second red STOP (mos-1560, sage-3864) | Isolated revoke-window whole-file run1 on unchanged571376c30 failed0/1 after20.126s, same no RESULT at ask167/test194. Start00:24:22Z, load1.46/1.68/2.70, busyhelperCLEAR, fresh scratch goldieBObDQ9. No run2/3/CLI retry/mutant/restored run; no source/old-test edit. Sage goldie-0018 has log/load/error; slot returned, awaits base-dd6cb5872 root cause. Remote remains unverified571.
|
||||
|
||||
2026-10-24 | Goldie | Row 323 / #1819 accepted evidence (mos-1561, sage-3869) | Sage traced revoke-window reds to37-byte TMPDIR making109-byte tools.sock >107 limit (session.mjs278), not Codex. Fresh31-byte /scratch/tmp/XXXXXX confirmation passes1/1 in0.84s. On unchanged571376c30 serialized timeout-k10: business261/261, new7/7, tester-switch mutant killed/restored SHA85e6b9ac, restored new7/7. Every log starts UTC/load/helperCLEAR; all positive runs0 fail/skip/cancel. CLI whole acceptance comes from Sage gate under mos-1561; no local CLI rerun. Evidence-only notes/log commit follows, no old test/source changes.
|
||||
|
||||
2026-10-24 | Goldie | Row325 / #1821 before (mos-1563/1564) | Real .mosaic/project.json idstack disagrees with Case1 project's mosaic-stack key; row323 scratch fixture omitted project file and only warned. New goldie/row325 at99930362e changes only that JSON key and expressly authorized old assertion key; new real-byte project-copy validation tests require valid and reject old key. Mutant prepared; no tests before Sage CLEAR, no queue writes/host actions.
|
||||
|
||||
2026-10-24 | Goldie | Row325 / #1821 after (sage-3882 CLEAR) | Only Case1 projects key becomesstack; only old test70 expected key updated under mos-1564. New3-case test copies real .mosaic/project.json bytes, requires CLI project valid, rejects old key. Serialized shortTMP timeout-k10: new3/3, business264/264, old-key mutant killed/restored SHA9e43bfef, restored new3/3; all positive0 fail/skip/cancel. Manifest4/4 verifies including unchanged project document. Logs/load lines and mutant diff in BUILD.md; no queue writes/host action, Gladstone review/Sage gate pending.
|
||||
|
||||
2026-10-24 | Bentina | Row157 / #1652 before (sage-3906, Q246, mos-1566) | Recovered seat/brief/design at ec86f2c84 on bentina/row157. Canonical queue next flags stale brief pin. Outbound audit finds broker gitea-grant node:https and web DNS-pinned node:https paths, triggering I2 explicit stop rule. Sage notified bentina-0001; BUILD.md records shifted cited sites. No implementation/test/Docker/credential/live-state action; awaiting lead re-pin and transport ruling.
|
||||
|
||||
2026-10-24 | Bentina | Row157 / #1652 first slot STOP (sage-3910) | New config/helper31/31 and proxy11/11 green. test-config exit1:55 pass/1 old output regression; new install20/20 green. Validator added absent install key to JSON. Stopped before Docker/install suite; slot returned Sage bentina-0004. sage-3916 authorizes preserving absent output shape/defaulting env to local: fixed own loader/new test only, no rerun before next CLEAR. test-config diff +4/-0. No commit/push; seat BUILD and01/02/03 logs.
|
||||
|
||||
2026-10-24 | Bentina | Row157 / #1652 after (sage-3919, sage-3923) | Absent install JSON shape preserved; helper defaults local. Existing config56/56, helper/config31/31, final proxy12/12, final test-install exit0 with three release0.0.12 rootless builds/pins, internal TLS/deny/DNS checks and real Vikunja publicGET200/notifier REST dummyGET401 through services proxy. Upstream-reset correction verified. Slot returned bentina-0007; no broader suites by Sage direction. Boyd then Darkwing review, Mos provider-host measurement and Sage landing gate pending. Evidence in seat BUILD and04-08 logs; only bentina/row157 candidate push authorized.
|
||||
|
||||
2026-10-24 | Bentina | Row157 / #1652 round2 before (boyd-0023, sage-3933) | Read reproduced B1 half-open refusal socket retention and B2 same-PID exec inherited cache bypass. Added two new regression files; no existing assertion edits. Proxy drains then destroys refused sockets with1s fallback; helper clears inherited cache/export attributes at source. No tests before CLEAR; Goldie holds slot. Source/causes recorded in seat BUILD.
|
||||
|
||||
2026-10-24 | Bentina | Row157 / #1652 round2 after (sage-3936) | B1 refused sockets now drain/destroy with1s bound; B2 source clears inherited cache/export attributes. Archived a14+new regressions red as intended (5controls pass/7defects fail), fixed12/12, helper/config33/33, egress22/22, rootless install exit0 incl image pins/network/services dummyGET. Logs09-13 in seat work. No existing test/assertion edit for round2. Slot returned bentina-0014; followup own-branch commit/manifest for Boyd re-review; Mos and Sage landing gates pending.
|
||||
@@ -76,6 +76,16 @@ Rules enforced by `scripts/mosaic-config.mjs`:
|
||||
|
||||
Run paths (`build/hello/verify/reset`) fail closed when configuration is missing or invalid; they never invent it.
|
||||
|
||||
The optional `install` key selects the containerized install explicitly:
|
||||
`"install": {"mode":"containerized","endpoint":"unix:///run/user/1000/docker.sock"}`.
|
||||
An absent key defaults to local mode and remains absent in validation output.
|
||||
Local mode forbids `endpoint`;
|
||||
containerized mode requires a Unix socket endpoint without whitespace
|
||||
(the actual pattern is `^unix:///[^\s]+\.sock$`). Unknown install keys or
|
||||
modes refuse with exit 2 without changing the config. Validation contacts no
|
||||
daemon; the endpoint helper checks rootless status before engine use. Keep
|
||||
existing live configs unchanged until the install rollout is authorized.
|
||||
|
||||
## Missions & tasks (M2)
|
||||
|
||||
Missions and tasks are validated JSON data (strict schemas, version-pinned). The M2 layer is host-side only: mission directives are recorded for provenance but do not yet reach the runtime system prompt (capability/policy layer comes later).
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
# Bentina
|
||||
|
||||
Builder seat, Codex gpt-6.1-sol, started by Sage under Q246 and mos-1566. Owns row 157, I2: the install images, the
|
||||
install config key, the endpoint helper and the egress proxy (docs/plans/2026-10-18_containerized-install-rows.md),
|
||||
reassigned from Researcher so the coding runs on Codex.
|
||||
|
||||
No launcher in this directory. The seat runs as a T3 thread and works in its own worktree. It has no credentials of its
|
||||
own; it pushes only its own `bentina/*` branches through the repository credential helper (mos-0981), never `refactor`,
|
||||
`main` or another seat's branch. Claude seats orchestrate and review the install path (mos-1542 rule 4). Registered by
|
||||
Sage on 2026-10-24 so the queue can name the seat as row owner (store.mjs isSeatDir).
|
||||
@@ -0,0 +1,43 @@
|
||||
2026-10-24T04:05:53Z
|
||||
23:05:53 up 10 days, 14:25, 2 users, load average: 2.29, 2.31, 1.89
|
||||
CLEAR sage-3910 TMPDIR=/mnt/storage/scratch/tmp/lEvgx1
|
||||
✔ install: absent (28.959485ms)
|
||||
✔ install: local (24.660305ms)
|
||||
✔ install: local endpoint (26.210523ms)
|
||||
✔ install: containerized missing endpoint (25.495263ms)
|
||||
✔ install: bad endpoint "tcp://localhost:2375" (28.62092ms)
|
||||
✔ install: bad endpoint "ssh://host/run/docker.sock" (32.126399ms)
|
||||
✔ install: bad endpoint "run/docker.sock" (26.69995ms)
|
||||
✔ install: bad endpoint "unix://relative.sock" (33.36326ms)
|
||||
✔ install: bad endpoint "unix:///a b.sock" (30.579866ms)
|
||||
✔ install: bad endpoint "unix:///a.sock\n" (40.844622ms)
|
||||
✔ install: bad endpoint "unix:///a" (36.371215ms)
|
||||
✔ install: bad endpoint "" (31.603044ms)
|
||||
✔ install: bad endpoint null (29.645166ms)
|
||||
✔ install: bad endpoint 4 (30.638453ms)
|
||||
✔ install: valid endpoint (28.088952ms)
|
||||
✔ install: unknown mode (28.182929ms)
|
||||
✔ install: missing mode (26.297287ms)
|
||||
✔ install: unknown key (27.205488ms)
|
||||
✔ install: null (29.189475ms)
|
||||
✔ install: array (30.347371ms)
|
||||
✔ helper refuses rootful (76.082505ms)
|
||||
✔ helper refuses missing (58.354463ms)
|
||||
✔ helper refuses substring (86.830586ms)
|
||||
✔ helper refuses object (108.0825ms)
|
||||
✔ helper refuses invalid JSON (85.203357ms)
|
||||
✔ helper refuses null success (85.225204ms)
|
||||
✔ helper refuses rootless output but failed (67.840402ms)
|
||||
✔ helper checks once and explicitly names host for both calls (119.763305ms)
|
||||
✔ local mode refuses before Docker null (35.42747ms)
|
||||
✔ local mode refuses before Docker {"mode":"local"} (30.531767ms)
|
||||
✔ invalid config refuses before Docker (30.092363ms)
|
||||
ℹ tests 31
|
||||
ℹ suites 0
|
||||
ℹ pass 31
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 833.790665
|
||||
EXIT=0
|
||||
@@ -0,0 +1,23 @@
|
||||
2026-10-24T04:06:01Z
|
||||
23:06:01 up 10 days, 14:25, 2 users, load average: 2.27, 2.30, 1.89
|
||||
CLEAR sage-3910 TMPDIR=/mnt/storage/scratch/tmp/lfgV9z
|
||||
✔ allows exact host and port, fragmented headers, and preserves tunnel head (21.273322ms)
|
||||
✔ unlisted host (1.324735ms)
|
||||
✔ unlisted port (1.081626ms)
|
||||
✔ GET (0.948442ms)
|
||||
✔ invalid port (0.95243ms)
|
||||
✔ userinfo (0.874803ms)
|
||||
✔ bad header (0.782898ms)
|
||||
✔ slow client has a total header deadline (81.829907ms)
|
||||
✔ oversized header refuses (1.267008ms)
|
||||
✔ missing, invalid and unknown lists fail closed (0.491274ms)
|
||||
✔ refused upstream returns 502 while retaining one allowed decision (1.716299ms)
|
||||
ℹ tests 11
|
||||
ℹ suites 0
|
||||
ℹ pass 11
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 156.342618
|
||||
EXIT=0
|
||||
@@ -0,0 +1,90 @@
|
||||
2026-10-24T04:06:08Z
|
||||
23:06:08 up 10 days, 14:25, 2 users, load average: 2.53, 2.36, 1.91
|
||||
CLEAR sage-3910 TMPDIR=/mnt/storage/scratch/tmp/6eJjJz
|
||||
OK absent adapter defaults to pi
|
||||
OK adapter mock validates (exit 0)
|
||||
OK unsupported adapter exits 2 (exit 2)
|
||||
OK env exports adapter
|
||||
OK bootstrap creates default when absent (exit 0)
|
||||
OK bootstrap wrote config file
|
||||
OK bootstrap is idempotent on existing config (exit 0)
|
||||
OK bootstrap did not rewrite existing config
|
||||
OK validate missing config exits 3 (exit 3)
|
||||
OK malformed JSON exits 2 (exit 2)
|
||||
OK unsupported configVersion exits 2 (exit 2)
|
||||
OK unknown top-level key exits 2 (exit 2)
|
||||
OK unknown execution key exits 2 (exit 2)
|
||||
OK unsupported backend exits 2 (exit 2)
|
||||
OK unsupported environment exits 2 (exit 2)
|
||||
OK relative dataRoot exits 2 (exit 2)
|
||||
OK non-canonical dataRoot exits 2 (exit 2)
|
||||
OK filesystem root dataRoot exits 2 (exit 2)
|
||||
OK home directory dataRoot exits 2 (exit 2)
|
||||
OK dataRoot containing config dir exits 2 (exit 2)
|
||||
OK control character in provider exits 2 (exit 2)
|
||||
OK symlinked config file exits 2 (exit 2)
|
||||
OK env exports resolve correctly
|
||||
OK valid console.sso loads and is in the output
|
||||
OK console.sso HS256 alg exits 2 naming the path (exit 2)
|
||||
OK console.sso alg none exits 2 naming the path (exit 2)
|
||||
OK console.sso missing slot field exits 2 naming the path (exit 2)
|
||||
OK console.sso slot name outside the pattern exits 2 naming the path (exit 2)
|
||||
OK unknown key under console exits 2 naming the path (exit 2)
|
||||
OK valid federation key loads and is in the output
|
||||
OK federation unknown sub-key exits 2 naming the key (exit 2)
|
||||
OK federation peer unknown sub-key exits 2 naming the key (exit 2)
|
||||
OK federation peer origin over http exits 2 naming the key (exit 2)
|
||||
OK federation peer origin with a path exits 2 naming the key (exit 2)
|
||||
OK federation peer origin with userinfo exits 2 naming the key (exit 2)
|
||||
OK federation peer id equal to self exits 2 naming the key (exit 2)
|
||||
OK federation peer label empty exits 2 naming the key (exit 2)
|
||||
OK federation not an object exits 2 naming the key (exit 2)
|
||||
OK federation self pattern exits 2 naming the key (exit 2)
|
||||
OK federation peers not an array exits 2 naming the key (exit 2)
|
||||
OK federation peer not an object exits 2 naming the key (exit 2)
|
||||
OK federation peer id pattern exits 2 naming the key (exit 2)
|
||||
OK federation peer label over 80 characters exits 2 naming the key (exit 2)
|
||||
OK federation duplicate peer id exits 2 naming the key (exit 2)
|
||||
OK federation peer origin with a trailing slash exits 2 naming the key (exit 2)
|
||||
OK federation self missing exits 2 naming the key (exit 2)
|
||||
OK federation peer id missing exits 2 naming the key (exit 2)
|
||||
OK federation peer label missing exits 2 naming the key (exit 2)
|
||||
OK config without federation loads and has no federation output
|
||||
FAIL config without console loads with unchanged output
|
||||
OK compactionReadBytes 8388608 loads and is in the output as written
|
||||
OK compactionReadBytes "x" loads and is in the output as written
|
||||
OK compactionStreams 2048 loads and is in the output as written
|
||||
OK compactionStreams "x" loads and is in the output as written
|
||||
OK failed validation modified nothing
|
||||
|
||||
✔ install: absent (28.022711ms)
|
||||
✔ install: local (26.314077ms)
|
||||
✔ install: local endpoint (27.030768ms)
|
||||
✔ install: containerized missing endpoint (26.036164ms)
|
||||
✔ install: bad endpoint "tcp://localhost:2375" (29.311242ms)
|
||||
✔ install: bad endpoint "ssh://host/run/docker.sock" (28.012086ms)
|
||||
✔ install: bad endpoint "run/docker.sock" (26.941261ms)
|
||||
✔ install: bad endpoint "unix://relative.sock" (26.90999ms)
|
||||
✔ install: bad endpoint "unix:///a b.sock" (23.646272ms)
|
||||
✔ install: bad endpoint "unix:///a.sock\n" (26.383344ms)
|
||||
✔ install: bad endpoint "unix:///a" (24.914913ms)
|
||||
✔ install: bad endpoint "" (24.320663ms)
|
||||
✔ install: bad endpoint null (25.688262ms)
|
||||
✔ install: bad endpoint 4 (26.934437ms)
|
||||
✔ install: valid endpoint (27.504294ms)
|
||||
✔ install: unknown mode (27.111833ms)
|
||||
✔ install: missing mode (26.365767ms)
|
||||
✔ install: unknown key (26.974983ms)
|
||||
✔ install: null (25.910901ms)
|
||||
✔ install: array (25.507423ms)
|
||||
ℹ tests 20
|
||||
ℹ suites 0
|
||||
ℹ pass 20
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 584.03015
|
||||
OK install configuration cases
|
||||
selftest: 55 passed, 1 failed
|
||||
EXIT=1
|
||||
@@ -0,0 +1,43 @@
|
||||
2026-10-24T04:08:52Z
|
||||
23:08:52 up 10 days, 14:28, 2 users, load average: 2.16, 2.49, 2.04
|
||||
CLEAR sage-3919 TMPDIR=/mnt/storage/scratch/tmp/o2n7Db
|
||||
✔ install: absent (31.8592ms)
|
||||
✔ install: local (29.807282ms)
|
||||
✔ install: local endpoint (24.009896ms)
|
||||
✔ install: containerized missing endpoint (26.296453ms)
|
||||
✔ install: bad endpoint "tcp://localhost:2375" (26.404525ms)
|
||||
✔ install: bad endpoint "ssh://host/run/docker.sock" (27.486365ms)
|
||||
✔ install: bad endpoint "run/docker.sock" (24.280876ms)
|
||||
✔ install: bad endpoint "unix://relative.sock" (24.363468ms)
|
||||
✔ install: bad endpoint "unix:///a b.sock" (24.920898ms)
|
||||
✔ install: bad endpoint "unix:///a.sock\n" (25.784031ms)
|
||||
✔ install: bad endpoint "unix:///a" (27.89803ms)
|
||||
✔ install: bad endpoint "" (28.939674ms)
|
||||
✔ install: bad endpoint null (26.129602ms)
|
||||
✔ install: bad endpoint 4 (27.601233ms)
|
||||
✔ install: valid endpoint (29.310223ms)
|
||||
✔ install: unknown mode (30.307014ms)
|
||||
✔ install: missing mode (26.432375ms)
|
||||
✔ install: unknown key (29.208115ms)
|
||||
✔ install: null (29.793ms)
|
||||
✔ install: array (28.956899ms)
|
||||
✔ helper refuses rootful (88.440017ms)
|
||||
✔ helper refuses missing (49.868469ms)
|
||||
✔ helper refuses substring (76.03165ms)
|
||||
✔ helper refuses object (77.494513ms)
|
||||
✔ helper refuses invalid JSON (78.525159ms)
|
||||
✔ helper refuses null success (79.166999ms)
|
||||
✔ helper refuses rootless output but failed (52.195589ms)
|
||||
✔ helper checks once and explicitly names host for both calls (124.380922ms)
|
||||
✔ local mode refuses before Docker null (28.199092ms)
|
||||
✔ local mode refuses before Docker {"mode":"local"} (29.618143ms)
|
||||
✔ invalid config refuses before Docker (27.543884ms)
|
||||
ℹ tests 31
|
||||
ℹ suites 0
|
||||
ℹ pass 31
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 762.528877
|
||||
EXIT=0
|
||||
@@ -0,0 +1,90 @@
|
||||
2026-10-24T04:09:01Z
|
||||
23:09:01 up 10 days, 14:28, 2 users, load average: 2.28, 2.50, 2.05
|
||||
CLEAR sage-3919 TMPDIR=/mnt/storage/scratch/tmp/hYypHU
|
||||
OK absent adapter defaults to pi
|
||||
OK adapter mock validates (exit 0)
|
||||
OK unsupported adapter exits 2 (exit 2)
|
||||
OK env exports adapter
|
||||
OK bootstrap creates default when absent (exit 0)
|
||||
OK bootstrap wrote config file
|
||||
OK bootstrap is idempotent on existing config (exit 0)
|
||||
OK bootstrap did not rewrite existing config
|
||||
OK validate missing config exits 3 (exit 3)
|
||||
OK malformed JSON exits 2 (exit 2)
|
||||
OK unsupported configVersion exits 2 (exit 2)
|
||||
OK unknown top-level key exits 2 (exit 2)
|
||||
OK unknown execution key exits 2 (exit 2)
|
||||
OK unsupported backend exits 2 (exit 2)
|
||||
OK unsupported environment exits 2 (exit 2)
|
||||
OK relative dataRoot exits 2 (exit 2)
|
||||
OK non-canonical dataRoot exits 2 (exit 2)
|
||||
OK filesystem root dataRoot exits 2 (exit 2)
|
||||
OK home directory dataRoot exits 2 (exit 2)
|
||||
OK dataRoot containing config dir exits 2 (exit 2)
|
||||
OK control character in provider exits 2 (exit 2)
|
||||
OK symlinked config file exits 2 (exit 2)
|
||||
OK env exports resolve correctly
|
||||
OK valid console.sso loads and is in the output
|
||||
OK console.sso HS256 alg exits 2 naming the path (exit 2)
|
||||
OK console.sso alg none exits 2 naming the path (exit 2)
|
||||
OK console.sso missing slot field exits 2 naming the path (exit 2)
|
||||
OK console.sso slot name outside the pattern exits 2 naming the path (exit 2)
|
||||
OK unknown key under console exits 2 naming the path (exit 2)
|
||||
OK valid federation key loads and is in the output
|
||||
OK federation unknown sub-key exits 2 naming the key (exit 2)
|
||||
OK federation peer unknown sub-key exits 2 naming the key (exit 2)
|
||||
OK federation peer origin over http exits 2 naming the key (exit 2)
|
||||
OK federation peer origin with a path exits 2 naming the key (exit 2)
|
||||
OK federation peer origin with userinfo exits 2 naming the key (exit 2)
|
||||
OK federation peer id equal to self exits 2 naming the key (exit 2)
|
||||
OK federation peer label empty exits 2 naming the key (exit 2)
|
||||
OK federation not an object exits 2 naming the key (exit 2)
|
||||
OK federation self pattern exits 2 naming the key (exit 2)
|
||||
OK federation peers not an array exits 2 naming the key (exit 2)
|
||||
OK federation peer not an object exits 2 naming the key (exit 2)
|
||||
OK federation peer id pattern exits 2 naming the key (exit 2)
|
||||
OK federation peer label over 80 characters exits 2 naming the key (exit 2)
|
||||
OK federation duplicate peer id exits 2 naming the key (exit 2)
|
||||
OK federation peer origin with a trailing slash exits 2 naming the key (exit 2)
|
||||
OK federation self missing exits 2 naming the key (exit 2)
|
||||
OK federation peer id missing exits 2 naming the key (exit 2)
|
||||
OK federation peer label missing exits 2 naming the key (exit 2)
|
||||
OK config without federation loads and has no federation output
|
||||
OK config without console loads with unchanged output
|
||||
OK compactionReadBytes 8388608 loads and is in the output as written
|
||||
OK compactionReadBytes "x" loads and is in the output as written
|
||||
OK compactionStreams 2048 loads and is in the output as written
|
||||
OK compactionStreams "x" loads and is in the output as written
|
||||
OK failed validation modified nothing
|
||||
|
||||
✔ install: absent (33.602997ms)
|
||||
✔ install: local (27.76628ms)
|
||||
✔ install: local endpoint (28.360207ms)
|
||||
✔ install: containerized missing endpoint (26.872508ms)
|
||||
✔ install: bad endpoint "tcp://localhost:2375" (27.729466ms)
|
||||
✔ install: bad endpoint "ssh://host/run/docker.sock" (29.663768ms)
|
||||
✔ install: bad endpoint "run/docker.sock" (28.875798ms)
|
||||
✔ install: bad endpoint "unix://relative.sock" (27.427064ms)
|
||||
✔ install: bad endpoint "unix:///a b.sock" (30.77649ms)
|
||||
✔ install: bad endpoint "unix:///a.sock\n" (34.03786ms)
|
||||
✔ install: bad endpoint "unix:///a" (31.459666ms)
|
||||
✔ install: bad endpoint "" (26.221924ms)
|
||||
✔ install: bad endpoint null (31.982463ms)
|
||||
✔ install: bad endpoint 4 (34.408275ms)
|
||||
✔ install: valid endpoint (30.184118ms)
|
||||
✔ install: unknown mode (26.58596ms)
|
||||
✔ install: missing mode (26.563043ms)
|
||||
✔ install: unknown key (28.663535ms)
|
||||
✔ install: null (26.567284ms)
|
||||
✔ install: array (24.756494ms)
|
||||
ℹ tests 20
|
||||
ℹ suites 0
|
||||
ℹ pass 20
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 640.201326
|
||||
OK install configuration cases
|
||||
selftest: 56 passed, 0 failed
|
||||
EXIT=0
|
||||
@@ -0,0 +1,245 @@
|
||||
2026-10-24T04:09:14Z
|
||||
23:09:14 up 10 days, 14:29, 2 users, load average: 2.58, 2.56, 2.08
|
||||
CLEAR sage-3919 TMPDIR=/mnt/storage/scratch/tmp/7xUNh5
|
||||
✔ install: absent (28.837858ms)
|
||||
✔ install: local (28.328967ms)
|
||||
✔ install: local endpoint (26.178892ms)
|
||||
✔ install: containerized missing endpoint (25.578947ms)
|
||||
✔ install: bad endpoint "tcp://localhost:2375" (24.988885ms)
|
||||
✔ install: bad endpoint "ssh://host/run/docker.sock" (25.850327ms)
|
||||
✔ install: bad endpoint "run/docker.sock" (26.499708ms)
|
||||
✔ install: bad endpoint "unix://relative.sock" (42.276491ms)
|
||||
✔ install: bad endpoint "unix:///a b.sock" (32.161423ms)
|
||||
✔ install: bad endpoint "unix:///a.sock\n" (26.834457ms)
|
||||
✔ install: bad endpoint "unix:///a" (30.552354ms)
|
||||
✔ install: bad endpoint "" (28.938768ms)
|
||||
✔ install: bad endpoint null (27.00598ms)
|
||||
✔ install: bad endpoint 4 (29.63046ms)
|
||||
✔ install: valid endpoint (28.707851ms)
|
||||
✔ install: unknown mode (25.507911ms)
|
||||
✔ install: missing mode (27.101142ms)
|
||||
✔ install: unknown key (32.316453ms)
|
||||
✔ install: null (27.273355ms)
|
||||
✔ install: array (25.503395ms)
|
||||
✔ helper refuses rootful (81.447175ms)
|
||||
✔ helper refuses missing (49.32616ms)
|
||||
✔ helper refuses substring (80.299191ms)
|
||||
✔ helper refuses object (96.465808ms)
|
||||
✔ helper refuses invalid JSON (89.854499ms)
|
||||
✔ helper refuses null success (79.934412ms)
|
||||
✔ helper refuses rootless output but failed (61.053445ms)
|
||||
✔ helper checks once and explicitly names host for both calls (120.469053ms)
|
||||
✔ local mode refuses before Docker null (28.252291ms)
|
||||
✔ local mode refuses before Docker {"mode":"local"} (32.631557ms)
|
||||
✔ invalid config refuses before Docker (28.529643ms)
|
||||
ℹ tests 31
|
||||
ℹ suites 0
|
||||
ℹ pass 31
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 798.423992
|
||||
✔ allows exact host and port, fragmented headers, and preserves tunnel head (18.932222ms)
|
||||
✔ unlisted host (1.309946ms)
|
||||
✔ unlisted port (1.067401ms)
|
||||
✔ GET (0.988233ms)
|
||||
✔ invalid port (0.919738ms)
|
||||
✔ userinfo (0.832286ms)
|
||||
✔ bad header (0.761085ms)
|
||||
✔ slow client has a total header deadline (80.345593ms)
|
||||
✔ oversized header refuses (1.044536ms)
|
||||
✔ missing, invalid and unknown lists fail closed (0.486134ms)
|
||||
✔ refused upstream returns 502 while retaining one allowed decision (1.562662ms)
|
||||
ℹ tests 11
|
||||
ℹ suites 0
|
||||
ℹ pass 11
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 149.849801
|
||||
#0 building with "default" instance using docker driver
|
||||
|
||||
#1 [internal] load build definition from Dockerfile
|
||||
#1 transferring dockerfile: 454B done
|
||||
#1 DONE 0.0s
|
||||
|
||||
#2 [internal] load metadata for docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
|
||||
#2 DONE 1.6s
|
||||
|
||||
#3 [internal] load .dockerignore
|
||||
#3 transferring context: 2B done
|
||||
#3 DONE 0.0s
|
||||
|
||||
#4 [1/9] FROM docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
|
||||
#4 resolve docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20 done
|
||||
#4 ...
|
||||
|
||||
#5 [internal] load build context
|
||||
#5 transferring context: 31.36MB 0.1s done
|
||||
#5 DONE 0.2s
|
||||
|
||||
#4 [1/9] FROM docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
|
||||
#4 sha256:08489011619dfda982191cb9cc0418dc48ea3b1341588aeef534e4db0ab4d3d0 447B / 447B 0.1s done
|
||||
#4 sha256:1a0cf4b1ffc8dd659bb8c6e90471bc8fec24620e4fdb68bb2b6faa56154ae146 0B / 1.71MB 0.2s
|
||||
#4 sha256:11ffcabe0f772f0f1a9c80b4b290b5ec0de54f11c671f0fa10e5844f9b52d17a 0B / 50.86MB 0.2s
|
||||
#4 sha256:517d640e32835287653c4b15eb5d9f6bcf48e103baea4f7e29bac937002f649b 3.31kB / 3.31kB 0.2s done
|
||||
#4 sha256:1a0cf4b1ffc8dd659bb8c6e90471bc8fec24620e4fdb68bb2b6faa56154ae146 1.71MB / 1.71MB 0.3s done
|
||||
#4 sha256:7fd1ebe418b5079d4aa36f416af9b143d127bc75b0155d71cb5fd559b5e04d39 3.15MB / 28.25MB 0.3s
|
||||
#4 sha256:11ffcabe0f772f0f1a9c80b4b290b5ec0de54f11c671f0fa10e5844f9b52d17a 6.29MB / 50.86MB 0.5s
|
||||
#4 sha256:7fd1ebe418b5079d4aa36f416af9b143d127bc75b0155d71cb5fd559b5e04d39 11.53MB / 28.25MB 0.5s
|
||||
#4 sha256:11ffcabe0f772f0f1a9c80b4b290b5ec0de54f11c671f0fa10e5844f9b52d17a 14.68MB / 50.86MB 0.6s
|
||||
#4 sha256:7fd1ebe418b5079d4aa36f416af9b143d127bc75b0155d71cb5fd559b5e04d39 19.92MB / 28.25MB 0.6s
|
||||
#4 sha256:11ffcabe0f772f0f1a9c80b4b290b5ec0de54f11c671f0fa10e5844f9b52d17a 24.12MB / 50.86MB 0.8s
|
||||
#4 sha256:7fd1ebe418b5079d4aa36f416af9b143d127bc75b0155d71cb5fd559b5e04d39 28.25MB / 28.25MB 0.8s done
|
||||
#4 sha256:11ffcabe0f772f0f1a9c80b4b290b5ec0de54f11c671f0fa10e5844f9b52d17a 49.28MB / 50.86MB 1.1s
|
||||
#4 extracting sha256:7fd1ebe418b5079d4aa36f416af9b143d127bc75b0155d71cb5fd559b5e04d39
|
||||
#4 sha256:11ffcabe0f772f0f1a9c80b4b290b5ec0de54f11c671f0fa10e5844f9b52d17a 50.86MB / 50.86MB 1.1s done
|
||||
#4 extracting sha256:7fd1ebe418b5079d4aa36f416af9b143d127bc75b0155d71cb5fd559b5e04d39 0.7s done
|
||||
#4 DONE 1.6s
|
||||
|
||||
#4 [1/9] FROM docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
|
||||
#4 extracting sha256:517d640e32835287653c4b15eb5d9f6bcf48e103baea4f7e29bac937002f649b 0.0s done
|
||||
#4 extracting sha256:11ffcabe0f772f0f1a9c80b4b290b5ec0de54f11c671f0fa10e5844f9b52d17a
|
||||
#4 extracting sha256:11ffcabe0f772f0f1a9c80b4b290b5ec0de54f11c671f0fa10e5844f9b52d17a 1.0s done
|
||||
#4 DONE 2.6s
|
||||
|
||||
#4 [1/9] FROM docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
|
||||
#4 extracting sha256:1a0cf4b1ffc8dd659bb8c6e90471bc8fec24620e4fdb68bb2b6faa56154ae146 0.0s done
|
||||
#4 extracting sha256:08489011619dfda982191cb9cc0418dc48ea3b1341588aeef534e4db0ab4d3d0 done
|
||||
#4 DONE 2.7s
|
||||
|
||||
#6 [2/9] WORKDIR /opt/mosaic
|
||||
#6 DONE 0.0s
|
||||
|
||||
#7 [3/9] COPY package.json package-lock.json ./
|
||||
#7 DONE 0.0s
|
||||
|
||||
#8 [4/9] RUN npm ci --omit=dev --ignore-scripts --no-audit --no-fund && npm cache clean --force
|
||||
#8 1.008 npm warn deprecated [email protected]: Use your platform's native DOMException instead
|
||||
#8 2.464
|
||||
#8 2.464 added 128 packages in 2s
|
||||
#8 2.465 npm notice
|
||||
#8 2.465 npm notice New major version of npm available! 11.19.0 -> 12.2.1
|
||||
#8 2.465 npm notice Changelog: https://github.com/npm/cli/releases/tag/v12.2.1
|
||||
#8 2.465 npm notice To update run: npm install -g [email protected]
|
||||
#8 2.465 npm notice
|
||||
#8 2.562 npm warn using --force Recommended protections disabled.
|
||||
#8 DONE 2.7s
|
||||
|
||||
#9 [5/9] COPY packages/ packages/
|
||||
#9 DONE 0.1s
|
||||
|
||||
#10 [6/9] COPY scripts/ scripts/
|
||||
#10 DONE 0.0s
|
||||
|
||||
#11 [7/9] COPY contracts/ contracts/
|
||||
#11 DONE 0.0s
|
||||
|
||||
#12 [8/9] COPY roles/ roles/
|
||||
#12 DONE 0.0s
|
||||
|
||||
#13 [9/9] COPY RELEASE ./
|
||||
#13 DONE 0.0s
|
||||
|
||||
#14 exporting to image
|
||||
#14 exporting layers
|
||||
#14 exporting layers 3.3s done
|
||||
#14 exporting manifest sha256:b288643bbe89b43f6ffe7890598fe70801933c9e1e5d64efbfc6d87bbc502389 done
|
||||
#14 exporting config sha256:c8e137b79bfd807d4d9fc37d5ac211476decc3cc3c8db1e39b2e5dedf5471191 done
|
||||
#14 exporting attestation manifest sha256:5f2e757dd0fc2a424c78eb4bdd2ddd3039067afb54bc76bc03d6a6f9f1e2ac75 done
|
||||
#14 exporting manifest list sha256:7b4fa741d785225148156d1962a1770b40ab84b55ce8ffde5a3485b316f93b3a done
|
||||
#14 naming to docker.io/library/mosaic-core:0.0.12 done
|
||||
#14 unpacking to docker.io/library/mosaic-core:0.0.12
|
||||
#14 unpacking to docker.io/library/mosaic-core:0.0.12 1.6s done
|
||||
#14 DONE 4.9s
|
||||
#0 building with "default" instance using docker driver
|
||||
|
||||
#1 [internal] load build definition from Dockerfile
|
||||
#1 transferring dockerfile: 424B done
|
||||
#1 WARN: InvalidDefaultArgInFrom: Default value for ARG ${CORE_IMAGE} results in empty or invalid base image name (line 2)
|
||||
#1 DONE 0.0s
|
||||
|
||||
#2 [internal] load metadata for docker.io/library/mosaic-core:0.0.12
|
||||
#2 DONE 0.0s
|
||||
|
||||
#3 [internal] load .dockerignore
|
||||
#3 transferring context: 2B done
|
||||
#3 DONE 0.0s
|
||||
|
||||
#4 [1/2] FROM docker.io/library/mosaic-core:0.0.12@sha256:7b4fa741d785225148156d1962a1770b40ab84b55ce8ffde5a3485b316f93b3a
|
||||
#4 resolve docker.io/library/mosaic-core:0.0.12@sha256:7b4fa741d785225148156d1962a1770b40ab84b55ce8ffde5a3485b316f93b3a done
|
||||
#4 DONE 0.0s
|
||||
|
||||
#5 [2/2] RUN npm install -g --no-audit --no-fund @anthropic-ai/[email protected] @earendil-works/[email protected] && npm cache clean --force && rm -rf /root/.npm /tmp/*
|
||||
#5 1.716 npm warn deprecated [email protected]: Use your platform's native DOMException instead
|
||||
#5 6.031
|
||||
#5 6.031 added 167 packages in 6s
|
||||
#5 6.032 npm warn install-scripts 4 packages have install scripts not yet covered by allowScripts:
|
||||
#5 6.032 npm warn install-scripts @anthropic-ai/[email protected] (postinstall: node install.cjs)
|
||||
#5 6.032 npm warn install-scripts @google/[email protected] (preinstall: echo 'preinstall: no-op')
|
||||
#5 6.032 npm warn install-scripts [email protected] (postinstall: node install.js)
|
||||
#5 6.032 npm warn install-scripts [email protected] (postinstall: node scripts/postinstall)
|
||||
#5 6.032 npm warn install-scripts
|
||||
#5 6.032 npm warn install-scripts Run `npm install -g --allow-scripts=@anthropic-ai/claude-code,@google/genai,esbuild,protobufjs` to allow these scripts once, or `npm config set allow-scripts=@anthropic-ai/claude-code,@google/genai,esbuild,protobufjs --location=user` to allow them for all global installs.
|
||||
#5 6.119 npm warn using --force Recommended protections disabled.
|
||||
#5 DONE 6.3s
|
||||
|
||||
#6 exporting to image
|
||||
#6 exporting layers
|
||||
#6 exporting layers 18.7s done
|
||||
#6 exporting manifest sha256:16af90e8e3291626f37fb5b2e78904d87126d875e7bcc240dd2ba453ca62bc10 done
|
||||
#6 exporting config sha256:ad269f0a84c0b1c5833b787793a45df782c1aae86e37f6216782a12a01f4557b done
|
||||
#6 exporting attestation manifest sha256:436490e0d139cb0db83f260c42ac3364377971160b2418cd03ee07c18873a5f6 done
|
||||
#6 exporting manifest list sha256:bc95495952e58a005c275a9d00842242c17965d0d9c2a83c476048cbfc110946 done
|
||||
#6 naming to docker.io/library/mosaic-session:0.0.12 done
|
||||
#6 unpacking to docker.io/library/mosaic-session:0.0.12
|
||||
#6 unpacking to docker.io/library/mosaic-session:0.0.12 5.3s done
|
||||
#6 DONE 24.0s
|
||||
|
||||
[33m1 warning found (use docker --debug to expand):
|
||||
[0m - InvalidDefaultArgInFrom: Default value for ARG ${CORE_IMAGE} results in empty or invalid base image name (line 2)
|
||||
#0 building with "default" instance using docker driver
|
||||
|
||||
#1 [internal] load build definition from Dockerfile
|
||||
#1 transferring dockerfile: 299B done
|
||||
#1 DONE 0.0s
|
||||
|
||||
#2 [internal] load metadata for docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
|
||||
#2 DONE 0.0s
|
||||
|
||||
#3 [internal] load .dockerignore
|
||||
#3 transferring context: 2B done
|
||||
#3 DONE 0.0s
|
||||
|
||||
#4 [1/4] FROM docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
|
||||
#4 resolve docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20 done
|
||||
#4 DONE 0.0s
|
||||
|
||||
#5 [2/4] WORKDIR /opt/mosaic
|
||||
#5 CACHED
|
||||
|
||||
#6 [internal] load build context
|
||||
#6 transferring context: 459B 0.0s done
|
||||
#6 DONE 0.0s
|
||||
|
||||
#7 [3/4] COPY packages/egress/ packages/egress/
|
||||
#7 DONE 0.0s
|
||||
|
||||
#8 [4/4] COPY contracts/egress.json contracts/egress.json
|
||||
#8 DONE 0.0s
|
||||
|
||||
#9 exporting to image
|
||||
#9 exporting layers 0.0s done
|
||||
#9 exporting manifest sha256:f3f318ddc871b5a5f953aa0709cf69194260e633e26b15daafefe40572a36f27 done
|
||||
#9 exporting config sha256:c315f86a3be49d29aa7cad287a1653424206adb43683a34b91633b9bb8a14c6b done
|
||||
#9 exporting attestation manifest sha256:d41ee39a55170821cf46236fbf6069519e52c7386e27259ee17113777fa348e1 done
|
||||
#9 exporting manifest list sha256:af29f552efea177d706779ed9afb43c89763b58de913da0a570208cdda619d50 done
|
||||
#9 naming to docker.io/library/mosaic-egress:0.0.12 done
|
||||
#9 unpacking to docker.io/library/mosaic-egress:0.0.12 0.0s done
|
||||
#9 DONE 0.1s
|
||||
internal network: listed TLS reached, unlisted host/port refused, external DNS refused
|
||||
Vikunja client public GET 200; notifier REST dummy-token GET 401
|
||||
test-install: PASS (provider-host measurement remains Mos gate)
|
||||
EXIT=0
|
||||
@@ -0,0 +1,24 @@
|
||||
2026-10-24T04:13:52Z
|
||||
23:13:52 up 10 days, 14:33, 2 users, load average: 2.19, 2.69, 2.26
|
||||
CLEAR sage-3923 TMPDIR=/mnt/storage/scratch/tmp/5Tw4Jn
|
||||
✔ allows exact host and port, fragmented headers, and preserves tunnel head (17.065009ms)
|
||||
✔ unlisted host (1.179696ms)
|
||||
✔ unlisted port (4.182798ms)
|
||||
✔ GET (0.916554ms)
|
||||
✔ invalid port (0.897474ms)
|
||||
✔ userinfo (0.772938ms)
|
||||
✔ bad header (0.737254ms)
|
||||
✔ slow client has a total header deadline (81.64527ms)
|
||||
✔ oversized header refuses (1.073337ms)
|
||||
✔ missing, invalid and unknown lists fail closed (0.464571ms)
|
||||
✔ refused upstream returns 502 while retaining one allowed decision (1.555082ms)
|
||||
✔ upstream reset after CONNECT closes the tunnel without a second HTTP response (21.757224ms)
|
||||
ℹ tests 12
|
||||
ℹ suites 0
|
||||
ℹ pass 12
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 173.90579
|
||||
EXIT=0
|
||||
@@ -0,0 +1,207 @@
|
||||
2026-10-24T04:13:56Z
|
||||
23:13:56 up 10 days, 14:33, 2 users, load average: 2.18, 2.67, 2.26
|
||||
CLEAR sage-3923 TMPDIR=/mnt/storage/scratch/tmp/Yb2lkZ
|
||||
✔ install: absent (26.874578ms)
|
||||
✔ install: local (25.86789ms)
|
||||
✔ install: local endpoint (24.545469ms)
|
||||
✔ install: containerized missing endpoint (26.458263ms)
|
||||
✔ install: bad endpoint "tcp://localhost:2375" (24.315713ms)
|
||||
✔ install: bad endpoint "ssh://host/run/docker.sock" (27.113397ms)
|
||||
✔ install: bad endpoint "run/docker.sock" (24.399221ms)
|
||||
✔ install: bad endpoint "unix://relative.sock" (26.259544ms)
|
||||
✔ install: bad endpoint "unix:///a b.sock" (24.051655ms)
|
||||
✔ install: bad endpoint "unix:///a.sock\n" (23.493287ms)
|
||||
✔ install: bad endpoint "unix:///a" (25.527734ms)
|
||||
✔ install: bad endpoint "" (24.647712ms)
|
||||
✔ install: bad endpoint null (24.029319ms)
|
||||
✔ install: bad endpoint 4 (22.545369ms)
|
||||
✔ install: valid endpoint (24.217907ms)
|
||||
✔ install: unknown mode (22.578207ms)
|
||||
✔ install: missing mode (26.668685ms)
|
||||
✔ install: unknown key (25.239503ms)
|
||||
✔ install: null (26.507504ms)
|
||||
✔ install: array (23.325961ms)
|
||||
✔ helper refuses rootful (83.142099ms)
|
||||
✔ helper refuses missing (48.938896ms)
|
||||
✔ helper refuses substring (74.574145ms)
|
||||
✔ helper refuses object (69.21506ms)
|
||||
✔ helper refuses invalid JSON (71.320245ms)
|
||||
✔ helper refuses null success (70.397988ms)
|
||||
✔ helper refuses rootless output but failed (50.077162ms)
|
||||
✔ helper checks once and explicitly names host for both calls (116.70882ms)
|
||||
✔ local mode refuses before Docker null (26.523468ms)
|
||||
✔ local mode refuses before Docker {"mode":"local"} (26.276186ms)
|
||||
✔ invalid config refuses before Docker (26.092328ms)
|
||||
ℹ tests 31
|
||||
ℹ suites 0
|
||||
ℹ pass 31
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 707.332707
|
||||
✔ allows exact host and port, fragmented headers, and preserves tunnel head (15.694598ms)
|
||||
✔ unlisted host (1.162772ms)
|
||||
✔ unlisted port (1.047448ms)
|
||||
✔ GET (0.913924ms)
|
||||
✔ invalid port (0.930799ms)
|
||||
✔ userinfo (0.750312ms)
|
||||
✔ bad header (0.739214ms)
|
||||
✔ slow client has a total header deadline (81.348784ms)
|
||||
✔ oversized header refuses (0.992296ms)
|
||||
✔ missing, invalid and unknown lists fail closed (0.441271ms)
|
||||
✔ refused upstream returns 502 while retaining one allowed decision (1.4408ms)
|
||||
✔ upstream reset after CONNECT closes the tunnel without a second HTTP response (21.481904ms)
|
||||
ℹ tests 12
|
||||
ℹ suites 0
|
||||
ℹ pass 12
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 169.285852
|
||||
#0 building with "default" instance using docker driver
|
||||
|
||||
#1 [internal] load build definition from Dockerfile
|
||||
#1 transferring dockerfile: 454B done
|
||||
#1 DONE 0.0s
|
||||
|
||||
#2 [internal] load metadata for docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
|
||||
#2 DONE 0.0s
|
||||
|
||||
#3 [internal] load .dockerignore
|
||||
#3 transferring context: 2B done
|
||||
#3 DONE 0.0s
|
||||
|
||||
#4 [1/9] FROM docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
|
||||
#4 resolve docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20 done
|
||||
#4 DONE 0.0s
|
||||
|
||||
#5 [internal] load build context
|
||||
#5 transferring context: 31.37MB 0.1s done
|
||||
#5 DONE 0.1s
|
||||
|
||||
#6 [3/9] COPY package.json package-lock.json ./
|
||||
#6 CACHED
|
||||
|
||||
#7 [2/9] WORKDIR /opt/mosaic
|
||||
#7 CACHED
|
||||
|
||||
#8 [4/9] RUN npm ci --omit=dev --ignore-scripts --no-audit --no-fund && npm cache clean --force
|
||||
#8 CACHED
|
||||
|
||||
#9 [5/9] COPY packages/ packages/
|
||||
#9 DONE 0.1s
|
||||
|
||||
#10 [6/9] COPY scripts/ scripts/
|
||||
#10 DONE 0.0s
|
||||
|
||||
#11 [7/9] COPY contracts/ contracts/
|
||||
#11 DONE 0.0s
|
||||
|
||||
#12 [8/9] COPY roles/ roles/
|
||||
#12 DONE 0.0s
|
||||
|
||||
#13 [9/9] COPY RELEASE ./
|
||||
#13 DONE 0.0s
|
||||
|
||||
#14 exporting to image
|
||||
#14 exporting layers
|
||||
#14 exporting layers 0.4s done
|
||||
#14 exporting manifest sha256:065dd55d59585806e540929e2ea66df397d47292cc84160bb7be491ecd2b9d59 done
|
||||
#14 exporting config sha256:3d97362dc6b6242fc540f08f538068b91ec28b1d3c9a34d7bc2bebff137a7a40 done
|
||||
#14 exporting attestation manifest sha256:f66bf47e4ec5b67db64f44d8554192f646111116e165ec3bc017b5a2b011204b done
|
||||
#14 exporting manifest list sha256:d840b0ec16ce6548549d18fb8bfc6b13c76ee77e9131a317582f919c61dcaf7f done
|
||||
#14 naming to docker.io/library/mosaic-core:0.0.12 done
|
||||
#14 unpacking to docker.io/library/mosaic-core:0.0.12
|
||||
#14 unpacking to docker.io/library/mosaic-core:0.0.12 0.2s done
|
||||
#14 DONE 0.6s
|
||||
#0 building with "default" instance using docker driver
|
||||
|
||||
#1 [internal] load build definition from Dockerfile
|
||||
#1 transferring dockerfile: 424B done
|
||||
#1 WARN: InvalidDefaultArgInFrom: Default value for ARG ${CORE_IMAGE} results in empty or invalid base image name (line 2)
|
||||
#1 DONE 0.0s
|
||||
|
||||
#2 [internal] load metadata for docker.io/library/mosaic-core:0.0.12
|
||||
#2 DONE 0.0s
|
||||
|
||||
#3 [internal] load .dockerignore
|
||||
#3 transferring context: 2B done
|
||||
#3 DONE 0.0s
|
||||
|
||||
#4 [1/2] FROM docker.io/library/mosaic-core:0.0.12@sha256:d840b0ec16ce6548549d18fb8bfc6b13c76ee77e9131a317582f919c61dcaf7f
|
||||
#4 resolve docker.io/library/mosaic-core:0.0.12@sha256:d840b0ec16ce6548549d18fb8bfc6b13c76ee77e9131a317582f919c61dcaf7f done
|
||||
#4 DONE 0.0s
|
||||
|
||||
#5 [2/2] RUN npm install -g --no-audit --no-fund @anthropic-ai/[email protected] @earendil-works/[email protected] && npm cache clean --force && rm -rf /root/.npm /tmp/*
|
||||
#5 1.700 npm warn deprecated [email protected]: Use your platform's native DOMException instead
|
||||
#5 5.518
|
||||
#5 5.518 added 167 packages in 5s
|
||||
#5 5.519 npm warn install-scripts 4 packages have install scripts not yet covered by allowScripts:
|
||||
#5 5.519 npm warn install-scripts @anthropic-ai/[email protected] (postinstall: node install.cjs)
|
||||
#5 5.519 npm warn install-scripts @google/[email protected] (preinstall: echo 'preinstall: no-op')
|
||||
#5 5.519 npm warn install-scripts [email protected] (postinstall: node install.js)
|
||||
#5 5.519 npm warn install-scripts [email protected] (postinstall: node scripts/postinstall)
|
||||
#5 5.519 npm warn install-scripts
|
||||
#5 5.519 npm warn install-scripts Run `npm install -g --allow-scripts=@anthropic-ai/claude-code,@google/genai,esbuild,protobufjs` to allow these scripts once, or `npm config set allow-scripts=@anthropic-ai/claude-code,@google/genai,esbuild,protobufjs --location=user` to allow them for all global installs.
|
||||
#5 5.590 npm warn using --force Recommended protections disabled.
|
||||
#5 DONE 5.7s
|
||||
|
||||
#6 exporting to image
|
||||
#6 exporting layers
|
||||
#6 exporting layers 16.9s done
|
||||
#6 exporting manifest sha256:d4c83e350da6f18bfa7f3dbcef9ab078d72ef7f1f957a2926726e17973969e6a done
|
||||
#6 exporting config sha256:8afa310f48e06614963dcbcdc9c3ac497d9e4bad4ddd0524062ba8a52bc3cda2 done
|
||||
#6 exporting attestation manifest sha256:b7f082a8cd17d2c90eb27a81359aea66186b80d56a6d0c5e89d29187a2f78ed4 done
|
||||
#6 exporting manifest list sha256:2b6299e5a329adf85ea36038cb0407f2328e72e848dbfe95a17bd1fefbf9cf92 done
|
||||
#6 naming to docker.io/library/mosaic-session:0.0.12 done
|
||||
#6 unpacking to docker.io/library/mosaic-session:0.0.12
|
||||
#6 unpacking to docker.io/library/mosaic-session:0.0.12 4.7s done
|
||||
#6 DONE 21.7s
|
||||
|
||||
[33m1 warning found (use docker --debug to expand):
|
||||
[0m - InvalidDefaultArgInFrom: Default value for ARG ${CORE_IMAGE} results in empty or invalid base image name (line 2)
|
||||
#0 building with "default" instance using docker driver
|
||||
|
||||
#1 [internal] load build definition from Dockerfile
|
||||
#1 transferring dockerfile: 299B done
|
||||
#1 DONE 0.0s
|
||||
|
||||
#2 [internal] load metadata for docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
|
||||
#2 DONE 0.0s
|
||||
|
||||
#3 [internal] load .dockerignore
|
||||
#3 transferring context: 2B done
|
||||
#3 DONE 0.0s
|
||||
|
||||
#4 [1/4] FROM docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
|
||||
#4 resolve docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20 done
|
||||
#4 DONE 0.0s
|
||||
|
||||
#5 [internal] load build context
|
||||
#5 transferring context: 459B 0.0s done
|
||||
#5 DONE 0.0s
|
||||
|
||||
#6 [2/4] WORKDIR /opt/mosaic
|
||||
#6 CACHED
|
||||
|
||||
#7 [3/4] COPY packages/egress/ packages/egress/
|
||||
#7 DONE 0.0s
|
||||
|
||||
#8 [4/4] COPY contracts/egress.json contracts/egress.json
|
||||
#8 DONE 0.0s
|
||||
|
||||
#9 exporting to image
|
||||
#9 exporting layers 0.0s done
|
||||
#9 exporting manifest sha256:e47c86064470f291d2ea468a1002f1e5f9b2e682e42940d06bd33d4e39945a6e done
|
||||
#9 exporting config sha256:ba520eadeb66ccfa2dbd3170744fca08affb4dac377d6cf2d26ec27d98d4ee25 done
|
||||
#9 exporting attestation manifest sha256:93cbd2f8a7fb1afb858b7b1a35a3020af8e9623e3a98be71d04e9d746c25d148 done
|
||||
#9 exporting manifest list sha256:a55e9d5ec6da74933bbefac6816efd0ea5246e825f61eaa4297e7baf7b09501c done
|
||||
#9 naming to docker.io/library/mosaic-egress:0.0.12 done
|
||||
#9 unpacking to docker.io/library/mosaic-egress:0.0.12 0.0s done
|
||||
#9 DONE 0.1s
|
||||
internal network: listed TLS reached, unlisted host/port refused, external DNS refused
|
||||
Vikunja client public GET 200; notifier REST dummy-token GET 401
|
||||
test-install: PASS (provider-host measurement remains Mos gate)
|
||||
EXIT=0
|
||||
@@ -0,0 +1,165 @@
|
||||
2026-10-24T04:29:25Z
|
||||
23:29:25 up 10 days, 14:49, 2 users, load average: 2.63, 2.97, 2.72
|
||||
CLEAR sage-3936 TMPDIR=/mnt/storage/scratch/tmp/jcNy2F archived=a14fdc443 expected RED
|
||||
✔ refusal 408 releases server socket (allowHalfOpen=false) (62.262337ms)
|
||||
✖ refusal 408 releases server socket (allowHalfOpen=true) (552.049114ms)
|
||||
✔ refusal 400 releases server socket (allowHalfOpen=false) (12.304169ms)
|
||||
✖ refusal 400 releases server socket (allowHalfOpen=true) (509.122366ms)
|
||||
✔ refusal 403 releases server socket (allowHalfOpen=false) (11.566877ms)
|
||||
✖ refusal 403 releases server socket (allowHalfOpen=true) (508.134074ms)
|
||||
✔ refusal 431 releases server socket (allowHalfOpen=false) (11.339612ms)
|
||||
✖ refusal 431 releases server socket (allowHalfOpen=true) (507.396552ms)
|
||||
✔ refusal 502 releases server socket (allowHalfOpen=false) (11.812225ms)
|
||||
✖ refusal 502 releases server socket (allowHalfOpen=true) (509.521901ms)
|
||||
✖ inherited cache is discarded; private cache stays unexported (rootless=false) (49.005306ms)
|
||||
✖ inherited cache is discarded; private cache stays unexported (rootless=true) (42.961333ms)
|
||||
ℹ tests 12
|
||||
ℹ suites 0
|
||||
ℹ pass 5
|
||||
ℹ fail 7
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 2739.328587
|
||||
|
||||
✖ failing tests:
|
||||
|
||||
test at ../../../../../../mnt/storage/scratch/tmp/jcNy2F/legacy/packages/egress/tests/refusal.test.mjs:11:79
|
||||
✖ refusal 408 releases server socket (allowHalfOpen=true) (552.049114ms)
|
||||
AssertionError [ERR_ASSERTION]: refused peer must not retain a server socket
|
||||
|
||||
1 !== 0
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/jcNy2F/legacy/packages/egress/tests/refusal.test.mjs:30:10)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 1,
|
||||
expected: 0,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at ../../../../../../mnt/storage/scratch/tmp/jcNy2F/legacy/packages/egress/tests/refusal.test.mjs:11:79
|
||||
✖ refusal 400 releases server socket (allowHalfOpen=true) (509.122366ms)
|
||||
AssertionError [ERR_ASSERTION]: refused peer must not retain a server socket
|
||||
|
||||
1 !== 0
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/jcNy2F/legacy/packages/egress/tests/refusal.test.mjs:30:10)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 1,
|
||||
expected: 0,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at ../../../../../../mnt/storage/scratch/tmp/jcNy2F/legacy/packages/egress/tests/refusal.test.mjs:11:79
|
||||
✖ refusal 403 releases server socket (allowHalfOpen=true) (508.134074ms)
|
||||
AssertionError [ERR_ASSERTION]: refused peer must not retain a server socket
|
||||
|
||||
1 !== 0
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/jcNy2F/legacy/packages/egress/tests/refusal.test.mjs:30:10)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 1,
|
||||
expected: 0,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at ../../../../../../mnt/storage/scratch/tmp/jcNy2F/legacy/packages/egress/tests/refusal.test.mjs:11:79
|
||||
✖ refusal 431 releases server socket (allowHalfOpen=true) (507.396552ms)
|
||||
AssertionError [ERR_ASSERTION]: refused peer must not retain a server socket
|
||||
|
||||
1 !== 0
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/jcNy2F/legacy/packages/egress/tests/refusal.test.mjs:30:10)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 1,
|
||||
expected: 0,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at ../../../../../../mnt/storage/scratch/tmp/jcNy2F/legacy/packages/egress/tests/refusal.test.mjs:11:79
|
||||
✖ refusal 502 releases server socket (allowHalfOpen=true) (509.521901ms)
|
||||
AssertionError [ERR_ASSERTION]: refused peer must not retain a server socket
|
||||
|
||||
1 !== 0
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/jcNy2F/legacy/packages/egress/tests/refusal.test.mjs:30:10)
|
||||
at process.processTicksAndRejections (node:internal/process/task_queues:104:5)
|
||||
at async Test.run (node:internal/test_runner/test:1409:7)
|
||||
at async Test.processPendingSubtests (node:internal/test_runner/test:974:7) {
|
||||
generatedMessage: false,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 1,
|
||||
expected: 0,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at ../../../../../../mnt/storage/scratch/tmp/jcNy2F/legacy/scripts/install-tests/review-cache.test.mjs:10:36
|
||||
✖ inherited cache is discarded; private cache stays unexported (rootless=false) (49.005306ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly equal:
|
||||
|
||||
0 !== 2
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/jcNy2F/legacy/scripts/install-tests/review-cache.test.mjs:19:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.start (node:internal/test_runner/test:1262:17)
|
||||
at startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: 0,
|
||||
expected: 2,
|
||||
operator: 'strictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
|
||||
test at ../../../../../../mnt/storage/scratch/tmp/jcNy2F/legacy/scripts/install-tests/review-cache.test.mjs:10:36
|
||||
✖ inherited cache is discarded; private cache stays unexported (rootless=true) (42.961333ms)
|
||||
AssertionError [ERR_ASSERTION]: Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
[
|
||||
'--host',
|
||||
+ 'unix:///inherited.sock',
|
||||
+ 'ps'
|
||||
- 'unix:///configured.sock',
|
||||
- 'info',
|
||||
- '--format',
|
||||
- '{{json .SecurityOptions}}'
|
||||
]
|
||||
|
||||
at TestContext.<anonymous> (file:///mnt/storage/scratch/tmp/jcNy2F/legacy/scripts/install-tests/review-cache.test.mjs:20:10)
|
||||
at Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
at Test.run (node:internal/test_runner/test:1402:25)
|
||||
at Test.processPendingSubtests (node:internal/test_runner/test:974:18)
|
||||
at Test.postRun (node:internal/test_runner/test:1542:19)
|
||||
at Test.run (node:internal/test_runner/test:1467:12)
|
||||
at async startSubtestAfterBootstrap (node:internal/test_runner/harness:387:3) {
|
||||
generatedMessage: true,
|
||||
code: 'ERR_ASSERTION',
|
||||
actual: [ '--host', 'unix:///inherited.sock', 'ps' ],
|
||||
expected: [ '--host', 'unix:///configured.sock', 'info', '--format', '{{json .SecurityOptions}}' ],
|
||||
operator: 'deepStrictEqual',
|
||||
diff: 'simple'
|
||||
}
|
||||
EXIT=1 (expected 1 for reproduced defects)
|
||||
@@ -0,0 +1,24 @@
|
||||
2026-10-24T04:29:39Z
|
||||
23:29:39 up 10 days, 14:49, 2 users, load average: 2.20, 2.85, 2.69
|
||||
CLEAR sage-3936 TMPDIR=/mnt/storage/scratch/tmp/0gZQZl
|
||||
✔ refusal 408 releases server socket (allowHalfOpen=false) (53.837502ms)
|
||||
✔ refusal 408 releases server socket (allowHalfOpen=true) (42.030239ms)
|
||||
✔ refusal 400 releases server socket (allowHalfOpen=false) (1.261482ms)
|
||||
✔ refusal 400 releases server socket (allowHalfOpen=true) (0.904817ms)
|
||||
✔ refusal 403 releases server socket (allowHalfOpen=false) (0.917847ms)
|
||||
✔ refusal 403 releases server socket (allowHalfOpen=true) (0.804008ms)
|
||||
✔ refusal 431 releases server socket (allowHalfOpen=false) (0.718814ms)
|
||||
✔ refusal 431 releases server socket (allowHalfOpen=true) (0.668466ms)
|
||||
✔ refusal 502 releases server socket (allowHalfOpen=false) (1.344911ms)
|
||||
✔ refusal 502 releases server socket (allowHalfOpen=true) (0.979004ms)
|
||||
✔ inherited cache is discarded; private cache stays unexported (rootless=false) (82.339935ms)
|
||||
✔ inherited cache is discarded; private cache stays unexported (rootless=true) (112.830463ms)
|
||||
ℹ tests 12
|
||||
ℹ suites 0
|
||||
ℹ pass 12
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 245.761199
|
||||
EXIT=0
|
||||
@@ -0,0 +1,45 @@
|
||||
2026-10-24T04:29:49Z
|
||||
23:29:49 up 10 days, 14:49, 2 users, load average: 2.09, 2.81, 2.68
|
||||
CLEAR sage-3936 TMPDIR=/mnt/storage/scratch/tmp/tUdE4l
|
||||
✔ install: absent (26.366604ms)
|
||||
✔ install: local (22.63247ms)
|
||||
✔ install: local endpoint (22.799393ms)
|
||||
✔ install: containerized missing endpoint (25.600693ms)
|
||||
✔ install: bad endpoint "tcp://localhost:2375" (23.03486ms)
|
||||
✔ install: bad endpoint "ssh://host/run/docker.sock" (23.131519ms)
|
||||
✔ install: bad endpoint "run/docker.sock" (24.109097ms)
|
||||
✔ install: bad endpoint "unix://relative.sock" (23.350143ms)
|
||||
✔ install: bad endpoint "unix:///a b.sock" (25.171936ms)
|
||||
✔ install: bad endpoint "unix:///a.sock\n" (23.993281ms)
|
||||
✔ install: bad endpoint "unix:///a" (23.480143ms)
|
||||
✔ install: bad endpoint "" (24.103262ms)
|
||||
✔ install: bad endpoint null (22.669641ms)
|
||||
✔ install: bad endpoint 4 (24.019575ms)
|
||||
✔ install: valid endpoint (24.603314ms)
|
||||
✔ install: unknown mode (24.602796ms)
|
||||
✔ install: missing mode (23.797819ms)
|
||||
✔ install: unknown key (22.998491ms)
|
||||
✔ install: null (25.129192ms)
|
||||
✔ install: array (24.482115ms)
|
||||
✔ helper refuses rootful (69.872106ms)
|
||||
✔ helper refuses missing (48.024972ms)
|
||||
✔ helper refuses substring (75.261764ms)
|
||||
✔ helper refuses object (69.618809ms)
|
||||
✔ helper refuses invalid JSON (69.071543ms)
|
||||
✔ helper refuses null success (72.882659ms)
|
||||
✔ helper refuses rootless output but failed (53.345431ms)
|
||||
✔ helper checks once and explicitly names host for both calls (102.552473ms)
|
||||
✔ local mode refuses before Docker null (26.011087ms)
|
||||
✔ local mode refuses before Docker {"mode":"local"} (28.185416ms)
|
||||
✔ invalid config refuses before Docker (27.514422ms)
|
||||
✔ inherited cache is discarded; private cache stays unexported (rootless=false) (75.055951ms)
|
||||
✔ inherited cache is discarded; private cache stays unexported (rootless=true) (109.227515ms)
|
||||
ℹ tests 33
|
||||
ℹ suites 0
|
||||
ℹ pass 33
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 690.153576
|
||||
EXIT=0
|
||||
@@ -0,0 +1,34 @@
|
||||
2026-10-24T04:29:59Z
|
||||
23:29:59 up 10 days, 14:49, 2 users, load average: 1.93, 2.75, 2.66
|
||||
CLEAR sage-3936 TMPDIR=/mnt/storage/scratch/tmp/TviERP
|
||||
✔ allows exact host and port, fragmented headers, and preserves tunnel head (23.927026ms)
|
||||
✔ unlisted host (1.732173ms)
|
||||
✔ unlisted port (1.604846ms)
|
||||
✔ GET (1.184716ms)
|
||||
✔ invalid port (1.343703ms)
|
||||
✔ userinfo (1.188622ms)
|
||||
✔ bad header (1.150055ms)
|
||||
✔ slow client has a total header deadline (81.793054ms)
|
||||
✔ oversized header refuses (1.244104ms)
|
||||
✔ missing, invalid and unknown lists fail closed (0.57805ms)
|
||||
✔ refused upstream returns 502 while retaining one allowed decision (1.746501ms)
|
||||
✔ upstream reset after CONNECT closes the tunnel without a second HTTP response (22.237884ms)
|
||||
✔ refusal 408 releases server socket (allowHalfOpen=false) (55.691211ms)
|
||||
✔ refusal 408 releases server socket (allowHalfOpen=true) (43.646383ms)
|
||||
✔ refusal 400 releases server socket (allowHalfOpen=false) (1.568816ms)
|
||||
✔ refusal 400 releases server socket (allowHalfOpen=true) (1.105107ms)
|
||||
✔ refusal 403 releases server socket (allowHalfOpen=false) (1.202959ms)
|
||||
✔ refusal 403 releases server socket (allowHalfOpen=true) (1.016559ms)
|
||||
✔ refusal 431 releases server socket (allowHalfOpen=false) (0.914639ms)
|
||||
✔ refusal 431 releases server socket (allowHalfOpen=true) (0.889516ms)
|
||||
✔ refusal 502 releases server socket (allowHalfOpen=false) (1.698346ms)
|
||||
✔ refusal 502 releases server socket (allowHalfOpen=true) (1.234793ms)
|
||||
ℹ tests 22
|
||||
ℹ suites 0
|
||||
ℹ pass 22
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 183.862745
|
||||
EXIT=0
|
||||
@@ -0,0 +1,219 @@
|
||||
2026-10-24T04:30:08Z
|
||||
23:30:08 up 10 days, 14:49, 2 users, load average: 1.79, 2.69, 2.64
|
||||
CLEAR sage-3936 TMPDIR=/mnt/storage/scratch/tmp/qmqk7B
|
||||
✔ install: absent (25.729884ms)
|
||||
✔ install: local (27.889152ms)
|
||||
✔ install: local endpoint (24.35751ms)
|
||||
✔ install: containerized missing endpoint (32.226985ms)
|
||||
✔ install: bad endpoint "tcp://localhost:2375" (36.932763ms)
|
||||
✔ install: bad endpoint "ssh://host/run/docker.sock" (30.311354ms)
|
||||
✔ install: bad endpoint "run/docker.sock" (25.198808ms)
|
||||
✔ install: bad endpoint "unix://relative.sock" (26.143708ms)
|
||||
✔ install: bad endpoint "unix:///a b.sock" (26.182728ms)
|
||||
✔ install: bad endpoint "unix:///a.sock\n" (24.78053ms)
|
||||
✔ install: bad endpoint "unix:///a" (25.183076ms)
|
||||
✔ install: bad endpoint "" (26.038913ms)
|
||||
✔ install: bad endpoint null (28.122111ms)
|
||||
✔ install: bad endpoint 4 (25.273681ms)
|
||||
✔ install: valid endpoint (24.978162ms)
|
||||
✔ install: unknown mode (25.362867ms)
|
||||
✔ install: missing mode (30.700943ms)
|
||||
✔ install: unknown key (25.157537ms)
|
||||
✔ install: null (25.311198ms)
|
||||
✔ install: array (22.791684ms)
|
||||
✔ helper refuses rootful (73.335956ms)
|
||||
✔ helper refuses missing (58.142601ms)
|
||||
✔ helper refuses substring (76.536037ms)
|
||||
✔ helper refuses object (71.066459ms)
|
||||
✔ helper refuses invalid JSON (75.615304ms)
|
||||
✔ helper refuses null success (74.72487ms)
|
||||
✔ helper refuses rootless output but failed (56.316538ms)
|
||||
✔ helper checks once and explicitly names host for both calls (116.375519ms)
|
||||
✔ local mode refuses before Docker null (33.189488ms)
|
||||
✔ local mode refuses before Docker {"mode":"local"} (33.416869ms)
|
||||
✔ invalid config refuses before Docker (33.260841ms)
|
||||
✔ inherited cache is discarded; private cache stays unexported (rootless=false) (77.918846ms)
|
||||
✔ inherited cache is discarded; private cache stays unexported (rootless=true) (125.24254ms)
|
||||
ℹ tests 33
|
||||
ℹ suites 0
|
||||
ℹ pass 33
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 751.025142
|
||||
✔ allows exact host and port, fragmented headers, and preserves tunnel head (21.619769ms)
|
||||
✔ unlisted host (1.449654ms)
|
||||
✔ unlisted port (1.146495ms)
|
||||
✔ GET (1.037357ms)
|
||||
✔ invalid port (0.994228ms)
|
||||
✔ userinfo (0.84581ms)
|
||||
✔ bad header (0.834734ms)
|
||||
✔ slow client has a total header deadline (81.37968ms)
|
||||
✔ oversized header refuses (1.096351ms)
|
||||
✔ missing, invalid and unknown lists fail closed (0.499339ms)
|
||||
✔ refused upstream returns 502 while retaining one allowed decision (1.730862ms)
|
||||
✔ upstream reset after CONNECT closes the tunnel without a second HTTP response (21.825124ms)
|
||||
✔ refusal 408 releases server socket (allowHalfOpen=false) (55.028256ms)
|
||||
✔ refusal 408 releases server socket (allowHalfOpen=true) (42.613145ms)
|
||||
✔ refusal 400 releases server socket (allowHalfOpen=false) (1.348936ms)
|
||||
✔ refusal 400 releases server socket (allowHalfOpen=true) (1.104981ms)
|
||||
✔ refusal 403 releases server socket (allowHalfOpen=false) (1.097498ms)
|
||||
✔ refusal 403 releases server socket (allowHalfOpen=true) (0.965577ms)
|
||||
✔ refusal 431 releases server socket (allowHalfOpen=false) (0.806917ms)
|
||||
✔ refusal 431 releases server socket (allowHalfOpen=true) (0.752832ms)
|
||||
✔ refusal 502 releases server socket (allowHalfOpen=false) (1.582846ms)
|
||||
✔ refusal 502 releases server socket (allowHalfOpen=true) (1.14509ms)
|
||||
ℹ tests 22
|
||||
ℹ suites 0
|
||||
ℹ pass 22
|
||||
ℹ fail 0
|
||||
ℹ cancelled 0
|
||||
ℹ skipped 0
|
||||
ℹ todo 0
|
||||
ℹ duration_ms 181.752441
|
||||
#0 building with "default" instance using docker driver
|
||||
|
||||
#1 [internal] load build definition from Dockerfile
|
||||
#1 transferring dockerfile: 454B done
|
||||
#1 DONE 0.0s
|
||||
|
||||
#2 [internal] load metadata for docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
|
||||
#2 DONE 0.7s
|
||||
|
||||
#3 [internal] load .dockerignore
|
||||
#3 transferring context: 2B done
|
||||
#3 DONE 0.0s
|
||||
|
||||
#4 [1/9] FROM docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
|
||||
#4 resolve docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20 done
|
||||
#4 DONE 0.0s
|
||||
|
||||
#5 [internal] load build context
|
||||
#5 transferring context: 31.38MB 0.1s done
|
||||
#5 DONE 0.1s
|
||||
|
||||
#6 [2/9] WORKDIR /opt/mosaic
|
||||
#6 CACHED
|
||||
|
||||
#7 [3/9] COPY package.json package-lock.json ./
|
||||
#7 CACHED
|
||||
|
||||
#8 [4/9] RUN npm ci --omit=dev --ignore-scripts --no-audit --no-fund && npm cache clean --force
|
||||
#8 CACHED
|
||||
|
||||
#9 [5/9] COPY packages/ packages/
|
||||
#9 DONE 0.1s
|
||||
|
||||
#10 [6/9] COPY scripts/ scripts/
|
||||
#10 DONE 0.0s
|
||||
|
||||
#11 [7/9] COPY contracts/ contracts/
|
||||
#11 DONE 0.0s
|
||||
|
||||
#12 [8/9] COPY roles/ roles/
|
||||
#12 DONE 0.0s
|
||||
|
||||
#13 [9/9] COPY RELEASE ./
|
||||
#13 DONE 0.0s
|
||||
|
||||
#14 exporting to image
|
||||
#14 exporting layers
|
||||
#14 exporting layers 0.4s done
|
||||
#14 exporting manifest sha256:cfd93bac400adc7580134f6177efd9d748e4b4ad65b0ff22515ca3b3ffafa530 done
|
||||
#14 exporting config sha256:bda6c7420c2df9510f081d28f50d205083867d0ce907c12290fff226c020fe38 done
|
||||
#14 exporting attestation manifest sha256:79c61e7daf7794a3db88121ce6260fafab69f182ef89773914c41ca75a44574c done
|
||||
#14 exporting manifest list sha256:82fc7a3df060c3e58799cca4f89dbd57dc007c5534fdedce54d3de637cc83b67 done
|
||||
#14 naming to docker.io/library/mosaic-core:0.0.12 done
|
||||
#14 unpacking to docker.io/library/mosaic-core:0.0.12
|
||||
#14 unpacking to docker.io/library/mosaic-core:0.0.12 0.2s done
|
||||
#14 DONE 0.7s
|
||||
#0 building with "default" instance using docker driver
|
||||
|
||||
#1 [internal] load build definition from Dockerfile
|
||||
#1 transferring dockerfile: 424B done
|
||||
#1 WARN: InvalidDefaultArgInFrom: Default value for ARG ${CORE_IMAGE} results in empty or invalid base image name (line 2)
|
||||
#1 DONE 0.0s
|
||||
|
||||
#2 [internal] load metadata for docker.io/library/mosaic-core:0.0.12
|
||||
#2 DONE 0.0s
|
||||
|
||||
#3 [internal] load .dockerignore
|
||||
#3 transferring context: 2B done
|
||||
#3 DONE 0.0s
|
||||
|
||||
#4 [1/2] FROM docker.io/library/mosaic-core:0.0.12@sha256:82fc7a3df060c3e58799cca4f89dbd57dc007c5534fdedce54d3de637cc83b67
|
||||
#4 resolve docker.io/library/mosaic-core:0.0.12@sha256:82fc7a3df060c3e58799cca4f89dbd57dc007c5534fdedce54d3de637cc83b67 done
|
||||
#4 DONE 0.0s
|
||||
|
||||
#5 [2/2] RUN npm install -g --no-audit --no-fund @anthropic-ai/[email protected] @earendil-works/[email protected] && npm cache clean --force && rm -rf /root/.npm /tmp/*
|
||||
#5 1.920 npm warn deprecated [email protected]: Use your platform's native DOMException instead
|
||||
#5 6.054
|
||||
#5 6.054 added 167 packages in 6s
|
||||
#5 6.055 npm warn install-scripts 4 packages have install scripts not yet covered by allowScripts:
|
||||
#5 6.055 npm warn install-scripts @anthropic-ai/[email protected] (postinstall: node install.cjs)
|
||||
#5 6.055 npm warn install-scripts @google/[email protected] (preinstall: echo 'preinstall: no-op')
|
||||
#5 6.055 npm warn install-scripts [email protected] (postinstall: node install.js)
|
||||
#5 6.055 npm warn install-scripts [email protected] (postinstall: node scripts/postinstall)
|
||||
#5 6.055 npm warn install-scripts
|
||||
#5 6.055 npm warn install-scripts Run `npm install -g --allow-scripts=@anthropic-ai/claude-code,@google/genai,esbuild,protobufjs` to allow these scripts once, or `npm config set allow-scripts=@anthropic-ai/claude-code,@google/genai,esbuild,protobufjs --location=user` to allow them for all global installs.
|
||||
#5 6.127 npm warn using --force Recommended protections disabled.
|
||||
#5 DONE 6.3s
|
||||
|
||||
#6 exporting to image
|
||||
#6 exporting layers
|
||||
#6 exporting layers 19.5s done
|
||||
#6 exporting manifest sha256:8b6b7de22cf08845f8fc02961c9677afb9faf0ddc4efe04ab4073a822a60518b done
|
||||
#6 exporting config sha256:94013fda37e404bc26b7b15281138cd60afbfea672cae2d2187acc0a7c3a0d7e done
|
||||
#6 exporting attestation manifest sha256:127fcf7ecd51a985d22f5a33d5c441d8fa1e246f88dc6e990c01197b46a61427 done
|
||||
#6 exporting manifest list sha256:9a272acf49ab376fa9739f66453979487ea8ab259bc43e56f6553e2da7b20835 done
|
||||
#6 naming to docker.io/library/mosaic-session:0.0.12 done
|
||||
#6 unpacking to docker.io/library/mosaic-session:0.0.12
|
||||
#6 unpacking to docker.io/library/mosaic-session:0.0.12 5.2s done
|
||||
#6 DONE 24.8s
|
||||
|
||||
[33m1 warning found (use docker --debug to expand):
|
||||
[0m - InvalidDefaultArgInFrom: Default value for ARG ${CORE_IMAGE} results in empty or invalid base image name (line 2)
|
||||
#0 building with "default" instance using docker driver
|
||||
|
||||
#1 [internal] load build definition from Dockerfile
|
||||
#1 transferring dockerfile: 299B done
|
||||
#1 DONE 0.0s
|
||||
|
||||
#2 [internal] load metadata for docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
|
||||
#2 DONE 0.0s
|
||||
|
||||
#3 [internal] load .dockerignore
|
||||
#3 transferring context: 2B done
|
||||
#3 DONE 0.0s
|
||||
|
||||
#4 [1/4] FROM docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
|
||||
#4 resolve docker.io/library/node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20 done
|
||||
#4 DONE 0.0s
|
||||
|
||||
#5 [internal] load build context
|
||||
#5 transferring context: 517B 0.0s done
|
||||
#5 DONE 0.0s
|
||||
|
||||
#6 [2/4] WORKDIR /opt/mosaic
|
||||
#6 CACHED
|
||||
|
||||
#7 [3/4] COPY packages/egress/ packages/egress/
|
||||
#7 DONE 0.0s
|
||||
|
||||
#8 [4/4] COPY contracts/egress.json contracts/egress.json
|
||||
#8 DONE 0.0s
|
||||
|
||||
#9 exporting to image
|
||||
#9 exporting layers 0.0s done
|
||||
#9 exporting manifest sha256:db3e8991a9330374257298a4a5d4ff36779e8c8d0b00c8b8ead47fc82e32fada done
|
||||
#9 exporting config sha256:e7d66f4f785e49a6529b3ed4b708e3b985f4e737d03a66a2c861666288d435d9 done
|
||||
#9 exporting attestation manifest sha256:8d2362ebe73a0b71b175ea4d9571ab841c6f5b0913e8fa265d1f4544307987d2 done
|
||||
#9 exporting manifest list sha256:36d0bbeb3abc92fc099d95e1fac416df5cc29edf147fcfc4d8743cbe1d60e592 done
|
||||
#9 naming to docker.io/library/mosaic-egress:0.0.12 done
|
||||
#9 unpacking to docker.io/library/mosaic-egress:0.0.12 0.0s done
|
||||
#9 DONE 0.1s
|
||||
internal network: listed TLS reached, unlisted host/port refused, external DNS refused
|
||||
Vikunja client public GET 200; notifier REST dummy-token GET 401
|
||||
test-install: PASS (provider-host measurement remains Mos gate)
|
||||
EXIT=0
|
||||
@@ -0,0 +1,363 @@
|
||||
# Row 157 / I2 / #1652 — Bentina build record
|
||||
|
||||
## 2026-10-24 — recovery and pre-build hold
|
||||
|
||||
Authority: Sage sage-3906 under Q246 / mos-1566. Seat thread:
|
||||
`d1ae468e-51ba-40c4-9513-b39ebbb40450`. Branch `bentina/row157`,
|
||||
base `ec86f2c84` (required registration commit), initially clean.
|
||||
Read root AGENTS.md, agents/bentina/README.md, the I2 brief and its shared
|
||||
rules, and the approved containerized-install design. CURRENT.md is a
|
||||
historical narrative; queue and Sage's assignment select this row. Goal
|
||||
order recovered from 2026-09-27_goals-review.md. Boyd first review, then
|
||||
Darkwing install-path review, per Sage's newer assignment.
|
||||
|
||||
### Queue boundary
|
||||
|
||||
The initial worktree `scripts/mosaic queue next bentina` refused because
|
||||
queue commands require the canonical checkout. Retried the same read using
|
||||
`/mnt/storage/src/mosaic-stack/scripts/mosaic` in that checkout (the documented
|
||||
queue location, without changing its configuration):
|
||||
|
||||
```
|
||||
start row 157: I2 install images, the install config key, the endpoint helper and the egress proxy; brief docs/plans/2026-10-18_containerized-install-rows.md § I2 install images, the install config key, the endpoint helper and the egress proxy; brief differs from pinned blob; ask the lead to re-pin
|
||||
```
|
||||
|
||||
No claimant move until Sage resolves the stale brief pin. No other queue
|
||||
write authorized. Sent Sage bentina-0001 with this refusal and the outbound
|
||||
path findings below. Implementation has not started.
|
||||
|
||||
### Re-read of the I2 brief's cited files at ec86f2c84
|
||||
|
||||
| Cited file / old site | Current observation |
|
||||
|---|---|
|
||||
| scripts/hello.sh:24 | Calls processing-control/src/container-cli.mjs:25; Docker moved behind processing control. |
|
||||
| scripts/build.sh:13 | Still ambient `docker compose build`:13. |
|
||||
| scripts/release.sh:39,122 | Still ambient image inspect:39 and compose build:122. |
|
||||
| scripts/agent.sh:208 | Now execs processing-control/src/container-cli.mjs --tty:209. |
|
||||
| scripts/verify.sh:36,39,45 | Ambient image inspect:37 and compose build:40; request now uses processing-control container CLI:46. |
|
||||
| scripts/land-work.sh:87-88 | Ambient network inspect/remove unchanged at 87-88. |
|
||||
| scripts/test-release.sh:58 | Docker probes now at 78,129; cleanup network rm:25; network checks:131,133. |
|
||||
| scripts/test-task.sh:139,395,442,517 | Docker probes now at 166,422,470,513,557,579; cleanup network rm:21 and network checks:581,583. |
|
||||
| scripts/mosaic-task.mjs:396 | Now calls runContainer:402, imported from processing control, rather than directly spawning Docker. |
|
||||
| packages/cli/src/git-socket.mjs:243 | dropNetwork now spawns ambient docker network rm:266; I6 retains ownership. |
|
||||
| scripts/mosaic-config.mjs:109-160 / :115 | Loader expanded for instances, Console, federation and compaction settings. validate starts:293; allowed-key expression:304 retains a prefix used by stack-release-fixture.mjs. No install key yet. |
|
||||
|
||||
The design says to read via scripts/load_config; this is a function, not a
|
||||
file. Actual entry is scripts/common.sh:16 `load_config`, invoking
|
||||
mosaic-config.mjs env, then exporting four existing variables. No install
|
||||
mode/endpoint exports yet. No modification made.
|
||||
|
||||
Design sections 3/4/7 re-read: exact base digest
|
||||
`node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20`;
|
||||
three release-tagged images; optional install with local default; exact
|
||||
rootless SecurityOptions array element check; baked agents/services CONNECT
|
||||
lists; JSON decision log. Spike proxy source re-read: one-chunk request
|
||||
parsing, no slow-client bound, environment allowlist; it needs the reviewed
|
||||
implementation required by I2, not a direct copy. package.json pins pi
|
||||
0.85.1; spike Dockerfile.clis pins Claude Code 2.1.296 (current session-code
|
||||
pin still needs checking before implementation).
|
||||
|
||||
### Outbound stop condition (brief What ships; design section 7)
|
||||
|
||||
The brief explicitly says: “If one doesn't, stop and tell Sage before
|
||||
adding a dependency.” Not all broker outbound paths use fetch at this base:
|
||||
|
||||
- packages/bus/src/gitea-grant.mjs:11 imports node:https request;
|
||||
send():91-140 calls httpsRequest:115 with `agent: false`, auth header,
|
||||
optional CA, timeout and bounded response. It is not the measured fetch
|
||||
path. runtime.mjs imports and wires the grant executor.
|
||||
- packages/bus/src/web.mjs delegates to packages/discord/src/web.mjs.
|
||||
That implementation imports node:https/node:http:23-24, resolves public
|
||||
addresses and pins the connection to a vetted address (:161 onward).
|
||||
Changing it to fetch without preserving that fence would be a boundary
|
||||
change. Its arbitrary public destinations also need reconciliation with
|
||||
the design's fixed services host list; do not widen the list speculatively.
|
||||
- packages/tasks/src/vikunja.mjs:30,41 uses injected/default global fetch,
|
||||
redirect:error, and processing cancellation/deadline.
|
||||
- packages/cli/src/notifier-process.mjs:41-46 supplies global fetch to
|
||||
openDirect. packages/discord/src/notify.mjs:17 builds REST only; it
|
||||
explicitly does not run the gateway. packages/discord/src/rest.mjs:45
|
||||
calls injected fetch. The separate Discord connector gateway uses native
|
||||
WebSocket (gateway.mjs:193); do not claim the notifier needs that gateway.
|
||||
- No NODE_USE_ENV_PROXY or HTTPS_PROXY setup found in host.mjs,
|
||||
notifier-process.mjs or bus/process.mjs. I6 owns container startup;
|
||||
I2's eventual image/process verification must demonstrate the actual path
|
||||
with dummy credentials, after the transport ruling and a CLEAR slot.
|
||||
|
||||
Recommendation sent to Sage: re-pin the approved brief and rule on the
|
||||
bounded handling/ownership of non-fetch paths before implementation. No new
|
||||
dependency, host-list expansion, or replacement of DNS pinning authorized.
|
||||
|
||||
### Environment and evidence limits
|
||||
|
||||
`/run/user/1000/docker.sock` exists as a Unix socket owned by jwoltje.
|
||||
Existence is not a rootless attestation. No Docker command or endpoint probe
|
||||
has been run. All test execution requires Sage CLEAR; none requested yet
|
||||
because there is no implementation candidate. No test, image build,
|
||||
credential read, live config/data access, daemon/volume/unit change, commit
|
||||
or push. Once unblocked, implement within the owned file list, add required
|
||||
acceptance cases, then request a concrete CLEAR with paths and suites;
|
||||
use fresh /mnt/storage/scratch/tmp/XXXXXX TMPDIR and timeout -k 10 for each
|
||||
scripts/test-*.sh. Old-test changes require Sage authorization before edits.
|
||||
Provider-host measurement ships with expected output for Mos; no real token
|
||||
will be used here.
|
||||
|
||||
## sage-3907 — stop resolved; implementation draft
|
||||
|
||||
Sage confirms only I6 changed since the brief pin, and will re-pin row 157.
|
||||
I2 must not change non-fetch security transports or add a dependency. Record
|
||||
all gaps, retain each named service host and code path in the contract with
|
||||
an explicit note, and build the rest. Sage owns the transport ruling before
|
||||
broker rollout in later rows. This supersedes the pre-build transport hold;
|
||||
the queue claim still awaits a current pin. No queue move attempted.
|
||||
|
||||
Additional non-fetch broker paths found:
|
||||
|
||||
- runtime.mjs:511 calls seat/src/working.mjs:461 publishRemote, which runs
|
||||
git ls-remote/push at :479/:490 with credential helper input on fd 3.
|
||||
checkout.mjs:80 gitEnv emits a fixed environment without HTTPS_PROXY.
|
||||
This is Git HTTPS, not fetch; core's no-git design also prevents it from
|
||||
working today. The Gitea host entry retains this caveat.
|
||||
- runtime.mjs:292 wires hostGrant; host-grant.mjs:174 spawns ssh-keyscan
|
||||
to read public host keys at the registered address/port. It uses fixed
|
||||
PATH-only env, not fetch or HTTP CONNECT. runtime.mjs:435 wires
|
||||
hostExecutor; host-run.mjs:30-45 sshArgs explicitly sets
|
||||
ProxyCommand=none and ProxyJump=none, then ssh to run authorized commands
|
||||
using a pinned host key and private agent. Neither routes through an env
|
||||
HTTP proxy. Hosts/ports are per registry and may be SSH, so no fabricated
|
||||
:443 allowlist entry or network expansion was added.
|
||||
- Search-source plug-ins delegate to discord/src/web.mjs. searxng-url
|
||||
sends search GETs to the operator's configured host; searxng-bundled points
|
||||
at 127.0.0.1:8888. Both are non-fetch and depend on the existing DNS checks.
|
||||
No fixed host can be truthfully named for arbitrary web.fetch or operator
|
||||
search; no wildcard entry was added. These are documented gaps, not
|
||||
claims of proxy coverage.
|
||||
- Separate Discord connector gateway uses native WebSocket; retained
|
||||
gateway.discord.gg:443 with a NOT PROXIED note, as Sage directs. Notifier
|
||||
is REST-only; no gateway test or live connector run added.
|
||||
|
||||
Choices: dependency-free net proxy; exact list lookup; 8192-byte/5s total
|
||||
header bounds, 10s upstream connect/120s tunnel idle bounds. JSON allowed
|
||||
means the contract permitted the destination, not that upstream succeeded.
|
||||
Images use exact I1 base, core has NODE_USE_ENV_PROXY=1, session globally
|
||||
installs Claude 2.1.296 and package.json pi pin. Source context includes only
|
||||
tracked runtime paths plus explicit new I2 runtime paths, never agent work
|
||||
or host node_modules/.git. Tags derive from RELEASE. Core receives no git.
|
||||
The zai host candidate api.z.ai is confirmed by the pinned dependency's
|
||||
providers/zai.js:9 in canonical node_modules; real-token measurement remains
|
||||
Mos's gate. OpenAI candidate is from S6 and is only measured when configured.
|
||||
No live model-table config was read.
|
||||
|
||||
New tests cover config exit/immutability; helper rootful, null/nonzero,
|
||||
substring, wrong type/JSON, exact rootless and hostile DOCKER_HOST; proxy
|
||||
fragmentation/pipelined data, deny/port/malformed/slow/oversize and JSON logs.
|
||||
test-install additionally builds, inspects pins and exercises an internal
|
||||
network plus actual Vikunja/public GET and notifier REST/dummy GET with
|
||||
proxy log evidence. The generic S5 fetch test is not repeated.
|
||||
|
||||
Old-suite change is prepared as test-config.patch (adds new config cases
|
||||
before the existing summary). It has NOT been applied: Sage's old-test
|
||||
change rule requires approval first. No old tests changed. No tests run.
|
||||
|
||||
## sage-3910 — first CLEAR
|
||||
|
||||
Authorized four serialized runs exactly as requested, plus applying the
|
||||
addition-only test-config.patch. No broader suites; Sage supplies the
|
||||
landing gate. Return slot on run 4 completion or an actual stop.
|
||||
|
||||
Additional paths retained under Sage's explicit ruling:
|
||||
- scripts/install-tests/config.test.mjs: table of install schema/exit/byte
|
||||
preservation cases, reused by test-config.sh and test-install.sh.
|
||||
- scripts/install-tests/helper.test.mjs: fake Docker executable to assert
|
||||
exact argv and rootless refusal before the requested call, without a daemon.
|
||||
- scripts/install-tests/network-probe.mjs: actual internal-network tunnel,
|
||||
TLS, unlisted host/port and external DNS refusal in a core image.
|
||||
- scripts/install-tests/services-probe.mjs: actual Vikunja and notifier REST
|
||||
client functions, public GET plus invalid dummy token, with proxy log proof.
|
||||
All are new files; no old assertion changes.
|
||||
|
||||
## First CLEAR result and sage-3916 correction
|
||||
|
||||
Run 1: exit 0, config/helper 31/31, zero fail/skip/cancel,
|
||||
01-install-unit.log. Run 2: exit 0, proxy 11/11, zero fail/skip/cancel,
|
||||
02-egress.log. Run 3: exit 1, config selftest 55 pass / 1 fail; new install
|
||||
cases 20/20, 03-config.log. Each log records UTC/load/CLEAR/fresh scratch TMPDIR.
|
||||
Run 4 NOT RUN: no Docker command, probe or build. Slot returned to Sage in
|
||||
bentina-0004 before any rerun.
|
||||
|
||||
Old red: config without console loads with unchanged output
|
||||
(test-config.sh:266; log:53). Cause: this implementation unconditionally
|
||||
added install:{mode:local} to resolved JSON. Sage-3916 approves the source-only
|
||||
fix: omit output key when input omits it (optional-key convention), default
|
||||
env export to local. Applied this fix to mosaic-config.mjs and adjusted only
|
||||
the new absent-key case. No old assertion changed. No verification after fix:
|
||||
Poe has the current slot, Bentina runs next only on a new CLEAR. Run order
|
||||
will be 1, 3, 4; no reason to rerun unchanged proxy unit tests separately.
|
||||
|
||||
The approved test-config patch is addition-only: git diff --numstat reports
|
||||
4 added, 0 removed lines for scripts/test-config.sh. No existing line or
|
||||
assertion changed. git diff --stat before first run:
|
||||
|
||||
```
|
||||
BUILD-LOG.md | 2 ++
|
||||
README.md | 9 +++++++++
|
||||
docs/SESSIONS.md | 2 ++
|
||||
docs/TOOLS.md | 38 ++++++++++++++++++++++++++++++++++++++
|
||||
scripts/mosaic-config.mjs | 20 +++++++++++++++++++-
|
||||
scripts/test-config.sh | 4 ++++
|
||||
6 files changed, 74 insertions(+), 1 deletion(-)
|
||||
```
|
||||
|
||||
New untracked paths are not included by git diff --stat; see explicit paths
|
||||
above. Claim bentina-0003 initially refused queue-lock-held; same op retried.
|
||||
Sage reports re-pin rev 3808 (sage-3913).
|
||||
|
||||
## sage-3919 — corrected acceptance green, slot returned
|
||||
|
||||
Run 1 exit 0, helper/config 31/31 (04-install-unit-fixed.log); run 3 exit 0,
|
||||
config selftest 56/56 plus new install 20/20 (05-config-fixed.log); run 4
|
||||
exit 0 (06-install.log), helper/config 31/31 and proxy 11/11, images built
|
||||
at RELEASE 0.0.12, rootless endpoint explicitly attested, image version
|
||||
checks passed, internal-network TLS to listed host reached, unlisted host
|
||||
and port refused, external DNS refused. Actual Vikunja public GET returned
|
||||
200 and notifier REST dummy-token GET returned 401; services proxy logs
|
||||
show both host decisions. No real token or broker started. Test-owned
|
||||
networks/containers cleaned by suite. Slot returned bentina-0005. No broader
|
||||
suites: Sage's landing gate supplies them. Claim bentina-0003 acknowledged
|
||||
rev 3812 briefed -> in-progress. No queue files changed in this worktree.
|
||||
|
||||
Post-pass source review found one small correctness defect before handoff:
|
||||
upstream errors after a successful CONNECT would append a plaintext 502 to
|
||||
an established TLS tunnel. Corrected to close an established tunnel and
|
||||
reserve 502 for establishment errors. Added a local reset-after-CONNECT
|
||||
regression alongside the existing establishment-error test. This creates
|
||||
a new verification need: request a new CLEAR for egress units and install
|
||||
rebuild/probe before committing. Existing green logs above predate this fix;
|
||||
do not claim them for the final proxy bytes.
|
||||
|
||||
## sage-3923 — final proxy and install evidence
|
||||
|
||||
07-egress-final.log: exit 0, 12/12, zero fail/skip/cancel, including upstream
|
||||
reset after a CONNECT200 closes the tunnel without a second HTTP response.
|
||||
08-install-final.log: exit 0, 31/31 config/helper and 12/12 proxy,
|
||||
zero fail/skip/cancel, three images rebuilt, version checks passed,
|
||||
internal-network listed TLS200/unlisted host403/unlisted port403/external
|
||||
DNS refused, Vikunja public GET200 and notifier REST dummy-token GET401
|
||||
through services proxy; JSON log decisions verified. Explicit endpoint
|
||||
unix:///run/user/1000/docker.sock passed rootless check. Suite removed only
|
||||
its named i2 containers/networks. Slot returned to Sage in bentina-0007.
|
||||
|
||||
No runtime source change after these final reruns. Mos-only measurement
|
||||
runner was statically hardened to report a fixed message on malformed
|
||||
credential JSON, avoiding JSON.parse's error snippet echoing private input.
|
||||
The runner and provider script have not been executed here: Mos owns the
|
||||
real-token/provider-model-table check and its output must land on #1652.
|
||||
No claim of completed provider-host measurement or final host-list approval.
|
||||
|
||||
Acceptance accounting:
|
||||
- Install validator cases and unchanged config bytes: new 20 cases green;
|
||||
entire existing config suite 56/56 (05-config-fixed.log); absent output
|
||||
shape preserved; env/helper default local proven by helper's absent test.
|
||||
- Rootless guard: exact argv/array membership/status/type/refusal fake
|
||||
cases plus real rootless check in 08-install-final.log. No ambient Docker
|
||||
context used by new code. Other scripts' migration remains I6.
|
||||
- Images: exact base digest in Dockerfiles, release 0.0.12 tags, core node
|
||||
24.21.0/no-git/RELEASE assertion; session Claude2.1.296/pi0.85.1 assertions;
|
||||
all built through the helper on the explicit rootless endpoint.
|
||||
- Proxy: allow/deny/port/malformed/slow/fragmented/header-bound/upstream-error
|
||||
and structured logs verified in final unit suite; actual TLS tunnel and
|
||||
internal-network denials plus services client fetch paths verified by
|
||||
install suite. The generic S5 fetch probe was not repeated.
|
||||
- Non-fetch broker/gateway/SSH/Git/web paths remain documented gaps under
|
||||
sage-3907, not modified or silently claimed to be covered. All fixed
|
||||
service hosts retain entries and code-path notes. No dependency added.
|
||||
- Full suites: not run here per sage-3910; Sage's landing gate supplies
|
||||
broader coverage. Reviewer and Mos gates still pending.
|
||||
|
||||
Static source diff whitespace check passed (excluding captured .log files
|
||||
and unified .patch context). Raw build logs retain Docker/npm output
|
||||
including whitespace; no evidence was normalized or rewritten. Old-suite
|
||||
change remains +4/-0 and every prior assertion byte-identical. No queue
|
||||
file changed here. No secret read, token test, live state/unit operation,
|
||||
daemon/volume/restart action, protected-branch push, or integration.
|
||||
|
||||
Candidate handoff: base ec86f2c84; commit only this I2 work and receipts on
|
||||
bentina/row157, push through configured repository helper, then send Sage
|
||||
and Boyd the tip/base/manifest for first review. Darkwing follows Boyd.
|
||||
The manifest lists each changed path except its own self-reference.
|
||||
|
||||
## Round 2 — Boyd B1/B2 (boyd-0023; Sage sage-3933)
|
||||
|
||||
First candidate a14fdc443, manifest b0ed8395, CHANGES on #1652 comment31494.
|
||||
Read Boyd's exact scratch harnesses and logs under /mnt/storage/scratch/tmp/IyNqMs.
|
||||
B1 reproduction: normal peer control releases its socket, allowHalfOpen peer
|
||||
receives408 but getConnections still reports1 after100ms (assertion1 != 0).
|
||||
Root cause: refusal end sends FIN but clears the only deadline and leaves the
|
||||
read side open. B2 reproduction: same-PID exec inherits exported checked_pid
|
||||
and endpoint; fake rootful Docker sees ps on inherited.sock, no info; rc0 vs
|
||||
expected2. Root cause: source initializes neither cache validity nor endpoint.
|
||||
These findings agree with source inspection and require no design departure.
|
||||
No local tests were run while Goldie holds the slot.
|
||||
|
||||
Added tests before fixes, adapted from the reviewer with explicit cleanup:
|
||||
- packages/egress/tests/refusal.test.mjs: real loopback sockets, normal and
|
||||
half-open controls for every400/403/408/431 refusal; asserts server socket
|
||||
count reaches0, one denial log, expected response. Removing destruction
|
||||
after flush should fail half-open cases while normal controls still pass.
|
||||
- scripts/install-tests/review-cache.test.mjs: same-PID exec with inherited
|
||||
exported cache, fake rootful/rootless Docker, actual loader/helper; asserts
|
||||
first call is info at configured.sock, refusal or exact once-per-process
|
||||
cache behavior, and no cache keys in Docker's child environment. Removing
|
||||
source-time reset should fail; no real engine, config or credentials used.
|
||||
No existing test file or assertion edited.
|
||||
|
||||
Minimal fixes: proxy uses endAndClose for all refusal responses, including
|
||||
upstream establishment502: destroy after the small response flushes, plus a
|
||||
fixed1s drain deadline cleared on close. Established-tunnel reset behavior
|
||||
stays unchanged. Helper unsets checked_pid/endpoint/mode on every source,
|
||||
clearing inherited export attributes; ordinary function calls still reuse
|
||||
only an attestation obtained in the current process. No dependency, contract,
|
||||
host-list or authority change. Request CLEAR for new regression red/green,
|
||||
full targeted units and test-install rebuild before commit. Old-test red
|
||||
still stops and returns slot. Sage's broader landing gate remains separate.
|
||||
|
||||
Sage-3935 approves the five-run plan including archived red-first proof;
|
||||
Goldie holds the slot and Bentina is next. Still no node/docker test run.
|
||||
The new refusal test also includes normal/half-open502 establishment failures
|
||||
on a closed loopback port, exercising the shared termination helper on that
|
||||
path (allowed log stays true for an approved destination whose upstream fails).
|
||||
This stays within the named new regression file and approved commands.
|
||||
|
||||
## Round 2 verified — sage-3936 slot returned
|
||||
|
||||
Claimant move bentina-0012 acknowledged rev3825 in-review -> in-progress,
|
||||
before the correction commit. Five serialized runs, each fresh short scratch
|
||||
TMPDIR/UTC/load/CLEAR and timeout -k10:
|
||||
|
||||
| Run | Log (this directory) | Exit | Evidence |
|
||||
|---|---|---|---|
|
||||
| 1 old archived runtime + new tests | 09-r2-archived-red.log | 1 expected | 12 tests:5 normal-peer controls pass;7 intended failures (five half-open400/403/408/431/502 cases, two inherited-cache cases);0skip/cancel |
|
||||
| 2 fixed regressions | 10-r2-regressions-green.log | 0 | 12/12,0fail/skip/cancel |
|
||||
| 3 install unit suite | 11-r2-install-units.log | 0 | 33/33,0fail/skip/cancel |
|
||||
| 4 egress suite | 12-r2-egress-suite.log | 0 | 22/22,0fail/skip/cancel |
|
||||
| 5 install images/network/services | 13-r2-install.log | 0 | 33+22 unit tests green;three images rebuilt/version pins checked;explicit rootless guard;listed TLS200,unlisted host/port403,external DNS refused;Vikunja publicGET200/notifier REST dummyGET401;JSON decisions verified |
|
||||
|
||||
Run1 unpacked git archive of a14fdc443 into its own scratch directory and
|
||||
overlaid only the two new tests; it did not revert or modify the builder
|
||||
worktree. Failures were exact behavioral assertions, not infrastructure or
|
||||
syntax errors. All test sockets cleaned in t.after; the install suite
|
||||
removed only its i2-prefixed containers/networks. Slot returned to Sage in
|
||||
bentina-0014. No existing test file/assertion changed for round2. No code
|
||||
change after verification. Earlier config56/56 evidence remains in05 log;
|
||||
loader/config unchanged for this review. No broader suites authorized here.
|
||||
|
||||
The round2 manifest MANIFEST-r2.sha256 covers the full candidate delta from
|
||||
original base ec86f2c84, including the unchanged historical round1 manifest;
|
||||
it excludes only its own self-reference (its hash is supplied in the review
|
||||
message). Historical round1 manifest still describes a14fdc443 and is not
|
||||
rewritten to describe later source. New manifest, candidate tip/base and
|
||||
receipts go to Sage and Boyd, then Darkwing follows Boyd's approve. The
|
||||
owner's automated review-request credential boundary still belongs to Sage;
|
||||
no token read or identity substitution is authorized. Mos provider evidence
|
||||
and Sage's broader landing gate remain required. No protected-branch push,
|
||||
real-token test, live config/state/unit action or daemon/volume operation.
|
||||
@@ -0,0 +1,44 @@
|
||||
58e473e1783a9f64f6b1a696f747925b9a444d85dbe72e48e7a0dcbd62a2d4e5 BUILD-LOG.md
|
||||
67bd811b3030ba0355af2316d02e8b2931b4b2c5a74c5be904b5311de6ef81dd README.md
|
||||
6161cc0505bf0be33ba0e7c7f0f07382cca0ec2b5ace65ac54e16c4d1e3424fc agents/bentina/work/queue-157/01-install-unit.log
|
||||
f13213941dab22955841108d85e62f67ede2ddbc95c25a7b351f30070b035a85 agents/bentina/work/queue-157/02-egress.log
|
||||
b46c3fb5f252100f3e2de5b97bd0efc0c1fbbef26230252945f7bb309d359f5e agents/bentina/work/queue-157/03-config.log
|
||||
fc76e5f9b19a31b494ebf4a128ef5f171f1f0712dc3928bebb6556815577015c agents/bentina/work/queue-157/04-install-unit-fixed.log
|
||||
c9a46d969c5d28814addccd7cafe8e734d15c02a407ee80434d4e3e70a3fa4b7 agents/bentina/work/queue-157/05-config-fixed.log
|
||||
1d5505a6aba0078e7cf9936fb683e06ac900af12e9e8faf224099a5e2d825fc7 agents/bentina/work/queue-157/06-install.log
|
||||
c684dfdd8a460a915e15291fc0f92c9ba30f38d3d3dfa5fa190e7835fbcd448f agents/bentina/work/queue-157/07-egress-final.log
|
||||
5c3efacdbcda5628882045644c32169b7020a6e49d6b89b273aba20ae042898b agents/bentina/work/queue-157/08-install-final.log
|
||||
d2f7bd37b5ac8cc781fae1308a5b047e0ed0d1b63f8d5e22d56e0efa328c4883 agents/bentina/work/queue-157/09-r2-archived-red.log
|
||||
4230f9fcc91d1670df0d975e9c5794edb7aa9b21b24c4f415d1594373e769da4 agents/bentina/work/queue-157/10-r2-regressions-green.log
|
||||
3e5cd8d154b01ede9a9533f4ff40ac29b4e8193131fd63ba04707df51217d0bb agents/bentina/work/queue-157/11-r2-install-units.log
|
||||
4d23ae4a6d3247a498bd446960c075235f632474fba9e8db374a32aaf4760219 agents/bentina/work/queue-157/12-r2-egress-suite.log
|
||||
a4652f0eb6dfcc177c24c5d85d7ec08096e9f49304085f817828d7ef451acd64 agents/bentina/work/queue-157/13-r2-install.log
|
||||
5b595798fa8184b15412276d070a0891fdf5cb629eabb8b7bb5f6b3d2b69b3a5 agents/bentina/work/queue-157/BUILD.md
|
||||
b0ed8395108ad398f5c9c08fbac8a1b5ca02f579a5d7b709d19d3aaf0860f9ae agents/bentina/work/queue-157/MANIFEST.sha256
|
||||
21f9783dcfe2cac00a49d0d7930a57134bc59ec19dd7bf2e96950f79fae5b1b1 agents/bentina/work/queue-157/PROVIDER-EXPECTED.md
|
||||
6518c574291db5816057fbbc438435d0571e088c8f26d1a47ec03ff3047a5efa agents/bentina/work/queue-157/provider-hosts.sh
|
||||
6dbef8d20ede98a9311ba47137b587c07cbc03e921b2a8429652fe362aeb4b85 agents/bentina/work/queue-157/provider-runner.mjs
|
||||
e7cb4d0c67eadec557a25385b4ea9803970818354a9e8b0780ac4de07cf34911 agents/bentina/work/queue-157/test-config.patch
|
||||
cb2b251342f0020764b7b757b22567d52380053fc15f0e1c7e665a5ec1838f24 contracts/egress.json
|
||||
58107cfa274b7551ae163ea77c56d08df530e7f08e06539ddd2999c22252d749 docs/SESSIONS.md
|
||||
edcaa526bf0a518716d4e87895d49920b8912295688809a25102ce6b9d6ebdc3 docs/TOOLS.md
|
||||
6bd1dc7c244cfb4134d813e0b149ef1182a810eb99f01a95d548f0885b216437 images/README.md
|
||||
9c6c3d5736f1ed7eb6dcdb893761b937b7108e29730daf28fe8dd0064df73740 images/core/Dockerfile
|
||||
0179d84a7b9321832efb1d90c0290c8f2f6de03db3b2b6d8379672aca598c2a0 images/egress/Dockerfile
|
||||
e246e3e3da1065f3c8cf24cc1f7ae15290f3e4c6508f0c6f17a4889a77f2d591 images/session/Dockerfile
|
||||
3930764cf5848571507eec3e5b3eda2ebb8aa2d9642f83615b462bf5035e0e75 packages/egress/README.md
|
||||
2c45bfed1f19f8a42e05552f8a10584feed8d25ccc506252d55a9a3b6c90a21c packages/egress/package.json
|
||||
257058faee55a429d98955b6253d6b8a0bb058c50446764566586b06a8f76b61 packages/egress/src/cli.mjs
|
||||
ce3adb0515ca7e9041f36d7a82df355a3fc97e2a96fc6811ea3b9b47e464787b packages/egress/src/proxy.mjs
|
||||
5c4e778ec9ec5b93ab681d615a2240cd342d09adfe4df1634f3d6abbf16a405b packages/egress/tests/proxy.test.mjs
|
||||
258e954ad7e441a7dd7d25ca9208691ea167e9cb7f34d7d689ed11005adeb4b4 packages/egress/tests/refusal.test.mjs
|
||||
86e0152120b04fcfe2a4081b7686f179a139c5370027828c79572033f3cf81c3 scripts/images.sh
|
||||
3647b06df02b4d9c0a74b62c774ecdff9272e68a84c06cd85861d7bf18655d5a scripts/install-tests/config.test.mjs
|
||||
21e05bc3e0639427f5a9d4cf166d2991964134845272df76c712ad00ea8033b6 scripts/install-tests/helper.test.mjs
|
||||
aa5328566d3e5b46fee587657dac146712735dca0aca90d3d0ad84bc20e313e1 scripts/install-tests/network-probe.mjs
|
||||
45274f6745cf931f3002e6c36dcfc46a96c8f09e01e6243c18659749673752fd scripts/install-tests/review-cache.test.mjs
|
||||
72ae66696ad2faaf258c832fdfdf680efc8fca7099359e58da1ee01a88541d8a scripts/install-tests/services-probe.mjs
|
||||
79a8ecfc5ab279111031d5a44906450b732b517fe98511992cafd873ab7a84d3 scripts/lib/docker.sh
|
||||
7398bb289d2f28ed15a2984bdeee7e30a704890a33b5e2a5c9f7554115aca8f9 scripts/mosaic-config.mjs
|
||||
dcf1799238141689c9d80adc6680694a609e5fb9deef7b78d6b3f4765163e3fe scripts/test-config.sh
|
||||
08be25f67d8fc6676218b1014d34a2ce36a939231f58b657972d856ee26169b2 scripts/test-install.sh
|
||||
@@ -0,0 +1,36 @@
|
||||
6995aa3bcdf50e2441142cc9b6fc9ec84853e353e7a408bbc877ee7e188e73c4 BUILD-LOG.md
|
||||
67bd811b3030ba0355af2316d02e8b2931b4b2c5a74c5be904b5311de6ef81dd README.md
|
||||
6161cc0505bf0be33ba0e7c7f0f07382cca0ec2b5ace65ac54e16c4d1e3424fc agents/bentina/work/queue-157/01-install-unit.log
|
||||
f13213941dab22955841108d85e62f67ede2ddbc95c25a7b351f30070b035a85 agents/bentina/work/queue-157/02-egress.log
|
||||
b46c3fb5f252100f3e2de5b97bd0efc0c1fbbef26230252945f7bb309d359f5e agents/bentina/work/queue-157/03-config.log
|
||||
fc76e5f9b19a31b494ebf4a128ef5f171f1f0712dc3928bebb6556815577015c agents/bentina/work/queue-157/04-install-unit-fixed.log
|
||||
c9a46d969c5d28814addccd7cafe8e734d15c02a407ee80434d4e3e70a3fa4b7 agents/bentina/work/queue-157/05-config-fixed.log
|
||||
1d5505a6aba0078e7cf9936fb683e06ac900af12e9e8faf224099a5e2d825fc7 agents/bentina/work/queue-157/06-install.log
|
||||
c684dfdd8a460a915e15291fc0f92c9ba30f38d3d3dfa5fa190e7835fbcd448f agents/bentina/work/queue-157/07-egress-final.log
|
||||
5c3efacdbcda5628882045644c32169b7020a6e49d6b89b273aba20ae042898b agents/bentina/work/queue-157/08-install-final.log
|
||||
f4755f24d626dc69b8159c6934e4213a907659dbb4d46d29d82efc0405b50152 agents/bentina/work/queue-157/BUILD.md
|
||||
21f9783dcfe2cac00a49d0d7930a57134bc59ec19dd7bf2e96950f79fae5b1b1 agents/bentina/work/queue-157/PROVIDER-EXPECTED.md
|
||||
6518c574291db5816057fbbc438435d0571e088c8f26d1a47ec03ff3047a5efa agents/bentina/work/queue-157/provider-hosts.sh
|
||||
6dbef8d20ede98a9311ba47137b587c07cbc03e921b2a8429652fe362aeb4b85 agents/bentina/work/queue-157/provider-runner.mjs
|
||||
e7cb4d0c67eadec557a25385b4ea9803970818354a9e8b0780ac4de07cf34911 agents/bentina/work/queue-157/test-config.patch
|
||||
cb2b251342f0020764b7b757b22567d52380053fc15f0e1c7e665a5ec1838f24 contracts/egress.json
|
||||
d6239336e48b6a78b80680c88e83ba4b67f8cbed99bf2ee3a697b1b0611e54e9 docs/SESSIONS.md
|
||||
edcaa526bf0a518716d4e87895d49920b8912295688809a25102ce6b9d6ebdc3 docs/TOOLS.md
|
||||
6bd1dc7c244cfb4134d813e0b149ef1182a810eb99f01a95d548f0885b216437 images/README.md
|
||||
9c6c3d5736f1ed7eb6dcdb893761b937b7108e29730daf28fe8dd0064df73740 images/core/Dockerfile
|
||||
0179d84a7b9321832efb1d90c0290c8f2f6de03db3b2b6d8379672aca598c2a0 images/egress/Dockerfile
|
||||
e246e3e3da1065f3c8cf24cc1f7ae15290f3e4c6508f0c6f17a4889a77f2d591 images/session/Dockerfile
|
||||
3930764cf5848571507eec3e5b3eda2ebb8aa2d9642f83615b462bf5035e0e75 packages/egress/README.md
|
||||
2c45bfed1f19f8a42e05552f8a10584feed8d25ccc506252d55a9a3b6c90a21c packages/egress/package.json
|
||||
257058faee55a429d98955b6253d6b8a0bb058c50446764566586b06a8f76b61 packages/egress/src/cli.mjs
|
||||
2ec533e8a2c9ace2d2dc17ac9efe75a9930ae50e93f6700e3de8aa0475b42295 packages/egress/src/proxy.mjs
|
||||
5c4e778ec9ec5b93ab681d615a2240cd342d09adfe4df1634f3d6abbf16a405b packages/egress/tests/proxy.test.mjs
|
||||
86e0152120b04fcfe2a4081b7686f179a139c5370027828c79572033f3cf81c3 scripts/images.sh
|
||||
3647b06df02b4d9c0a74b62c774ecdff9272e68a84c06cd85861d7bf18655d5a scripts/install-tests/config.test.mjs
|
||||
21e05bc3e0639427f5a9d4cf166d2991964134845272df76c712ad00ea8033b6 scripts/install-tests/helper.test.mjs
|
||||
aa5328566d3e5b46fee587657dac146712735dca0aca90d3d0ad84bc20e313e1 scripts/install-tests/network-probe.mjs
|
||||
72ae66696ad2faaf258c832fdfdf680efc8fca7099359e58da1ee01a88541d8a scripts/install-tests/services-probe.mjs
|
||||
e524a285dcebe2a6350e7690650c2796e54661225ce00a67b4559e519f9e3fea scripts/lib/docker.sh
|
||||
7398bb289d2f28ed15a2984bdeee7e30a704890a33b5e2a5c9f7554115aca8f9 scripts/mosaic-config.mjs
|
||||
dcf1799238141689c9d80adc6680694a609e5fb9deef7b78d6b3f4765163e3fe scripts/test-config.sh
|
||||
08be25f67d8fc6676218b1014d34a2ce36a939231f58b657972d856ee26169b2 scripts/test-install.sh
|
||||
@@ -0,0 +1,37 @@
|
||||
# Mos provider-host gate (not run by Bentina)
|
||||
|
||||
Run only after candidate images build and Mos authorizes the real-token calls.
|
||||
Use a scratch config with install.mode containerized and the rootless endpoint;
|
||||
do not change the live config. Provide one JSON object on stdin with
|
||||
`claudeOAuth` and `zai`, plus optional `anthropic` / `openai` keys if those
|
||||
providers are in the actual model table. The script opens no host token file.
|
||||
Never paste the input or raw CLI output on the issue. It prints success flags
|
||||
and the proxy's host decisions only. Wrap in `timeout -k 10 600`.
|
||||
|
||||
From the checkout:
|
||||
|
||||
```
|
||||
Mos-private-json-producer | MOSAIC_CONFIG=/path/to/scratch-config.json agents/bentina/work/queue-157/provider-hosts.sh
|
||||
```
|
||||
|
||||
Expected required output (with just Claude Code and pi/zai):
|
||||
|
||||
```
|
||||
{"case":"claude","ok":true}
|
||||
{"case":"pi-zai","ok":true}
|
||||
{"hosts":["api.anthropic.com:443","api.z.ai:443"],"denied":[]}
|
||||
```
|
||||
|
||||
Optional pi/anthropic adds `{"case":"pi-anthropic","ok":true}` and no
|
||||
host. Optional pi/openai adds `{"case":"pi-openai","ok":true}` and
|
||||
`api.openai.com:443` to sorted hosts. All cases must return ok true, exit 0,
|
||||
and denied empty. Model names are explicit candidates; Mos must reconcile
|
||||
them to the actual model table without broadening a host list on failure.
|
||||
A new host, refusal, failure, or different model requirement returns to Sage;
|
||||
there is no wildcard or automatic discovery/update. The baked agents list
|
||||
remains provisional until Mos posts matching output on #1652.
|
||||
|
||||
Each resource name begins i2-provider; cleanup removes only this run's two
|
||||
networks and two containers. No volumes, daemon or restart operations. The
|
||||
measurement uses container root as the design requires; I5 still owns the
|
||||
full root filesystem re-measurement and final session limits.
|
||||
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
# Mos runs this explicitly with a caller-supplied scratch MOSAIC_CONFIG and
|
||||
# credential JSON on stdin. This script reads no host credential file.
|
||||
set -euo pipefail
|
||||
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/../../../.." && pwd)"
|
||||
cd "$REPO"
|
||||
source scripts/lib/docker.sh
|
||||
mosaic_docker version >/dev/null
|
||||
release="$(tr -d '[:space:]' < RELEASE)"
|
||||
prefix="i2-provider-$(date -u +%s)-$$"
|
||||
nets=("$prefix-internal" "$prefix-egress")
|
||||
cleanup(){
|
||||
rc=$?; trap - EXIT
|
||||
mosaic_docker container rm -f "$prefix-session" "$prefix-proxy" >/dev/null 2>&1 || true
|
||||
for net in "${nets[@]}"; do mosaic_docker network rm "$net" >/dev/null 2>&1 || true; done
|
||||
exit "$rc"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
mosaic_docker network create --internal "${nets[0]}" >/dev/null
|
||||
mosaic_docker network create "${nets[1]}" >/dev/null
|
||||
mosaic_docker create --name "$prefix-proxy" --network "${nets[1]}" --read-only --cap-drop ALL --security-opt no-new-privileges --pids-limit 64 --memory 128m --memory-swap 128m --cpus 0.5 "mosaic-egress:$release" agents >/dev/null
|
||||
mosaic_docker network connect --alias proxy "${nets[0]}" "$prefix-proxy"
|
||||
mosaic_docker start "$prefix-proxy" >/dev/null
|
||||
ready=0
|
||||
for attempt in {1..50}; do
|
||||
if mosaic_docker exec "$prefix-proxy" node -e 'const s=require("node:net").connect(3128,"127.0.0.1");s.on("connect",()=>{s.destroy();process.exit(0)});s.on("error",()=>process.exit(1))'; then ready=1; break; fi
|
||||
sleep 0.1
|
||||
done
|
||||
[ "$ready" = 1 ] || { echo proxy-not-ready >&2; exit 1; }
|
||||
rc=0
|
||||
# Token travels only in stdin and the CLI child's env; never an engine env or log.
|
||||
mosaic_docker run --rm --init -i --name "$prefix-session" --network "${nets[0]}" --log-driver none --read-only --tmpfs /root:rw,nosuid --tmpfs /tmp:rw,noexec,nosuid --cap-drop ALL --security-opt no-new-privileges --pids-limit 512 --memory 4g --memory-swap 4g --cpus 2 \
|
||||
--mount "type=bind,src=$REPO/agents/bentina/work/queue-157/provider-runner.mjs,dst=/opt/mosaic/provider-runner.mjs,readonly" \
|
||||
"mosaic-session:$release" node provider-runner.mjs || rc=$?
|
||||
mosaic_docker logs "$prefix-proxy" | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{const rows=s.trim().split("\n").filter(Boolean).map(JSON.parse);const deny=rows.filter(r=>r.host&&!r.allowed);console.log(JSON.stringify({hosts:[...new Set(rows.filter(r=>r.allowed).map(r=>r.host+":"+r.port))].sort(),denied:[...new Set(deny.map(r=>r.host+":"+r.port))].sort()}));if(deny.length)process.exitCode=1})' || rc=$?
|
||||
exit "$rc"
|
||||
@@ -0,0 +1,17 @@
|
||||
// Mos-only real-token measurement. Reads one JSON object on stdin. Never logs CLI output.
|
||||
import { execFile } from 'node:child_process';
|
||||
import { promisify } from 'node:util';
|
||||
const exec = promisify(execFile);
|
||||
let input='';for await(const chunk of process.stdin){input+=chunk;if(input.length>65536)throw new Error('measurement-input-too-large');}
|
||||
let keys;try{keys=JSON.parse(input);}catch{input='';process.stderr.write('measurement-input-invalid\n');process.exit(2);}input='';
|
||||
if(!keys.claudeOAuth||!keys.zai)throw new Error('measurement-needs-claudeOAuth-and-zai');
|
||||
const prompt='Reply with exactly I2_PROVIDER_OK and no other text.';
|
||||
const env={PATH:process.env.PATH,HOME:'/root',HTTPS_PROXY:'http://proxy:3128',HTTP_PROXY:'http://proxy:3128',NODE_USE_ENV_PROXY:'1',CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC:'1'};
|
||||
const pi=['--no-session','--no-tools','--no-approve','--no-extensions','--no-skills','--no-prompt-templates','--no-themes','--no-context-files'];
|
||||
const cases=[['claude','claude',['-p',prompt,'--tools',''],{CLAUDE_CODE_OAUTH_TOKEN:keys.claudeOAuth}],['pi-zai','pi',[...pi,'--provider','zai','--model','glm-5.3-flash','-p',prompt],{ZAI_API_KEY:keys.zai}]];
|
||||
if(keys.anthropic)cases.push(['pi-anthropic','pi',[...pi,'--provider','anthropic','--model','claude-sonnet-5-5','-p',prompt],{ANTHROPIC_API_KEY:keys.anthropic}]);
|
||||
if(keys.openai)cases.push(['pi-openai','pi',[...pi,'--provider','openai','--model','gpt-5.4','-p',prompt],{OPENAI_API_KEY:keys.openai}]);
|
||||
for(const [name,bin,args,secrets] of cases){
|
||||
let ok=false;try{const {stdout}=await exec(bin,args,{env:{...env,...secrets},timeout:120000,killSignal:'SIGKILL',maxBuffer:1024*1024});ok=stdout.includes('I2_PROVIDER_OK');}catch{}
|
||||
console.log(JSON.stringify({case:name,ok}));if(!ok)process.exitCode=1;
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
--- a/scripts/test-config.sh
|
||||
+++ b/scripts/test-config.sh
|
||||
@@ -291,5 +291,9 @@
|
||||
fi
|
||||
|
||||
echo
|
||||
+# I2: each install shape asserts its exit code and config-byte preservation.
|
||||
+node --test scripts/install-tests/config.test.mjs
|
||||
+check "install configuration cases" $?
|
||||
+
|
||||
echo "selftest: $PASS passed, $FAIL failed"
|
||||
[ "$FAIL" -eq 0 ]
|
||||
@@ -0,0 +1,74 @@
|
||||
# Row 323 round 1 verdict: goldie/row323 6a0f0f4f8 (duckworth-0300)
|
||||
|
||||
Request: sage-3865 (early look at draft 571376c30) and sage-3870 (candidate 6a0f0f4f8, base dd6cb5872,
|
||||
manifest 903ef0225f2f…cda79f). Brief: docs/plans/2026-10-23_case1-business-file-brief.md, section "Brief:
|
||||
the Case 1 factory business file".
|
||||
Method: static. Git reads of local objects only. No node, no tests, no fetch, no queue reads.
|
||||
|
||||
## Verdict: APPROVE (static), with one condition
|
||||
|
||||
- **The manifest matches.** MANIFEST.sha256 hashes to 903ef0225f2f…cda79f, and both of its blobs match at
|
||||
6a0f0f4f8: the JSON 85e6b9ac… and the test aecd1c0f…. Neither file changed between 571376c30 and
|
||||
6a0f0f4f8. The rest of that range is BUILD.md, logs, BUILD-LOG and SESSIONS. dd6cb5872 is an ancestor.
|
||||
No existing test or source file changed.
|
||||
- **Fields against the brief and the cited rulings:**
|
||||
- id `mosaic-stack` (DESIGN section 7). `vars.bus.tester` true. There is no `library` key and no
|
||||
library project.
|
||||
- human `jason`. I can't read mos-1554 myself, so this rests on Sage's relay in sage-3823. BUILD.md is
|
||||
explicit that this is the only literal value that comes from mos-1554.
|
||||
- The project root /srv/mosaic/src/mosaic-stack is under the DESIGN home /srv/mosaic, and the value
|
||||
comes from sage-3822. It passes the normalized-absolute check at business.mjs:274. A missing project
|
||||
file there only gives a warning in validate (cli.mjs:73-77), so the path doesn't need to exist at
|
||||
install.
|
||||
- Roles are chief (chief-of-staff) and assistant. The assistant is needed because checkLaunch requires
|
||||
`launch.instances` to be nonempty and to exclude the launcher (business.mjs:186-190). BUILD.md names
|
||||
that check, as the brief asks. Both arbiters are chief, under sage-3820. launch.by is chief, which
|
||||
F7 (case1-accept.sh:545-559) reads.
|
||||
- Claude only. `plugins.harness` is [claude-code] and both roles use harness claude-code. There is no
|
||||
pi.
|
||||
- No tracker, no credentials, no library, and no credential value or reference anywhere in the file.
|
||||
Q247 covers the tracker by omission.
|
||||
- **The model, provider and effort have sources.**
|
||||
- The model claude-opus-5-5 is Sage's ruling in sage-3822.
|
||||
- The provider mapping anthropic/claude-opus-5-5 matches packages/business/README.md:185.
|
||||
- `thinking: high` is one of the claude-code LEVELS (index.mjs:28) and matches the chief convention at
|
||||
session-launch.test.mjs:83.
|
||||
- With no `launch.limits`, there is no ceiling: limitHit (launch.mjs:137-155) checks only the limits
|
||||
that are defined. That is a valid choice, and N1 records it.
|
||||
- **The tests read the shipped bytes.**
|
||||
- DOCUMENT is `REPO/docs/guides/case1-business.mosaic-stack.json`. Case 1 writes those exact bytes,
|
||||
loads them through loadBusiness, checks the bytes didn't change, and then asserts every field.
|
||||
- The validate, install, boot and refusal cases copy the document and change only the project root,
|
||||
under sage-3820. Since an absent root only warns, the copy differs from the shipped file only in a
|
||||
way validate doesn't refuse.
|
||||
- The brief's three tests are all there:
|
||||
- (1) The loader case.
|
||||
- (2) The real case2-install.sh factory profile with `--business-file`, with only stack-copy stubbed.
|
||||
- (3) `bus-tester-off`, the literal library key through the final validate (sage-3820), and the
|
||||
library project through `profile-library-business`.
|
||||
- The boot case also covers the 324 boundary: bootConfig gives no trackers, credentials.status() is
|
||||
[], and the reader's credentials are [].
|
||||
- **The tester-switch mutant is a real kill target.**
|
||||
- mutant.py asserts that the needle `"bus.tester": true` occurs exactly once, flips only that value,
|
||||
and restores in a finally block with a byte-equality check.
|
||||
- The value is asserted at test :66. mutant.log shows `not ok 1`, `false !== true`, `# fail 1`, and the
|
||||
restored sha256 85e6b9ac… equals the manifest.
|
||||
- The flip would also make the install case at :95 fail, because the install refuses with
|
||||
bus-tester-off.
|
||||
- **The run, as Goldie reported it (sage-3869 CLEAR, 31-byte TMPDIRs, on 571376c30). I didn't rerun it.**
|
||||
- The new file passed 7/7, the business suite 261/261, and the restored file 7/7 again.
|
||||
- The mutant was killed.
|
||||
- The earlier revoke-window red is the tools.sock length limit (session.mjs:278, dda2d07b4). It fails
|
||||
the same way on the bare base dd6cb5872, so it isn't this row's.
|
||||
|
||||
## Condition
|
||||
|
||||
- C1. Under mos-1561, the cli suite counts only from Sage's full gate on 6a0f0f4f8. This approval
|
||||
depends on that gate passing with no new failures.
|
||||
|
||||
## Notes
|
||||
|
||||
- N1 (doc). `launch` has no `limits`, so the business has no session ceiling. If Mos wants a cap on .53,
|
||||
it's a later launch-settings edit and doesn't need a new file.
|
||||
- N2 (doc). An install on .53 with no project file at /srv/mosaic/src/mosaic-stack still passes. It
|
||||
warns "no project variables" (cli.mjs:76), which the run sheet could mention.
|
||||
@@ -0,0 +1,48 @@
|
||||
# Row 325 / #1821 — Case 1 project key
|
||||
|
||||
Goldie, 2026-10-24. Branch goldie/row325, base99930362e4b8a64d5425b10ca087b2d1f93ae538. Separate worktree /home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row325. Brief pinned at60d78e44e:docs/plans/2026-10-24_case1-project-key-brief.md, section Brief: re-key the Case1 project to stack. Mos-1563 fixes the business document and preserves project id. Reviewer Gladstone; Sage owns review posting/landing. No queue writes authorized or performed.
|
||||
|
||||
## Defect and change
|
||||
|
||||
The real .mosaic/project.json declares id stack; the Case1 business document incorrectly keyed its project mosaic-stack. Real business validation checks equality at packages/business/src/cli.mjs:80, so the install refused once the host held the project file. Row323's scratch execution root omitted that file and exercised only the warning/absent branch, missing the deployment mismatch.
|
||||
|
||||
Only the projects object's key changes to stack in docs/guides/case1-business.mosaic-stack.json. Business id stays mosaic-stack, root stays /srv/mosaic/src/mosaic-stack, and every other field is unchanged. No .mosaic/project.json edit or product/schema/role change.
|
||||
|
||||
The new packages/business/tests/case1-project-key.test.mjs reads the repo's real .mosaic/project.json bytes, asserts exactly one matching Case1 key, copies those bytes unchanged into a scratch project root, and calls the actual business CLI validate path with a temporary system/business config. Positive output must report the project valid, not absent. Negative coverage re-keys the same fixture to mosaic-stack and requires exit2 plus the real project-id mismatch message; missing-file warnings cannot stand in for validation.
|
||||
|
||||
Mos-1564 (mos-1272) authorizes exactly one pre-existing test line: case1-business-file.test.mjs:70's expected key changes to stack; all other bytes/assertions remain unchanged. No other old test is touched. This is explicitly the obsolete expectation caused by the product correction, not a relaxed assertion.
|
||||
|
||||
## Verification plan / state
|
||||
|
||||
No tests run before Sage CLEAR. Static node --check, JSON parse and diff checks only. On CLEAR, one run at a time: new test file whole, business suite whole (includes the one updated old test), one old-key mutant, restored whole new file to verify restoration. Every run gets fresh `export TMPDIR=$(mktemp -d /mnt/storage/scratch/tmp/XXXXXX)`, timeout-k10, and the established UTC/load/busy-helper log header. Stop/report on any other old-test red; no further old-test edits. First unexpected new-test red is recorded/reported under the continuing mos-1272 rule.
|
||||
|
||||
mutant.py changes only the projects key back to mosaic-stack, runs the new exact-key assertion, requires expected exit1/fail1, then restores/checks bytes in finally. Prepared, not run. Sage's landing gate supplies CLI suite counts.
|
||||
|
||||
The dependency setup is an ignored root node_modules symlink to /mnt/storage/src/mosaic-stack/node_modules; no dependency installation/pin change. MANIFEST.sha256 covers business JSON, the one old test, new test, and real project document as an unchanged reference. Final review report goes to both Sage and Gladstone with tip/base/manifest. No host contact, credential access, queue write or refactor push.
|
||||
|
||||
## Verification — sage-3882 CLEAR
|
||||
|
||||
All four runs were serial on the same final source bytes, using timeout-k10, fresh31-byte TMPDIRs from /mnt/storage/scratch/tmp/XXXXXX, and date-u/uptime/busy-helper headers. Node v26.8.1. No first-run new-test red and no old-test red. No CLI suite run; Sage's landing gate supplies that scope.
|
||||
|
||||
| Run / log | UTC start | Load averages | Busy | Result / TMPDIR |
|
||||
| --- | --- | --- | --- | --- |
|
||||
| test-new.log | 2026-10-24 01:13:40 | 1.34,1.60,2.50 | CLEAR | Whole new file3/3 pass, 172.161691ms; hchZu2 |
|
||||
| test-business.log | 2026-10-24 01:13:50 | 1.36,1.59,2.49 | CLEAR | Whole business suite264/264 pass, 6478.924028ms; yWVbwY |
|
||||
| mutant-driver.log + mutant.log | 2026-10-24 01:14:04 | 1.36,1.58,2.47 | CLEAR | Old-key mutant killed, targeted new test0/1, expected exit1; driver exit0; YsOE6N |
|
||||
| test-restored.log | 2026-10-24 01:14:14 | 1.15,1.53,2.44 | CLEAR | Restored new file3/3 pass, 169.948442ms; Y43Jju |
|
||||
|
||||
All TMPDIRs are under /mnt/storage/scratch/tmp/. New/restored/mutant driver limits180s, business600s, each timeout-k10. Positive runs have0 failures/skips/cancellations. The business suite includes the entire old case1-business-file.test.mjs, with only the mos-1564-approved key assertion changed.
|
||||
|
||||
Mutant diff in business JSON:
|
||||
|
||||
```diff
|
||||
"projects": {
|
||||
- "stack": {
|
||||
+ "mosaic-stack": {
|
||||
```
|
||||
|
||||
The exact-key new test fails deepStrictEqual (actual mosaic-stack, expected stack); raw evidence is mutant.log. mutant.py restores in finally and checks exact bytes. Restored JSON SHA256 `9e43bfef326e568488aaa4f0d8882e9b932750aeb39f587a113a226bfb30cf0f`; all4 MANIFEST.sha256 entries verify, including unchanged .mosaic/project.json. Whole new file passed again after restoration. git diff --check passed.
|
||||
|
||||
Final source accounting: business JSON1+/1-, only projects key; old test1+/1-, only expected key at70; new test file with3 cases; no other old-test line touched. Business id/root remain unchanged. Root-cause gap is now covered by byte-identical real project fixture plus required valid result and old-key refusal. No host action, credentials, queue writes, refactor push or unapproved scope change.
|
||||
|
||||
Logs have trailing whitespace normalized for git diff --check, with substantive output/results unchanged. The slot is returned in the report accompanying the pushed candidate; Gladstone first review and Sage landing gate remain pending.
|
||||
@@ -0,0 +1,4 @@
|
||||
9e43bfef326e568488aaa4f0d8882e9b932750aeb39f587a113a226bfb30cf0f docs/guides/case1-business.mosaic-stack.json
|
||||
9c3bf7f349d0ee8626743e1388b9a596c7f3c274beaedad5106db5f2ff8f65e7 packages/business/tests/case1-business-file.test.mjs
|
||||
b543ac6afbfb336c5566f359ff357904b36f413d5f75fcd54a5a8b72aef1dbd6 packages/business/tests/case1-project-key.test.mjs
|
||||
044714a37c267b70279977ea8ca429df7a28463271b884afd79a01bc27afa259 .mosaic/project.json
|
||||
@@ -0,0 +1,6 @@
|
||||
Sat Oct 24 01:14:04 UTC 2026
|
||||
20:14:04 up 10 days, 11:33, 2 users, load average: 1.36, 1.58, 2.47
|
||||
CLEAR
|
||||
TMPDIR=/mnt/storage/scratch/tmp/YsOE6N bytes=31
|
||||
old-key mutant: exit 1
|
||||
restored sha256 9e43bfef326e568488aaa4f0d8882e9b932750aeb39f587a113a226bfb30cf0f
|
||||
@@ -0,0 +1,40 @@
|
||||
TAP version 13
|
||||
# Subtest: row 325: the Case 1 project's sole key matches the repository project id
|
||||
not ok 1 - row 325: the Case 1 project's sole key matches the repository project id
|
||||
---
|
||||
duration_ms: 2.058961
|
||||
type: 'test'
|
||||
location: '/home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row325/packages/business/tests/case1-project-key.test.mjs:38:1'
|
||||
failureType: 'testCodeFailure'
|
||||
error: |-
|
||||
Expected values to be strictly deep-equal:
|
||||
+ actual - expected
|
||||
|
||||
[
|
||||
+ 'mosaic-stack'
|
||||
- 'stack'
|
||||
]
|
||||
|
||||
code: 'ERR_ASSERTION'
|
||||
name: 'AssertionError'
|
||||
expected:
|
||||
0: 'stack'
|
||||
actual:
|
||||
0: 'mosaic-stack'
|
||||
operator: 'deepStrictEqual'
|
||||
stack: |-
|
||||
TestContext.<anonymous> (file:///home/jwoltje/.t3/worktrees/mosaic-stack/goldie-row325/packages/business/tests/case1-project-key.test.mjs:42:10)
|
||||
Test.runInAsyncScope (node:async_hooks:226:14)
|
||||
Test.run (node:internal/test_runner/test:1402:25)
|
||||
Test.start (node:internal/test_runner/test:1262:17)
|
||||
startSubtestAfterBootstrap (node:internal/test_runner/harness:387:17)
|
||||
...
|
||||
1..1
|
||||
# tests 1
|
||||
# suites 0
|
||||
# pass 0
|
||||
# fail 1
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 44.416722
|
||||
@@ -0,0 +1,26 @@
|
||||
"""Row 325 old-project-key mutant; run only in Sage's test slot."""
|
||||
import hashlib
|
||||
import os
|
||||
from pathlib import Path
|
||||
import subprocess
|
||||
|
||||
root = Path(__file__).resolve().parents[4]
|
||||
source = root / 'docs/guides/case1-business.mosaic-stack.json'
|
||||
fixed = source.read_bytes()
|
||||
needle = b'"projects": {\n "stack": {'
|
||||
assert fixed.count(needle) == 1
|
||||
try:
|
||||
source.write_bytes(fixed.replace(needle, b'"projects": {\n "mosaic-stack": {'))
|
||||
result = subprocess.run([
|
||||
'timeout', '-k', '10', '90', 'node', '--test', '--test-reporter=tap',
|
||||
'--test-name-pattern=row 325: the Case 1 project',
|
||||
'packages/business/tests/case1-project-key.test.mjs',
|
||||
], cwd=root, env=os.environ, text=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT)
|
||||
Path(__file__).with_suffix('.log').write_text(result.stdout)
|
||||
print(f'old-key mutant: exit {result.returncode}', flush=True)
|
||||
assert result.returncode == 1, result.stdout
|
||||
assert '# fail 1\n' in result.stdout and 'mosaic-stack' in result.stdout, result.stdout
|
||||
finally:
|
||||
source.write_bytes(fixed)
|
||||
assert source.read_bytes() == fixed
|
||||
print('restored sha256 ' + hashlib.sha256(fixed).hexdigest(), flush=True)
|
||||
File diff suppressed because it is too large.
Load diff
@@ -0,0 +1,32 @@
|
||||
Sat Oct 24 01:13:40 UTC 2026
|
||||
20:13:40 up 10 days, 11:33, 2 users, load average: 1.34, 1.60, 2.50
|
||||
CLEAR
|
||||
TMPDIR=/mnt/storage/scratch/tmp/hchZu2 bytes=31
|
||||
TAP version 13
|
||||
# Subtest: row 325: the Case 1 project's sole key matches the repository project id
|
||||
ok 1 - row 325: the Case 1 project's sole key matches the repository project id
|
||||
---
|
||||
duration_ms: 4.262251
|
||||
type: 'test'
|
||||
...
|
||||
# Subtest: row 325: real business validate reports the copied repository project valid
|
||||
ok 2 - row 325: real business validate reports the copied repository project valid
|
||||
---
|
||||
duration_ms: 67.962164
|
||||
type: 'test'
|
||||
...
|
||||
# Subtest: row 325: the old mosaic-stack project key refuses against those same project bytes
|
||||
ok 3 - row 325: the old mosaic-stack project key refuses against those same project bytes
|
||||
---
|
||||
duration_ms: 57.926665
|
||||
type: 'test'
|
||||
...
|
||||
1..3
|
||||
# tests 3
|
||||
# suites 0
|
||||
# pass 3
|
||||
# fail 0
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 172.161691
|
||||
@@ -0,0 +1,32 @@
|
||||
Sat Oct 24 01:14:14 UTC 2026
|
||||
20:14:14 up 10 days, 11:34, 2 users, load average: 1.15, 1.53, 2.44
|
||||
CLEAR
|
||||
TMPDIR=/mnt/storage/scratch/tmp/Y43Jju bytes=31
|
||||
TAP version 13
|
||||
# Subtest: row 325: the Case 1 project's sole key matches the repository project id
|
||||
ok 1 - row 325: the Case 1 project's sole key matches the repository project id
|
||||
---
|
||||
duration_ms: 4.401838
|
||||
type: 'test'
|
||||
...
|
||||
# Subtest: row 325: real business validate reports the copied repository project valid
|
||||
ok 2 - row 325: real business validate reports the copied repository project valid
|
||||
---
|
||||
duration_ms: 60.555647
|
||||
type: 'test'
|
||||
...
|
||||
# Subtest: row 325: the old mosaic-stack project key refuses against those same project bytes
|
||||
ok 3 - row 325: the old mosaic-stack project key refuses against those same project bytes
|
||||
---
|
||||
duration_ms: 63.16989
|
||||
type: 'test'
|
||||
...
|
||||
1..3
|
||||
# tests 3
|
||||
# suites 0
|
||||
# pass 3
|
||||
# fail 0
|
||||
# cancelled 0
|
||||
# skipped 0
|
||||
# todo 0
|
||||
# duration_ms 169.948442
|
||||
@@ -0,0 +1,10 @@
|
||||
# Poe
|
||||
|
||||
Builder seat, Codex gpt-6.1-sol, started by Sage under Q246 and mos-1566. Owns row 208, 193d: the Console's shared
|
||||
list, share controls and viewer read-only (docs/plans/2026-10-20_human-users-build.md), reassigned from Morgana so the
|
||||
coding runs on Codex.
|
||||
|
||||
No launcher in this directory. The seat runs as a T3 thread and works in its own worktree. It has no credentials of its
|
||||
own; it pushes only its own `poe/*` branches through the repository credential helper (mos-0981), never `refactor`,
|
||||
`main` or another seat's branch. Claude seats orchestrate and review guards (mos-1542 rule 4). Registered by Sage on
|
||||
2026-10-24 so the queue can name the seat as row owner (store.mjs isSeatDir).
|
||||
@@ -0,0 +1,55 @@
|
||||
{
|
||||
"version": 1,
|
||||
"agents": [
|
||||
{
|
||||
"host": "api.anthropic.com",
|
||||
"port": 443,
|
||||
"path": "adapters/claude-code; adapters/pi (anthropic)",
|
||||
"note": "Candidate from S6; real-token provider-host measurement by Mos pending. Claude nonessential traffic disabled."
|
||||
},
|
||||
{
|
||||
"host": "api.z.ai",
|
||||
"port": 443,
|
||||
"path": "adapters/pi (zai)",
|
||||
"note": "Candidate for the configured zai provider; unmeasured until Mos runs provider-hosts.sh. No authority to widen on failure."
|
||||
},
|
||||
{
|
||||
"host": "api.openai.com",
|
||||
"port": 443,
|
||||
"path": "adapters/pi (openai)",
|
||||
"note": "S6 dummy-key CONNECT measured; real-token provider-host measurement by Mos pending."
|
||||
}
|
||||
],
|
||||
"services": [
|
||||
{
|
||||
"host": "tasks.woltje.com",
|
||||
"port": 443,
|
||||
"path": "packages/tasks/src/vikunja.mjs:41",
|
||||
"note": "fetch; requires NODE_USE_ENV_PROXY=1 and HTTPS_PROXY in container startup (I6)."
|
||||
},
|
||||
{
|
||||
"host": "git.mosaicstack.dev",
|
||||
"port": 443,
|
||||
"path": "packages/bus/src/gitea-grant.mjs:115",
|
||||
"note": "NOT PROXIED today: https.request agent:false with custom CA; transport ruling owned by Sage before broker container rollout."
|
||||
},
|
||||
{
|
||||
"host": "git.mosaicstack.dev",
|
||||
"port": 443,
|
||||
"path": "packages/seat/src/working.mjs:471",
|
||||
"note": "NOT VERIFIED through proxy: broker publishRemote spawns git with a scrubbed environment and credential on fd 3; core design excludes git. Sage transport ruling required."
|
||||
},
|
||||
{
|
||||
"host": "discord.com",
|
||||
"port": 443,
|
||||
"path": "packages/cli/src/notifier-process.mjs:45 -> packages/discord/src/rest.mjs:45",
|
||||
"note": "Notifier REST fetch only; requires NODE_USE_ENV_PROXY=1 and HTTPS_PROXY (I6)."
|
||||
},
|
||||
{
|
||||
"host": "gateway.discord.gg",
|
||||
"port": 443,
|
||||
"path": "packages/discord/src/gateway.mjs:193",
|
||||
"note": "NOT PROXIED today: native WebSocket in separate Discord connector, not notifier. Design names gateway; retained pending Sage transport ruling."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -1617,3 +1617,14 @@ are never rewritten or removed; corrections are new entries.
|
||||
2026-10-24T00:22Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row 323 / #1819 first slot stop | Commit571376c30 new7/7/business261/261; CLI existing row306 revoke-window red no RESULT, suite stopped (partial631 pass/1 fail). No old-test changes/reruns/mutant; manifest unchanged. Sage goldie-0016 has failure evidence and slot back; awaits ruling.
|
||||
2026-10-24T00:25Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row 323 / #1819 mos-1560 stop | First isolated revoke-window whole-file rerun failed0/1 at20.126s under load1.46/1.68/2.70/helperCLEAR. Second-red rule: stopped with no further tests/edits, manifest unchanged, remote571 unverified. Log and load lines in BUILD.md; Sage notified goldie-0018, slot returned.
|
||||
2026-10-24T00:38Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row 323 / #1819 accepted verification | mos-1561/sage-3869: short31-byte TMPDIR fixes socket-length refusal; revoke-window1/1, business261/261, new7/7, mutant killed/bytes restored, new7/7 again. Every run logs time/load/helper; no local CLI rerun, Sage gate supplies it. Notes/logs evidence only; Duckworth review/in-review move next.
|
||||
2026-10-24T00:41Z | Duckworth (Opus 5.5) | row 323 round 1 static review on 6a0f0f4f8 (sage-3865, sage-3870) | APPROVE (static) C1 Sage full gate cli, manifest 903ef022 matches, fields sourced, tests read shipped bytes, tester mutant real, N1-N2 doc (duckworth-0300) |
|
||||
2026-10-24T01:13Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row325 / #1821 Case1 project key | New goldie/row325 worktree at99930362e; pinned brief60d78e44e. Draft JSON key correction, new real-project-byte tests, sole old line70 key edit under mos-1564. Static checks only; requesting Sage CLEAR, no queue writes.
|
||||
2026-10-24T01:14Z | Goldie (a13dd6d5-5e73-4b7d-8318-02abeff24940) | Row325 / #1821 verified | sage-3882: new3/3, business264/264, one key mutant killed/restored, new3/3 again, per-run fresh shortTMP/load/helperCLEAR. Four manifest entries verify; sole old assertion key change, no queue writes. Candidate push/report to Sage and Gladstone next.
|
||||
|
||||
2026-10-24 | Bentina (d1ae468e-51ba-40c4-9513-b39ebbb40450) | Row157 I2 / #1652 pre-build recovery at ec86f2c84 | Blocked: canonical queue stale brief pin plus non-fetch broker outbound stop condition; sent Sage bentina-0001, recorded source drift in agents/bentina/work/queue-157/BUILD.md. No tests, Docker, credential reads, queue writes, commit or push.
|
||||
|
||||
2026-10-24 | Bentina (d1ae468e-51ba-40c4-9513-b39ebbb40450) | Row157 / #1652 first CLEAR stop | sage-3910 tests31/31 helper/config,11/11 proxy. Own output regression stops old config run3 (55 pass/1 fail); run4 not run, no Docker. Slot returned Sage bentina-0004; source-only correction applied under sage-3916, no rerun until next CLEAR. Logs in agents/bentina/work/queue-157.
|
||||
|
||||
2026-10-24 | Bentina (d1ae468e-51ba-40c4-9513-b39ebbb40450) | Row157 / #1652 candidate verified | sage-3919 config56/56/helper31/31; sage-3923 final proxy12/12 and install exit0 (rootless builds/pins, internal network, services fetch). Source-only transport gaps recorded under sage-3907. Slot returned bentina-0007; preparing own-branch candidate for Boyd then Darkwing, with Mos measurement/Sage full gate still pending. No protected branch/live-state/real-token action.
|
||||
|
||||
2026-10-24 | Bentina (d1ae468e-51ba-40c4-9513-b39ebbb40450) | Row157 / #1652 round2 B1/B2 | sage-3933 scoped corrections; sage-3936 archived red proves7defect assertions/5controls, fixed12/12, helper/config33/33, egress22/22, rootless install exit0. No old assertion changes. Slot returned bentina-0014; preparing followup commit/push for Boyd then Darkwing, provider measurement/Sage gate still pending.
|
||||
@@ -1365,3 +1365,42 @@ public sign-in smoke and a forged-loopback 401 check; rollback drops flags
|
||||
before middleware. Same-uid callers can still read the file; separate service
|
||||
uid is Jason's question on #385. The Authentik allowed-uid file remains an
|
||||
identity input and is never reused for proof.
|
||||
|
||||
## Containerized image preparation (I2)
|
||||
|
||||
`scripts/images.sh build|list` uses RELEASE tags mosaic-core, mosaic-session
|
||||
and mosaic-egress. It requires install.mode containerized and an explicit
|
||||
Unix endpoint in the selected config. Build prepares an isolated runtime
|
||||
source context and uses only the configured rootless daemon; list inspects
|
||||
the three exact tags. It does not start the stack. See images/README.md.
|
||||
|
||||
`scripts/lib/docker.sh` is a sourceable Bash helper exposing
|
||||
`mosaic_docker <arguments...>`. It uses common.sh's load_config (which reads
|
||||
mosaic-config.mjs env), caches one successful rootless check in that Bash
|
||||
process, and adds --host to every Docker call. The first call checks the
|
||||
exit status of info before parsing .SecurityOptions as a JSON array for
|
||||
exactly name=rootless. Failure prints endpoint-not-rootless, exits the
|
||||
operation with status 2, and never forwards the requested command. Local
|
||||
mode prints containerized-install-required. No ambient context, DOCKER_HOST
|
||||
or caller-supplied mode/endpoint variable selects the target.
|
||||
|
||||
The config loader accepts optional install with mode local/containerized.
|
||||
Absent defaults to local without adding a validation-output key; local forbids
|
||||
endpoint, containerized requires
|
||||
`^unix:///[^\s]+\.sock$`. Unknown keys, mode or bad type exit 2. validate
|
||||
checks shape only and never changes config. env adds MOSAIC_INSTALL_MODE
|
||||
and MOSAIC_DOCKER_ENDPOINT for the helper; no configVersion bump.
|
||||
|
||||
`scripts/test-install.sh` runs the I2 config/helper/proxy cases, then builds
|
||||
and probes the images on MOSAIC_TEST_DOCKER_ENDPOINT (default explicitly
|
||||
unix:///run/user/1000/docker.sock). It creates only i2-prefixed containers
|
||||
and networks, cleans only those names, and refuses an unavailable or
|
||||
non-rootless endpoint. It neither installs nor starts a daemon. It uses
|
||||
scratch MOSAIC_CONFIG and dummy/no tokens. All test execution requires the
|
||||
assigned test slot. Run with fresh short scratch TMPDIR and timeout -k 10.
|
||||
|
||||
Mos's real-provider measurement script and expected output are in
|
||||
agents/bentina/work/queue-157/provider-hosts.sh and PROVIDER-EXPECTED.md.
|
||||
The baked agents list is provisional until matching evidence appears on
|
||||
#1652. The services contract notes transport gaps; I2 starts no broker behind
|
||||
the proxy. Sage owns the ruling on those gaps before later install rows.
|
||||
@@ -7,7 +7,7 @@
|
||||
"technical": "chief"
|
||||
},
|
||||
"projects": {
|
||||
"mosaic-stack": {
|
||||
"stack": {
|
||||
"root": "/srv/mosaic/src/mosaic-stack"
|
||||
}
|
||||
},
|
||||
|
||||
@@ -0,0 +1,47 @@
|
||||
# Case 1 business file: project key must match the project file (#414 stage 2)
|
||||
|
||||
Written 2026-10-24 by Sage for ops-0288 and Mos's ruling mos-1563.
|
||||
|
||||
## Brief: re-key the Case 1 project to stack
|
||||
|
||||
**Why.** The .53 stage 2 run on 99930362e stopped at step 8 (ops-0288). `case2-install.sh --profile factory`
|
||||
refused with:
|
||||
`validate: mosaic business: project file /srv/mosaic/src/mosaic-stack/.mosaic/project.json has id stack, but
|
||||
business mosaic-stack declares it as mosaic-stack`. Row 323's `docs/guides/case1-business.mosaic-stack.json`
|
||||
keys its one project `mosaic-stack`. The repo's `.mosaic/project.json` has id `stack`, and the loader requires
|
||||
the key to equal that id (packages/business/src/cli.mjs:80, packages/cli/src/config.mjs:67). Row 323's test
|
||||
missed it because it validates against a scratch root with no project file, which only warns (cli.mjs:74-78).
|
||||
|
||||
**Ruling (mos-1563).** Fix the business file. Don't touch the project id: `stack` is read elsewhere.
|
||||
|
||||
**What to change.**
|
||||
1. In `docs/guides/case1-business.mosaic-stack.json`, rename the `projects` key from `mosaic-stack` to
|
||||
`stack`. Keep its `root` (`/srv/mosaic/src/mosaic-stack`). Business id stays `mosaic-stack`. Change nothing
|
||||
else in the file.
|
||||
2. A new test file, `packages/business/tests/case1-project-key.test.mjs`, that validates the committed case1
|
||||
file against the repo's own `.mosaic/project.json`, so a mismatch fails in CI instead of on a host:
|
||||
- read the repo's `.mosaic/project.json` and assert the case1 file has exactly one project whose key equals
|
||||
its `id`;
|
||||
- run the business `validate` path (the same one cli.mjs:80 runs) with that project's root pointed at a
|
||||
scratch copy holding the repo's real `.mosaic/project.json` bytes, and assert it passes with the project
|
||||
reported `valid`, not `absent`;
|
||||
- a negative case: the same run with the key set back to `mosaic-stack` refuses with the cli.mjs:80
|
||||
message.
|
||||
3. The one old-test line this change breaks, `packages/business/tests/case1-business-file.test.mjs:70`
|
||||
(`assert.deepEqual(b.projects, { "mosaic-stack": ... })`), changes its key to `stack` and nothing else. This
|
||||
edit has Mos's go (mos-1564, under mos-1272). Don't change any other line of any old test. If another old
|
||||
test reds, stop and report it.
|
||||
|
||||
**Gate.** Use `export TMPDIR=$(mktemp -d /mnt/storage/scratch/tmp/XXXXXX)` (BRIEF-TEMPLATE Gate). On my CLEAR
|
||||
only: the new test file whole, then the packages/business suite whole. One mutant: set the case1 key back to
|
||||
`mosaic-stack`, show the new test red, restore and check the bytes. The cli suite counts from my landing gate.
|
||||
|
||||
**Boundaries.** No host action, no .53 contact, no credential reads, no queue writes, no push to refactor.
|
||||
Work in a new worktree off refactor at 99930362e or later, on branch `goldie/row325`. Push the branch and
|
||||
report the tip, base and manifest to Sage and to the reviewer. Record design choices in
|
||||
agents/goldie/work/queue-325/BUILD.md.
|
||||
|
||||
**Acceptance.** At a landed commit, `case2-install.sh --business mosaic-stack --business-file
|
||||
docs/guides/case1-business.mosaic-stack.json --profile factory` passes step 8's validate against
|
||||
`/srv/mosaic/src/mosaic-stack/.mosaic/project.json`, and the new test fails if the key and the project id
|
||||
drift apart again.
|
||||
@@ -0,0 +1,78 @@
|
||||
# Host run sheet: five operator gaps from the .53 and Run B installs (#414 stage 2)
|
||||
|
||||
Written 2026-10-24 by Sage for Mos's mos-1566 item 1, from ops-0282, ops-0288, ops-0291, ops-0292,
|
||||
Duckworth's 323 N2 and Mos's rulings mos-1563 and mos-1565.
|
||||
|
||||
## Brief: five run-sheet gaps
|
||||
|
||||
**Why.** Five things stopped or slowed operators on the .53 stage 2 install and 433 Run B. Each is a gap
|
||||
in `docs/guides/host-run-sheet.md` (checked at 3d2569ce2), not a product bug. The code they describe has
|
||||
landed. This row writes them down.
|
||||
|
||||
**What to write.** Edit only `docs/guides/host-run-sheet.md`. Source every command, path and message from
|
||||
the code or the receipts named here, and cite the line you read, the way the sheet already does
|
||||
(`scripts/stack-release.sh:14`). Don't invent a flag, path or exit code.
|
||||
|
||||
1. **B2-2, the Console proof file format** (DEFERRED.md "ops-0282 B2-2", ops-0282). In section 9's .54
|
||||
library table (:321), the proof file row's File cell gives mode and owner but no format. Add it: one
|
||||
base64url line of at least 32 characters, `[A-Za-z0-9_-]` only (packages/webui/src/serve.mjs:264). The
|
||||
Console exits 2 at start otherwise (:267). `case2-unit.sh` doesn't open the file, so a bad proof fails
|
||||
only at Console start. Read serve.mjs:255-270 yourself and match what it checks, including whether a
|
||||
trailing newline is accepted.
|
||||
2. **N2, a missing project file only warns** (Duckworth 323 round 1 N2, ROUND1-6a0f0f4f8.md). When the
|
||||
project root a business file names has no `.mosaic/project.json`, `mosaic business validate` passes
|
||||
with the warning "no project file at ... it has no project variables" (packages/business/src/cli.mjs:74-78).
|
||||
An install on such a host passes step 7 with the project's variables missing. Say so where the sheet
|
||||
describes `case2-install.sh` (near :278), and tell the operator to treat that warning as a stop.
|
||||
3. **The pre-install validate step** (mos-1563, ops-0288). Before `case2-install.sh`, check by hand that
|
||||
each `projects` key in the business file equals the `id` in `<that project's root>/.mosaic/project.json`
|
||||
in the stack's user's clone, and that the file exists. The loader refuses a mismatch
|
||||
(packages/business/src/cli.mjs:80) at step 7, after the copy is made. The check must use only what the
|
||||
host has at that point: coreutils, grep, or the pinned node under `/opt/mosaic-stack/node-<v>/bin`
|
||||
(section 2), run as the stack's user. Give the expected output for a match and say a mismatch is a
|
||||
stop: fix the business file, not the project id (mos-1563).
|
||||
4. **The retry path after a failed first install** (mos-1565, ops-0291). Section 6 has no retry form.
|
||||
What happened on .53: a refused `case2-install.sh` left `<home>/.config/mosaic-dev/config.json` behind
|
||||
(bootstrap ran at step 1), and the first refusal also left an empty `businesses/` dir in that config
|
||||
dir. The operator then used the upgrade form's fetch, which writes the bundle to
|
||||
`refs/remotes/origin/refactor` and doesn't move `refs/heads/refactor`, so `stack-copy.sh copy` refused
|
||||
with "not reachable from refs/heads/refactor". Mos's ruling: move the clones aside and rerun section
|
||||
6's first-install form. Write that as a short "If the first install refuses" block in section 6:
|
||||
- which paths move aside: root's checkout `/root/mosaic-stack`, the stack's user's clone
|
||||
`/srv/mosaic/src/mosaic-stack`, and the stack's user's config dir. Read `scripts/case2-install.sh`
|
||||
(bootstrap at step 1, :172, :200, :210-211) and `scripts/bootstrap.sh` to list what an install
|
||||
writes before it can refuse, and name only those paths;
|
||||
- move, never delete: `mv` to a name with a UTC timestamp suffix, as root, keeping owner and mode;
|
||||
- what the operator must re-place afterwards if a credential slot under that config dir was already
|
||||
filled (section 9). If nothing in section 9 can be filled before the first install, say that;
|
||||
- then rerun the first-install block from its first line. Never use the upgrade form's fetch on a
|
||||
clone that a first install hasn't finished with.
|
||||
5. **Checking a backup's SHA256SUMS by hand** (ops-0292). The sheet has no backup check today. A backup set
|
||||
under `<dataRoot>/backups/stack/<activation>/` holds `SHA256SUMS` with paths relative to `bus/`
|
||||
(scripts/stack-release.sh:482 writes `(cd "$o/bus" && sha256sum bus.sqlite) > "$o/SHA256SUMS"`), and
|
||||
the tool checks it with `(cd "$o/bus" && sha256sum -c --quiet ../SHA256SUMS)` (:502, :2244). Running
|
||||
`sha256sum -c SHA256SUMS` from the backup dir itself fails (rc 1, "FAILED"), because `bus.sqlite` is
|
||||
in `bus/`. Add a short paragraph after the upgrade exits in section 6 that gives the `bus/` form for an
|
||||
operator who wants to check a backup before a rollback or restore. Read :475-510 to confirm the
|
||||
layout first.
|
||||
|
||||
Keep the sheet's voice: short sentences, the path and line for each claim, no em dashes. Don't renumber
|
||||
sections. Don't touch any other file except your BUILD.md.
|
||||
|
||||
**Test.** None ships. Before the review request, show that no test or script reads the sheet:
|
||||
`git grep -n host-run-sheet -- 'packages/*/tests/*' 'scripts/*'` prints nothing, with a positive control
|
||||
that the same command form finds a path you know a test reads. Also grep the tests for a 64-hex pin next
|
||||
to the sheet's name. If any test reads the sheet, stop and tell Sage.
|
||||
|
||||
**Gate.** Doc only (decision 137). No suite run is needed from you, and no CLEAR. Sage's landing gate
|
||||
runs every suite (mos-1455). The reviewer checks every new claim against the cited line.
|
||||
|
||||
**Boundaries.** No host action, no .53, .54 or 1013 contact, no credential reads, no push to refactor.
|
||||
Claimant queue moves are yours (start, in-review). Work in a new worktree off refactor at 3d2569ce2 or
|
||||
later, on branch `goldie/row-run-sheet`. Push the branch and report the tip, base and manifest to Sage
|
||||
and to the reviewer. Record choices in `agents/goldie/work/queue-<row>/BUILD.md`.
|
||||
|
||||
**Acceptance.** An operator following the sheet can see the proof file format, treats the missing
|
||||
project file warning as a stop, checks the project key before install, has a written retry path after a
|
||||
refused first install, and can check a backup by hand with the form that works. Every new claim cites a
|
||||
line that says it.
|
||||
@@ -48,6 +48,17 @@ What this piece deliberately leaves alone, and where that work goes.
|
||||
Who says yes, and on what evidence: the command to run, the output to
|
||||
expect, or the comment to read.
|
||||
|
||||
Every seat, Claude or Codex, runs the suites with a short TMPDIR:
|
||||
`export TMPDIR=$(mktemp -d /mnt/storage/scratch/tmp/XXXXXX)`, 31 bytes. A
|
||||
launch refuses when its run's `tools.sock` path passes 107 bytes
|
||||
(`packages/seat/src/session.mjs:278`). Tests build that path under TMPDIR
|
||||
and drop the launcher log, so the refusal shows only as a 20-second "no
|
||||
RESULT". `packages/cli/tests/revoke-window.test.mjs` needs TMPDIR at 35
|
||||
bytes or less. A seat-named TMPDIR such as `.../tmp/goldieXXXXXX` (37)
|
||||
fails it on any commit (row 323, mos-1561). A builder whose cli suite
|
||||
can't run says so in the row, and the cli suite counts from the lead's
|
||||
landing gate.
|
||||
|
||||
### Handoffs
|
||||
|
||||
Every seat hands off directly (DECISIONS Q210, mos-1221). When the owner
|
||||
|
||||
@@ -742,3 +742,17 @@ one base64url line of at least 32 characters, `[A-Za-z0-9_-]` only (packages/web
|
||||
exits 2 at start otherwise (:267). case2-unit.sh doesn't open the file, so a short or malformed proof fails
|
||||
only at Console start. That's what stopped Run B P1 (B2-1, ops-01's 24-byte fixture). Fix: add the format to
|
||||
the File cell of that row. Doc only, no test reads it; bound to the next doc row that edits the run sheet.
|
||||
|
||||
## Row 323: revoke-window hides a socket-path refusal as a 20-second "no RESULT"
|
||||
|
||||
Sage, for Mos's mos-1560/mos-1561. packages/cli/tests/revoke-window.test.mjs failed on every commit in
|
||||
Goldie's environment (base dd6cb5872 included) and passed in Sage's. The cause is TMPDIR length, not Codex.
|
||||
The test's root is `$TMPDIR/mosaic-revoke-window-XXXXXX`, and a run's `tools.sock` sits at
|
||||
`<root>/data/launches/acme/r<12 hex>/tools.sock`. newRun refuses past 107 bytes
|
||||
(packages/seat/src/session.mjs:278). Measured lengths: `.../tmp/sageXXXXXX` (35 bytes) gives 107 and passes,
|
||||
`.../tmp/goldieXXXXXX` (37 bytes) gives 109 and fails. Sage reproduced it with a 37-byte TMPDIR: 0/1 after
|
||||
20235 ms, the same "no RESULT". The test passes `log: () => {}` to the launcher, so the refusal never shows.
|
||||
The guard is right. The test is the weak part: its prefix leaves no margin, and a launch refusal reads as a
|
||||
timeout. Fix: shorten the prefix and fail on a launch-failed log line instead of waiting 20 s. That's an
|
||||
old-test change, so it needs Mos's go (mos-1272). Bound to the next row that edits revoke-window.test.mjs.
|
||||
Until then, docs/plans/BRIEF-TEMPLATE.md's Gate section sets TMPDIR to `mktemp -d /mnt/storage/scratch/tmp/XXXXXX`.
|
||||
+5
-4
@@ -33,7 +33,7 @@ Gaps found while working go to `docs/plans/DEFERRED.md`, not here.
|
||||
|
||||
<!-- mosaic-queue:begin -->
|
||||
|
||||
Generated from `docs/plans/queue.json` revision 3765 by `queue render`. Do not edit between the markers; change the queue with `scripts/mosaic queue`.
|
||||
Generated from `docs/plans/queue.json` revision 3788 by `queue render`. Do not edit between the markers; change the queue with `scripts/mosaic queue`.
|
||||
|
||||
| # | Piece | Owner | Issues | State | After | Gate | Brief | Note |
|
||||
|---|---|---|---|---|---|---|---|---|
|
||||
@@ -78,7 +78,7 @@ Generated from `docs/plans/queue.json` revision 3765 by `queue render`. Do not e
|
||||
| 39 | Slice 1 S4: the mosaic CLI, inbox, decide, tasks, agents, trail | rocko; reviewers darkwing, filbert | #1521 | done | 37 done | filbert approves on #1521, darkwing approves the packages/discord change and the broker host; cli tests, test-discord, every test-*.sh green (sage) | `docs/plans/2026-10-04_slice-1.md` § Slice 1 S4: the `mosaic` CLI (inbox, decide, tasks, agents, trail) | Follow-ups from round 1 (sage): (a) one host that boots every business, since writer.lock allows one broker per dataRoot while the unit is per business; accepted for slice 1 with one business. (b) the queue's PATH_RE refuses '@' in manifest paths, so the S4 unit template was renamed to mosaic-bus.service.in; packages/discord/systemd/[email protected] would hit the same refusal in any future candidate manifest. |
|
||||
| 40 | Slice 1 S5: WebUI inbox, tasks, agents and trails; CHAT-03 Gate E | dewey; reviewers darkwing, filbert | #1522 | done | 39 done | darkwing and filbert approve on #1522; webui, conversation, control-board and every test-*.sh green; row 5 Gate E is Jason's (sage) | `docs/plans/2026-10-04_slice-1.md` § Slice 1 S5: WebUI (inbox, tasks, agents and trails; CHAT-03 Gate E) | — |
|
||||
| 41 | Slice 1 S6: meta-harness and launching; the PM moves off T3 | filbert; reviewers darkwing | #1523 | done | 34 done, 36 done, 38 done | darkwing approves on #1523; harness and seat tests, every test-*.sh; recorded PM launch of a coder (sage) | `docs/plans/2026-10-04_slice-1.md` § Slice 1 S6: meta-harness and launching (the PM moves off T3) | — |
|
||||
| 42 | Slice 1 S7: acceptance, five pieces in a row | sage | #1524 | in-progress | 40 done, 41 done | Jason, from the five trails: only gated decisions reached him, every step left a trail (jason) | `docs/plans/2026-10-04_slice-1.md` § Slice 1 S7: acceptance, five pieces in a row | mos-1184 (2026-10-21T16:58Z): pieces 1-5 are Jason's requests to pm through the Console or mosaic talk pm, a route Q194 suspends until Jason says the Stack is operational. The pieces are paused, the 10-23 23:59Z date is dropped and no new date is set. mos-1148's C1/C2 rules wait with them. The 10-24 window goes ahead without the pieces (sage-1622). |
|
||||
| 42 | Slice 1 S7: acceptance, five pieces in a row | sage | #1524 | in-progress | 40 done, 41 done | Jason, from the five trails: only gated decisions reached him, every step left a trail (jason) | `docs/plans/2026-10-04_slice-1.md` § Slice 1 S7: acceptance, five pieces in a row | mos-1559 (00:00Z 10-24): the 10-23 23:59Z date passed with no pieces sent. Under the mos-1148 default the unopened pieces stay unopened. New latest-by date: Vikunja 442 due 2026-10-26 18:00Z. Pieces stay paused under Q194 until Jason says the Stack is operational; Mos raises them with Jason in the 10-24 session. |
|
||||
| 43 | S2b: gated approvals are single-use (authorize records consumption) | rocko; reviewers darkwing | #1525 | done | 37 done | darkwing approves on #1525; Sage's integration gate (sage) | `docs/plans/2026-10-05_s2b-single-use-approvals.md` § S2b: gated approvals are single-use (authorize records consumption) | 2026-10-05: S2 is in the tree at 38828a2c. Rocko's README edit (ae643fba, decision 62 human-proof note) is an uncommitted change on packages/bus/README.md and belongs in this row's candidate. |
|
||||
| 44 | S2c: a within-role message cites a decision without consuming it | rocko; reviewers darkwing | #1526 | done | 43 done | darkwing approves on #1526; Sage's integration gate (sage) | `docs/plans/2026-10-05_s2c-message-decision-citation.md` § S2c: a within-role message cites a decision without consuming it | — |
|
||||
| 45 | S4 follow-up: notifier retry limit, journal types and the round 2 test gaps | rocko; reviewers darkwing, filbert | #1527 | done | — | filbert and darkwing approve on #1527; every node package suite and every test-*.sh green on Sage's gate rerun (sage) | `docs/plans/2026-10-09_s4-follow-up-and-cohort.md` § S4 follow-up: notifier retry limit, journal types and the round 2 test gaps | lead decision 72; follows row 39 (2f5303c1) |
|
||||
@@ -359,8 +359,9 @@ Generated from `docs/plans/queue.json` revision 3765 by `queue render`. Do not e
|
||||
| 320 | Case 1/2 host fixes for the 433 P1 rerun: hostcheck item 5 mode 750, run sheet bundle verify, case2-unit --claude @, stack-copy unreadable cwd, notify.json step and precheck, mosaic wrapper step (10-27 path) | gladstone; reviewers morgana | #1816 | required; done | — | Morgana approves. Touched test files run whole (case1-hostcheck, case2-unit, case2-install, stack-copy) and the 8 named mutants are killed; Sage's full landing gate runs every package suite and every scripts/test-*.sh. After landing Mos sends ops-01 the new N. (sage) | `docs/plans/2026-10-23_host-fixes-1816-brief.md` § Brief: hostcheck item 5, bundle verify, case2-unit @, stack-copy cwd | LANDED 094124b4e 21:53Z (replay of faf55618e onto 45e3ca7bd, code paths patch-id equal, log appends in one final commit). F1-F6 hostcheck item 5, bundle verify, case2-unit scoped paths and cwd, precheck notify decision, run sheet 9a notify dirs 0700, wrapper step. Morgana APPROVE r2 c31403 rev 3715. Full gate GREEN 21:53:00Z, 0 fail (mos-1453/1455/1456). Vikunja 614 done. 433 P1 reruns on this N (mos-1532). |
|
||||
| 321 | F7, F10, F11: run sheet wrapper step umask and node PATH under sudo; adopt step after a first install; --no-rehearsal on the first upgrade; wait for healthz 200 before a precheck (433, ops-0271/0274/0275); docs only | gladstone; reviewers boyd | #1817 | required; done | — | Boyd (Codex) approves. A git grep with a positive control shows no test reads the changed run sheet lines; BUILD.md records the diff and the grep. Doc only, lands with any row (mos-1544). (sage) | `docs/plans/2026-10-23_host-fixes-1816-brief.md` § Brief: F7, the wrapper step's PATH and umask under sudo | LANDED 5283d3745, 3fd5307e1, 3569bf54f: cherry-picks of b5d4d7903, d9400f0d3, 9aae73ade with equal patch-ids. Doc only. Pushed tip 3569bf54f 23:32:54Z 10-23. Boyd APPROVE c31450 rev 3757, grep with positive control in BUILD.md. Rode the full gate GREEN 23:32:28Z on 3569bf54f. Live unchanged 45dca69f6. |
|
||||
| 322 | F8/F9: case2-install.sh runs the library persona import from the stack copy, and its refusal carries the error line (433 Run B, ops-0273) | goldie; reviewers morgana | #1818 | required; done | — | Morgana approves (install path). The new case2-install test fails on 094124b4e and passes on the candidate; a mutant restoring REPO at :185 is killed; Sage's full landing gate runs every package suite and scripts/test-*.sh. Then ops-01 reruns Run B P1. (sage) | `docs/plans/2026-10-23_host-fixes-1816-brief.md` § Brief: F8 and F9, the library persona import runs without the clone's node_modules | LANDED 467b05fe2, cherry-pick of 7b4ea4217 on 860afc2b6; added and removed lines identical, patch-id differs only by BUILD-LOG/SESSIONS append context (both sides kept). Pushed with tip 3569bf54f 23:32:54Z 10-23. Morgana APPROVE c31451 rev 3758, N1-N3 non-blocking. Sage full gate GREEN 23:32:28Z on 3569bf54f, all suites 0 fail, every scripts/test-*.sh rc 0. Next: ops-01 Run B P1 rerun on 467b05fe2 (mos-1553). Live unchanged 45dca69f6. |
|
||||
| 323 | Case 1 factory business file for .53: docs/guides/case1-business.mosaic-stack.json plus a loader and factory-check test (#414 stage 2, ops-0277) | goldie; reviewers duckworth | #1819 | in-progress | — | Sage full gate: all package suites except queue plus every scripts/test-*.sh (mos-1455); Duckworth review; new test file and one bus.tester mutant on CLEAR (sage) | `docs/plans/2026-10-23_case1-business-file-brief.md` § Brief: the Case 1 factory business file | — |
|
||||
| 324 | Tracker optional in the business file (Q247) | goldie; reviewers duckworth | #1820 | in-progress | — | New tests only. Absent tracker: loader and validate pass, the bus boots with no credential-file error and no @sync. Present tracker unchanged. A mutant that skips the presence check is killed. Then whole touched test files, then the full landing gate. (sage) | `docs/plans/2026-10-23_optional-tracker-brief.md` § Brief: tracker becomes optional in the business file | — |
|
||||
| 323 | Case 1 factory business file for .53: docs/guides/case1-business.mosaic-stack.json plus a loader and factory-check test (#414 stage 2, ops-0277) | goldie; reviewers duckworth | #1819 | done | — | Sage full gate: all package suites except queue plus every scripts/test-*.sh (mos-1455); Duckworth review; new test file and one bus.tester mutant on CLEAR (sage) | `docs/plans/2026-10-23_case1-business-file-brief.md` § Brief: the Case 1 factory business file | LANDED 571376c30 + evidence 6a0f0f4f8 (ff from dd6cb5872), tip 99930362e. Duckworth approve c31477 rev 3778 on 6a0f0f4f8. Sage gate green 00:54Z on 6a0f0f4f8: all package suites 0 fail, every scripts/test-*.sh rc 0. Root cause of the revoke-window red: TMPDIR length, tools.sock over 107 bytes (session.mjs:278). Brief template rule and DEFERRED entry in c25799928 (mos-1561). N1/N2 doc only. |
|
||||
| 324 | Tracker optional in the business file (Q247) | goldie; reviewers duckworth | #1820 | done | — | New tests only. Absent tracker: loader and validate pass, the bus boots with no credential-file error and no @sync. Present tracker unchanged. A mutant that skips the presence check is killed. Then whole touched test files, then the full landing gate. (sage) | `docs/plans/2026-10-23_optional-tracker-brief.md` § Brief: tracker becomes optional in the business file | LANDED 7e59ba8b3 + evidence dbdfcfd52 (ff from 3569bf54f), pushed with tip dd6cb5872 00:06:58Z 10-24. Duckworth APPROVE c31470 rev 3770 (static, N1 doc only). Goldie: new 5/5 and 4/4, business.test 21/21, presence mutant killed. Sage full gate GREEN 00:05:49Z on 64ea54083: every package suite 0 fail 0 skip (business 254, cli 986, bus 924, plugins 119, webui 411), every scripts/test-*.sh rc 0. Live unchanged 45dca69f6. Run B N1 for ops-01 P2/P3 (mos-1558). |
|
||||
| 325 | Case 1 business file: re-key project to stack to match .mosaic/project.json, plus a project-file test (#414 stage 2, ops-0288, mos-1563) | goldie; reviewers gladstone | #1821 | in-review, round 1, request posted | — | New test file and packages/business whole on CLEAR; one key mutant killed; Gladstone review; Sage full landing gate (mos-1455) (sage) | `docs/plans/2026-10-24_case1-project-key-brief.md` § Brief: re-key the Case 1 project to stack | — |
|
||||
|
||||
<!-- mosaic-queue:end -->
|
||||
|
||||
|
||||
+766
-17
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"version": 1,
|
||||
"canonicalRoot": "/mnt/storage/src/mosaic-stack",
|
||||
"revision": 3765,
|
||||
"revision": 3788,
|
||||
"rows": [
|
||||
{
|
||||
"id": 1,
|
||||
@@ -2549,10 +2549,10 @@
|
||||
"seat": "sage",
|
||||
"op": "sage-42-start-q118"
|
||||
},
|
||||
"note": "mos-1184 (2026-10-21T16:58Z): pieces 1-5 are Jason's requests to pm through the Console or mosaic talk pm, a route Q194 suspends until Jason says the Stack is operational. The pieces are paused, the 10-23 23:59Z date is dropped and no new date is set. mos-1148's C1/C2 rules wait with them. The 10-24 window goes ahead without the pieces (sage-1622).",
|
||||
"note": "mos-1559 (00:00Z 10-24): the 10-23 23:59Z date passed with no pieces sent. Under the mos-1148 default the unopened pieces stay unopened. New latest-by date: Vikunja 442 due 2026-10-26 18:00Z. Pieces stay paused under Q194 until Jason says the Stack is operational; Mos raises them with Jason in the 10-24 session.",
|
||||
"blockedReason": null,
|
||||
"createdAt": "2026-10-05T02:51:49.652Z",
|
||||
"updatedAt": "2026-10-21T16:58:26.582Z",
|
||||
"updatedAt": "2026-10-24T00:01:20.990Z",
|
||||
"updatedBy": "sage"
|
||||
},
|
||||
{
|
||||
@@ -32646,7 +32646,7 @@
|
||||
"closes": [
|
||||
1819
|
||||
],
|
||||
"state": "in-progress",
|
||||
"state": "done",
|
||||
"previousState": null,
|
||||
"required": false,
|
||||
"requiredSince": null,
|
||||
@@ -32661,16 +32661,70 @@
|
||||
"reviewers": [
|
||||
"duckworth"
|
||||
],
|
||||
"review": null,
|
||||
"claim": {
|
||||
"seat": "goldie",
|
||||
"op": "goldie-0323-start-1"
|
||||
"review": {
|
||||
"rounds": [
|
||||
{
|
||||
"n": 1,
|
||||
"op": "goldie-0323-review-1",
|
||||
"by": "goldie",
|
||||
"at": "2026-10-24T00:39:25.465Z",
|
||||
"issue": 1819,
|
||||
"candidate": {
|
||||
"kind": "commit",
|
||||
"digest": "6a0f0f4f8065c2171044f28c309dba4602414397",
|
||||
"text": null
|
||||
},
|
||||
"request": "comment",
|
||||
"attempts": [
|
||||
{
|
||||
"op": "goldie-0323-review-1",
|
||||
"by": "goldie",
|
||||
"at": "2026-10-24T00:39:25.465Z",
|
||||
"state": "failed",
|
||||
"comment": null,
|
||||
"transport": {
|
||||
"outcome": "failed",
|
||||
"status": null,
|
||||
"comment": null,
|
||||
"detail": "pre-send: the credential or account check failed"
|
||||
},
|
||||
"resolutions": []
|
||||
},
|
||||
{
|
||||
"op": "sage-3871",
|
||||
"by": "sage",
|
||||
"at": "2026-10-24T00:42:07.619Z",
|
||||
"state": "posted",
|
||||
"comment": 31475,
|
||||
"transport": {
|
||||
"outcome": "posted",
|
||||
"status": 201,
|
||||
"comment": 31475,
|
||||
"detail": "created"
|
||||
},
|
||||
"resolutions": []
|
||||
}
|
||||
],
|
||||
"duplicateRisk": false,
|
||||
"receipts": [
|
||||
{
|
||||
"reviewer": "duckworth",
|
||||
"op": "duckworth-323-verdict-r1",
|
||||
"at": "2026-10-24T00:44:15.270Z",
|
||||
"verdict": "approve",
|
||||
"comment": 31477,
|
||||
"candidate": "6a0f0f4f8065c2171044f28c309dba4602414397"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"note": null,
|
||||
"claim": null,
|
||||
"note": "LANDED 571376c30 + evidence 6a0f0f4f8 (ff from dd6cb5872), tip 99930362e. Duckworth approve c31477 rev 3778 on 6a0f0f4f8. Sage gate green 00:54Z on 6a0f0f4f8: all package suites 0 fail, every scripts/test-*.sh rc 0. Root cause of the revoke-window red: TMPDIR length, tools.sock over 107 bytes (session.mjs:278). Brief template rule and DEFERRED entry in c25799928 (mos-1561). N1/N2 doc only.",
|
||||
"blockedReason": null,
|
||||
"createdAt": "2026-10-23T22:50:41.494Z",
|
||||
"updatedAt": "2026-10-23T23:03:02.369Z",
|
||||
"updatedBy": "goldie"
|
||||
"updatedAt": "2026-10-24T01:00:54.851Z",
|
||||
"updatedBy": "sage"
|
||||
},
|
||||
{
|
||||
"id": 324,
|
||||
@@ -32682,7 +32736,7 @@
|
||||
"closes": [
|
||||
1820
|
||||
],
|
||||
"state": "in-progress",
|
||||
"state": "done",
|
||||
"previousState": null,
|
||||
"required": false,
|
||||
"requiredSince": null,
|
||||
@@ -32697,16 +32751,163 @@
|
||||
"reviewers": [
|
||||
"duckworth"
|
||||
],
|
||||
"review": null,
|
||||
"review": {
|
||||
"rounds": [
|
||||
{
|
||||
"n": 1,
|
||||
"op": "goldie-0324-review-1",
|
||||
"by": "goldie",
|
||||
"at": "2026-10-23T23:46:09.632Z",
|
||||
"issue": 1820,
|
||||
"candidate": {
|
||||
"kind": "commit",
|
||||
"digest": "dbdfcfd52caccee915c25c9979771fcb233d6139",
|
||||
"text": null
|
||||
},
|
||||
"request": "comment",
|
||||
"attempts": [
|
||||
{
|
||||
"op": "goldie-0324-review-1",
|
||||
"by": "goldie",
|
||||
"at": "2026-10-23T23:46:09.632Z",
|
||||
"state": "failed",
|
||||
"comment": null,
|
||||
"transport": {
|
||||
"outcome": "failed",
|
||||
"status": null,
|
||||
"comment": null,
|
||||
"detail": "pre-send: the credential or account check failed"
|
||||
},
|
||||
"resolutions": []
|
||||
},
|
||||
{
|
||||
"op": "sage-3852",
|
||||
"by": "sage",
|
||||
"at": "2026-10-23T23:48:13.612Z",
|
||||
"state": "posted",
|
||||
"comment": 31469,
|
||||
"transport": {
|
||||
"outcome": "posted",
|
||||
"status": 201,
|
||||
"comment": 31469,
|
||||
"detail": "created"
|
||||
},
|
||||
"resolutions": []
|
||||
}
|
||||
],
|
||||
"duplicateRisk": false,
|
||||
"receipts": [
|
||||
{
|
||||
"reviewer": "duckworth",
|
||||
"op": "duckworth-324-verdict-r1",
|
||||
"at": "2026-10-23T23:53:49.526Z",
|
||||
"verdict": "approve",
|
||||
"comment": 31470,
|
||||
"candidate": "dbdfcfd52caccee915c25c9979771fcb233d6139"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"claim": null,
|
||||
"note": "LANDED 7e59ba8b3 + evidence dbdfcfd52 (ff from 3569bf54f), pushed with tip dd6cb5872 00:06:58Z 10-24. Duckworth APPROVE c31470 rev 3770 (static, N1 doc only). Goldie: new 5/5 and 4/4, business.test 21/21, presence mutant killed. Sage full gate GREEN 00:05:49Z on 64ea54083: every package suite 0 fail 0 skip (business 254, cli 986, bus 924, plugins 119, webui 411), every scripts/test-*.sh rc 0. Live unchanged 45dca69f6. Run B N1 for ops-01 P2/P3 (mos-1558).",
|
||||
"blockedReason": null,
|
||||
"createdAt": "2026-10-23T23:12:07.446Z",
|
||||
"updatedAt": "2026-10-24T00:08:57.664Z",
|
||||
"updatedBy": "sage"
|
||||
},
|
||||
{
|
||||
"id": 325,
|
||||
"piece": "Case 1 business file: re-key project to stack to match .mosaic/project.json, plus a project-file test (#414 stage 2, ops-0288, mos-1563)",
|
||||
"owner": "goldie",
|
||||
"issues": [
|
||||
1821
|
||||
],
|
||||
"closes": [
|
||||
1821
|
||||
],
|
||||
"state": "in-review",
|
||||
"previousState": null,
|
||||
"required": false,
|
||||
"requiredSince": null,
|
||||
"gate": "New test file and packages/business whole on CLEAR; one key mutant killed; Gladstone review; Sage full landing gate (mos-1455)",
|
||||
"gateOwner": "sage",
|
||||
"brief": {
|
||||
"path": "docs/plans/2026-10-24_case1-project-key-brief.md",
|
||||
"anchor": "Brief: re-key the Case 1 project to stack",
|
||||
"blob": "8d91484c6731c7f7ceaf197a483c95024dee7448"
|
||||
},
|
||||
"after": [],
|
||||
"reviewers": [
|
||||
"gladstone"
|
||||
],
|
||||
"review": {
|
||||
"rounds": [
|
||||
{
|
||||
"n": 1,
|
||||
"op": "goldie-0325-review-1",
|
||||
"by": "goldie",
|
||||
"at": "2026-10-24T01:18:04.364Z",
|
||||
"issue": 1821,
|
||||
"candidate": {
|
||||
"kind": "commit",
|
||||
"digest": "c5f96836d5ffa6cc0160da7360321706bef86a17",
|
||||
"text": null
|
||||
},
|
||||
"request": "comment",
|
||||
"attempts": [
|
||||
{
|
||||
"op": "goldie-0325-review-1",
|
||||
"by": "goldie",
|
||||
"at": "2026-10-24T01:18:04.364Z",
|
||||
"state": "failed",
|
||||
"comment": null,
|
||||
"transport": {
|
||||
"outcome": "failed",
|
||||
"status": null,
|
||||
"comment": null,
|
||||
"detail": "pre-send: the credential or account check failed"
|
||||
},
|
||||
"resolutions": []
|
||||
},
|
||||
{
|
||||
"op": "sage-3884",
|
||||
"by": "sage",
|
||||
"at": "2026-10-24T01:20:34.463Z",
|
||||
"state": "posted",
|
||||
"comment": 31482,
|
||||
"transport": {
|
||||
"outcome": "posted",
|
||||
"status": 201,
|
||||
"comment": 31482,
|
||||
"detail": "created"
|
||||
},
|
||||
"resolutions": []
|
||||
}
|
||||
],
|
||||
"duplicateRisk": false,
|
||||
"receipts": [
|
||||
{
|
||||
"reviewer": "gladstone",
|
||||
"op": "gladstone-325-verdict-r1",
|
||||
"at": "2026-10-24T01:22:42.822Z",
|
||||
"verdict": "approve",
|
||||
"comment": 31484,
|
||||
"candidate": "c5f96836d5ffa6cc0160da7360321706bef86a17"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
"claim": {
|
||||
"seat": "goldie",
|
||||
"op": "goldie-0324-start-1"
|
||||
"op": "goldie-0325-start-1"
|
||||
},
|
||||
"note": null,
|
||||
"blockedReason": null,
|
||||
"createdAt": "2026-10-23T23:12:07.446Z",
|
||||
"updatedAt": "2026-10-23T23:15:58.025Z",
|
||||
"updatedBy": "goldie"
|
||||
"createdAt": "2026-10-24T01:09:57.971Z",
|
||||
"updatedAt": "2026-10-24T01:22:42.822Z",
|
||||
"updatedBy": "gladstone"
|
||||
}
|
||||
],
|
||||
"log": [
|
||||
@@ -131357,6 +131558,554 @@
|
||||
"receipt": "ok sage-3847 rev 3765 row 321 in-review→done round 1"
|
||||
},
|
||||
"viewSha": "a0c965a44bbfae9c701bb3edef7dae7e3b76110bd834db6a412b6bc2891d0708"
|
||||
},
|
||||
{
|
||||
"rev": 3766,
|
||||
"op": "goldie-0324-review-1",
|
||||
"verb": "move",
|
||||
"args": {
|
||||
"id": 324,
|
||||
"to": "in-review",
|
||||
"reason": null,
|
||||
"candidate": "dbdfcfd52caccee915c25c9979771fcb233d6139",
|
||||
"evidence": null,
|
||||
"issue": null
|
||||
},
|
||||
"by": "goldie",
|
||||
"at": "2026-10-23T23:46:09.632Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 324,
|
||||
"from": "in-progress",
|
||||
"to": "in-review",
|
||||
"round": 1,
|
||||
"issue": 1820,
|
||||
"candidate": {
|
||||
"kind": "commit",
|
||||
"digest": "dbdfcfd52caccee915c25c9979771fcb233d6139",
|
||||
"text": null
|
||||
},
|
||||
"receipt": "ok goldie-0324-review-1 rev 3766 row 324 in-progress→in-review round 1 on #1820; request goldie-0324-review-1 requesting"
|
||||
},
|
||||
"viewSha": "a7f646d3c079b4791bf24687f6dd9c0779bfd24804f52caa94a1b472a21d9f3d"
|
||||
},
|
||||
{
|
||||
"rev": 3767,
|
||||
"op": "goldie-0324-review-1.outcome",
|
||||
"verb": "review-outcome",
|
||||
"args": {
|
||||
"id": 324,
|
||||
"attempt": "goldie-0324-review-1",
|
||||
"outcome": "failed",
|
||||
"status": null,
|
||||
"comment": null,
|
||||
"detail": "pre-send: the credential or account check failed"
|
||||
},
|
||||
"by": "goldie",
|
||||
"at": "2026-10-23T23:47:00.333Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 324,
|
||||
"round": 1,
|
||||
"attempt": "goldie-0324-review-1",
|
||||
"state": "failed",
|
||||
"receipt": "ok goldie-0324-review-1.outcome rev 3767 row 324 round 1 request goldie-0324-review-1 failed"
|
||||
},
|
||||
"viewSha": "6302ae6f4c2eb7c16d3ab4b7f76c71c7f95fac2d7512588f15bb3ab37cd05ad7"
|
||||
},
|
||||
{
|
||||
"rev": 3768,
|
||||
"op": "sage-3852",
|
||||
"verb": "review-request",
|
||||
"args": {
|
||||
"id": 324
|
||||
},
|
||||
"by": "sage",
|
||||
"at": "2026-10-23T23:48:13.612Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 324,
|
||||
"round": 1,
|
||||
"attempt": "sage-3852",
|
||||
"state": "requesting",
|
||||
"receipt": "ok sage-3852 rev 3768 row 324 round 1 request sage-3852 requesting"
|
||||
},
|
||||
"viewSha": "abb1f0659abe54b896133bdda093a9d01e0a5bc8048f7b6022da19d4f7d8ac0e"
|
||||
},
|
||||
{
|
||||
"rev": 3769,
|
||||
"op": "sage-3852.outcome",
|
||||
"verb": "review-outcome",
|
||||
"args": {
|
||||
"id": 324,
|
||||
"attempt": "sage-3852",
|
||||
"outcome": "posted",
|
||||
"status": 201,
|
||||
"comment": 31469,
|
||||
"detail": "created"
|
||||
},
|
||||
"by": "sage",
|
||||
"at": "2026-10-23T23:49:03.433Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 324,
|
||||
"round": 1,
|
||||
"attempt": "sage-3852",
|
||||
"state": "posted",
|
||||
"receipt": "ok sage-3852.outcome rev 3769 row 324 round 1 request sage-3852 posted comment 31469"
|
||||
},
|
||||
"viewSha": "6850671baeae0739abb6bc9518722ffa91c9c1efd32e6a07dafe6703e29ecfc0"
|
||||
},
|
||||
{
|
||||
"rev": 3770,
|
||||
"op": "duckworth-324-verdict-r1",
|
||||
"verb": "review-record",
|
||||
"args": {
|
||||
"id": 324,
|
||||
"verdict": "approve",
|
||||
"comment": 31470,
|
||||
"candidate": "dbdfcfd52caccee915c25c9979771fcb233d6139"
|
||||
},
|
||||
"by": "duckworth",
|
||||
"at": "2026-10-23T23:53:49.526Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 324,
|
||||
"round": 1,
|
||||
"verdict": "approve",
|
||||
"receipt": "ok duckworth-324-verdict-r1 rev 3770 row 324 round 1 approve by duckworth (comment 31470)"
|
||||
},
|
||||
"viewSha": "02bd1d4e4b3c98af3517d3d621d8435790cd70c393d3fc988d206eed49e1ae6e"
|
||||
},
|
||||
{
|
||||
"rev": 3771,
|
||||
"op": "sage-3855",
|
||||
"verb": "note",
|
||||
"args": {
|
||||
"id": 42,
|
||||
"text": "mos-1559 (00:00Z 10-24): the 10-23 23:59Z date passed with no pieces sent. Under the mos-1148 default the unopened pieces stay unopened. New latest-by date: Vikunja 442 due 2026-10-26 18:00Z. Pieces stay paused under Q194 until Jason says the Stack is operational; Mos raises them with Jason in the 10-24 session."
|
||||
},
|
||||
"by": "sage",
|
||||
"at": "2026-10-24T00:01:20.990Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 42,
|
||||
"field": "note",
|
||||
"receipt": "ok sage-3855 rev 3771 row 42 note"
|
||||
},
|
||||
"viewSha": "a397c2a14b85071a6c6ef17f8c6ec79e45135a8d34229fe8b8039aa90fa07595"
|
||||
},
|
||||
{
|
||||
"rev": 3772,
|
||||
"op": "sage-3856",
|
||||
"verb": "note",
|
||||
"args": {
|
||||
"id": 324,
|
||||
"text": "LANDED 7e59ba8b3 + evidence dbdfcfd52 (ff from 3569bf54f), pushed with tip dd6cb5872 00:06:58Z 10-24. Duckworth APPROVE c31470 rev 3770 (static, N1 doc only). Goldie: new 5/5 and 4/4, business.test 21/21, presence mutant killed. Sage full gate GREEN 00:05:49Z on 64ea54083: every package suite 0 fail 0 skip (business 254, cli 986, bus 924, plugins 119, webui 411), every scripts/test-*.sh rc 0. Live unchanged 45dca69f6. Run B N1 for ops-01 P2/P3 (mos-1558)."
|
||||
},
|
||||
"by": "sage",
|
||||
"at": "2026-10-24T00:08:03.422Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 324,
|
||||
"field": "note",
|
||||
"receipt": "ok sage-3856 rev 3772 row 324 note"
|
||||
},
|
||||
"viewSha": "b3cc913bb83c54bd8fe8ac9b6ffcb76a1dcc9805171cb7e81a056016db9babf0"
|
||||
},
|
||||
{
|
||||
"rev": 3773,
|
||||
"op": "sage-3857",
|
||||
"verb": "move",
|
||||
"args": {
|
||||
"id": 324,
|
||||
"to": "done",
|
||||
"reason": null,
|
||||
"candidate": null,
|
||||
"evidence": null,
|
||||
"issue": null
|
||||
},
|
||||
"by": "sage",
|
||||
"at": "2026-10-24T00:08:57.664Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 324,
|
||||
"from": "in-review",
|
||||
"to": "done",
|
||||
"round": 1,
|
||||
"issue": null,
|
||||
"candidate": null,
|
||||
"receipt": "ok sage-3857 rev 3773 row 324 in-review→done round 1"
|
||||
},
|
||||
"viewSha": "27401b14330cff219803f2767761922a8082b7575dbcd873406da14865fe2f5a"
|
||||
},
|
||||
{
|
||||
"rev": 3774,
|
||||
"op": "goldie-0323-review-1",
|
||||
"verb": "move",
|
||||
"args": {
|
||||
"id": 323,
|
||||
"to": "in-review",
|
||||
"reason": null,
|
||||
"candidate": "6a0f0f4f8065c2171044f28c309dba4602414397",
|
||||
"evidence": null,
|
||||
"issue": null
|
||||
},
|
||||
"by": "goldie",
|
||||
"at": "2026-10-24T00:39:25.465Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 323,
|
||||
"from": "in-progress",
|
||||
"to": "in-review",
|
||||
"round": 1,
|
||||
"issue": 1819,
|
||||
"candidate": {
|
||||
"kind": "commit",
|
||||
"digest": "6a0f0f4f8065c2171044f28c309dba4602414397",
|
||||
"text": null
|
||||
},
|
||||
"receipt": "ok goldie-0323-review-1 rev 3774 row 323 in-progress→in-review round 1 on #1819; request goldie-0323-review-1 requesting"
|
||||
},
|
||||
"viewSha": "e5443f2cffe39261fc91f3aaf6d9966778a9f6e75d8c6c99793eb9cdbd27a092"
|
||||
},
|
||||
{
|
||||
"rev": 3775,
|
||||
"op": "goldie-0323-review-1.outcome",
|
||||
"verb": "review-outcome",
|
||||
"args": {
|
||||
"id": 323,
|
||||
"attempt": "goldie-0323-review-1",
|
||||
"outcome": "failed",
|
||||
"status": null,
|
||||
"comment": null,
|
||||
"detail": "pre-send: the credential or account check failed"
|
||||
},
|
||||
"by": "goldie",
|
||||
"at": "2026-10-24T00:40:43.664Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 323,
|
||||
"round": 1,
|
||||
"attempt": "goldie-0323-review-1",
|
||||
"state": "failed",
|
||||
"receipt": "ok goldie-0323-review-1.outcome rev 3775 row 323 round 1 request goldie-0323-review-1 failed"
|
||||
},
|
||||
"viewSha": "1fb5e5dec6a0c383d06ddd9a2d1f775706fef061c549ab9b3a6a7c875fadf320"
|
||||
},
|
||||
{
|
||||
"rev": 3776,
|
||||
"op": "sage-3871",
|
||||
"verb": "review-request",
|
||||
"args": {
|
||||
"id": 323
|
||||
},
|
||||
"by": "sage",
|
||||
"at": "2026-10-24T00:42:07.619Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 323,
|
||||
"round": 1,
|
||||
"attempt": "sage-3871",
|
||||
"state": "requesting",
|
||||
"receipt": "ok sage-3871 rev 3776 row 323 round 1 request sage-3871 requesting"
|
||||
},
|
||||
"viewSha": "7f80ed332d55d7bdeef940273c50cbd9683a420d418e23ef0151f573793dfc47"
|
||||
},
|
||||
{
|
||||
"rev": 3777,
|
||||
"op": "sage-3871.outcome",
|
||||
"verb": "review-outcome",
|
||||
"args": {
|
||||
"id": 323,
|
||||
"attempt": "sage-3871",
|
||||
"outcome": "posted",
|
||||
"status": 201,
|
||||
"comment": 31475,
|
||||
"detail": "created"
|
||||
},
|
||||
"by": "sage",
|
||||
"at": "2026-10-24T00:43:04.250Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 323,
|
||||
"round": 1,
|
||||
"attempt": "sage-3871",
|
||||
"state": "posted",
|
||||
"receipt": "ok sage-3871.outcome rev 3777 row 323 round 1 request sage-3871 posted comment 31475"
|
||||
},
|
||||
"viewSha": "6c034db294c905c3735ad996fdaf28da3ec1fbee48d9d70aeb122745ffdda1f3"
|
||||
},
|
||||
{
|
||||
"rev": 3778,
|
||||
"op": "duckworth-323-verdict-r1",
|
||||
"verb": "review-record",
|
||||
"args": {
|
||||
"id": 323,
|
||||
"verdict": "approve",
|
||||
"comment": 31477,
|
||||
"candidate": "6a0f0f4f8065c2171044f28c309dba4602414397"
|
||||
},
|
||||
"by": "duckworth",
|
||||
"at": "2026-10-24T00:44:15.270Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 323,
|
||||
"round": 1,
|
||||
"verdict": "approve",
|
||||
"receipt": "ok duckworth-323-verdict-r1 rev 3778 row 323 round 1 approve by duckworth (comment 31477)"
|
||||
},
|
||||
"viewSha": "f776c94408564a716ca7efbc70ad058cb2b8704d4c8a6dfedb66beee4810f2c1"
|
||||
},
|
||||
{
|
||||
"rev": 3779,
|
||||
"op": "sage-3873",
|
||||
"verb": "note",
|
||||
"args": {
|
||||
"id": 323,
|
||||
"text": "LANDED 571376c30 + evidence 6a0f0f4f8 (ff from dd6cb5872), tip 99930362e. Duckworth approve c31477 rev 3778 on 6a0f0f4f8. Sage gate green 00:54Z on 6a0f0f4f8: all package suites 0 fail, every scripts/test-*.sh rc 0. Root cause of the revoke-window red: TMPDIR length, tools.sock over 107 bytes (session.mjs:278). Brief template rule and DEFERRED entry in c25799928 (mos-1561). N1/N2 doc only."
|
||||
},
|
||||
"by": "sage",
|
||||
"at": "2026-10-24T01:00:03.010Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 323,
|
||||
"field": "note",
|
||||
"receipt": "ok sage-3873 rev 3779 row 323 note"
|
||||
},
|
||||
"viewSha": "3bdae29d120d73111f7a31bd3a2457c9916b14ff609bff845d037b65da3cbce9"
|
||||
},
|
||||
{
|
||||
"rev": 3780,
|
||||
"op": "sage-3874",
|
||||
"verb": "move",
|
||||
"args": {
|
||||
"id": 323,
|
||||
"to": "done",
|
||||
"reason": null,
|
||||
"candidate": null,
|
||||
"evidence": null,
|
||||
"issue": null
|
||||
},
|
||||
"by": "sage",
|
||||
"at": "2026-10-24T01:00:54.851Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 323,
|
||||
"from": "in-review",
|
||||
"to": "done",
|
||||
"round": 1,
|
||||
"issue": null,
|
||||
"candidate": null,
|
||||
"receipt": "ok sage-3874 rev 3780 row 323 in-review→done round 1"
|
||||
},
|
||||
"viewSha": "f3418b4c61363a419ac03600b3feb6cc9a4bbce4405e8980bf2787801672809f"
|
||||
},
|
||||
{
|
||||
"rev": 3781,
|
||||
"op": "sage-3879",
|
||||
"verb": "add",
|
||||
"args": {
|
||||
"piece": "Case 1 business file: re-key project to stack to match .mosaic/project.json, plus a project-file test (#414 stage 2, ops-0288, mos-1563)",
|
||||
"gate": "New test file and packages/business whole on CLEAR; one key mutant killed; Gladstone review; Sage full landing gate (mos-1455)",
|
||||
"brief": "docs/plans/2026-10-24_case1-project-key-brief.md#Brief: re-key the Case 1 project to stack",
|
||||
"issues": [
|
||||
1821
|
||||
],
|
||||
"note": null,
|
||||
"owner": "goldie",
|
||||
"gateOwner": "sage",
|
||||
"after": null,
|
||||
"reviewers": [
|
||||
"gladstone"
|
||||
],
|
||||
"required": null
|
||||
},
|
||||
"by": "sage",
|
||||
"at": "2026-10-24T01:09:57.971Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 325,
|
||||
"from": null,
|
||||
"to": "queued",
|
||||
"brief": {
|
||||
"path": "docs/plans/2026-10-24_case1-project-key-brief.md",
|
||||
"anchor": "Brief: re-key the Case 1 project to stack",
|
||||
"blob": "8d91484c6731c7f7ceaf197a483c95024dee7448"
|
||||
},
|
||||
"receipt": "ok sage-3879 rev 3781 row 325 none→queued"
|
||||
},
|
||||
"viewSha": "c59e1401c4689a96fa467c787bd9691b729eda3e2af4679188235eeb6d233094"
|
||||
},
|
||||
{
|
||||
"rev": 3782,
|
||||
"op": "sage-3880",
|
||||
"verb": "move",
|
||||
"args": {
|
||||
"id": 325,
|
||||
"to": "briefed",
|
||||
"reason": null,
|
||||
"candidate": null,
|
||||
"evidence": null,
|
||||
"issue": null
|
||||
},
|
||||
"by": "sage",
|
||||
"at": "2026-10-24T01:10:53.568Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 325,
|
||||
"from": "queued",
|
||||
"to": "briefed",
|
||||
"round": null,
|
||||
"issue": null,
|
||||
"candidate": null,
|
||||
"receipt": "ok sage-3880 rev 3782 row 325 queued→briefed"
|
||||
},
|
||||
"viewSha": "41afd165d5a71f6af386de17bf8f6e9c6fe107dbe99956b5f7a86731fa361228"
|
||||
},
|
||||
{
|
||||
"rev": 3783,
|
||||
"op": "goldie-0325-start-1",
|
||||
"verb": "move",
|
||||
"args": {
|
||||
"id": 325,
|
||||
"to": "in-progress",
|
||||
"reason": null,
|
||||
"candidate": null,
|
||||
"evidence": null,
|
||||
"issue": null
|
||||
},
|
||||
"by": "goldie",
|
||||
"at": "2026-10-24T01:16:58.930Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 325,
|
||||
"from": "briefed",
|
||||
"to": "in-progress",
|
||||
"round": null,
|
||||
"issue": null,
|
||||
"candidate": null,
|
||||
"receipt": "ok goldie-0325-start-1 rev 3783 row 325 briefed→in-progress"
|
||||
},
|
||||
"viewSha": "d768e4bf91d9908b5697649267a4393f7c4c7a9db0d05ec377906e318d522d87"
|
||||
},
|
||||
{
|
||||
"rev": 3784,
|
||||
"op": "goldie-0325-review-1",
|
||||
"verb": "move",
|
||||
"args": {
|
||||
"id": 325,
|
||||
"to": "in-review",
|
||||
"reason": null,
|
||||
"candidate": "c5f96836d5ffa6cc0160da7360321706bef86a17",
|
||||
"evidence": null,
|
||||
"issue": null
|
||||
},
|
||||
"by": "goldie",
|
||||
"at": "2026-10-24T01:18:04.364Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 325,
|
||||
"from": "in-progress",
|
||||
"to": "in-review",
|
||||
"round": 1,
|
||||
"issue": 1821,
|
||||
"candidate": {
|
||||
"kind": "commit",
|
||||
"digest": "c5f96836d5ffa6cc0160da7360321706bef86a17",
|
||||
"text": null
|
||||
},
|
||||
"receipt": "ok goldie-0325-review-1 rev 3784 row 325 in-progress→in-review round 1 on #1821; request goldie-0325-review-1 requesting"
|
||||
},
|
||||
"viewSha": "23408b96e46a3d6f5f80470448c475622261ebc99fa6ed3cd3d0339e79f4e69b"
|
||||
},
|
||||
{
|
||||
"rev": 3785,
|
||||
"op": "goldie-0325-review-1.outcome",
|
||||
"verb": "review-outcome",
|
||||
"args": {
|
||||
"id": 325,
|
||||
"attempt": "goldie-0325-review-1",
|
||||
"outcome": "failed",
|
||||
"status": null,
|
||||
"comment": null,
|
||||
"detail": "pre-send: the credential or account check failed"
|
||||
},
|
||||
"by": "goldie",
|
||||
"at": "2026-10-24T01:19:00.773Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 325,
|
||||
"round": 1,
|
||||
"attempt": "goldie-0325-review-1",
|
||||
"state": "failed",
|
||||
"receipt": "ok goldie-0325-review-1.outcome rev 3785 row 325 round 1 request goldie-0325-review-1 failed"
|
||||
},
|
||||
"viewSha": "a098b29709b88484d98350f44750a37a621751a982c47fe0863ae9b6c938d224"
|
||||
},
|
||||
{
|
||||
"rev": 3786,
|
||||
"op": "sage-3884",
|
||||
"verb": "review-request",
|
||||
"args": {
|
||||
"id": 325
|
||||
},
|
||||
"by": "sage",
|
||||
"at": "2026-10-24T01:20:34.463Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 325,
|
||||
"round": 1,
|
||||
"attempt": "sage-3884",
|
||||
"state": "requesting",
|
||||
"receipt": "ok sage-3884 rev 3786 row 325 round 1 request sage-3884 requesting"
|
||||
},
|
||||
"viewSha": "44e292c9544263d2093cacc4f643bf452c9bdd21c72c64d0ac5a169cb8d900e8"
|
||||
},
|
||||
{
|
||||
"rev": 3787,
|
||||
"op": "sage-3884.outcome",
|
||||
"verb": "review-outcome",
|
||||
"args": {
|
||||
"id": 325,
|
||||
"attempt": "sage-3884",
|
||||
"outcome": "posted",
|
||||
"status": 201,
|
||||
"comment": 31482,
|
||||
"detail": "created"
|
||||
},
|
||||
"by": "sage",
|
||||
"at": "2026-10-24T01:21:30.422Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 325,
|
||||
"round": 1,
|
||||
"attempt": "sage-3884",
|
||||
"state": "posted",
|
||||
"receipt": "ok sage-3884.outcome rev 3787 row 325 round 1 request sage-3884 posted comment 31482"
|
||||
},
|
||||
"viewSha": "25a06deff4a77b1cf1a889e39edfbdc2e73a9dd3f93653e4b91b0e6400549348"
|
||||
},
|
||||
{
|
||||
"rev": 3788,
|
||||
"op": "gladstone-325-verdict-r1",
|
||||
"verb": "review-record",
|
||||
"args": {
|
||||
"id": 325,
|
||||
"verdict": "approve",
|
||||
"comment": 31484,
|
||||
"candidate": "c5f96836d5ffa6cc0160da7360321706bef86a17"
|
||||
},
|
||||
"by": "gladstone",
|
||||
"at": "2026-10-24T01:22:42.822Z",
|
||||
"semantics": 2,
|
||||
"result": {
|
||||
"row": 325,
|
||||
"round": 1,
|
||||
"verdict": "approve",
|
||||
"receipt": "ok gladstone-325-verdict-r1 rev 3788 row 325 round 1 approve by gladstone (comment 31484)"
|
||||
},
|
||||
"viewSha": "422f9329c44229356521749dc31d56b1130fbee51659c18a58657003cf4880a5"
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
# Containerized install images (I2)
|
||||
|
||||
All images use the I1 base digest
|
||||
`node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20`
|
||||
(Node 24.21.0). A base or contract change requires a reviewed commit and
|
||||
new build. Tags are mosaic-core:RELEASE, mosaic-session:RELEASE and
|
||||
mosaic-egress:RELEASE, where RELEASE is the repository file's value.
|
||||
|
||||
- core: packages, scripts, roles, contracts, production npm dependencies and
|
||||
RELEASE, under /opt/mosaic. No git is installed. NODE_USE_ENV_PROXY=1
|
||||
enables fetch proxy support; startup in I6 supplies HTTPS_PROXY.
|
||||
- session: core plus global Claude Code 2.1.296 (decision 91; harness README)
|
||||
and pi at package.json's exact pin, currently 0.85.1. Claude nonessential
|
||||
traffic is disabled. No host MOSAIC_CLAUDE_BIN is used.
|
||||
- egress: Node base, dependency-free packages/egress and baked egress contract.
|
||||
Its required argument selects agents or services. No engine socket mount.
|
||||
|
||||
`scripts/images.sh build` and `list` require a valid scratch/operator
|
||||
containerized config and rootless endpoint. Both use scripts/lib/docker.sh;
|
||||
local mode refuses before Docker. Build first verifies the endpoint, then
|
||||
copies only versioned runtime source plus I2's explicit new runtime paths
|
||||
into an ephemeral context. No .git, host credentials, agent work directories,
|
||||
or host node_modules enter the context. npm never receives a credential.
|
||||
|
||||
These are image definitions, not a deployment: I4-I6 own mounting, hardening,
|
||||
networks, process entry points and actual stack launch. All run as container
|
||||
uid 0; the later launchers must set read-only rootfs, dropped caps, no new
|
||||
privileges, resource limits and their exact allowed mount lists.
|
||||
|
||||
Verification is scripts/test-install.sh with a scratch config and explicit
|
||||
rootless endpoint. It builds images, checks pins, runs an internal-network
|
||||
CONNECT test, and exercises the Vikunja and notifier REST clients through
|
||||
the services proxy without a real credential. No broker is started behind
|
||||
that proxy by I2. The provider-host measurement is a separate Mos gate.
|
||||
@@ -0,0 +1,12 @@
|
||||
FROM node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
|
||||
WORKDIR /opt/mosaic
|
||||
COPY package.json package-lock.json ./
|
||||
RUN npm ci --omit=dev --ignore-scripts --no-audit --no-fund && npm cache clean --force
|
||||
COPY packages/ packages/
|
||||
COPY scripts/ scripts/
|
||||
COPY contracts/ contracts/
|
||||
COPY roles/ roles/
|
||||
COPY RELEASE ./
|
||||
ENV NODE_USE_ENV_PROXY=1
|
||||
USER 0
|
||||
CMD ["node", "packages/bus/src/process.mjs"]
|
||||
@@ -0,0 +1,7 @@
|
||||
FROM node@sha256:d6aa754f16b3197301076f047b5def2f02ea1dbbc2ca920407d46d7ec7f87b20
|
||||
WORKDIR /opt/mosaic
|
||||
COPY packages/egress/ packages/egress/
|
||||
COPY contracts/egress.json contracts/egress.json
|
||||
USER 0
|
||||
EXPOSE 3128
|
||||
ENTRYPOINT ["node", "packages/egress/src/cli.mjs"]
|
||||
@@ -0,0 +1,9 @@
|
||||
ARG CORE_IMAGE
|
||||
FROM ${CORE_IMAGE}
|
||||
ARG PI_VERSION
|
||||
# Decision 91 and packages/harness/README.md pin Claude Code to this version.
|
||||
RUN npm install -g --no-audit --no-fund @anthropic-ai/[email protected] @earendil-works/pi-coding-agent@${PI_VERSION} \
|
||||
&& npm cache clean --force && rm -rf /root/.npm /tmp/*
|
||||
ENV CLAUDE_CODE_DISABLE_NONESSENTIAL_TRAFFIC=1
|
||||
USER 0
|
||||
CMD ["node", "--version"]
|
||||
@@ -67,7 +67,7 @@ test("row 323: committed Case 1 document loads unchanged through the business lo
|
||||
assert.equal(b.tracker, null);
|
||||
assert.equal(Object.hasOwn(document(), "tracker"), false);
|
||||
assert.equal(Object.hasOwn(document(), "library"), false);
|
||||
assert.deepEqual(b.projects, { "mosaic-stack": { root: "/srv/mosaic/src/mosaic-stack" } });
|
||||
assert.deepEqual(b.projects, { "stack": { root: "/srv/mosaic/src/mosaic-stack" } });
|
||||
assert.deepEqual(Object.keys(b.roles), ["chief", "assistant"]);
|
||||
assert.deepEqual(b.plugins.harness, ["claude-code"]);
|
||||
assert.equal(b.launch.by, "chief");
|
||||
|
||||
@@ -0,0 +1,65 @@
|
||||
// Row 325 (#1821): factory validation must load the repository's actual project document.
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdirSync, readFileSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { join } from "node:path";
|
||||
import { REPO, REPO_ROLES, systemConfig, tmp, writeJson } from "./helpers.mjs";
|
||||
|
||||
const CASE1 = join(REPO, "docs/guides/case1-business.mosaic-stack.json");
|
||||
const PROJECT = join(REPO, ".mosaic/project.json");
|
||||
const document = () => JSON.parse(readFileSync(CASE1, "utf8"));
|
||||
|
||||
function fixture(t, { oldKey = false } = {}) {
|
||||
const root = tmp();
|
||||
t.after(() => rmSync(root, { recursive: true, force: true }));
|
||||
const doc = document();
|
||||
assert.equal(Object.keys(doc.projects).length, 1);
|
||||
const [key] = Object.keys(doc.projects);
|
||||
const projectRoot = join(root, "checkout");
|
||||
const projectFile = join(projectRoot, ".mosaic/project.json");
|
||||
const bytes = readFileSync(PROJECT);
|
||||
mkdirSync(join(projectRoot, ".mosaic"), { recursive: true });
|
||||
writeFileSync(projectFile, bytes);
|
||||
assert.deepEqual(readFileSync(projectFile), bytes, "fixture must use the real project's unmodified bytes");
|
||||
const entry = { ...doc.projects[key], root: projectRoot };
|
||||
doc.projects = { [oldKey ? "mosaic-stack" : key]: entry };
|
||||
const config = systemConfig(root);
|
||||
writeJson(join(root, "config/businesses/mosaic-stack.json"), doc);
|
||||
return {
|
||||
project: JSON.parse(bytes), projectFile,
|
||||
validate: () => spawnSync(process.execPath, [join(REPO, "packages/business/src/cli.mjs"), "validate", "mosaic-stack"], {
|
||||
env: { ...process.env, HOME: root, MOSAIC_CONFIG: config, MOSAIC_ROLES_DIR: REPO_ROLES },
|
||||
encoding: "utf8", timeout: 30000,
|
||||
}),
|
||||
};
|
||||
}
|
||||
|
||||
test("row 325: the Case 1 project's sole key matches the repository project id", () => {
|
||||
const doc = document();
|
||||
const project = JSON.parse(readFileSync(PROJECT, "utf8"));
|
||||
assert.equal(project.id, "stack");
|
||||
assert.deepEqual(Object.keys(doc.projects), [project.id]);
|
||||
assert.equal(doc.id, "mosaic-stack");
|
||||
assert.equal(doc.projects[project.id].root, "/srv/mosaic/src/mosaic-stack");
|
||||
});
|
||||
|
||||
test("row 325: real business validate reports the copied repository project valid", (t) => {
|
||||
const x = fixture(t);
|
||||
const r = x.validate();
|
||||
assert.equal(r.status, 0, r.stdout + r.stderr);
|
||||
const result = JSON.parse(r.stdout);
|
||||
assert.equal(result.business, "mosaic-stack");
|
||||
assert.deepEqual(result.projects, { [x.project.id]: "valid" });
|
||||
assert.doesNotMatch(r.stderr, /no project file|project variables/);
|
||||
});
|
||||
|
||||
test("row 325: the old mosaic-stack project key refuses against those same project bytes", (t) => {
|
||||
const x = fixture(t, { oldKey: true });
|
||||
const r = x.validate();
|
||||
assert.equal(r.status, 2, r.stdout + r.stderr);
|
||||
assert.equal(r.stdout, "");
|
||||
assert.match(r.stderr, /has id stack, but business mosaic-stack declares it as mosaic-stack/);
|
||||
assert.ok(r.stderr.includes(x.projectFile));
|
||||
assert.doesNotMatch(r.stderr, /no project file/);
|
||||
});
|
||||
@@ -0,0 +1,30 @@
|
||||
# Egress CONNECT proxy
|
||||
|
||||
`node packages/egress/src/cli.mjs agents|services` reads the image-baked
|
||||
`contracts/egress.json`, binds port 3128, and refuses an unknown or empty
|
||||
list. It accepts CONNECT only, matching exact lower-case DNS host and port
|
||||
pairs, never a wildcard. A malformed request returns 400; an unlisted pair
|
||||
403; an incomplete header after 5 seconds 408; headers over 8192 bytes 431;
|
||||
an upstream connection failure 502. Connections have a 10-second establishment
|
||||
and 120-second idle bound. Fragmented headers and bytes following the header
|
||||
are preserved. TLS stays between the client and destination.
|
||||
|
||||
Each decision writes exactly one JSON line:
|
||||
`{at,list,host,port,allowed}`. Malformed/slow requests have null host/port;
|
||||
allowed means the contract permitted the CONNECT, even if upstream later
|
||||
fails. Headers, tokens, URLs and tunneled data are never logged. I6's host
|
||||
appends these lines to the instance's append-only state/egress.jsonl.
|
||||
|
||||
`node --test packages/egress/tests/*.test.mjs` uses loopback echo servers,
|
||||
no credential or external service. `scripts/test-install.sh` additionally
|
||||
runs an internal-network client and checks proxy log decisions. Every test
|
||||
run needs the assigned testing slot.
|
||||
|
||||
The services entries describe current transport gaps in their own notes.
|
||||
Gitea's custom-CA https.request, DNS-pinned web requests, SSH operations and
|
||||
broker git publication have no measured env-proxy path. The separate Discord
|
||||
gateway uses WebSocket; the notifier only uses REST fetch. I2 changes none
|
||||
of those transports. Sage owns the ruling before broker container rollout.
|
||||
Provider hosts are candidates until Mos's measurement on #1652 matches
|
||||
agents/bentina/work/queue-157/PROVIDER-EXPECTED.md. No real token is used
|
||||
in the package or install tests.
|
||||
@@ -0,0 +1 @@
|
||||
{"name":"@mosaic/egress","private":true,"type":"module","scripts":{"test":"node --test tests/*.test.mjs"}}
|
||||
@@ -0,0 +1,13 @@
|
||||
import { readFileSync } from 'node:fs';
|
||||
import { createProxy } from './proxy.mjs';
|
||||
try {
|
||||
if (process.argv.length !== 3) throw new Error('usage: cli.mjs agents|services');
|
||||
const contract = JSON.parse(readFileSync(new URL('../../../contracts/egress.json', import.meta.url), 'utf8'));
|
||||
const server = createProxy({ contract, list: process.argv[2] });
|
||||
server.on('error', () => { process.stderr.write('egress-listen-failed\n'); process.exitCode = 1; });
|
||||
server.listen(3128, '0.0.0.0');
|
||||
for (const signal of ['SIGTERM', 'SIGINT']) process.once(signal, () => server.shutdown());
|
||||
} catch {
|
||||
process.stderr.write('invalid-egress-contract-or-list\n');
|
||||
process.exitCode = 2;
|
||||
}
|
||||
@@ -0,0 +1,92 @@
|
||||
import net from 'node:net';
|
||||
|
||||
const HOST = /^(?=.{1,253}$)[a-z0-9](?:[a-z0-9.-]*[a-z0-9])?$/;
|
||||
// A contract entry always documents its consumer. Metadata never grants a wildcard.
|
||||
export function readList(contract, name) {
|
||||
if (!contract || contract.version !== 1 || !['agents', 'services'].includes(name)
|
||||
|| !Array.isArray(contract[name]) || contract[name].length === 0) throw new Error('invalid-egress-contract');
|
||||
const allowed = new Set();
|
||||
for (const entry of contract[name]) {
|
||||
if (!entry || typeof entry.host !== 'string' || !HOST.test(entry.host)
|
||||
|| entry.host.includes('..') || !Number.isInteger(entry.port) || entry.port < 1 || entry.port > 65535
|
||||
|| typeof entry.path !== 'string' || !entry.path || typeof entry.note !== 'string' || !entry.note) {
|
||||
throw new Error('invalid-egress-contract');
|
||||
}
|
||||
allowed.add(`${entry.host}:${entry.port}`);
|
||||
}
|
||||
return allowed;
|
||||
}
|
||||
|
||||
export function createProxy({ contract, list, log = line => process.stdout.write(line + '\n'),
|
||||
headerTimeoutMs = 5000, connectTimeoutMs = 10000, idleTimeoutMs = 120000, maxHeaderBytes = 8192 } = {}) {
|
||||
const allowed = readList(contract, list);
|
||||
const sockets = new Set();
|
||||
const server = net.createServer(client => {
|
||||
sockets.add(client);
|
||||
client.on('close', () => sockets.delete(client));
|
||||
client.on('error', () => {});
|
||||
let head = Buffer.alloc(0), decided = false;
|
||||
const record = (host, port, permit) => {
|
||||
if (decided) return;
|
||||
decided = true;
|
||||
log(JSON.stringify({ at: new Date().toISOString(), list, host, port, allowed: permit }));
|
||||
};
|
||||
// FIN alone leaves a half-open peer holding our read side. Flush the small
|
||||
// response, then destroy both halves; a fixed drain bound covers a stuck write.
|
||||
const endAndClose = response => {
|
||||
const drain = setTimeout(() => client.destroy(), 1000);
|
||||
client.once('close', () => clearTimeout(drain));
|
||||
client.end(response, () => client.destroy());
|
||||
};
|
||||
const refuse = (code, label, host = null, port = null) => {
|
||||
clearTimeout(timer);
|
||||
client.removeListener('data', onData);
|
||||
record(host, port, false);
|
||||
endAndClose(`HTTP/1.1 ${code} ${label}\r\nConnection: close\r\nContent-Length: 0\r\n\r\n`);
|
||||
};
|
||||
const timer = setTimeout(() => refuse(408, 'Request Timeout'), headerTimeoutMs);
|
||||
client.on('close', () => clearTimeout(timer));
|
||||
function onData(chunk) {
|
||||
head = Buffer.concat([head, chunk]);
|
||||
const end = head.indexOf('\r\n\r\n');
|
||||
if ((end < 0 && head.length > maxHeaderBytes) || (end >= 0 && end + 4 > maxHeaderBytes)) return refuse(431, 'Request Header Fields Too Large');
|
||||
if (end < 0) return;
|
||||
const lines = head.subarray(0, end).toString('latin1').split('\r\n');
|
||||
const match = /^CONNECT ([A-Za-z0-9.-]+):([1-9][0-9]{0,4}) HTTP\/1\.[01]$/.exec(lines.shift());
|
||||
if (!match || lines.some(line => !/^[!#$%&'*+.^_`|~0-9A-Za-z-]+:[\t\x20-\x7e]*$/.test(line))) return refuse(400, 'Bad Request');
|
||||
const host = match[1].toLowerCase(), port = Number(match[2]);
|
||||
if (!HOST.test(host) || host.includes('..') || port > 65535) return refuse(400, 'Bad Request');
|
||||
if (!allowed.has(`${host}:${port}`)) return refuse(403, 'Forbidden', host, port);
|
||||
clearTimeout(timer);
|
||||
client.removeListener('data', onData);
|
||||
client.pause();
|
||||
record(host, port, true);
|
||||
let established = false;
|
||||
const upstream = net.connect({ host, port });
|
||||
sockets.add(upstream);
|
||||
upstream.on('close', () => { sockets.delete(upstream); if (!client.writableEnded) client.destroy(); });
|
||||
client.on('close', () => upstream.destroy());
|
||||
upstream.setTimeout(connectTimeoutMs, () => upstream.destroy(new Error('timeout')));
|
||||
upstream.on('error', () => {
|
||||
if (established) return client.destroy();
|
||||
endAndClose('HTTP/1.1 502 Bad Gateway\r\nConnection: close\r\nContent-Length: 0\r\n\r\n');
|
||||
});
|
||||
upstream.once('connect', () => {
|
||||
established = true;
|
||||
upstream.setTimeout(idleTimeoutMs, () => upstream.destroy());
|
||||
client.setTimeout(idleTimeoutMs, () => client.destroy());
|
||||
client.write('HTTP/1.1 200 Connection Established\r\n\r\n');
|
||||
const pending = head.subarray(end + 4);
|
||||
head = null;
|
||||
if (pending.length) upstream.write(pending);
|
||||
upstream.pipe(client);
|
||||
client.pipe(upstream);
|
||||
client.resume();
|
||||
});
|
||||
}
|
||||
client.on('data', onData);
|
||||
});
|
||||
// Callers close all tunnels explicitly on shutdown, including half headers.
|
||||
server.shutdown = () => { for (const socket of sockets) socket.destroy(); return new Promise(resolve => server.close(resolve)); };
|
||||
return server;
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import net from 'node:net';
|
||||
import { once } from 'node:events';
|
||||
import { createProxy, readList } from '../src/proxy.mjs';
|
||||
const entry=(host,port)=>({host,port,path:'fixture',note:'dummy loopback echo; no external service'});
|
||||
async function fixture(t){
|
||||
const echo=net.createServer(s=>{s.on('error',()=>{});s.pipe(s)});echo.listen(0,'127.0.0.1');await once(echo,'listening');
|
||||
const port=echo.address().port,logs=[];
|
||||
const proxy=createProxy({contract:{version:1,agents:[entry('127.0.0.1',port)]},list:'agents',log:s=>logs.push(JSON.parse(s)),headerTimeoutMs:80});
|
||||
proxy.listen(0,'127.0.0.1');await once(proxy,'listening');
|
||||
t.after(async()=>{await proxy.shutdown();await new Promise(r=>echo.close(r));});
|
||||
function request(bytes,fragment=false){return new Promise((resolve,reject)=>{
|
||||
const s=net.connect(proxy.address().port,'127.0.0.1');let out='';
|
||||
s.on('error',reject);s.setTimeout(2000,()=>s.destroy(new Error('test timeout')));
|
||||
s.on('data',c=>{out+=c.toString();if(out.includes('hello'))s.end();});s.on('close',()=>resolve(out));
|
||||
s.on('connect',()=>{if(fragment){s.write(bytes.slice(0,8));setTimeout(()=>s.write(bytes.slice(8)),5);}else if(bytes)s.write(bytes);});
|
||||
});}
|
||||
return{port,logs,request};
|
||||
}
|
||||
test('allows exact host and port, fragmented headers, and preserves tunnel head',async t=>{
|
||||
const f=await fixture(t);const r=await f.request(`CONNECT 127.0.0.1:${f.port} HTTP/1.1\r\nHost: fixture\r\n\r\nhello`,true);assert.match(r,/^HTTP\/1.1 200/);assert.ok(r.endsWith('hello'));
|
||||
assert.equal(f.logs.length,1);assert.deepEqual(Object.keys(f.logs[0]),['at','list','host','port','allowed']);assert.equal(f.logs[0].allowed,true);assert.equal(f.logs[0].port,f.port);assert.equal(f.logs[0].host,'127.0.0.1');assert.equal(f.logs[0].list,'agents');assert.ok(Number.isFinite(Date.parse(f.logs[0].at)));
|
||||
});
|
||||
for(const [name,line,code] of [['unlisted host','CONNECT example.invalid:443 HTTP/1.1',403],['unlisted port','CONNECT 127.0.0.1:1 HTTP/1.1',403],['GET','GET / HTTP/1.1',400],['invalid port','CONNECT example.com:99999 HTTP/1.1',400],['userinfo','CONNECT [email protected]:443 HTTP/1.1',400],['bad header','CONNECT example.com:443 HTTP/1.1\r\nbad',400]])test(name,async t=>{
|
||||
const f=await fixture(t);assert.match(await f.request(line+'\r\n\r\n'),new RegExp('^HTTP/1.1 '+code));assert.equal(f.logs.length,1);assert.equal(f.logs[0].allowed,false);
|
||||
});
|
||||
test('slow client has a total header deadline',async t=>{const f=await fixture(t);assert.match(await f.request('CON'),/^HTTP\/1.1 408/);assert.equal(f.logs.length,1);assert.equal(f.logs[0].host,null);});
|
||||
test('oversized header refuses',async t=>{const f=await fixture(t);assert.match(await f.request('X'.repeat(9000)),/^HTTP\/1.1 431/);});
|
||||
test('missing, invalid and unknown lists fail closed',()=>{
|
||||
for(const args of [[{},'agents'],[{version:1,agents:[]},'agents'],[{version:1,agents:[entry('*.example.com',443)]},'agents'],[{version:1,agents:[entry('example.com',443)]},'other']])assert.throws(()=>readList(...args),/invalid-egress-contract/);
|
||||
});
|
||||
|
||||
test('refused upstream returns 502 while retaining one allowed decision',async t=>{
|
||||
const closed=net.createServer();closed.listen(0,'127.0.0.1');await once(closed,'listening');const port=closed.address().port;await new Promise(r=>closed.close(r));
|
||||
const logs=[],proxy=createProxy({contract:{version:1,agents:[entry('127.0.0.1',port)]},list:'agents',log:s=>logs.push(JSON.parse(s))});
|
||||
proxy.listen(0,'127.0.0.1');await once(proxy,'listening');t.after(()=>proxy.shutdown());
|
||||
const output=await new Promise((resolve,reject)=>{const s=net.connect(proxy.address().port,'127.0.0.1');let result='';s.on('error',reject);s.setTimeout(2000,()=>s.destroy(new Error('timeout')));s.on('connect',()=>s.write(`CONNECT 127.0.0.1:${port} HTTP/1.1\r\n\r\n`));s.on('data',b=>result+=b);s.on('close',()=>resolve(result));});
|
||||
assert.match(output,/^HTTP\/1.1 502/);assert.equal(logs.length,1);assert.equal(logs[0].allowed,true);
|
||||
});
|
||||
|
||||
test('upstream reset after CONNECT closes the tunnel without a second HTTP response',async t=>{
|
||||
const upstream=net.createServer(s=>{s.on('error',()=>{});s.write('payload');setTimeout(()=>s.resetAndDestroy(),20);});
|
||||
upstream.listen(0,'127.0.0.1');await once(upstream,'listening');const port=upstream.address().port;
|
||||
const proxy=createProxy({contract:{version:1,agents:[entry('127.0.0.1',port)]},list:'agents',log:()=>{}});
|
||||
proxy.listen(0,'127.0.0.1');await once(proxy,'listening');t.after(async()=>{await proxy.shutdown();await new Promise(r=>upstream.close(r));});
|
||||
const output=await new Promise((resolve,reject)=>{const s=net.connect(proxy.address().port,'127.0.0.1');let result='';s.on('error',reject);s.setTimeout(2000,()=>s.destroy(new Error('timeout')));s.on('connect',()=>s.write(`CONNECT 127.0.0.1:${port} HTTP/1.1\r\n\r\n`));s.on('data',b=>result+=b);s.on('close',()=>resolve(result));});
|
||||
assert.match(output,/^HTTP\/1.1 200/);assert.ok(output.endsWith('payload'));assert.equal(output.split('HTTP/1.1').length,2);
|
||||
});
|
||||
@@ -0,0 +1,32 @@
|
||||
// Boyd B1: response receipt is insufficient; the server must release the socket.
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import net from 'node:net';
|
||||
import { once } from 'node:events';
|
||||
import { setTimeout as delay } from 'node:timers/promises';
|
||||
import { createProxy } from '../src/proxy.mjs';
|
||||
|
||||
const count = server => new Promise((resolve,reject) => server.getConnections((error,n) => error ? reject(error) : resolve(n)));
|
||||
const cases = [[408,'CON'],[400,'GET / HTTP/1.1\r\n\r\n'],[403,'CONNECT denied.invalid:443 HTTP/1.1\r\n\r\n'],[431,'X'.repeat(9000)],[502,null]];
|
||||
for (const [code,request] of cases) for (const allowHalfOpen of [false,true]) test(`refusal ${code} releases server socket (allowHalfOpen=${allowHalfOpen})`,async t => {
|
||||
const logs=[];
|
||||
let host='example.invalid',port=443;
|
||||
if(code===502){
|
||||
const closed=net.createServer();closed.listen(0,'127.0.0.1');await once(closed,'listening');
|
||||
host='127.0.0.1';port=closed.address().port;await new Promise(resolve=>closed.close(resolve));
|
||||
}
|
||||
const proxy=createProxy({contract:{version:1,agents:[{host,port,path:'fixture',note:'loopback refusal test'}]},list:'agents',log:line=>logs.push(JSON.parse(line)),headerTimeoutMs:40});
|
||||
proxy.listen(0,'127.0.0.1');await once(proxy,'listening');
|
||||
const client=net.connect({host:'127.0.0.1',port:proxy.address().port,allowHalfOpen});
|
||||
t.after(async()=>{client.destroy();await proxy.shutdown();});
|
||||
let output='';client.on('data',chunk=>output+=chunk.toString());
|
||||
client.setTimeout(2000,()=>client.destroy(new Error('test timeout')));
|
||||
const ended=once(client,'end');
|
||||
await once(client,'connect');client.write(request??`CONNECT ${host}:${port} HTTP/1.1\r\n\r\n`);await ended;
|
||||
assert.match(output,new RegExp('^HTTP/1.1 '+code));
|
||||
const deadline=Date.now()+500;
|
||||
let remaining=await count(proxy);
|
||||
while(remaining && Date.now()<deadline){await delay(10);remaining=await count(proxy);}
|
||||
assert.equal(remaining,0,'refused peer must not retain a server socket');
|
||||
assert.equal(logs.length,1);assert.equal(logs[0].allowed,code===502);
|
||||
});
|
||||
Executable
+36
@@ -0,0 +1,36 @@
|
||||
#!/usr/bin/env bash
|
||||
# Build/list the I2 images, explicitly on the configured rootless endpoint.
|
||||
set -euo pipefail
|
||||
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
source "$REPO/scripts/lib/docker.sh"
|
||||
cd "$REPO"
|
||||
release="$(tr -d '[:space:]' < RELEASE)"
|
||||
[[ "$release" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo invalid-release >&2; exit 2; }
|
||||
[ $# -eq 1 ] || { echo 'usage: images.sh build|list' >&2; exit 4; }
|
||||
case "$1" in
|
||||
list)
|
||||
for image in mosaic-core mosaic-session mosaic-egress; do
|
||||
mosaic_docker image inspect "$image:$release" --format '{{.RepoTags}} {{.Id}}'
|
||||
done
|
||||
;;
|
||||
build)
|
||||
# Attest before preparing the context or making any engine mutation.
|
||||
mosaic_docker version >/dev/null
|
||||
context="$(mktemp -d)"
|
||||
trap 'rm -rf "$context"' EXIT
|
||||
# A source-only context: no .git, host credentials, node_modules or agent work.
|
||||
# Tracked runtime files plus only I2's new runtime paths, from this candidate.
|
||||
while IFS= read -r -d '' file; do
|
||||
[ -f "$file" ] && [ ! -L "$file" ] || { echo "invalid-image-source: $file" >&2; exit 2; }
|
||||
mkdir -p "$context/$(dirname "$file")"
|
||||
cp -- "$file" "$context/$file"
|
||||
done < <({ git ls-files -z -- packages scripts roles contracts package.json package-lock.json RELEASE images;
|
||||
git ls-files --others --exclude-standard -z -- packages/egress contracts/egress.json images scripts/lib/docker.sh scripts/images.sh; })
|
||||
pi_version="$(node -p 'require("./package.json").dependencies["@earendil-works/pi-coding-agent"]')"
|
||||
[[ "$pi_version" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]] || { echo invalid-pi-version >&2; exit 2; }
|
||||
mosaic_docker build --file "$context/images/core/Dockerfile" --tag "mosaic-core:$release" "$context"
|
||||
mosaic_docker build --file "$context/images/session/Dockerfile" --build-arg "CORE_IMAGE=mosaic-core:$release" --build-arg "PI_VERSION=$pi_version" --tag "mosaic-session:$release" "$context"
|
||||
mosaic_docker build --file "$context/images/egress/Dockerfile" --tag "mosaic-egress:$release" "$context"
|
||||
;;
|
||||
*) echo 'usage: images.sh build|list' >&2; exit 4 ;;
|
||||
esac
|
||||
@@ -0,0 +1,28 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync, writeFileSync, readFileSync, rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
const root = new URL('../../', import.meta.url).pathname;
|
||||
const cases = [
|
||||
['absent', undefined, 0], ['local', {mode:'local'}, 0],
|
||||
['local endpoint', {mode:'local',endpoint:'unix:///run/user/1000/docker.sock'}, 2],
|
||||
['containerized missing endpoint', {mode:'containerized'}, 2],
|
||||
...['tcp://localhost:2375','ssh://host/run/docker.sock','run/docker.sock','unix://relative.sock','unix:///a b.sock','unix:///a.sock\n','unix:///a','',null,4].map(endpoint => ['bad endpoint '+JSON.stringify(endpoint),{mode:'containerized',endpoint},2]),
|
||||
['valid endpoint', {mode:'containerized',endpoint:'unix:///run/user/1000/docker.sock'},0],
|
||||
['unknown mode',{mode:'guess'},2], ['missing mode',{},2],
|
||||
['unknown key',{mode:'local',extra:true},2], ['null',null,2], ['array',[],2],
|
||||
];
|
||||
for (const [name, install, code] of cases) test('install: '+name, () => {
|
||||
const scratch=mkdtempSync(join(tmpdir(),'i2-config-'));
|
||||
try {
|
||||
const config=join(scratch,'config.json');
|
||||
const doc={configVersion:1,environment:'development',dataRoot:join(scratch,'data'),execution:{backend:'docker',provider:'zai',model:'m'},...(install===undefined?{}:{install})};
|
||||
const bytes=JSON.stringify(doc)+'\n';writeFileSync(config,bytes);
|
||||
const result=spawnSync(process.execPath,[join(root,'scripts/mosaic-config.mjs'),'validate'],{encoding:'utf8',env:{...process.env,MOSAIC_CONFIG:config}});
|
||||
assert.equal(result.status,code,result.stderr);
|
||||
assert.equal(readFileSync(config,'utf8'),bytes);
|
||||
if(code===0)assert.deepEqual(JSON.parse(result.stdout).install,install);
|
||||
} finally {rmSync(scratch,{recursive:true,force:true});}
|
||||
});
|
||||
@@ -0,0 +1,29 @@
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync, writeFileSync, readFileSync, rmSync, mkdirSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
const root=new URL('../../',import.meta.url).pathname;
|
||||
function fixture(t,{options='["name=rootless"]',status=0,install={mode:'containerized',endpoint:'unix:///i2/test.sock'}}={}){
|
||||
const dir=mkdtempSync(join(tmpdir(),'i2-helper-'));t.after(()=>rmSync(dir,{recursive:true,force:true}));mkdirSync(join(dir,'bin'));
|
||||
const config=join(dir,'config.json'), calls=join(dir,'calls');
|
||||
writeFileSync(config,JSON.stringify({configVersion:1,environment:'development',dataRoot:join(dir,'data'),execution:{backend:'docker',provider:'zai',model:'m'},...(install===null?{}:{install})}));
|
||||
writeFileSync(join(dir,'bin/docker'),`#!/usr/bin/env node\nconst fs=require('node:fs');const a=process.argv.slice(2);fs.appendFileSync(process.env.CALLS,JSON.stringify(a)+'\\n');if(a[2]==='info'){process.stdout.write(process.env.OPTIONS);process.exit(Number(process.env.STATUS));}\n`,{mode:0o755});
|
||||
const run=body=>spawnSync('bash',['-c',`source "$1/scripts/lib/docker.sh"; ${body}`,'bash',root],{encoding:'utf8',env:{...process.env,PATH:join(dir,'bin')+':'+process.env.PATH,MOSAIC_CONFIG:config,CALLS:calls,OPTIONS:options,STATUS:String(status),DOCKER_HOST:'tcp://wrong:2375'}});
|
||||
const read=()=>{try{return readFileSync(calls,'utf8').trim().split('\n').map(JSON.parse)}catch{return []}};
|
||||
return {run,read};
|
||||
}
|
||||
for(const [name,options,status] of [['rootful','["name=seccomp"]',0],['missing','null',1],['substring','["prefix-name=rootless"]',0],['object','{"name":"name=rootless"}',0],['invalid JSON','not-json',0],['null success','null',0],['rootless output but failed','["name=rootless"]',1]])test('helper refuses '+name,t=>{
|
||||
const f=fixture(t,{options,status}),r=f.run('mosaic_docker ps');assert.equal(r.status,2,r.stderr);assert.match(r.stderr,/endpoint-not-rootless/);assert.equal(f.read().length,1);
|
||||
});
|
||||
test('helper checks once and explicitly names host for both calls',t=>{
|
||||
const f=fixture(t),r=f.run('mosaic_docker ps && mosaic_docker image ls');assert.equal(r.status,0,r.stderr);
|
||||
assert.deepEqual(f.read(),[['--host','unix:///i2/test.sock','info','--format','{{json .SecurityOptions}}'],['--host','unix:///i2/test.sock','ps'],['--host','unix:///i2/test.sock','image','ls']]);
|
||||
});
|
||||
for(const install of [null,{mode:'local'}])test('local mode refuses before Docker '+JSON.stringify(install),t=>{
|
||||
const f=fixture(t,{install}),r=f.run('mosaic_docker ps');assert.equal(r.status,2);assert.match(r.stderr,/containerized-install-required/);assert.deepEqual(f.read(),[]);
|
||||
});
|
||||
test('invalid config refuses before Docker',t=>{
|
||||
const f=fixture(t,{install:{mode:'containerized',endpoint:'tcp://wrong'}}),r=f.run('mosaic_docker ps');assert.equal(r.status,2);assert.deepEqual(f.read(),[]);
|
||||
});
|
||||
@@ -0,0 +1,25 @@
|
||||
// Runs in a core image on an internal network. Dummy token only; public GETs only.
|
||||
import assert from 'node:assert/strict';
|
||||
import net from 'node:net';
|
||||
import tls from 'node:tls';
|
||||
import { lookup } from 'node:dns/promises';
|
||||
async function tunnel(host, port, handshake=false) {
|
||||
return new Promise((resolve,reject)=>{
|
||||
const socket=net.connect(3128,'proxy');let head='';
|
||||
const timer=setTimeout(()=>socket.destroy(new Error('proxy timeout')),15000);
|
||||
socket.on('error',reject);socket.on('close',()=>clearTimeout(timer));
|
||||
socket.on('connect',()=>socket.write(`CONNECT ${host}:${port} HTTP/1.1\r\n\r\n`));
|
||||
socket.on('data',function read(chunk){
|
||||
head+=chunk.toString('latin1');if(!head.includes('\r\n\r\n'))return;
|
||||
socket.off('data',read);const status=Number(head.split(' ')[1]);
|
||||
if(status!==200||!handshake){socket.destroy();return resolve(status);}
|
||||
const secure=tls.connect({socket,servername:host});
|
||||
secure.on('error',reject);secure.on('secureConnect',()=>{const ok=secure.authorized;secure.destroy();ok?resolve(status):reject(new Error('TLS unauthorized'));});
|
||||
});
|
||||
});
|
||||
}
|
||||
assert.equal(await tunnel('api.anthropic.com',443,true),200);
|
||||
assert.equal(await tunnel('example.com',443),403);
|
||||
assert.equal(await tunnel('api.anthropic.com',22),403);
|
||||
await assert.rejects(lookup('example.com'));
|
||||
console.log('internal network: listed TLS reached, unlisted host/port refused, external DNS refused');
|
||||
@@ -0,0 +1,25 @@
|
||||
// Boyd B2: exec preserves PID, so a fresh Bash cannot trust inherited cache markers.
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { mkdtempSync,mkdirSync,writeFileSync,readFileSync,rmSync } from 'node:fs';
|
||||
import { tmpdir } from 'node:os';
|
||||
import { join } from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
import { spawnSync } from 'node:child_process';
|
||||
const repo=fileURLToPath(new URL('../../',import.meta.url));
|
||||
for(const rootless of [false,true])test('inherited cache is discarded; private cache stays unexported (rootless='+rootless+')',t=>{
|
||||
const dir=mkdtempSync(join(tmpdir(),'i2-cache-'));t.after(()=>rmSync(dir,{recursive:true,force:true}));
|
||||
const bin=join(dir,'bin'),calls=join(dir,'calls'),config=join(dir,'config.json'),runner=join(dir,'run.sh');mkdirSync(bin);
|
||||
writeFileSync(config,JSON.stringify({configVersion:1,environment:'development',dataRoot:join(dir,'data'),execution:{backend:'docker',provider:'zai',model:'fixture'},install:{mode:'containerized',endpoint:'unix:///configured.sock'}}));
|
||||
writeFileSync(join(bin,'docker'),`#!/usr/bin/env node\nconst fs=require('node:fs');const args=process.argv.slice(2);const cache=Object.keys(process.env).filter(k=>['_mosaic_docker_checked_pid','_mosaic_docker_endpoint','_mosaic_docker_mode'].includes(k));fs.appendFileSync(process.env.CALLS,JSON.stringify({args,cache})+'\\n');if(args[2]==='info')process.stdout.write(process.env.OPTIONS);\n`,{mode:0o755});
|
||||
writeFileSync(runner,'#!/usr/bin/env bash\nset -euo pipefail\nsource "$1/scripts/lib/docker.sh"\nmosaic_docker ps\nmosaic_docker image ls\n');
|
||||
const env={PATH:bin+':'+process.env.PATH,HOME:dir,TMPDIR:dir,MOSAIC_CONFIG:config,CALLS:calls,OPTIONS:rootless?'["name=rootless"]':'["name=seccomp"]'};
|
||||
const result=spawnSync('bash',['-c','export _mosaic_docker_checked_pid="$BASHPID" _mosaic_docker_endpoint=unix:///inherited.sock _mosaic_docker_mode=containerized; exec bash "$1" "$2"','outer',runner,repo],{env,encoding:'utf8',timeout:5000});
|
||||
const rows=readFileSync(calls,'utf8').trim().split('\n').map(JSON.parse);
|
||||
assert.equal(result.status,rootless?0:2,result.stderr);
|
||||
assert.deepEqual(rows[0].args,['--host','unix:///configured.sock','info','--format','{{json .SecurityOptions}}']);
|
||||
assert.equal(rows.length,rootless?3:1);
|
||||
for(const row of rows)assert.deepEqual(row.cache,[],'attestation cache must not reach child environment');
|
||||
if(rootless)assert.deepEqual(rows.slice(1).map(r=>r.args),[['--host','unix:///configured.sock','ps'],['--host','unix:///configured.sock','image','ls']]);
|
||||
else assert.match(result.stderr,/endpoint-not-rootless/);
|
||||
});
|
||||
@@ -0,0 +1,9 @@
|
||||
// Real service-client code, dummy/no token, public GET only. No notifier state/config.
|
||||
import assert from 'node:assert/strict';
|
||||
import { client } from '../../packages/tasks/src/vikunja.mjs';
|
||||
import { createRest } from '../../packages/discord/src/rest.mjs';
|
||||
assert.equal(process.env.NODE_USE_ENV_PROXY,'1');
|
||||
const result=await client({baseUrl:'https://tasks.woltje.com'})('', 'GET','/info');
|
||||
assert.equal(result.status,200);
|
||||
await assert.rejects(createRest({token:'i2-dummy-invalid-token'}).getMe(),e=>e.details?.status===401);
|
||||
console.log('Vikunja client public GET 200; notifier REST dummy-token GET 401');
|
||||
@@ -0,0 +1,33 @@
|
||||
# Containerized-install Docker entry point. Source from a host script, then
|
||||
# call mosaic_docker. No ambient Docker context or endpoint environment is used.
|
||||
# scripts/common.sh owns load_config (there is no scripts/load_config file).
|
||||
# An exec can inherit the same PID and exported cache values. Only attestations
|
||||
# performed after this source may populate the process-local, unexported cache.
|
||||
unset _mosaic_docker_checked_pid _mosaic_docker_endpoint _mosaic_docker_mode
|
||||
_mosaic_docker_repo="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
|
||||
source "$_mosaic_docker_repo/scripts/common.sh"
|
||||
|
||||
mosaic_docker() {
|
||||
if [ "${_mosaic_docker_checked_pid:-}" != "$BASHPID" ]; then
|
||||
# load_config's loader path is repository relative.
|
||||
local config_env
|
||||
config_env="$(cd "$_mosaic_docker_repo" && load_config && printf '%s\n' "$MOSAIC_INSTALL_MODE" "$MOSAIC_DOCKER_ENDPOINT")" || return 2
|
||||
_mosaic_docker_mode="${config_env%%$'\n'*}"
|
||||
_mosaic_docker_endpoint="${config_env#*$'\n'}"
|
||||
if [ "$_mosaic_docker_mode" != containerized ]; then
|
||||
echo "containerized-install-required" >&2
|
||||
return 2
|
||||
fi
|
||||
local options
|
||||
if ! options="$(command docker --host "$_mosaic_docker_endpoint" info --format '{{json .SecurityOptions}}' 2>/dev/null)"; then
|
||||
echo "endpoint-not-rootless" >&2
|
||||
return 2
|
||||
fi
|
||||
if ! printf '%s' "$options" | node -e 'let s="";process.stdin.on("data",c=>s+=c).on("end",()=>{try {const a=JSON.parse(s);process.exit(Array.isArray(a)&&a.includes("name=rootless")?0:1)}catch{process.exit(1)}})'; then
|
||||
echo "endpoint-not-rootless" >&2
|
||||
return 2
|
||||
fi
|
||||
_mosaic_docker_checked_pid="$BASHPID"
|
||||
fi
|
||||
command docker --host "$_mosaic_docker_endpoint" "$@"
|
||||
}
|
||||
@@ -290,6 +290,21 @@ function validateFederation(value) {
|
||||
return { self: value.self, peers };
|
||||
}
|
||||
|
||||
// I2: shape only. The endpoint helper, never configuration validation, checks Docker.
|
||||
function validateInstall(value) {
|
||||
if (!isPlainObject(value)) fail(2, '"install" must be a JSON object');
|
||||
rejectUnknownKeys(value, ["mode", "endpoint"], '"install"');
|
||||
if (!["local", "containerized"].includes(value.mode)) fail(2, 'install.mode must be local or containerized');
|
||||
if (value.mode === "local") {
|
||||
if (Object.hasOwn(value, "endpoint")) fail(2, 'install.endpoint is forbidden in local mode');
|
||||
return { mode: "local" };
|
||||
}
|
||||
if (typeof value.endpoint !== "string" || !/^unix:\/\/\/[^\s]+\.sock$/.test(value.endpoint) || /\s/.test(value.endpoint) || value.endpoint.includes("\0")) {
|
||||
fail(2, 'install.endpoint must match ^unix:///[^\\s]+\\.sock$');
|
||||
}
|
||||
return { mode: "containerized", endpoint: value.endpoint };
|
||||
}
|
||||
|
||||
async function validate(document, file) {
|
||||
if (!isPlainObject(document)) {
|
||||
fail(2, "configuration must be a JSON object");
|
||||
@@ -301,7 +316,7 @@ async function validate(document, file) {
|
||||
// packages/cli/tests/stack-release-fixture.mjs edits it to make an older loader.
|
||||
rejectUnknownKeys(
|
||||
document,
|
||||
["configVersion", "environment", "dataRoot", "execution", "console", "federation"].concat(["compactionReadBytes", "compactionStreams"]),
|
||||
["configVersion", "environment", "dataRoot", "execution", "console", "federation"].concat(["compactionReadBytes", "compactionStreams", "install"]),
|
||||
"configuration",
|
||||
);
|
||||
|
||||
@@ -343,6 +358,7 @@ async function validate(document, file) {
|
||||
const federationKey = Object.hasOwn(document, "federation") ? validateFederation(document.federation) : undefined;
|
||||
|
||||
return {
|
||||
...(Object.hasOwn(document, "install") ? { install: validateInstall(document.install) } : {}),
|
||||
configVersion: document.configVersion,
|
||||
environment: document.environment,
|
||||
dataRoot,
|
||||
@@ -537,6 +553,8 @@ try {
|
||||
`MOSAIC_PROVIDER=${shellQuote(resolved.execution.provider)}`,
|
||||
`MOSAIC_MODEL=${shellQuote(resolved.execution.model)}`,
|
||||
`MOSAIC_ADAPTER=${shellQuote(resolved.execution.adapter)}`,
|
||||
`MOSAIC_INSTALL_MODE=${shellQuote(resolved.install?.mode ?? "local")}`,
|
||||
`MOSAIC_DOCKER_ENDPOINT=${shellQuote(resolved.install?.endpoint ?? "")}`,
|
||||
"",
|
||||
].join("\n"),
|
||||
);
|
||||
|
||||
@@ -291,5 +291,9 @@ else
|
||||
fi
|
||||
|
||||
echo
|
||||
# I2: each install shape asserts its exit code and config-byte preservation.
|
||||
node --test scripts/install-tests/config.test.mjs
|
||||
check "install configuration cases" $?
|
||||
|
||||
echo "selftest: $PASS passed, $FAIL failed"
|
||||
[ "$FAIL" -eq 0 ]
|
||||
Executable
+75
@@ -0,0 +1,75 @@
|
||||
#!/usr/bin/env bash
|
||||
# I2 only. No live config, credentials, daemon/volume changes or ambient endpoint.
|
||||
set -euo pipefail
|
||||
REPO="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
|
||||
cd "$REPO"
|
||||
node --test scripts/install-tests/*.test.mjs
|
||||
node --test packages/egress/tests/*.test.mjs
|
||||
endpoint="${MOSAIC_TEST_DOCKER_ENDPOINT:-unix:///run/user/1000/docker.sock}"
|
||||
# No skip: a missing/refused endpoint is a failed prerequisite to report to Sage.
|
||||
scratch="$(mktemp -d)"
|
||||
prefix="i2-$(date -u +%s)-$$"
|
||||
containers=(); networks=()
|
||||
cleanup() {
|
||||
local rc=$?
|
||||
trap - EXIT
|
||||
for name in "${containers[@]}"; do mosaic_docker container rm -f "$name" >/dev/null 2>&1 || true; done
|
||||
for name in "${networks[@]}"; do mosaic_docker network rm "$name" >/dev/null 2>&1 || true; done
|
||||
rm -rf "$scratch"
|
||||
exit "$rc"
|
||||
}
|
||||
trap cleanup EXIT
|
||||
node - "$scratch" "$endpoint" <<'JS'
|
||||
const fs=require('node:fs'),path=require('node:path');const [dir,endpoint]=process.argv.slice(2);
|
||||
fs.writeFileSync(path.join(dir,'config.json'),JSON.stringify({configVersion:1,environment:'development',dataRoot:path.join(dir,'data'),execution:{backend:'docker',provider:'zai',model:'fixture'},install:{mode:'containerized',endpoint}}));
|
||||
JS
|
||||
export MOSAIC_CONFIG="$scratch/config.json"
|
||||
source scripts/lib/docker.sh
|
||||
mosaic_docker version >/dev/null
|
||||
scripts/images.sh build
|
||||
release="$(tr -d '[:space:]' < RELEASE)"
|
||||
# Image assertions: versions, release and no git in core; no stack is launched.
|
||||
containers+=("$prefix-core-check")
|
||||
mosaic_docker run --rm --name "$prefix-core-check" --read-only --network none --cap-drop ALL --security-opt no-new-privileges "mosaic-core:$release" node -e 'const fs=require("node:fs");if(process.version!=="v24.21.0")process.exit(1);if(fs.existsSync("/usr/bin/git"))process.exit(1);if(fs.readFileSync("RELEASE","utf8").trim()!==process.argv[1])process.exit(1)' "$release"
|
||||
containers+=("$prefix-session-check")
|
||||
mosaic_docker run --rm --name "$prefix-session-check" --read-only --network none --cap-drop ALL --security-opt no-new-privileges "mosaic-session:$release" sh -c 'test "$(claude --version | cut -d " " -f1)" = 2.1.296 && test "$(pi --version)" = 0.85.1'
|
||||
networks+=("$prefix-internal" "$prefix-egress")
|
||||
mosaic_docker network create --internal "$prefix-internal" >/dev/null
|
||||
mosaic_docker network create "$prefix-egress" >/dev/null
|
||||
# Two lists are tested one after the other. The I6 two-network separation gate stays I6's.
|
||||
for list in agents services; do
|
||||
proxy="$prefix-$list"; containers+=("$proxy")
|
||||
mosaic_docker create --name "$proxy" --network "$prefix-egress" --read-only --cap-drop ALL --security-opt no-new-privileges --pids-limit 64 --memory 128m --memory-swap 128m --cpus 0.5 "mosaic-egress:$release" "$list" >/dev/null
|
||||
mosaic_docker network connect --alias proxy "$prefix-internal" "$proxy"
|
||||
mosaic_docker start "$proxy" >/dev/null
|
||||
# Bounded readiness on the container's own loopback; no published port.
|
||||
ready=0
|
||||
for attempt in {1..50}; do
|
||||
if mosaic_docker exec "$proxy" node -e 'const s=require("node:net").connect(3128,"127.0.0.1");s.on("connect",()=>{s.destroy();process.exit(0)});s.on("error",()=>process.exit(1));s.setTimeout(500,()=>process.exit(1))'; then ready=1; break; fi
|
||||
sleep 0.1
|
||||
done
|
||||
[ "$ready" -eq 1 ] || { echo proxy-not-ready >&2; exit 1; }
|
||||
if [ "$list" = agents ]; then
|
||||
containers+=("$prefix-probe-$list")
|
||||
mosaic_docker run --rm --name "$prefix-probe-$list" --network "$prefix-internal" --read-only --cap-drop ALL --security-opt no-new-privileges \
|
||||
--mount "type=bind,src=$REPO/scripts/install-tests/network-probe.mjs,dst=/opt/mosaic/scripts/install-tests/network-probe.mjs,readonly" \
|
||||
"mosaic-core:$release" node scripts/install-tests/network-probe.mjs
|
||||
else
|
||||
# Service entry points are checked directly; S5's generic fetch probe is not repeated.
|
||||
containers+=("$prefix-probe-$list")
|
||||
mosaic_docker run --rm --name "$prefix-probe-$list" --network "$prefix-internal" --read-only --cap-drop ALL --security-opt no-new-privileges \
|
||||
-e NODE_USE_ENV_PROXY=1 -e HTTPS_PROXY=http://proxy:3128 \
|
||||
--mount "type=bind,src=$REPO/scripts/install-tests/services-probe.mjs,dst=/opt/mosaic/scripts/install-tests/services-probe.mjs,readonly" \
|
||||
"mosaic-core:$release" node scripts/install-tests/services-probe.mjs
|
||||
fi
|
||||
mosaic_docker logs "$proxy" > "$scratch/$list.jsonl"
|
||||
node - "$scratch/$list.jsonl" "$list" <<'JS'
|
||||
const fs=require('node:fs'),assert=require('node:assert/strict');const [file,list]=process.argv.slice(2);
|
||||
const lines=fs.readFileSync(file,'utf8').trim().split('\n').map(JSON.parse);
|
||||
assert.ok(lines.some(l=>l.allowed));for(const line of lines){assert.deepEqual(Object.keys(line),['at','list','host','port','allowed']);assert.equal(line.list,list);}
|
||||
if(list==='agents')assert.ok(lines.some(l=>l.host==='example.com'&&!l.allowed));
|
||||
else for(const host of ['tasks.woltje.com','discord.com'])assert.ok(lines.some(l=>l.host===host&&l.allowed));
|
||||
JS
|
||||
mosaic_docker container rm -f "$proxy" >/dev/null
|
||||
done
|
||||
echo 'test-install: PASS (provider-host measurement remains Mos gate)'
|
||||
Reference in new issue
Block a user