Skip to content

fix(runtime): read the boot envelope as a rendering, and walk the scan window - #235

Merged
gcko merged 2 commits into
mainfrom
fix/spacedock-boot-rendering
Aug 28, 2026
Merged

fix(runtime): read the boot envelope as a rendering, and walk the scan window#235
gcko merged 2 commits into
mainfrom
fix/spacedock-boot-rendering

Conversation

@gcko

@gcko gcko commented Aug 28, 2026

Copy link
Copy Markdown
Contributor

What

The Spacedock strip showed A workflow exists, but nothing is fresh enough to show. for the entire life of a running workflow. It is not a freshness problem. Two independent defects in the boot-envelope read, each fatal on its own.

The envelope arrives rendered, not raw

boot_records looked for the literal {"command" of a JSON object. The first-officer skill tells it to run status --boot --identify --json and "consume JSON, not the human table", but nothing tells it to echo that JSON. Both real first-officer sessions measured piped it through a formatter, so the transcript carried an indented key/value rendering and the object never appeared.

Across 120 transcripts over 21 days the JSON branch matched exactly one file, and that one was this repository's own test fixtures catted into a tool result. The strip had never fired on a real session.

The fields are now read line by line as well. The trust model is unchanged: a rendered path is gated on a top-level command: boot exactly as the JSON branch is gated on envelope["command"] == "boot", only column-0 keys are read so a nested decoy cannot nominate one, and every downstream guard (_usable_dir, canonicalisation, the symlink and identity checks, commissioned-by: spacedock@) still stands between an extracted path and anything published.

And it is not in the head

The scan read the first spacedock_boot_scan_bytes on the reasoning that boot output is written at session start. A first officer greets and discovers before it boots: the two sessions measured booted at 69% and 73% of the way through their transcripts, at bytes 803,503 and 821,199.

Raising the cap only moves the guess and is the expensive direction, since the old (path, size) key misses on every write below the cap and re-reads the whole file under the collection lock. The window walks instead: each pass reads at most spacedock_boot_scan_bytes of not-yet-scanned bytes and remembers how far it reached, stopping on a line boundary and stepping over a record longer than the window. A shorter file restarts the walk.

Verification

Run against the live transcript that produced the empty panel, at default config:

refresh 1: envelopes=0 scanned=511,391
refresh 2: envelopes=1 scanned=984,242
  STRIP: roadmap-burndown  stages=[selection, triage, implementation, review, done]
    - drc-4029     triage         live=True
    - drc-4021     selection      live=False

drc-4029 on triage matches the running worker spacedock-ensign-drc-4029-triage.

6 new tests: the rendered envelope parses; a rendering without command: boot nominates nothing; a nested key cannot nominate a path; a rendering with no definition_dir is refused; the scan walks forward across refreshes (asserted non-vacuous); a shorter transcript restarts the walk.

Full pre-PR suite green: ruff, ruff format --check, mypy --strict, lint_embedded, validate_plugins, 1941 tests, coverage 90.8% (spacedock.py 90.3%). No version fields touched.

Docs

Reasoning recorded as S-7 in docs/design-spacedock.md. Corrected the shipped skill body (which told readers a deep boot output means no strip at all) and the architecture doc's spacedock_boot_cache description. Sync marker left alone per the parallel-branch rule.

🤖 Generated with Claude Code

gcko and others added 2 commits August 28, 2026 10:29
…n window

The Spacedock strip published nothing while a workflow was plainly running,
reporting "A workflow exists, but nothing is fresh enough to show" — which
reads like a freshness problem and is not one. Two independent defects, each
fatal on its own.

The envelope arrives rendered, not raw. `boot_records` looked for the literal
`{"command"` of a JSON object. The first officer's skill tells it to run
`status --boot --identify --json`, but nothing tells it to echo that JSON, and
both real first-officer sessions measured piped it through a formatter, so the
transcript carried an indented key/value rendering and the object never
appeared. Across 120 transcripts over 21 days the JSON branch matched exactly
one file, and that one was this repository's own fixtures catted into a tool
result: the strip had never fired on a real session.

So the fields are read line by line as well. The trust model is unchanged — a
rendered path is gated on a top-level `command: boot` exactly as the JSON
branch is gated on `envelope["command"] == "boot"`, only column-0 keys are
read so a nested decoy cannot nominate one, and every downstream guard still
stands between an extracted path and anything published.

And it is not in the head. The scan read the first `spacedock_boot_scan_bytes`
on the reasoning that boot output is written at session start. A first officer
greets and discovers before it boots: the two sessions measured booted at 69%
and 73% of the way through their transcripts, at bytes 803,503 and 821,199.
Raising the cap only moves the guess and is the expensive direction, because
the old `(path, size)` key misses on every write below the cap. The window
walks instead — each pass reads at most `spacedock_boot_scan_bytes` of
not-yet-scanned bytes and remembers how far it reached, stopping on a line
boundary and stepping over a record longer than the window.

Verified end to end against the live transcript: the strip now renders
roadmap-burndown with drc-4029 live on triage, matching the running worker.

Records the reasoning as S-7 in docs/design-spacedock.md.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
The shipped skill body told readers no strip appears when the boot output sits
outside the scanned head of a long transcript. The window walks now, so a late
boot is a lag of a refresh or two rather than a permanent blank.

The architecture doc described `spacedock_boot_cache` as keyed on a path and a
bounded size; it holds a scan position beside its records instead. Its
`spacedock.py` row gains the S-7 distinction that provenance settles where an
envelope may come from, not what it looks like once it is there.

S-7 rewritten to the voice standard: no em dashes, no boldface leads.

Marker left alone, per the parallel-branch rule.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Jared Scott <jared.scott@variable.team>
@github-actions

Copy link
Copy Markdown
Contributor

Coverage

Name                                                                  Stmts   Miss Branch BrPart  Cover
-------------------------------------------------------------------------------------------------------
cargento/skills/cargento/agy_hook.py                                     79     14     28      7  78.5%
cargento/skills/cargento/cargento_runtime/__init__.py                     0      0      0      0 100.0%
cargento/skills/cargento/cargento_runtime/aggregate.py                  212      1     64      0  99.6%
cargento/skills/cargento/cargento_runtime/asks.py                       110      0     28      0 100.0%
cargento/skills/cargento/cargento_runtime/claude_data.py                305     33    142     16  89.0%
cargento/skills/cargento/cargento_runtime/cli.py                        126     14     26      3  87.5%
cargento/skills/cargento/cargento_runtime/collectors/__init__.py          0      0      0      0 100.0%
cargento/skills/cargento/cargento_runtime/collectors/antigravity.py     410     41    166     25  87.2%
cargento/skills/cargento/cargento_runtime/collectors/claude.py          261     19     94     12  90.7%
cargento/skills/cargento/cargento_runtime/collectors/codex.py            98      7     38      7  89.7%
cargento/skills/cargento/cargento_runtime/collectors/copilot.py         148      6     50      2  96.0%
cargento/skills/cargento/cargento_runtime/collectors/cursor.py          277     20    106     17  89.8%
cargento/skills/cargento/cargento_runtime/collectors/droid.py            32      3      6      1  89.5%
cargento/skills/cargento/cargento_runtime/collectors/gemini.py           53      7     16      4  84.1%
cargento/skills/cargento/cargento_runtime/collectors/goose.py            89     11     28      4  87.2%
cargento/skills/cargento/cargento_runtime/collectors/opencode.py         78      6     26      2  92.3%
cargento/skills/cargento/cargento_runtime/collectors/pi.py              326     34    152     20  88.7%
cargento/skills/cargento/cargento_runtime/config.py                     187      1     20      1  99.0%
cargento/skills/cargento/cargento_runtime/diagnostics.py                 84      4     26      4  92.7%
cargento/skills/cargento/cargento_runtime/dismissals.py                 113      2     28      2  97.2%
cargento/skills/cargento/cargento_runtime/events.py                     162      0     62      0 100.0%
cargento/skills/cargento/cargento_runtime/http_api.py                   515     31    176      9  94.2%
cargento/skills/cargento/cargento_runtime/io.py                         126      2     28      0  98.7%
cargento/skills/cargento/cargento_runtime/lifecycle.py                  323     15    104      6  95.1%
cargento/skills/cargento/cargento_runtime/notifications.py              174     14     60      4  91.5%
cargento/skills/cargento/cargento_runtime/observation.py                235      2     64      0  99.3%
cargento/skills/cargento/cargento_runtime/observer.py                   249     34    110     13  84.7%
cargento/skills/cargento/cargento_runtime/probe.py                       44      0     18      1  98.4%
cargento/skills/cargento/cargento_runtime/quota.py                      333      2    112      1  99.3%
cargento/skills/cargento/cargento_runtime/records.py                    256      5    114      9  96.2%
cargento/skills/cargento/cargento_runtime/sessions.py                   101      0     44      0 100.0%
cargento/skills/cargento/cargento_runtime/snapshot.py                    36      0      4      0 100.0%
cargento/skills/cargento/cargento_runtime/spacedock.py                  456     48    238     26  89.0%
cargento/skills/cargento/cargento_runtime/state.py                       64      0      2      0 100.0%
cargento/skills/cargento/cargento_runtime/stream.py                      57      0      8      0 100.0%
cargento/skills/cargento/cargento_runtime/transcripts.py                361     16    192     15  94.4%
cargento/skills/cargento/cargento_runtime/turns.py                      197     14    104     12  90.7%
cargento/skills/cargento/cargento_runtime/web/__init__.py                 0      0      0      0 100.0%
cargento/skills/cargento/cargento_runtime/web/page.py                    54      0     14      0 100.0%
cargento/skills/cargento/event_hook.py                                   86      4     28      3  93.9%
cargento/skills/cargento/mcp_server.py                                  377     22    112     14  92.2%
cargento/skills/cargento/notify_hook.py                                  49     15      6      1  67.3%
cargento/skills/cargento/server.py                                        3      0      2      1  80.0%
cargento/skills/cargento/statusline_hook.py                             131     13     46      8  87.0%
scripts/bench_collect.py                                                211     10     54      6  94.0%
scripts/bench_event_latency.py                                           67     67     14      0   0.0%
scripts/bump_version.py                                                  60     12     24      5  77.4%
scripts/capture_hook.py                                                 287     30     86     11  88.5%
scripts/derive_prompt_shapes.py                                         210     16     88     14  89.3%
scripts/lint_embedded.py                                                 92      3     28      2  95.8%
scripts/validate_plugins.py                                             663    186    386     58  69.3%
-------------------------------------------------------------------------------------------------------
TOTAL                                                                  8967    784   3372    346  89.8%

Threshold: fail_under in pyproject.toml · label coverage-exception to bypass (visible in PR timeline).

@gcko
gcko merged commit 0c2c943 into main Aug 28, 2026
12 checks passed
@gcko
gcko deleted the fix/spacedock-boot-rendering branch August 28, 2026 04:14
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant