- 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.
- 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.
The darwin-parity job failed intermittently on the SIGINT test with the
operation buffer holding only the echoed command line, never RAW_READY. The
harness sets idleSilenceMs to 250, so when a cold python3 start stays silent
past that bound the send settles as inferred_idle; PtySendOperation.append then
drops all later output, and the marker reaches only the scrollback.
Give the harness per-test idleSilenceMs/timeoutMs overrides and let this
scenario raise both above interpreter startup latency, so the readiness marker
lands inside the send it belongs to. waitForOutput's own deadline and the test
timeout grow to match the new bounds.
The product timings are unchanged; the pty Agent Note records why a test that
waits on an operation must outlast the child's startup.
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.
Review follow-up on the symmetric-cache conversion: provisioning and
caching are separable concerns, so the remaining 8 corepack enable
sites (the three enterprise restore-only PR jobs, the required Windows
job, serial-macos/serial-windows, the cache-less ci.yml jobs, and
sandbox.yml) now provision pnpm via pnpm/action-setup@v4 too — only
the provisioning line moves; every deliberate cache shape (enterprise
restore-only pairing, serial-linux producer, Windows cache-less) is
byte-preserved. corepack is gone from CI entirely.
Agent Note retitled to the decision's real shape (provisioning policy,
with caching as per-job policy on top), EN+ZH, pair re-recorded;
composite-action and stop-at-cache-workflows alternatives recorded.
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.
vi.waitFor retries every callback throw, so the mismatch branch inside
the callback waited the full 30s deadline for a fixture that writes the
marker once and cannot recover. Terminal states (complete marker, or
content that can no longer become the expected marker) now resolve out
of the retry loop and the mismatch throws after it, restoring the old
loop's immediate failure.
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.
The todo row and the todo plan strip were registered without a contract in this
package's README, which still described only the bash sample. Both sides now
carry the two registrations: TodoRow's `todo_write` toolview key with its
attempted-write summary and non-ok dot semantics, and TodoDock's
`conversation.input.dock` entry at `order: -1` with the dock-owned selection,
empty-hidden panel, collapse behavior, and the composer-takeover consequence
that hides the whole dock. README.i18n.yaml re-recorded.
Three ds-review-bot round-8 findings on the todo display surfaces.
TodoPanel took the whole `useSession` hook and cast the snapshot to reach
`todos`, which put slot plumbing and an unchecked cast inside the presentation
component. The panel now takes `todos: readonly TodoItem[]`; TodoDock does the
selecting, matching the QueueDock posture the dock slot already establishes.
The todo row carried `onClick` with no keyboard route, so its details panel was
mouse-only. It now takes ToolRow's route verbatim: `role="button"`,
`tabIndex={0}`, and an Enter/Space handler that claims the event. The row stays
a `<div>` because a `<button>` flattens its inline spans.
`session.history`'s tail-only `todos` field was documented at the TS signature
but not in the apiproxy README pair, and the Agent Note claimed the feature
added no new wire vocabulary. Both README sides now state the tail/omission
semantics (an omitted field on a tail response is the empty plan, not unchanged
state), and the note records the one added field instead of denying it.
Tests: TodoPanel specs render the plain list; new TodoDock specs cover selection,
live follow, rollback-to-empty, and the registration shape; a new row spec pins
Enter/Space activation and non-activating keys.