- Sweep every remaining 'admin-only' claim (workflow comments, runbook
lines 13/40, topology note, all zh pairs): the variable is
writer-manageable, and the boundary against untrusted code is
repository membership (private, forking disabled, Dependabot
excluded) — stated identically at every site instead of only in the
'who can flip' paragraph.
- Serial cross-platform reference note (both languages): master now
runs four references — the three hosted OS legs plus the self-hosted
standby drill, linked to the failover runbook.
Static gate green locally: 32 passed, 0 failed.
The linked implemented note is the current-state authority for both one-line
todo surfaces and still specified a single in-progress item: "the in-progress
item as the collapsed one-line hint" and "N/M done · active item". Since the
cap on parallel in_progress items was lifted, both surfaces name the first
active item and count the rest in a separate non-shrinking span.
Bilingual pair updated together and re-recorded.
- serial-linux-selfhosted now fetches depth 2 and passes
DSH_ARCHIVE_BASE_REF=github.event.before, running the same
frozen-archive comparison as serial-linux instead of diffing the
new manifest against itself.
- Runbook (both languages): documents the deliberate dependabot
exception (queued-on-hosted during failover is expected, not a
failed switch); corrects the emergency-capacity bootstrap to
exclude .runner/.credentials when cloning a runner directory; and
replaces the 'admin-only' variable claim with the accurate
trust-model statement — repository variables are writer-manageable,
which in this private fork-disabled repo with an all-workflows
runner group is routing among members, not an escalation.
Static gate green locally: 32 passed, 0 failed.
- All four failover selectors (three workers + the verdict job) and the
paired env/cache expressions now exclude dependabot[bot]: under
failover, dependency-supplied code keeps queueing for the hosted pool
instead of executing on the persistent VM. A delayed Dependabot PR
during an outage is an acceptable cost; dependency code on the
privileged host is not.
- Runbook (both languages): records the shipped failover bounds
(coverage 8, snapshots 12, sized for six instances) and documents
that the verdict job follows the selector too — operators previously
had no explanation for a verdict queued after all workers passed.
- Local static gate green: 32 passed, 0 failed (translation pairing
519 pairs consistent).
The pairing gate requires link target #9 to be byte-identical between
the language sides; my earlier 'fix' pointed the zh side at the zh
runbook and broke the contract. Reverted to the shared target and
re-recorded the pairing hash.
- all-checks-passed now resolves its pool through the same
DSH_CI_FAILOVER expression as the worker jobs it aggregates.
Pinned to the hosted pool it would leave the branch-protection
verdict queued on the failed pool after every failover job passed —
observed live during the 2026-07-27 outage as a required check
looping against dead capacity.
- Coverage worker bound under failover drops 12 → 8 and snapshot
concurrency 16 → 12: the pool now runs six always-on instances (the
spare tier was retired), so worst case is 6 × 8 = 48 coverage
workers on the shared 64-core VM.
Both todo one-line surfaces truncate the active hint with overflow: hidden
and text-overflow: ellipsis. A "+N" appended to the first active task's name
therefore sat at the far end of the truncatable text, so a long task name or a
narrow viewport clipped exactly the part that reports the other running tasks,
leaving a parallel plan indistinguishable from a sequential one.
planSummary now returns activeContent and activeExtra as separate fields
instead of one joined activeHint, and each surface renders the count in its own
flex: none span beside the ellipsized name: .activeExtra in the collapsed plan
strip header, .extra in the todo_write row. Putting the count in front of the
name was rejected — the task name is what the reader looks for first.
The parallel-plan cases in todo-panel.spec.tsx now assert the count is a
separate element from the name, and both fail if the two are rejoined. The
assembled web snapshot re-records: the flex gap supplies the visual space, so
the transcript reads "实现 fixture 样本+1" with no space in the text nodes.
Lifting the single-in_progress cap makes a list shape reachable that the
web surfaces never received. Two sites derived their one-line summary with
todos.find(t => t.status === 'in_progress') — the collapsed TodoPanel header
and the todo_write row — which was total under the old cap and silently
dropped every active item but the first once several could match: a plan
with three running tasks collapsed to the name of one. The expanded list
was always correct, so neither PR's tests covered it.
Both sites now take planSummary in contract/todo-plan-model.ts, the
domain-shared face both the skeleton and toolviews domains may import; the
duplicated derivation was why one find could be fixed while the other
stayed wrong. The hint names the first active item and suffixes +<n> for
the rest, so the collapsed line reports how many tasks are running.
The web fixture's todo sample now runs two items in_progress, so the
assembled web transcript replays a parallel plan: the row reads
'1/4 已完成 · 实现 fixture 样本 +1' over the built bundles.
Stack the parallel-in_progress change on the web todo display (#497): the
GUI is now the surface where several active items are visible, so the two
land as a chain rather than colliding on tool-todo at merge time.
Conflicts combined rather than resolved to one side: tool-todo keeps this
branch's parallel-allowing validation AND web2-todo's additionalProperties
unknown-key rejection, in src/index.ts and both README sides; the spec
drops web2-todo's 'two in_progress' rejection case and keeps its unknown-key
case; the two headless advanced-toolchain session fixtures keep this
branch's parallel transcripts.
The required windows job moves from windows-2025 to ubuntu-latest, running
checksum-verified Windows Node under Wine at Linux-job wall clock (2m46s
warm vs 7-9min); master's serial-windows native-kernel reference is
untouched, and a new master-only wine-apt-cache job seeds the apt cache
every pull request restores. The experiment workflow folds into ci.yml,
the Agent Note moves to implemented with measured results, and the two CI
topology notes update to the shipped facts.
The spare tier is retired. Steady-state pool load is one serial standby
job per master push, so six always-on instances already are the
failover capacity; pre-registered offline runners are a silently
expiring guarantee (GitHub garbage-collects them after 30 days
offline). Incident-time extra capacity is a one-minute org-token
registration, now documented in the runbook.
Two review findings on the turndown swap, both verified empirically:
- Unclosed-tag nesting makes the synchronous turndown/domino walk
superlinear (measured: depth 512 ~0.15s, 2k ~2s, 20k ~5s), during
which the cooperative fetchTimeoutMs timer cannot fire. renderBody
now preflights nesting depth with a linear tag scan and passes
bodies past 512 levels through raw; the try/catch stays for markup
the scan cannot see (comment-hidden tags), simulated in tests via a
converter throw.
- Markdown escaping can expand converted HTML ~2x (100k underscores
render as 200k chars), so provider body caps no longer bounded the
model-visible result. formatFetchOutput now caps the complete output
(header + body + footer) under new fetchMaxOutputChars config
(default 200000 = 2x the local provider's default body cap), reusing
the truncation notice.
README EN+ZH, config catalog, Agent Note EN+ZH updated; the new
web-fetch fixture is migrated to the packed layout master now
requires; tool-web coverage stays 100% per-file.