Integrate runtime repair stack and close the cumulative CI census - #32
Merged
Merged
Conversation
added 30 commits
September 21, 2026 07:42
The loader tried VOOL_KEY_PASSPHRASE first against any encrypted-seed record, so an account-file-sealed record (written by an unattended boot) hit InvalidTag on the next boot that carried an operator passphrase — observed as the CI pollution-matrix child session failing all 96 default-home signer reads. The record's protection stamp now decides which seal reopens it, and a user-passphrase record never falls back to the account-file secret. key_storage_class() reuses the same reader.
…o this tree The 101-row ACCEPTANCE_MATRIX.json never migrated into the public tree, so the C12 join gate (tools/code_assistant_matrix_join.py) and its suite had no document to join: 20 failures locally and in every CI shard that carries the file pair. Restored from the pre-migration original (101 rows, identical lineage) with two release-critical authorities rebound to the current tree: the read-provenance caller edge follows CodeTaskRuntime._run_step (the _execute seam no longer exists), and the permission refusal names _decide_tool_call as the producing function (decide_tool_call is now a consuming wrapper; the edges are its two public doors) with the split recorded in the authority note. Join report: 101 rows, 8 typed authorities, release_critical_prose_only 0; suite 26 passed.
network.signer freezes its record paths at first import, so a whole pytest process shared ONE keys directory -- resolved against the default home whenever no test had set VOOL_HOME yet. Any test that overrides the passphrase in-process re-seals that shared record, and the next suite-passphrase reader dies with InvalidTag: measured on CI as the demand-ownership '(InvalidTag:)' demand failures and the pollution-matrix child session, both surviving the loader-side seal fix alone. The keychain pin fixture now repoints the signer paths into a per-test directory and resets the cached keypair, so each test seals its own record with whatever passphrase it uses and the default home is never written by the suite again.
The execution gate's tightened hands-off law (only proven read-only commands run unprompted outside Auto) means a modeless context is MANUAL, so every workspace.run_tests / apply step in these suites refused with user_action_required before the planner ever saw a failed validation -- 16 operator-phase1 and 8 orchestration-phase1 failures locally and in CI, with the planner honestly falling back to workspace_stop_after_read. The green sibling (test_code_task_reviewed_base) already passes operating_mode auto; these suites now do the same at every envelope and source_context site. The approval law itself stays policed by the permission-authority suites.
… gated write The two manual-mode sabotage tests set manual BEFORE the journey, so the tightened hands-off law refused the repro step itself (user_action_required) and the drive never reached the assertion under test -- that manual mode blocks the WRITE. The journey now runs in auto and the mode switches at the mutate step, which is the behavior both tests actually pin.
…search fetch tests record_runtime_tool_progress raised CheckpointTransitionRefused when a tool-progress record arrived after the checkpoint finalized (measured: the research tool loop's late recorder crashed three turn-contract flows). The A7 monotone law's own pins cover downgrades and resumes; a progress record on a finished turn carries no state meaning, so it is now discarded with the durable row untouched -- final truth stays byte-identical and the late recorder cannot crash its caller. The two web-search backend tests that drive a real _fetch now open the owning named_background_effect_scope, matching the effect-gateway law every other fetch caller already follows.
…ules hid The bare MEMORY.md/TOOLS.md personal-file rules match the public concept pages docs/concepts/memory.md and docs/concepts/tools.md under core.ignoreCase=true (the macOS default), so git add silently refused them and git status never showed the loss -- the same silent-loss mode the memory/ negation documents. Negate exactly those two tracked pages; every other MEMORY.md/TOOLS.md anywhere stays ignored, on both ignoreCase settings (new probe test). The restored memory page drops the absolutes the owning implementation contradicts: memory lives in VOOL's data home (core/memory/files.py), not the workspace; it is one store per home, not per-workspace; MEMORY.md is a mirror of the entries store, so deleting the file is not erasure -- VOOL's forget operation rewrites both and verifies.
The tools page said changes always ask first. The mode matrix contradicts that: Auto allows creating and editing files and side-effecting commands to run on their own, while deletes, overwrites, installs, git history, deploy, messages, spending and settings still prompt (MODE_PERMISSION_MATRIX in core/mode_permission_policy.py). Say which mode does what, and that the workspace and sandbox checks stay underneath either way. Scope the workspace claim to file tools -- public retrieval is read-only too and reaches outside the workspace (PUBLIC_READ_ONLY_RETRIEVAL). The ignore comment and probe docstring overstates the case-insensitive side: with core.ignoreCase=false only the exact names are ignored; lookalike lowercase files are merely untracked. Describe the matching per setting; rules and assertions unchanged.
Measured on the runner (instrumented capture, run 35622837193, SQLite 3.45.1): the writer's bare autocommit CREATE TABLE raised OperationalError SQLITE_SCHEMA (17, "database schema has changed"), exactly once per round, in all four rounds; runs 35615031134 and 35626506389 leg A showed the same failing assertion at the pinned baseline. The interleaving was not traced directly: SQLITE_SCHEMA's documented meaning is that the schema changed after the statement was prepared, so "a concurrent first-use migration committed while the statement was in flight" is an inference from that documented semantics, not a captured timeline. storage.db's contract makes the caller owner of the transaction, so the writer now opens BEGIN IMMEDIATE before its schema-touching work: the iteration takes the database's write lock across CREATE and INSERT (documented SQLite locking; the production stores already serialize their write paths on it), so a migration commit lands entirely before or after the iteration. A failed iteration rolls back its own statements and closes its handle, recording rollback/close problems rather than swallowing them. A distinct regression case pins that a failed caller transaction rolls back its own statements while previously committed rows survive.
tests/test_legacy_migration_repair.py built its legacy database from `git show 7ff50e8d...:storage/migrations.py` -- the reviewed tip whose defining property was zero occurrences of retry_idempotency_key. That hash does not exist in the public tree (history was rewritten at migration; the public root is 78f818b), so git show exited 128 on every clone this suite can run on and all five fixture-dependent cases failed (CI run 35570948370, and locally: CalledProcessError in _legacy_schema_sql; the three cases not touching the fixture passed). The shape is the contract, not the hash -- the same repair the store-upgrade fixtures already received. The fixture is now the current SCHEMA_SQL minus exactly the retry-idempotency upgrade (its column and its dependent unique index), which is what a real pre-upgrade installation looks like to run_migrations(). Every original case and assertion is kept, including both sabotage cases. Adds FixtureContractTests so the fixture cannot silently rot: the built database must contain runtime_attempts, lack the column and the index, and the live SCHEMA_SQL must still define the column the synthesis strips -- otherwise these cases would degrade into fresh current-schema installs that can never catch the Final-Repair-1 defect class.
network.signer resolves _KEY_DIR = data_path("keys") once, at import.
pytest loads the root conftest and the args' directory conftests
(tests/conftest.py -> apps.vool_agent -> network.signer) as INITIAL
conftests, BEFORE pytest_configure runs, so pinning VOOL_HOME only in
pytest_configure never reached import-frozen paths: _KEY_DIR stayed at
the repository checkout's own .vool_local -- one key record shared by
every pytest session in a job.
Measured locally: a record created under that shared path by an
unattended no-passphrase persist (the random account-file secret) made
a later passphrase-pinned session fail 24/51
test_public_hive_bridge.py cases with cryptography InvalidTag -- the
exact CI signature of the hermetic gauntlet's child sessions (runs
35546610740 and 35570948370, shard tests (3): the pollution matrix
fails because its spawned pytest session's hive cases go red). The
parent session stayed green because its already-cached keypair never
re-read the disk. Which process wrote the mismatched record on the
runner was not identified; the sharing mechanism and the consequence
were both measured and are what this removes.
The root conftest now pins the session runtime home (and the
unattended key-storage modes) at MODULE IMPORT TIME, before any other
conftest or runtime module can load. Every session -- top-level or
spawned child, whose own root-conftest import re-pins to its own fresh
home -- resolves import-frozen AND runtime-resolved paths inside its
own private home. pytest_configure keeps re-asserting the same pins
idempotently.
tests/test_session_home_pins_import_frozen_paths.py guards the
ordering contract: the import-frozen signer key dir must sit inside
the session home and outside the repository checkout, and a SPAWNED
pytest session (the CI failure's shape) must resolve its own home --
not the parent's, not the checkout's.
google_html._fetch goes through open_remote, the one outbound door, which fails closed outside any turn or background effect ledger -- denied before any socket (R2b). The five transport cases here called _fetch with no scope at all, so every one died with RemoteFetchRefusedError(no active turn or background effect ledger) before the mocked urlopen could deliver the HTTP behavior under test (4 CI failures on main run 35570948370, reproduced locally: 5 failed). The production caller always has a ledger; the tests now open the same sanctioned named_background_effect_scope at their own entry point -- attribution only, nothing granted -- and the retry/refusal semantics are asserted exactly as before. Adds the missing ceiling case for the retry contract: a SECOND consecutive transient status must surface its HTTPError after exactly two attempts and one backoff -- one retry, never a loop. The scopeless-refusal law itself stays policed where it already lives (test_effect_gateway, test_effect_identity_scope_r2b1, test_live_search_is_one_governed_path); nothing here duplicates that.
configure_home and the provider_home fixtures pin VOOL_HOME, the runtime-home override, and the db paths, but every test-side restore was partial: configure_runtime_home(None) cleared the session home the root conftest installed, and nothing restored the VOOL_HOME env write. The race children seal their node signer record under the pinned home with their own passphrase, so the next signer reload in the same pytest process resolves into that home and fails with InvalidTag under the suite passphrase. Measured 2026-09-21: shard tests(3) of PR run 35640946053 sent test_identity_lifecycle (2) and tests/legacy/* (2) red this way; the same four-file order fails identically on main 8153a96 (shards merely did not collide them there). money_race_probe gains runtime_state_snapshot/restore_runtime_state (and pinned_runtime composing them) beside configure_home, the mutation owner; the served-daemon journey, shared_home, and both provider_home fixtures restore exactly what they pinned. A guard case directly after the served-daemon journey pins the invariant.
_agent_node_emitter takes session_id as its documented per-session parameter, but the closure never used it: the streaming seam persists only what the source context names, so a turn whose context carries no session key (the conductor hands the raw ask context) wrote its node rows nowhere -- the blank-Agents-panel regression this emitter exists to prevent. The emitter now captures a sink context once at creation, defaulting session_id to the session it was built for, additive and never overriding a session the caller already named. The client-turn-id suite drove this seam through a bare object(), which predates the streaming route and died with AttributeError; its emitter helper now binds the factory to a carrier of exactly the two production mixins the streaming route reads, so the real emission code still runs without booting an agent. 4 CI failures (main run 35570948370, every PR run since) -> 7 passed; the twelve node-event neighbor suites are identical to clean main.
The suite still pinned the pre-migration single-job ops/verify.py gate: the 'Run authoritative verification' step it demands has not existed since the workflow became the ten-shard matrix + routed macOS job (owner commits 9fd7ee7/006d874), so the gate case failed at the missing step and the collect_only/step_disabled sabotage cases crashed mutating a step that is not there. The ruff pin still asserted 0.15.16, two versions behind the 0.16.7 the tree adopted with dependabot PR #5 and that test_install_surface_contracts.py already pins. The weakening law is restated against the current structure: exact lint and manifest-collection commands in the verify job, the full ten-shard matrix, the exact shard pytest prefix consuming the resolver's own file list, unconditional hidden-file log uploads (PR #11's law) on both test jobs, build gated on all three, and the same no-disabled-step, no-swallowed-output scan. The sabotage cases mutate the shard RUN step now -- a shard weakened to --collect-only executes nothing. 4 CI failures (main run 35570948370, every PR run) -> 6 passed; install-surface and delivery contracts unchanged and green.
run_once forwards the served turn's request identity to _run_once_inner as turn_request (agent.py:1619, present since the public root); the two lifecycle doubles still had the older three-parameter signature and died with TypeError before the override's in-force/reset assertions could run. The doubles now accept the kwarg; the override lifecycle under test is unchanged. 2 CI failures (main run 35570948370) -> 5 passed; execution-gate neighbors green.
finalization calls gate_publishable_content with runtime_notice (the gated-publication seam); the suite's double predated the kwarg, so the gate crashed and finalization published UNGATED -- the exact corruption the suite pins, silently: with ungated bytes every demand reads satisfied and the withheld statement wrongly satisfies its slot. The double accepts the kwarg; every assertion is unchanged. 3 CI failures (main run 35570948370) -> 5 passed; the six grounding-publication neighbor suites run identically apart from two pre-existing conductor cases that fail the same on clean main.
The three direct-call cases (market-quote fallback through urlopen, the nested wttr.in payload, the empty RSS feed) mock urllib one level BELOW the canonical fetch door, so open_remote runs for real and the R2b law denies the fetch before any socket -- the fallbacks silently stood down (provider 'none') or surfaced the refusal. Their siblings that mock http_fetch_text above the door never see this. The tests open the same sanctioned named_background_effect_scope the web-search backend tests adopted (PR #15/#23 pattern); every assertion is unchanged. 3 CI failures (main run 35570948370) -> 21 passed; the 5 web-search backend failures beside them are the cluster owned by open PR #15/#23.
gitlab.py aliased _forge_reads.json_payload -- the response PARSER
(json.loads(body.decode())) -- as _json and used it to SERIALIZE POST
and PUT request bodies. Every GitLab mutating call died at request
construction with AttributeError ('dict' object has no attribute
'decode'), surfaced as ForgeRefusedError(malformed_response): no
merge-request create, no text update, no comment could ever leave the
adapter, on any wire, since the public root. The two serialize sites
use json.dumps; the listing parse site keeps json_payload under its own
name. 3 CI failures (main run 35570948370, failing identically at the
public root) -> 28 passed; the whole tests/repoops package is green
(353 passed).
…ommit The differential driver froze its turn universe as git show c23257da...:ops/semantic_phase0_frozen_corpus.json -- a commit that no longer exists in the public tree (history rewritten at migration, public root 78f818b). Every public clone exited 128 and the loader correctly refused, failing the three eligibility cases in CI since the root. The corpus is now addressed by its content-addressed BLOB id: the loader still reads the trusted repository's object database, never the candidate worktree, a tampered corpus committed anywhere mints a different blob id that the pin refuses, and the pinned sha256 digest and count checks are unchanged. 3 CI failures (35570948370) -> 30 passed; the package's remaining 4 failures are the pre-existing replay/shadow/nonconsuming baseline clusters.
CI run 35665692856: the guard's module-import snapshot blamed a runtime-home override that an UNRELATED earlier test in the shard had cleared and never restored -- correct detection of drift, wrong attribution. The journey now records the exact state it restored in its finally, and the guard asserts the present state equals that give-back: immune to earlier drift, still failing on any leak of the journey itself (verified by removing the restore and watching the guard name the served home). The default-db piece is excluded -- the package's own autouse store fixture repoints it per test by design.
#22's guard compares live signer._KEY_DIR against the live session home. #10's autouse fixture legitimately repoints _KEY_DIR into per-test directories, so the live-containment assertion fails under the combined tree even though the import-time pin it guards is intact. The guard now snapshots _KEY_DIR and the active home at guard-module import (collection time, before any fixture) and asserts containment against those; the repo-checkout exclusion stays asserted against both the import-time and live values. Compatibility fix for merging #10 + #22 on this candidate. Verified: both guard cases + 16 signer-storage cases pass; the Cluster-7 poisoned order (money served journey + identity + legacy) passes 9/9.
added 19 commits
September 22, 2026 12:33
The prepaid-balance half of the composer preflight ran for every usepod pin, including the accountless x402 lane, where no prepaid account exists: the balance door answered credential_pair_unresolved and the composer blocked every wallet-paid send before the payment card could rise. The lane's own readiness (wallet_approval_required) already says the wallet is the liquidity gate -- the money law mints x402 grants with credit_liquidity not_required -- so the preflight stops demanding a prepaid balance for that lane and lets the card ask the operator. Proven by the served x402 chat journey (card, preview, approval, receipt) passing again end to end.
A turn the operator stopped between model calls finalized as failed/chat_model_unavailable_degraded at the HTTP front door, so the sufficiency writer joined the completed first call with the trace's success verdict and recorded a fabricated verified_success observation for a provider the operator simply stopped using. The terminal outcome now consults the same cancellation marker the attempt spine already honors (a fired marker plus a non-fulfilled, non-waiting outcome is CANCELLED), and the failure-stage taxonomy gains operator_stopped so the trace names the stop instead of "answer delivered" over text the stop prevented from being delivered. operator_stopped maps to no learned outcome, so a stopped turn writes nothing, in either direction. Cumulative: stage-verdict, sufficiency (8 scenarios + restart), fulfillment lifecycle, provider timeout, conductor wire and spine cancellation suites all pass (350 tests).
The funded journey staged refusals the product no longer produces in that order: accepting the price gate at pin time now binds the route with the operator's own ceilings, the composer itself refuses a budgetless paid pin (reason, spend panel, draft kept, nothing sent), and the per-turn CONFIRM THIS TURN re-ask was removed with the conversation-scope price acceptance. The journey now proves the real order -- pin gate, budgetless composer refusal, money-law refusal through the served door, one settled dispatch with a correlated receipt, and that a disabled budget is not standing authority -- and the tool-flow suite asserts the router's internal-tool-selection narration instead of the removed refusal code. The wallet sheet test asserts the labeled exact figures and the server's review line the sheet actually renders. Funded journey 6, x402 chat 3, tool flow 10, wallet sheet 2 -- all passing against the served daemon.
Mutation anchor follows the permission seam's function signature after its refactor; the research acceptance recognizes the current honest could-not-ground refusal beside the older phrases; the tool-surface golden records the two calendar-read actions as network reads (their reviewed contracts in core/runtime_tool_contracts.py are read-only network operations, effects unchanged); the tooling-context tests follow the bounded initial catalog with expansion owning domain tools, keeping the blocked-read refusal and recovery assertions.
…ness contracts The negative controls "free up space in my schedule" and "free space in my calendar" are owned by the operator's calendar lane now: it answers with a real calendar when a provider is configured and an honest unavailability refusal when none is. The corpus marks them calendar cases and the acceptance asserts that ownership -- still no machine intent, no model substitution. The freshness pair follows the current truth guards: with the web fallback disabled a value-asserting model answer about a live price is replaced by the honest no-reading refusal (the stale figure never reaches the user), and an empty fresh lookup that resolves to memory degrades honestly through the memory-runtime guard whatever search seam ran. Routing acceptance 100 and freshness 25 pass.
…ixtures A question phrased in local-currency units asks for the place's own money: holding USD in an XAF jurisdiction is a coherent holding, but a same-currency subtraction never answers "how many units of local currency". The recognizer now treats that conversion as missing when no supplied rate covers it, the request carries the actually-missing pair, and the refusal names the local currency of the place beside the holdings. The meet-and-greet HTTP client test opens the named background effect scope the outbound door law requires; the intent-arbiter test asserts the manual-mode authorization the execution boundary now carries; the retraction fixture keeps a live imperative continuation instead of a prohibition clause the constraint analyzer now reads. CI's Linux shards run under a virtual display so the headful wallet-handoff session can open its window. Currency lane 347, meet 15, arbiter, retrieval-obligation and delivery contracts all pass.
A comparison distributed over coordinated attributes asks for EACH attribute: merged into one unit, a partial answer closed the whole unit and the missing dimensions silently vanished from the census (the CT-301 recorded run-on). The pure "compare X vs Y on A, B, C and D" shape now mints one unit per attribute -- the compared subjects stay joined, the colon-introduced facet list keeps its own one-demand contract, and an explicit "..., A and so on" tail opens its coordinated item at the comma. A receipt-binding overreach is closed on the same seam: a one-typo recovery now requires the first letter to survive, so "sold" is never read as a mistyped "gold" and a gold receipt can no longer discharge a units-sold demand (the CP3 census arithmetic reproduces exactly with the content guard severed again). The CT-302 repros pin the law where it now lives: a weather followup mints no inherited gold obligation at all, with or without the content guard, and no gold needle binds any of its units. Demand-unit consumers (1329 tests), the live-data continuation lane (360) and the test-kit census (37) all pass.
Under the Local Only composite a weather ask ran its contained lookup, produced the deterministic "live web lookup is disabled" answer, and then continued -- the frontdoor's whole-turn refusal declines to finalize so other lanes can still serve the demand. But the composite blocks every lane the continuation could reach: the research pass found no certified author and marked the completed honest refusal as task-failed, destroying it (the first-run denial demo measured exactly this shape). The refusal now finalizes under the composite, and an attempt whose every subtask failed while the policy blocks all egress closes as REFUSED_POLICY -- a refusal is complete evidence about the policy, never unfulfilled work the record admits -- so the turn's proof reads RECORDED and the denial claim verifies against the genuinely contained attempt with zero effect. Pact family 58, live-data and attempt-continuity 571, grounded and sufficiency 90 -- all pass.
The journey minted its two pending approvals in a sibling process AFTER the daemon had already run its once-per-process pending-approval restore, so the seeded approvals were invisible and the needs-approval leg failed. The harness now mints both through the same permission gate BEFORE the daemon boots and hands their ids to the journey, the daemon log goes to a file instead of an undrained pipe, and the journey evidence lands under the test's tmp_path -- the committed evidence copy only behind an explicit VOOL_MOBILE_EVIDENCE_DIR opt-in. The full journey passes end to end: pairing, both sabotages, attachment, chat, proof chip, allow and refuse decisions, notifications, replay refusal, revocation, and the receipt chain.
Sixteen unique failures over the eight test shards, each traced to the contract that owns it and repaired there; no failure was skipped, retried away, or had its assertion weakened. - semantic replay (2): the run_once finalization spine (public tree) stashes per-turn identity records on every served result (_closure_verdict, _execution_identity, _presentation_selection, _semantic_admission, details' tool_call_id), so every one of the 252 corpus turns failed its same-flag control and the proof covered nothing. Those records are now compared by content, identity keys dropped per field, in a dedicated test; the identity test's subject moved from "weather today" (now a task-bound tool-intent lane) to the corpus's own empty turns, which still mint turn ids per run. - tool surface (2): runtime_tool_specs ignored its declared web policy for the contract-backed half of the catalog, so demo.plan and web.fetch flipped with the process-global policy cache mid-shard. The declared policy now governs both halves; a regression test poisons the ambient cache and pins the snapshot against it. - council (2): _await_state returned at the store's word while the run thread still owed its hand-back (pin release, live->paused); CI measured the gap at 0.03s and the mid-finally thread re-registering after teardown. The wait now covers the full quiesce: state reached AND the run not live. - mcp env allowlist: the API runtime started ComputeModeDaemon with no shutdown path; every booted runtime leaked a thread spawning an idle probe subprocess forever, and one such spawn clobbered a later test's Popen capture. RuntimeServices owns and stops the daemon; the pact rig shuts the runtime down. - first-run pact wal: the file snapshot stat'd files mid-walk while sqlite removed a checkpointed -wal; a vanished file can only shrink a side, so it is skipped instead of raising. - toolsmith parity: the blackbox record (public tree) carries the effect ledger's minted identities; they are stripped, with effect count and outcome multiset still compared. - v050 remote attempt accounting: the notes seam followed the routing to _collect_live_web_notes; the synthetic ledger attempt and the accounting law are unchanged. - swarm query overrides: the dispatch is research networking and fails closed without VOOL_RESEARCH_NETWORKING (its own boundary test proves that side); the positive test now runs under the same opt-in the research runners export. - c18 multilingual: the planner consumes the repair demand itself (planned_code_task_start) and pre-opens the typed task through the tool door, so the model round seats the CONTINUATION control plane; step 1 asserts the journal's literal objective plus the continue-not-reopen law instead of the retired model-called-open flow. - rename compatibility (3): the checks hardcoded the macOS state layout while vool_window resolves per-platform; they now read the paths the window module itself resolves. - ci verification contract: the Linux shards run pytest under a virtual display for the headful wallet-handoff lane; the pinned xvfb-run prefix is accepted token for token, everything behind it unchanged. - launcher ollama: the nohup'd fake server writes its log line from a background process; the assertion now waits briefly for that writer instead of racing it. - c18 parity typo-heavy and the bundled-chromium render stay asserted exactly as they were; their CI-only failures now dump the recorded wire calls and the browser's stderr tail so the next run names the divergence instead of a bare count.
added 7 commits
September 22, 2026 19:13
The engine now captures the browser's stderr for its typed empty-DOM diagnosis, so _run_chrome returns (returncode, stdout, stderr). The test fakes that stub the seam still returned the 2-tuple and every drive through them surfaced as browser_error:ValueError. The fakes speak the same seam; no assertion changed.
…persona Run 35752689216 census closure, shards 2 and 0, both failures at their owners: Shard 2 (tests/test_keychain_unattended_preflight.py real bundled-chromium render): browser_empty_dom:rc-5 with Chromium's own "SUID sandbox" refusal on stderr. The workflow's AppArmor profile granted the userns right to bwrap alone, so on Ubuntu 24.04 (kernel.apparmor_restrict_unprivileged_userns=1) the playwright-cache chromium could not create its namespace sandbox and refused to start -- a runner-confinement fact, not a product defect; the render never ran. The chromium provisioning step now resolves the exact (versioned) playwright-cache binaries and loads one userns-only profile per binary -- Chromium's sandbox stays kernel-enforced and ON, --no-sandbox stays out of the default argv -- with the same global-sysctl fallback the bwrap step already carries and a loud probe that runs every binary the way the render engine launches it (full build with --headless=new, headless shell without) and fails the step on a refusal or empty DOM instead of letting the render suites cascade. No error suppression: the workflow contract's || true ban holds (tests/test_ci_verification_contract.py green). Shard 0 (tests/test_c18_language_parity_served.py typo-heavy English anti-drift guard): 1 != 2 generations with the wire dump showing three empty /api/show probes and one prompt-bearing /api/chat answered as "You are VOOL...". Root cause is rig fidelity: the stub's answer_marker "You are Atlas" matched the daemon persona only on a machine whose operator registration (~/.openclaw) renamed the agent; first boot otherwise names it from VOOL_AGENT_NAME, then that registration, then "VOOL". On the runner the marker never matched, the queued CJK answer was never served, the guard found nothing to repair, and the exact-spend law read the rig, not the guard. The rig now pins VOOL_AGENT_NAME=Atlas for the daemon (the wire is identical everywhere) and the stub pops scripted replies only for genuine prompt-bearing chat-endpoint calls, so capability probes and auxiliary lanes can structurally never drain the queue. The 2-generation assertion is unchanged. Reproduced pre-fix under a registration-less HOME (identical failure bytes), fixed code passes; both c18 suites 10 passed + 8 subtests; ruff clean.
… lane Run 35752689216 shard 4 census closure, tests/test_chat_documents_ui.py::test_served_end_to_end_paste_door_retained_bytes_receipt_restart_retry_and_deletion failed as "the retry never reached the runtime" (served.captured stayed 1). Root cause, reproduced against the real served page: the answer bubble loses its .pending class at the FIRST content chunk, but the run only ends and releases the chat's slot when the NDJSON stream closes. The test's waits (content painted + 500ms) can therefore pass while the chat is still busy, and resendLastTurn -- which silently returns while isChatBusy or before run.ended -- no-ops. The retry wait that follows is satisfied instantly by the undisturbed DOM (one non-pending assistant, one user bubble), so the assertion sampled a resend that never ran. On this machine the window closes within the 500ms slack; on a loaded two-core CI runner it does not have to. The rig now waits for the page's own resend precondition -- run.ended AND run.released AND !isChatBusy -- before driving resendLastTurn directly; a user driving the ended-turn affordance meets the same bar. Nothing else changes: the exact-2-captured law, same-turn identity, retained-bytes and deletion assertions are untouched. Proven both ways: resend issued inside the busy window no-ops with captured==1 (the CI failure bit for bit), and with the precondition wait the same in-window start reaches the runtime (captured==2). Full module 12 passed; ruff clean.
…le output Run 35767154971 (cancelled at 84 minutes) never ran a single test: every shard sat inside the new "Provision the served-browser lane" step from 18:29Z until cancel, and the job cleanup killed bash + chrome + two chrome_crashpad_handler orphans. Two defects in the probe added in 44a27a3, both now closed: 1. It launched the FULL Chrome-for-Testing build first (sorted order) with --headless=new on a fresh --user-data-dir and NO --virtual-time-budget or timeout -- the exact unbounded-launch/fresh-profile hang class the preflight's binary preference documents. The probe now runs ONLY the headless-shell build the render lane actually pins (select_bundled_chromium / VOOL_BROWSER_BINARY), carries the engine's own --virtual-time-budget so --dump-dom terminates deterministically, and is wrapped in timeout -k 5s 60s. The full build keeps its userns grant (identical profile mechanism) but is never launched here; its served lanes go through playwright's own launcher, green unmodified in 35752689216. 2. Output was captured in a command-substitution PIPE. Chromium forks chrome_crashpad_handler helpers that inherit that pipe; when the parent is killed at the deadline the orphans hold the pipe open and the caller blocks forever -- a timeout cannot save a pipe the orphans own. The probe's stdout now goes to a file, so no orphan can block the step. The DOM assertion is stronger, not weaker: the dumped DOM must contain the probe page's own bytes (vool-probe), not merely be non-empty. All paths verified in a sandboxed dry run against fake binaries: pass (0.4s), hang with a pipe-holding orphan (bounded kill, loud error, step not blocked), rc-5 refusal with stderr surfaced (the original 35752689216 failure shape), empty DOM, and a hanging full build skipped. Contract tests 13 passed (no || true, no | tail/tee); ruff clean; YAML valid.
…aptive lane Run 35777904719 shard 8: test_actual_current_turn_remote_attempt_is_counted failed as call_count 0 == 1 -- intermittently, ~50% measured locally, and independently of this stack's commits (shard 8 was green at 53a0a4d; nothing since touched this area). Cause, demonstrated with an in-process diagnostic (since deleted): the test's stated premise is that the sealed test network answers none of the adaptive research fan-out's engines, but this rig seals nothing -- the adaptive lane runs LIVE (20-30 real ledger attempts per run, which is also why every invocation took 33-40s). When a real engine delivers a note, the product behaves CORRECTLY: prefetch_current_evidence binds the pre-model evidence (bound=True, ran=False), the turn keeps one governed retrieval, and the ordinary path's notes seam is legitimately never called -- so the assertion read the network, not the accounting law. The test now pins its own premise the way the tree already does everywhere else (test_agent_runtime_turn_reasoning and a dozen siblings): _collect_adaptive_research returns the canonical empty double. The seam assertion, the synthetic ledger attempt, and the web_calls == len(seen) accounting are unchanged. Five consecutive lone runs pass at ~2.3s each; full module 67 passed; ruff clean.
…TTP errors Run 35777904719 census closure for shards 5 and 6. Shard 5 (three failures in tests/test_provider_answer_recovery_served.py, all one root): the journey's step turns came back as publication-gate REFUSALS quoting live summarizer-tool marketing pages (scribbr.com, quillbot.com) as the turn's retrieved sources, and the two price-axis tests failed downstream because the journey's reviewed maxima were never saved. Root cause: the rig's daemon leaves the live web lane open, and the adaptive-research recognizer widened the ordinary summarization turn (escalate_current_requirement); its live search then determined the outcome -- where engines answer nothing the provisional widening is retracted and the journey passes, which is exactly how the suite passed before. The product behaved correctly throughout (escalate-on-found- evidence, gate on support); the rig's premise is a sealed network whose only remote peer is the synthetic UsePod service. The price_review_journey daemon now runs with VOOL_DISABLE_WEB=1 via a scoped subclass -- the sibling suites that share UsePodServedDaemon legitimately ride the web lane and are untouched (verified: sealing the shared base broke three conversation-intent tests; the scoped seal leaves them green). Recovery module 5 passed. Shard 6 (one failure): test_served_cancel_interrupts_a_running_command died as a bare 'HTTP Error 500' on its first chat call -- the daemon's own exception, whose traceback lived in the runner's ephemeral home and is gone. Passed 4/4 locally at this commit and on CI at 53a0a4d; not reproducible with the evidence available, so the repair is diagnosability, not a blind mutation: the served rig's chat/chat_stream now raise the same HTTPError with the daemon log tail in the message (failure path only; the type is unchanged so callers that catch it keep their behavior -- the module's own sabotage/claim lanes exercise that and stay green). The next 500 names itself.
Run 35790183687 shard 7, the run's ONLY failure (every other job green): test_notes_append_whose_payload_reads_as_a_lookup_stays_with_notes failed as route='' with the model lane answering a turn whose operator intent parses fine (apple_note_append) -- intermittently, ~50% locally across repeated runs of the same command, and at the 53a0a4d baseline too (not caused by this stack's commits). Root cause, traced through the failing turn's events: the live-info fast path claimed the whole turn and its retrieval returned TWO REAL SOURCES (domains discussions.apple.com and reddit.com) -- the rig patched WebAdapter.search_query for weather queries only, and the lane searches non-weather queries through WebAdapter.planned_search_query, which was LIVE. Where the engines answer nothing the lane declines by its own nothing-retrieved law and the turn stays with Notes, so this suite's verdict rode on live search results (fourth rig of this stack found searching the real web; the product behaved per its design throughout). The fixture now answers planned_search_query with an empty list: the weather arm above stays the one synthetic retrieval this rig serves, routing and result publication remain real, and every assertion is unchanged. Target test 5/5 deterministic at ~2.3s (was 13-22s of live searching); full file 54 passed + 3 xfailed twice; ruff clean.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Runtime repair stack integration
The runtime repairs were split across independent branches while main still failed cumulative verification. This candidate integrates the reviewed heads of #10, #12, #13, #14, #15, #16, and #20–#31 (23 commits after
ac4bd99), resolves their overlap, and repairs the failures exposed when they run together. Excluded dependencies: #1, #4, #17, #18, #19. Dependabot and nulla CI stay disabled. Merge requires explicit owner approval.What landed
Integrated branch heads (14 commits)
83f34a9)02460ff)def2cf8)faf83ee)9906832)f436a54)26a7df7)bc9765b)b1be4a2)e4305bf)d7a7329)f68ffb0)dd5d363)2364753)Product repairs from the integration sessions (8 commits)
b299db9— wallet-paid (x402) sends pass the composer preflight. The prepaid-balance check ran for the accountless lane; the lane's own readiness (wallet approval) is the liquidity gate. Served x402 chat journey passes end to end.d00f0d2— an operator-stopped turn records CANCELLED, never provider evidence. The front door consults the same cancellation marker the attempt spine honors; the sufficiency writer no longer fabricatesverified_successfor stopped turns. 350+ cumulative tests including stage-verdict and sufficiency.5597fbe— served payment journeys aligned to the current money contracts. Pin-time price acceptance binds route ceilings (saveReviewedMaxima); budgetless composer refusal, money-law refusals, one settled dispatch with correlated receipt, disabled budget = no standing authority. Funded 6 / x402 3 / tool-flow 10 / sheet 2.2f38ecd— contract fixtures refreshed for the current authority surfaces (mutation anchor, honest research-refusal phrase, tool-surface golden's two calendar reads as network reads, bounded initial tooling catalog).67fe48a— schedule wording attributed to the calendar lane; web-freshness contracts follow the truth guards (disabled web ⇒ honest "didn't run any live lookup" refusal; empty fresh lookup degrades honestly). Routing 100, freshness 25.7f11b33— a local-currency ask stays in the place's own money (missing conversion named with the actually-missing pair; never a same-currency subtraction); meet-and-greet scope, arbiter authorization, retraction fixture and CI's virtual display for the headful wallet-handoff session.f9e8276— a comparison's coordinated attributes split into their own demand units (CT-301); typo recovery requires the first letter to survive so a gold receipt can no longer discharge a units-sold demand (CP3). Demand-unit consumers 1329, live-data continuation 360, test-kit 37.73549c4— a policy-contained live-data turn closes as the refusal it is. Under the Local Only composite the deterministic disabled-answer finalizes; all-failed attempts under the composite close asREFUSED_POLICY(non-retryable refusal — renderers listing lifecycle states exhaustively should add it there, never as aFAILED_*alias). Pact family 58, live-data/continuity 571, grounded/sufficiency 90.e57fe1c— the phone journey's approvals are minted through the real permission gate before daemon boot; ids handed to the journey viaVOOL_JOURNEY_APPROVAL_IDS; daemon stdout to a file; evidence undertmp_pathwith an opt-in dir for committed copies. Full mobile journey passes end to end.CI census closure (
67e0870)Sixteen unique failures over the eight Linux shards of run 35730553862, each traced to its owning contract and repaired there — no skipped failures, weakened consent, suppressed errors, or removed safety assertions:
run_oncefinalization spine stashes per-turn identity records (_closure_verdict,_execution_identity,_presentation_selection,_semantic_admission,details'tool_call_id) on every served result, which re-classified all 252 corpus turns as nondeterministic. Records are now compared by content with identity keys dropped per field, in a dedicated test; the per-run-identity test's subject moved to the corpus's empty turns, which still mint turn ids.runtime_tool_specsignored its declared web policy for the contract-backed half of the catalog, sodemo.plan/web.fetchflipped with the process-global policy cache mid-shard. The declared policy now governs both halves; a regression test poisons the ambient cache and pins the snapshot.ComputeModeDaemonwith no shutdown path, leaking a subprocess-spawning poller into the process;RuntimeServicesnow owns and stops it, and the pact rig shuts the runtime down.-walmid-walk._collect_live_web_notes; the synthetic ledger attempt and the accounting law are unchanged.VOOL_RESEARCH_NETWORKINGopt-in (the fail-closed side has its own boundary test).planned_code_task_start), the journal objective is the literal demand, and the model round seats the continuation control plane under the continue-not-reopen law.xvfb-rundisplay prefix is accepted token for token (headful wallet-handoff lane); everything behind it unchanged.Gate census closure, run 35752689216 (
44a27a3,17f633c)The self-diagnosing outputs added in
67e0870named all three remaining failures; each is repaired at its owner (the last line of the previous section held — nothing was weakened):44a27a3— the bundled chromium gets its userns right on CI; the c18 rig's persona is pinned. Shard 2's render refused to start (rc-5, Chromium's own SUID-sandbox refusal on stderr): the workflow's AppArmor profile granted the userns right to bwrap alone, so on Ubuntu 24.04 the playwright-cache chromium could not create its namespace sandbox — a runner-confinement fact, not a product defect. The provisioning step now loads one userns-only profile per exact (versioned) playwright-cache binary with the same global-sysctl fallback the bwrap step carries. Chromium's sandbox stays ON and--no-sandboxstays out of the default argv (the workflow contract's no-error-suppression law holds — its tests are green). Shard 0's1 != 2exact-spend failure was rig fidelity: the stub'sanswer_markermatched only a persona named by the operator's own~/.openclawregistration, which CI does not have; the rig pinsVOOL_AGENT_NAMEand the stub pops scripted replies only for genuine prompt-bearing chat-endpoint calls. Reproduced pre-fix under a registration-less HOME with identical failure bytes; both c18 suites 10 passed + 8 subtests.7cb7c4c— the provisioning probe is bounded and cannot hang a shard. The first cut of44a27a3's probe (run35767154971, cancelled at 84 min) hung every shard before a single test ran: it launched the full Chrome-for-Testing build with no virtual-time budget and no timeout (the fresh-profile hang the preflight's binary preference documents), and captured output in a command-substitution pipe that chromium's forkedchrome_crashpad_handlerorphans keep open even after the parent is killed. The probe now runs only the headless-shell build the render lane pins, with the engine's own--virtual-time-budget, wrapped intimeout -k 5s 60s, output to a file, and requires the probe page's own bytes in the dumped DOM. All five paths (pass / hang-with-orphan / rc-5 refusal / empty DOM / hanging full build skipped) verified in a sandboxed dry run.31f875d— the v050 notes-accounting test's sealed-network premise is pinned. Run35777904719shard 8 failed intermittently (call_count 0 == 1, ~50% locally, fails lone at53a0a4dtoo): the rig never sealed the adaptive-research lane, whose live engine fan-out occasionally delivered a note — after which the product correctly binds it pre-model and never calls the notes seam. The test now pins the empty adaptive double (the tree-wide idiom); the seam assertion and accounting law are unchanged, and each run drops from ~35s of live fan-out to ~2.3s.1a57469— the recovery journey's web lane is sealed; rig HTTP errors carry the daemon log. Run35777904719shard 5's three failures shared one root: the journey daemon left the live web open, the adaptive-research recognizer widened an ordinary summarization turn, its live search returned summarizer-tool marketing pages, and the publication gate honestly refused the answer quoting them — the suite's outcome rode on live search results. Theprice_review_journeydaemon now runsVOOL_DISABLE_WEB=1(scoped: sealing the shared base broke three conversation-intent suites that legitimately ride the web — verified, then narrowed). Shard 6's one-off "HTTP Error 500" on the cancel test's first call is unattributed (traceback lost with the runner's ephemeral home; passes 4/4 locally), so the served rig'schat/chat_streamnow raise the sameHTTPErrorwith the daemon log tail in the message — the next 500 names itself. Run35777904719also carried the first CI proof of the earlier repairs: shards 0 (c18 persona pin), 2 (bundled-chromium userns render), and 4 (documents resend precondition + semantic replay) all green.17f633c— the served documents retry lane meets the page's own resend precondition. Shard 4's "the retry never reached the runtime": the answer bubble loses.pendingat the first content chunk while the run only ends/releases when the NDJSON stream closes, so the rig could callresendLastTurninside that window, where it silently returns; the retry wait then passed on the undisturbed DOM and sampled a resend that never ran. Reproduced bit for bit (in-window resend no-ops,captured==1); the rig now waits forrun.ended && run.released && !isChatBusyfirst, and the same in-window start then reaches the runtime (captured==2). The exact-2-captured, same-turn, retained-bytes and deletion assertions are untouched; full module 12 passed.aa4bef9— the notes-precedence rig's live-info search lane is sealed. Run35790183687— every job green except one test: the notes-append-whose-payload-reads-as-a-lookup variant lost its route to the model lane, ~50% flaky locally and at the53a0a4dbaseline (not caused by this stack). The failing turn's receipt carried two real sources (discussions.apple.com, reddit.com): the rig patchedWebAdapter.search_queryfor weather only, while the live-info lane searches non-weather queries throughWebAdapter.planned_search_query— live. Engines answering nothing → the lane declines by its own law and the turn stays with Notes (pass); engines answering → claim → model lane (fail). The fixture now answersplanned_search_querywith an empty list (weather stays the one synthetic retrieval; assertions unchanged); the target is 5/5 deterministic at ~2.3s and the file is 54 passed + 3 xfailed twice. This was the fourth rig of this stack found searching the real web — the pattern (v050's adaptive lane, the recovery journey's daemon, this suite's fast path) is recorded in the evidence dir for the owner.Validation
67e0870: semantic replay 13/13 (full 252-turn corpus), capability-spec consumers 799, served payment journeys, pact families, council, c18, rename, launcher, API server 111, mobile journey end to end.44a27a3/17f633c: both c18 served suites (10 + 8 subtests), the full served documents module (12), the CI verification and delivery contracts (13), and pinned ruff — green.#31note:f9e8276touchedanswer_coverage, which that corpus pins — the CI run is the check; re-pin per that PR's own law only if the blob-pin tests say so.Known limitations (unchanged by this PR)
583c4fa).Main remains unchanged pending the completed verification and owner approval.