fix: give the isolated soak config the theme packs the launcher needs to import - #86
Conversation
… to import The short endurance run stops before it measures anything. Measured on Ubuntu 22.04.5 in a worktree cut from a native Linux clone: the run reaches the runner phase, writes seven evidence files, passes the exact-six integration gate, and then fails with "source stack did not reach the exact four-role startup cut". The launcher log names the real cause: cryodaq.gui.theme resolves a theme at module import, and the default pack is not found. The pack is not missing from the tree. It is tracked, and git archive carries it into the sealed snapshot. It is missing from where the child is told to look: _source_environment sets CRYODAQ_ROOT to the ISOLATED root, get_config_dir is that root plus config, and the isolated config holds only the curated passive set, which has no themes. The curated set exists so the endurance run cannot reach real hardware configuration. Theme packs carry no hardware authority -- they are colours -- so copying them in does not weaken that. The alternative, pointing CRYODAQ_ROOT at the application tree and relocating only the writable state with CRYODAQ_STATE_ROOT, also imports cleanly and was measured to do so, but it hands the child the whole real config directory, which is the thing the isolated root exists to prevent. The directory copy is refused by name when the source has no themes directory or when it is empty. Left to the launcher, the same condition arrives as "source stack did not reach the exact four-role startup cut", which names a symptom without its subject, and tracing that back cost a full measurement round. Control, run by reverting production to master rather than by reasoning: the new presence test fails there because the themes directory is absent, not because a symbol is missing.
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…ely adding it Measured, not predicted. Running the short soak at the previous head of this branch on Ubuntu 22.04.5 moved the failure EARLIER rather than removing it: phase runner, state FAIL, reason "passive source fixture topology is not exact" and only summary.json was written, where the unfixed run had reached the runner phase and written seven files. Adding a directory to the isolated configuration broke the fixture seal, which walks an exact topology: a fixed set of files plus one EMPTY directory. That is the whole point of the seal, so the seal is what had to learn about the new directory. The theme packs are now sealed the way the files are -- identity, no links, exact mode, content hashed -- and the per-file check is a nested function so both loops run the same code rather than a copy of it. The directory is created with mode 0o700 and its files with 0o600, because the caller's permission sweep only reaches the top level and the seal demands those modes. A populated tracked directory is also checked for emptiness and re-listed after sealing, so a file appearing between the listing and the hash is a refusal rather than an unsealed byte. A directory the launcher must read at import is the last place an unsealed byte belongs. tests/scripts: 132 passed, 94 skipped. The next measurement is the same run at this head, which is what decides whether the barrier is actually behind us.
|
@codex review Head is |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
Measured end to end in the real runner, on the laboratory machineUbuntu 22.04.5, worktrees cut from the native Linux clone. This branch and #87 are sequential, and both are needed. Running the short soak at #87's head alone — Merging both branches locally — never pushed, So the theme barrier is gone and the process gets as far as constructing the engine. What stops it next, and the gap that hides whyOne second later: The engine started, was assigned a process id, and did not reach readiness. Its reason is not in the evidence. The launcher forwards engine stderr to So the run reports a condition without its subject, which is the shape this project already treats as a defect in its own right. The next change is to publish Not started here — this branch stays about the theme packs. |
Review found a break this branch introduced and the branch's own tests could not see. The seal now carries the theme directory and its packs, so the payload holds 27 entries where the ledger's validator still required exactly 14 and rejected every path under themes. The cost is worse than a red test: every soak run -- short, twelve-hour or seventy-two-hour, all sealing through the same ledger -- would run its full duration, write all its evidence, and then be destroyed at the last gate by an error naming neither themes nor the manifest. The topology is now checked by SHAPE rather than by a fixed entry count. The count would otherwise have to know how many theme packs the tracked tree happens to hold, which is exactly the coupling that drifted. Top-level files must be the expected set, both sealed directories must be present as directory entries, and every remaining path must be a file one level inside a sealed directory, with at least one such file. The second finding was the more important one: every manifest test hand-builds the payload, so the runner's seal and the ledger's validator were free to drift and nothing connected them. Two tests close that. One feeds a payload the runner ACTUALLY produced through the validator the ledger ACTUALLY calls. The other asserts the validator still REFUSES a fixture with no theme packs, so teaching it a new shape did not teach it to accept a missing one -- the launcher resolves a theme at import, so a fixture without the packs cannot start the program. tests/scripts: 133 passed, 95 skipped.
|
@codex review Head is |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
|
Both findings are closed at The acceptance validator now knows the topology this branch creates. The seal carries the theme directory and its packs, so the payload holds 27 entries where The topology is checked by shape now, not by a fixed entry count. The count would otherwise have to know how many theme packs the tracked tree happens to hold, which is exactly the coupling that drifted. Top-level files must be the expected set; both sealed directories must appear as directory entries; every remaining path must be a file one level inside a sealed directory; and there must be at least one such file. The second finding was the more important one, and it is the reason the first shipped invisible: every manifest test hand-builds the payload, so the runner's seal and the ledger's validator were free to drift and nothing connected them. Two tests close that:
Measured: Thank you for the read — neither of my own runs reached the sealing stage, so nothing I measured could have caught this. |
The second review round found that my own repair opened two holes, and it was right about both. The membership helper treated BOTH sealed directories alike, so a single experiment_templates/anything entry satisfied the theme-presence check while the payload carried no theme pack at all. The test that claimed to cover it passed only because its hand-built payload had no members of either kind -- a control passing for the wrong reason, which is the exact shape the round before this was about. Membership now belongs to the populated directory only, and the runner seals experiment_templates EMPTY, so a member of it in an accepted manifest means the manifest and the seal disagree. Dropping the exact entry count also made duplicates acceptable: adjacent duplicates satisfy the sortedness check, set equality collapses them, and the tree digest is recomputed from the payload's own entries so it stays self-consistent. An explicit uniqueness check is restored. And the themes copy loop now refuses a non-file BY NAME instead of skipping it. A skipped entry makes the isolated root diverge from the tracked tree in silence, and the seal seals what the child sees without ever comparing back to the source, so the divergence would surface later as a launcher failure with no named cause. The tracked directory is flat today, so nothing was being dropped; the refusal is for the day it is not. Controls run against the COMMITTED HEAD rather than master, because master's validator still carries the exact count check and would reject the same payloads for a different reason. Both new tests fail against the head that has the holes and pass against this one. tests/scripts: 136 passed, 95 skipped.
# Conflicts: # docs/architecture-montana-important.svg # docs/current_candidate_metrics.md
|
@codex review Head is |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
The materialiser returns a measured count only when it is given an interpreter to measure with; on the plain path it returns None, and the seal refuses a non-positive count. The fixture's declared value is what the acceptance validator checks, so that is what the seal is asked for. Windows skips this POSIX-only test, so the mistake was invisible in every local run and only the Ubuntu job could show it. Measured directly at the pushed head in a native Linux worktree rather than by waiting for another CI round; that probe is now a workspace tool.
|
@codex review Head is |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
The seal walks the topology BOTH materialisers produce: the complete one writes two files and the empty template directory, the isolated one writes the rest. The round-trip test built only the isolated half, so the seal refused it with a topology error on Ubuntu while Windows skipped the test entirely and said nothing. The complete materialiser cannot be called here because it also measures the reading count in a subprocess and refuses without one, so its two files and its directory are written the same way, and the assertions afterwards check the union against what the seal expects. If either materialiser grows a file, this test says so instead of drifting. Found by fetching the preserved output from the candidate artifact. The job summary prints only the failed node ID, which is how the previous round of this same test was misread.
|
@codex review Head is |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
# Conflicts: # docs/architecture-montana-important.svg # docs/current_candidate_metrics.md
|
@codex review Head is |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
# Conflicts: # docs/architecture-montana-important.svg # docs/current_candidate_metrics.md # scripts/soak_mock_stack.py
|
@codex review Head is |
|
You have reached your Codex usage limits for code reviews. You can see your limits in the Codex usage dashboard. |
…he wrong reason The reviewer found that three tests passed without exercising anything, and measurement confirmed it: with the theme presence check and the uniqueness check both deleted from production, all three still passed. Two causes, and the first is a merge interaction rather than an error at writing time. The payload helpers carried the instrument name as a literal, and the fixture moved to another instrument on master, so the validator rejected every payload on the instrument before the condition under test was reached. Both helpers now read the runner constant that the validator's own helper reads, so the two cannot drift apart again. The second is the theme presence test itself. It hand built a payload with one entry and a zeroed tree hash, so the validator rejected it on the hash long before the theme check. It now uses the same helper the positive cases use, so the tree hash is real and the file set is complete, and the only thing missing is a member inside the themes directory. That makes the theme check the condition that decides. Each of the three guards was then removed from production one at a time, and each is now caught by exactly the test that should catch it: the theme presence check, the uniqueness check, and the rule that the two sealed directories are not alike. The last one is the original defect this branch exists to remove.
|
@codex review Head is |
|
Codex Review: Didn't find any major issues. Delightful! Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
What this is for
The short endurance run is the evidence the laboratory week rests on, and it stops before it measures anything. This makes it start.
The measurement
On Ubuntu 22.04.5, in a worktree cut from a native Linux clone, the short soak:
exit_code 0,status PASS);_RunnerFoundationError: source stack did not reach the exact four-role startup cut.log-launcher.txtnames the real cause:cryodaq.gui.themeresolves a theme at module import, and the default pack is not found.Why the pack is missing, which is not what it looks like
The pack is not missing from the tree. It is tracked, and
git archivecarries it into the sealed snapshot. It is missing from where the child is told to look._source_environmentsetsCRYODAQ_ROOTto the isolated root.get_config_dir()isget_project_root() / "config". That isolatedconfig/holds only the curated passive set —_ISOLATED_TRACKED_CONFIG_FILESplus_ISOLATED_STATIC_CONFIGS— and there is nothemes/among them.Measured both ways with
evidence/tools/rootprobe.shin the workspace repository:CRYODAQ_ROOTat a directory withoutconfig/reproduces the recorded traceback exactly, exit 1.CRYODAQ_ROOTat the application tree withCRYODAQ_STATE_ROOTat the isolated directory imports cleanly, exit 0.Why the copy rather than arrangement B
Arrangement B is the split
src/cryodaq/paths.pydocuments and it works. It also hands the child the whole real configuration directory, which is exactly what the isolated root exists to prevent. The curated set is a safety property, not an accident.Theme packs carry no hardware authority. They are colours. Copying them into the isolated set restores the import without giving the run anything it could act on.
What the change is
A new
_ISOLATED_TRACKED_CONFIG_DIRS = ("themes",), copied file by file into the isolated config, and refused by name when the source has nothemesdirectory or when it is empty. Left to the launcher, the same condition arrives as "source stack did not reach the exact four-role startup cut" — a symptom without its subject — and tracing that back cost a full measurement round.Control
Run by reverting production to master, not by reasoning about it. With
scripts/soak_mock_stack_runner.pyrestored to master,test_the_isolated_config_carries_the_theme_pack_the_launcher_resolves_at_importfails because thethemesdirectory is absent, not because a symbol is missing:With the change:
tests/scripts/test_soak_mock_fixture.pyreports 11 passed, 12 skipped;ruff check src/ tests/clean; the workflow-exact format check clean over 702 changed files.What this does not do
It does not change the refusal itself.
resolve_theme()stopping the program over a colour file is pinned bytests/gui/test_theme_loader.py::test_missing_default_pack_raises, and a tree with noconfig/has no safety configuration either, so stopping is defensible. Whether it should stop is a behaviour decision and belongs to the owner.It also does not prove the endurance run completes. It removes the first barrier; what the run finds after it is the next measurement.
Written with assistance from Claude (Anthropic).