- DispatchSection: add cloud-hypervisor option (labelled experimental,
default off); default stays docker; hint states a KVM-host CHV worker
must claim the task (otherwise it stays queued; attachments are
rejected by the backend at creation) and that the frontend only passes
the executor value through, never bypassing the backend allowlist.
- EnvSection: same option, because the backend env-create allowlist
(local/docker/cloud-hypervisor) does allow it; note states env
allowlist excludes cloud.
- TaskListSection: add cloud-hypervisor to the executor filter so CHV
tasks are findable; list rows already showed executor.
- TaskDetailPanel: pure display note for cloud-hypervisor tasks
(experimental, default off, no VM controls); detail already showed
executor from the task API.
- README: document the CHV console entry, experimental marking, and
verbatim backend-error display.
- Rebuilt cloud/console.html from src (tsc + vite single-file build);
no backend change: executor is already returned by _task_to_dict.
Stack: #216 -> #221 -> this. Worker breakpoint branch now calls any
executor providing goal_resume_breakpoint_task (Local keeps its
original path, gateway tasks still loud-fail); WarmPool
reconcile_orphans is called on CHV worker startup and close with
audit-on-failure; per-VM SSH host keys are provisioned before VMM
start via cloud-init NoCloud seed (keyscan kept only as weak
fallback, CHV_HOST_KEY_MODE=provisioned|keyscan); CHV_NET_JSON is
validated in preflight and explicit direct egress is audited;
chv-guest/build.sh is now real steps with --dry-run and fails fast
without /dev/kvm. Tests: new test_chv_gaps.py (18), cloud full
1514 OK (skipped=4). No real VM verification: no KVM host.
Startup race: concurrent init_db (cloud-server + worker) ran
CREATE TABLE / CREATE INDEX IF NOT EXISTS against a DB where the
object did not exist yet; the check-then-create is not atomic, so
both sessions created it and one died with UniqueViolation on
pg_type_typname_nsp_index (tables) or pg_class_relname_nsp_index
(indexes). Hit on the 37ba687 and 7983567 deploys (7983567 added
idx_tasks_parent); systemd restart healed it every time.
Fix (advisory lock, not catch-duplicate_object): init_postgres_schema
takes pg_advisory_xact_lock (key 20261108, released at its commit),
and server.init_db takes the session-level lock for the whole PG
branch so the _ensure_* objects outside POSTGRES_SCHEMA
(task_continue_messages, user_gateway_keys, task_attempts,
task_audit_loop_state) are covered too. Same idiom as the existing
RATE_LIMIT_SWEEP_ADVISORY_KEY sweep lock; key is distinct.
Tests: two new real-PG regression tests (skipped without
CLOUD_TEST_PG_DSN), 4 processes racing on an empty schema, for
init_postgres_schema and for full init_db; both fail on the
unfixed code and pass with the fix.
Tasks gain kind='main'|'side' (NULL=main). A side thread hangs under a
parent task, defaults to the local executor, never builds a worktree,
branch or container unless explicitly asked, and writes its conclusion
back to the parent via task_followups on terminal transition.
- server: kind validation + side-thread gates (parent required, no
coordinator_parts/upload_id/environment_id, parent must not be a
coordinator), local executor default, GET /tasks kind filter,
/worktree 404 and /fork /merge /pr 409 for side threads.
- worker: side-default shunt in _run_attempt (no-worktree scratch cwd,
in-memory plan=1 when a repo is present, seed/checkpoint skipped),
_writeback_side_thread, marker task-create kind field + inheritance,
side threads excluded from marker spawn counts.
- host/sandbox: --no-worktree plumbing for dispatch/goal/popen
(Local + Docker), scratch cwd work/side-threads/<task-id>.
- console (classic only): kind filter + [旁聊] rows + '开旁聊' entry,
side-thread dispatch mode, side-thread list in detail, merge/PR/fork
hidden for side threads; build exit 0, console.html sha df39db5a.
- tests: new test_sidethread.py (29 tests) + kind backfill tests in
test_p2_migrations.py; full cloud suite 3900 tests OK (skipped=13).
Design: verification/codex-muse-role-alignment-design-20261108.md
Summary: verification/codex-muse-role-align-p1p2-20261108.md
Local only: not pushed, not merged, not deployed.
Platform coordinator.py is the single source of truth; the skill
coordinator template (templates/roles/00-coordinator.md + SKILL.md
appendix 00 + body) now derives from it instead of describing a
second, model-played coordinator (U1-U13).
- server: _create_task accepts parent_task_id (404/400 checks,
coordinator-under-coordinator rejected); follow-ups to a running
coordinator return 409 at entry (_create_followup /
_deliver_to_thread) with documented alternatives (U14/U15).
- tests: 8 new coordinator tests (depth limit, lineage, replay,
follow-up 409); appendix sync/provenance/transplant suites green.
- check_appendix.py: 12 chapters match templates/roles/.
Tenant networks were created bare (docker network create <name>),
letting Docker's default pool hand out subnets outside the iptables
8889 allowlist (172.16.0.0/12). The smoke tenant landed on
192.168.32.0/20, so containers never reached the egress proxy and
every model turn timed out at 600s twice.
Fix (B): derive a deterministic /24 inside 172.16.0.0/16 from
sha256(network name) with linear probing on IPAM overlap, max 32
probes. 172.16.0.0/16 is the one /16 in the allowlist Docker never
auto-assigns (default pool starts at 172.17.0.0/16). No persistent
state; capacity 256 tenant networks per host, explicit failure when
exhausted. Pre-existing out-of-range networks are reused as-is and
need manual removal (see README).
Tests: 7 new (candidate properties incl. wrap-around, pinned argv,
overlap probe, exhaustion, empty name).
- Vendor codex-ui-stack host/ as cloud/codex-ui-host/ (original/ not vendored, 650MB)
- Bridge emulates 7 methods missing in codex 0.164.0-alpha.9, exposes 29
experimental-protocol methods (150 -> 179), Linux binary candidates,
read-only POST /__codex_git
- GET /console 302s to the UI host (:8095) when reachable, else falls back
to the classic React console; GET /console-classic always serves it
- Alignment tables: docs/codex-native-ui-alignment.md
- Tests: test_console 7, new test_codex_ui_host 4, handler method pin 247 -> 249
DispatchSection keeps a single 'goal 模式' checkbox (the former
invincible option): checking it sends goal_mode=1 plus
no_time_limit=1. TaskDetailPanel drops the '无敌版' label.
Ported verbatim from feat-console-goal-unify commit 395dffb
(base 622c51e); console.html rebuilt from cloud/console-react
with npm run build:console (tsc+vite pass) and synced verbatim
from dist/index.html (sha256 daba1941...).
- test_column_notnull_probe_sees_gap3_outcome now branches on the
post-ensure probe: when outcome is relaxed (PG; SQLite 3.53+)
the resume write must land and read back NULL; on older SQLite
the ALTER is swallowed and INSERT OR IGNORE drops the row
(version-dependent boundary, not a migration failure).
- attempt_ledger.py comments updated: ALTER COLUMN DROP NOT NULL
works on Postgres and SQLite 3.53.0+, swallowed before that.
- PG proof summary section 3.9/8 corrected to the same boundary.
- attempt_ledger.ensure: relax gap-3 outcome NOT NULL on Postgres
(ALTER COLUMN outcome DROP NOT NULL, probe-gated); without it
every resume write on a gap-3-migrated PG table fails with
NotNullViolation. SQLite keeps NOT NULL (no such ALTER).
- db.PgCursor: PRAGMA table_info fake rows had the notnull flag
inverted (NO -> 0); now matches sqlite semantics (1 = NOT NULL).
No caller read row[3] before, so this was latent.
- ledger_onboarding: stale 'PG path unverified' notes updated to
point at the real-PG proof.
- tests: pragma_info notnull pin (fake driver) + _column_notnull
probe pins (fresh + gap-3 SQLite).
Proof: 28/28 checks on real Postgres 16.15 (see verification/
codex-task-attempts-pg-proof-20261106.md).
User decision 2026-11-05: unified schema, rejecting the rehearsal
runtime column-union workaround and a ledger rename. One definition
(cloud.attempt_ledger.TASK_ATTEMPTS_DDL, 12 columns) shared by
cloud.attempts and cloud.db.POSTGRES_SCHEMA; ensure builds the
unified table fresh and migrates old resume, gap-3 ledger, and
rehearsal shapes in place with rows intact. aggregate counts
ledger rows only (outcome NULL = resume row, never counted).
SQLite verified by 8 new tests; Postgres real-DB upgrade path
explicitly NOT verified (see batch summary). Local commit only:
no merge, no push, no deploy.
H1 worker.py 92%->94% (277->210 miss): 35 real-assertion tests
covering goal-signal, tenant/env, followup, poll/wait,
process/watchdog and injected defensive branches; prod code 0 changes.
H2 sub-90 sweep: approval_timeout 100%, merge_gate_hard 100%,
worker_sweep 98% (only __main__ line 174 left); 14 tests.
H3 G7 rollback except:pass (server.py:6615-6616) covered by an
injected rollback-raising connection; original error still propagates,
status stays completed, retry succeeds; no semantics change.
H4 caller contract documented only (docstrings in _audit_scorecard
endpoint, check_baseline_stale, build_scorecard, baseline store):
current_main_head caller-supplied, any string as-is, no format checks,
missing/empty treated as not passed with no stale determination at the
endpoint, always 200, overall never rewritten; no new validation or
rejection, no response-structure change. 3 doc tests pin the wording.
H5 design notes (no decisions, no code changes) live in
verification/code-to10-gapfill2-design-notes.md, referenced by the
batch summary.
No bug found by the new tests; no existing assertion touched.
Target-line bug, not introduced by the replay: Worker._tenant_dirs
only receives project_id, yet the script_skipped_modified log line
used task.get("project_id"), raising NameError whenever seeding
reported a tenant-modified script (the refreshed sibling branch was
already fixed to project_id with its own regression test; this test
only covered the refreshed branch, so the full suite stayed green).
Fix mirrors the sibling (project_id) plus a regression test for the
skipped branch. Ruff F821 in worker.py is thereby cleared.
Target keeps the 6-mixin split, so the source line's 239/223/16 and
its mixin-homed 17-method list do not apply here. Recomputed by AST
on this line: handler accounting 244 total / 150 in server.py /
94 moved, no duplicates (243/149 + _update_goal_task); longfunc
still 17 methods with the same names, 16 homed in server.py and
_full_auto_execute in handler_fullauto.py, plus module-level
migrate_tasks_table in server.py (114 lines). No method split or
dropped; both tests' docstrings state the re-pin explicitly.
(cherry picked from commit 073950043ed65039e19be8a4b6d725a44a662879)
Reconcile-note: this datefix was missing from the solidify line (aa30d40's test_arch_converge.py had no _current_month_day and still failed in November, while 10023fb/6841f28 contained it); added here as the separately-labeled fix for that replay omission, per the reconcile brief.
Server writes goal + monotonic goal_version + task_goal_history in one
transaction (CAS on version, approve_merge only, new-semantic goal tasks
only, terminal/merged 409, same-goal noop). Worker mirror thread (30s)
and breakpoint-resume claim hook write the update as a signal file next
to host-state.json; host run_goal_loop applies it at round boundaries
(top of round and before the declaration check, so a declaration made
for the old goal in a turn during which the goal changed is voided),
with an explicit old->new notice, same thread/worktree, rounds/usage/
budget/failed counters untouched. handler method accounting is a
contract update: 239 total / 223 mixins / 16 in server.py.
(cherry picked from commit 6841f287219d2b36cfbb8803642344b84dae4377)
Reconcile-port: handler_lifecycle.py contents redirected — _update_goal_task method + _goal_update_reject/_goal_signal_status pure functions + goal_live_update import into cloud/server.py (target keeps 6-mixin split); _task_to_dict goal fields into cloud/handler_support.py; migrate columns into cloud/server.py migrate_tasks_table; test patch target handler_lifecycle→server; test_handler_split_conformance.py kept at target version, to be re-pinned for the unified line in a final commit.
- LiveEvidenceReader protocol: get_checks(repo, ref) /
get_branch_protection(repo, branch), results carry status
plus raw evidence; production code has no HTTP call, no
token read, no real GitHub API call (AST-asserted).
- verify_live_evidence: no reader / no repo-ref / reader
failure / mismatch all verified=False; comparison shared
with verify_gate_evidence via _compare_claimed_vs_live.
- live_gate_decision: verified=False -> allowed=False,
http_status=409, status=blocked; verified=True -> 200.
- Endpoints _merge_task/_release_task NOT wired yet (no
production reader exists; wiring None would block all
merges/releases); real live verification still pending
B6 real App install and B1 user-side items.
(cherry picked from commit 7e8bfdee1f22c28329602314f3e166cde34eb6cf)