From 21a15df7f1506c323fc93d408a423baf27ab9aee Mon Sep 17 00:00:00 2001 From: seathatflowsinourveins <234074349+seathatflowsinourveins@users.noreply.github.com> Date: Mon, 5 Oct 2026 16:26:15 -0400 Subject: [PATCH 01/10] User-facing times: local time first with UTC beside it; the recipe writes WSL's time-zone default explicitly On 2026-10-05 the user asked that times shown to them be in their local time, resolved at the root of the harness rather than in a memory note or per-host edits. Records (ledger rows, receipts, evidence, commits) stay UTC. - One sentence, one wording, in both user-level templates: examples/claude-native/CLAUDE.md (Core rule bullet) and adoption/templates/codex.AGENTS.template.md (session-lanes line). The F9 carriers are regenerated and hold it once each; the nativestack2604 render matches them byte for byte. - Pins amended together: a fail-first StandingRuleSurfacesTests check, the Codex TOP_RULE_SHA256 and size comments, the client-configuration projection addendum, and a context-budget addendum (startup scopes 23,796/19,560 bytes under the unchanged 24,458/20,103 constants; new pin value recorded). - The new-distro recipe writes [time] useWindowsTimezone=true, WSL's documented default, so W5 reads it back: cloud-init user-data (path A), W6 (path B), the W5 read-back, the first-boot checklist, the receipt example, the command table and a dated amendment of the recipe record. Source: Microsoft wsl-config "Time settings" (MicrosoftDocs/WSL 7ea1c6f9, WSL/wsl-config.md:152-158). - The WSL image experiment's four edited frozen inputs move to evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/ with unchanged hashes (fix-wave precedent). - Decision: docs/decisions/2026-10-05-user-facing-local-time.md. Rebased onto main ecfa1127 (#703). The shared hot files (the new-WSL handbook pair, its receipt and manifests/evidence.json) are regenerated and re-registered in the branch's last commit, under the hot-file protocol of docs/lanes.md. Source review and local integration only: no first boot, import, host re-render or model run. Co-Authored-By: Claude Opus 5.5 --- adoption/new-wsl/claude-user-instructions.md | 1 + adoption/new-wsl/codex-user-instructions.md | 1 + adoption/platforms/linux-wsl2-new-distro.md | 8 +- adoption/templates/codex.AGENTS.template.md | 1 + .../wsl/cloud-init.user-data.template | 5 + .../templates/wsl/first-boot-checklist.md | 2 +- .../templates/wsl/stage1-receipt.example.json | 6 + .../experiment.json | 8 +- .../2026-10-01-new-wsl-distro-recipe.md | 22 +- ...2026-10-02-new-wsl-client-configuration.md | 14 + .../2026-10-05-harness-context-budget.md | 15 + .../2026-10-05-user-facing-local-time.md | 216 +++ .../user-facing-local-time-20261005/README.md | 12 + .../2026-10-01-new-wsl-distro-recipe.md.txt | 1425 +++++++++++++++++ .../cloud-init.user-data.template.txt | 30 + .../frozen-inputs/first-boot-checklist.md.txt | 74 + .../stage1-receipt.example.json.txt | 1404 ++++++++++++++++ examples/claude-native/CLAUDE.md | 1 + tests/test_codex_worker_lane.py | 7 +- tests/test_install_claude_profile.py | 14 + tests/test_wsl_new_distro_recipe.py | 8 +- tools/adoption/host-baseline.md | 2 +- tools/adoption/host_baseline_probe.py | 2 +- tools/adoption/new_wsl_client_config.py | 4 +- 24 files changed, 3263 insertions(+), 19 deletions(-) create mode 100644 docs/decisions/2026-10-05-user-facing-local-time.md create mode 100644 evidence/artifacts/user-facing-local-time-20261005/README.md create mode 100644 evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/2026-10-01-new-wsl-distro-recipe.md.txt create mode 100644 evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/cloud-init.user-data.template.txt create mode 100644 evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/first-boot-checklist.md.txt create mode 100644 evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/stage1-receipt.example.json.txt diff --git a/adoption/new-wsl/claude-user-instructions.md b/adoption/new-wsl/claude-user-instructions.md index 4128a034f..c9017bd81 100644 --- a/adoption/new-wsl/claude-user-instructions.md +++ b/adoption/new-wsl/claude-user-instructions.md @@ -21,6 +21,7 @@ Decide by evidence and research convergence: a choice stands when current primar - Resolve supported findings before claiming completion. Distinguish measured results, simulations and untested boundaries; unchanged upstream tests, local integration checks, synthetic fixtures and actual provider execution are different evidence classes. New machines collect their own evidence; historical receipts do not certify the new host. - Settle a discoverable harness capability by step 2 before asking the user; compare community alternatives against demonstrated gaps, retain source pins and reasons, and run the selected native path through returned results. Dated exclusions are not current availability evidence. Research only the relevant layers. - Carry the user's authorized work through implementation, relevant verification and a concise handoff. Use a short plan for bounded work; do not add intake, repeated approvals, diagnostic campaigns or restarts without a concrete need. Preserve existing edits, native accounts, model choices and project scope. +- When you tell the user a time, give it first in the host's local time zone (read it with `timedatectl` or `date`), with UTC beside it, for example "4:00 PM EDT (20:00Z)"; keep UTC in ledger rows, receipts, evidence and commits. - Use the project's canonical instructions for memory scope, tests and domain rules, and keep durable memory and indexes scoped to that project. ## Token practice (base layer) diff --git a/adoption/new-wsl/codex-user-instructions.md b/adoption/new-wsl/codex-user-instructions.md index 213967611..5d1e28af8 100644 --- a/adoption/new-wsl/codex-user-instructions.md +++ b/adoption/new-wsl/codex-user-instructions.md @@ -26,6 +26,7 @@ Long commands: set `yield_time_ms` 30000; while a `session_id` returns, poll `wr Web research: if the stack installs GPT Researcher, run `bash ~/code/native-agent-stack/tools/research/gpt_researcher.sh ""` as a long command (stops at 1,500 s); use short, unseeded current-month queries; reports are leads: re-read facts in primary sources. Message Claude Code in one long command: set `msg` via a quoted heredoc (`msg=$(cat <<'MSG'`, text, `MSG`, `)` each on its own line), then `printf '%s\n\nreply: codex queue --thread %s\n' "$msg" "$CODEX_THREAD_ID" | claude -p -n "codex-$(printf '%.8s' "$CODEX_THREAD_ID")" --permission-mode bypassPermissions --max-turns 3 --output-format stream-json --verbose "Send the text on stdin, complete and verbatim, to the session named with exactly one SendMessage call, then stop."`. Codex receives queued messages only between turns; idle delay is up to ~20 s. +When you tell the user a time, give it first in the host's local time zone (read it with `timedatectl` or `date`), with UTC beside it, for example "4:00 PM EDT (20:00Z)"; keep UTC in ledger rows, receipts, evidence and commits. # RTK diff --git a/adoption/platforms/linux-wsl2-new-distro.md b/adoption/platforms/linux-wsl2-new-distro.md index 6fb81cf61..d7f435ead 100644 --- a/adoption/platforms/linux-wsl2-new-distro.md +++ b/adoption/platforms/linux-wsl2-new-distro.md @@ -449,7 +449,7 @@ systemctl show getty@tty1.service -p Result -p NRestarts Pro for WSL writes it), that line lists its top-level keys: record them, and stop if `users:` or `write_files:` is among them. cloud-init 26.1 merges `agent.yaml` over the user-data one top-level key at a time, and an agent key replaces the user-data key entirely (`DataSourceWSL.py:317-336`, called at `:490`). Either key would replace the user or the - `[user] default` of W3. + `[time]` and `[user] default` of W3. - The `.wslconfig` line only reads: it prints the file's section headers and its `instanceIdleTimeout` and `vmIdleTimeout` lines, or nothing when the file or the keys are absent. Record both values in the receipt's `idle_keys`; a missing key has its default. `instanceIdleTimeout` (under `[general]`, default 15000 ms, `-1` turns it @@ -620,7 +620,10 @@ Proof (path A, cloud-init provisioned the instance): schema exits 1 (`:1493-1498`). The `schema` subcommand never reads the marker (`cloudinit/cmd/main.py:1240-1241`, `:1286-1293`). The 26.04.1 rehearsal returned `Valid schema user-data` after the marker on both host releases; each run must repeat that observation. Record the output (`schema_system`). -- `/etc/wsl.conf` holds `[boot]`, `systemd=true`, `[user]` and `default=`, each once. +- `/etc/wsl.conf` holds `[boot]`, `systemd=true`, `[time]`, `useWindowsTimezone=true`, `[user]` and + `default=`, each once. The explicit `[time]` key keeps the distribution on the time zone set in Windows + ([Microsoft wsl-config, "Time settings"](https://learn.microsoft.com/en-us/windows/wsl/wsl-config#time-settings): + `useWindowsTimezone`, default `true`); W6 writes the same key on path B. - The marker file exists, and `sudo -l` lists `(ALL) NOPASSWD: ALL`. - The starred line of `--list --verbose` is still W1's (`default_distribution_after`). @@ -778,6 +781,7 @@ printf '%s ALL=(ALL) NOPASSWD:ALL\n' '' > /etc/sudoers.d/90-wsl-defaul chmod 0440 /etc/sudoers.d/90-wsl-default-user visudo -cf /etc/sudoers.d/90-wsl-default-user grep -q '^\[user\]' /etc/wsl.conf || printf '\n[user]\ndefault=%s\n' '' >> /etc/wsl.conf +grep -q '^\[time\]' /etc/wsl.conf || printf '\n[time]\nuseWindowsTimezone=true\n' >> /etc/wsl.conf touch /etc/cloud/cloud-init.disabled ``` diff --git a/adoption/templates/codex.AGENTS.template.md b/adoption/templates/codex.AGENTS.template.md index add2f0ac3..9feccea69 100644 --- a/adoption/templates/codex.AGENTS.template.md +++ b/adoption/templates/codex.AGENTS.template.md @@ -26,6 +26,7 @@ Long commands: set `yield_time_ms` 30000; while a `session_id` returns, poll `wr Web research: if the stack installs GPT Researcher, run `bash ~/code/native-agent-stack/tools/research/gpt_researcher.sh ""` as a long command (stops at 1,500 s); use short, unseeded current-month queries; reports are leads: re-read facts in primary sources. Message Claude Code in one long command: set `msg` via a quoted heredoc (`msg=$(cat <<'MSG'`, text, `MSG`, `)` each on its own line), then `printf '%s\n\nreply: codex queue --thread %s\n' "$msg" "$CODEX_THREAD_ID" | claude -p -n "codex-$(printf '%.8s' "$CODEX_THREAD_ID")" --permission-mode bypassPermissions --max-turns 3 --output-format stream-json --verbose "Send the text on stdin, complete and verbatim, to the session named with exactly one SendMessage call, then stop."`. Codex receives queued messages only between turns; idle delay is up to ~20 s. +When you tell the user a time, give it first in the host's local time zone (read it with `timedatectl` or `date`), with UTC beside it, for example "4:00 PM EDT (20:00Z)"; keep UTC in ledger rows, receipts, evidence and commits. diff --git a/adoption/templates/wsl/cloud-init.user-data.template b/adoption/templates/wsl/cloud-init.user-data.template index cf5ac78e7..ff67b0c4c 100644 --- a/adoption/templates/wsl/cloud-init.user-data.template +++ b/adoption/templates/wsl/cloud-init.user-data.template @@ -8,6 +8,8 @@ # Keys follow Canonical's "Automatic setup of Ubuntu on WSL with cloud-init" example and the cloud-init WSL datasource # "Default user" example. The groups are the ones the image's wsl-setup gives a user it creates interactively. uid 1000 # matches defaultUid in the image's /etc/wsl-distribution.conf. The password stays locked; sudo needs none. +# [time] useWindowsTimezone=true makes WSL use and sync to the time zone set in Windows (Microsoft wsl-config, "Time +# settings"; default true). It is written explicitly so W5 reads it back: docs/decisions/2026-10-05-user-facing-local-time.md. # P2 checks the workstation schema; W5 must validate the selected image's own schema. Full new-host acceptance is owed. users: - name: ${WSL_USER} @@ -21,6 +23,9 @@ write_files: - path: /etc/wsl.conf append: true content: | + [time] + useWindowsTimezone=true + [user] default=${WSL_USER} diff --git a/adoption/templates/wsl/first-boot-checklist.md b/adoption/templates/wsl/first-boot-checklist.md index cf32faeb7..1760abae1 100644 --- a/adoption/templates/wsl/first-boot-checklist.md +++ b/adoption/templates/wsl/first-boot-checklist.md @@ -42,7 +42,7 @@ readlink /proc/self/ns/cgroup - [ ] **W2** Explicit `` is one of the two controlled arms; download URL, filename and catalog entry match that release. The actual whole-image, signed Canonical publication and pinned DistributionInfo.json SHA-256 all equal that release's exact hash above; actual file size recorded (run 2 observed 418,495,746 bytes for 26.04.1); no `throw`. - [ ] **W3** `%USERPROFILE%\.cloud-init\.user-data` exists, starts with `#cloud-config`, names `` twice and contains no `${`; its SHA-256 equals P2's. - [ ] **W4** The selected `ubuntu--wsl-amd64.wsl` installs with exit 0; `Distribution successfully installed`; `` is `Stopped` at version 2; the starred line is unchanged. -- [ ] **W5** First launch exit 0, without the prompt marker; `wsl: Failed to start the systemd user session` stops even with exit 0; `id -un` is `` and `id -u` is `1000`; getty `is-enabled` prints `masked` (normal exit 1), and `show` prints `LoadState=masked`, `ActiveState=inactive`, `NRestarts=0`, recorded as `getty_mask`; selected-image `cloud-init --version` recorded; `cloud-init status --long` prints `status: disabled` with `boot_status_code: disabled-by-marker-file` (with the template's `bootcmd` it exits 2 on one expected recoverable error, `Failed to wait for network`, because WSL masks `systemd-networkd-wait-online.service`; any other recoverable error is recorded and reviewed); `/var/lib/cloud/data/result.json` names `DataSourceWSL` with `"errors": []`; `/var/lib/cloud/data/status.json` shows the four stages finished without errors; `cloud-init schema --system` prints `Valid schema user-data` and exits 0; `/etc/wsl.conf` names the user once; `/etc/cloud/cloud-init.disabled` exists; sudo is NOPASSWD; the starred line is unchanged. After the first launch, while both distributions run and before F1, the already-running distribution reads a system unit active in both: its `cgroup.procs` has local process ids and no `0` entry, and the new distribution's `user@.service` is active. An empty or unreadable file is inconclusive and stops. The paired block below records each of the five commands on each side in its own receipt entry with its own exit, in `paired_isolation`: the same uid, both user managers `active`, the new distribution `running` with no failed unit or `degraded` with `systemd-binfmt.service` as its only failed unit (message proved at F1), two `cgroup:[...]` values that differ from each other and are not `cgroup:[4026531835]`, and all five workstation values equal to its W1 baseline. A getty failed in the new distribution or newly failed in the workstation fails the proof. Any failed W5 proof terminates only the new distribution, never stops or restarts a unit in either, exports `-w5-failed.tar` with the exit guard and its cause (`cgroup`, `tty` or a short text) in `w5_failure_export`, unregisters and checks surviving `WSLInterop` and a Windows executable, with R1's records and stop for review. Do not take path B for any such failure. On both paths, record P3's baseline before the import and `second_count` after the first launch. An increase confined to the window in which the new disk is attached, before cloud-init starts, is recorded with the new lines and does not stop the run. Any storage error after that window, or any provisioning step that fails with a storage cause, stops the run. An unknown attachment window stops for review; storage failures retain microsoft/WSL#41482 and their own cause. +- [ ] **W5** First launch exit 0, without the prompt marker; `wsl: Failed to start the systemd user session` stops even with exit 0; `id -un` is `` and `id -u` is `1000`; getty `is-enabled` prints `masked` (normal exit 1), and `show` prints `LoadState=masked`, `ActiveState=inactive`, `NRestarts=0`, recorded as `getty_mask`; selected-image `cloud-init --version` recorded; `cloud-init status --long` prints `status: disabled` with `boot_status_code: disabled-by-marker-file` (with the template's `bootcmd` it exits 2 on one expected recoverable error, `Failed to wait for network`, because WSL masks `systemd-networkd-wait-online.service`; any other recoverable error is recorded and reviewed); `/var/lib/cloud/data/result.json` names `DataSourceWSL` with `"errors": []`; `/var/lib/cloud/data/status.json` shows the four stages finished without errors; `cloud-init schema --system` prints `Valid schema user-data` and exits 0; `/etc/wsl.conf` names the user once and sets `useWindowsTimezone=true` under `[time]`; `/etc/cloud/cloud-init.disabled` exists; sudo is NOPASSWD; the starred line is unchanged. After the first launch, while both distributions run and before F1, the already-running distribution reads a system unit active in both: its `cgroup.procs` has local process ids and no `0` entry, and the new distribution's `user@.service` is active. An empty or unreadable file is inconclusive and stops. The paired block below records each of the five commands on each side in its own receipt entry with its own exit, in `paired_isolation`: the same uid, both user managers `active`, the new distribution `running` with no failed unit or `degraded` with `systemd-binfmt.service` as its only failed unit (message proved at F1), two `cgroup:[...]` values that differ from each other and are not `cgroup:[4026531835]`, and all five workstation values equal to its W1 baseline. A getty failed in the new distribution or newly failed in the workstation fails the proof. Any failed W5 proof terminates only the new distribution, never stops or restarts a unit in either, exports `-w5-failed.tar` with the exit guard and its cause (`cgroup`, `tty` or a short text) in `w5_failure_export`, unregisters and checks surviving `WSLInterop` and a Windows executable, with R1's records and stop for review. Do not take path B for any such failure. On both paths, record P3's baseline before the import and `second_count` after the first launch. An increase confined to the window in which the new disk is attached, before cloud-init starts, is recorded with the new lines and does not stop the run. Any storage error after that window, or any provisioning step that fails with a storage cause, stops the run. An unknown attachment window stops for review; storage failures retain microsoft/WSL#41482 and their own cause. ```sh id -u diff --git a/adoption/templates/wsl/stage1-receipt.example.json b/adoption/templates/wsl/stage1-receipt.example.json index 94a42e8b5..b7486f3d3 100644 --- a/adoption/templates/wsl/stage1-receipt.example.json +++ b/adoption/templates/wsl/stage1-receipt.example.json @@ -1060,6 +1060,12 @@ "exit": "", "output_excerpt": "" }, + { + "step": "W6", + "cmd": "grep -q '^\\[time\\]' /etc/wsl.conf || printf '\\n[time]\\nuseWindowsTimezone=true\\n' >> /etc/wsl.conf", + "exit": "", + "output_excerpt": "" + }, { "step": "W6", "cmd": "touch /etc/cloud/cloud-init.disabled", diff --git a/blueprints/convergence-practice/wsl-new-distro-image-20261001/experiment.json b/blueprints/convergence-practice/wsl-new-distro-image-20261001/experiment.json index 1c1f38f90..120648b50 100644 --- a/blueprints/convergence-practice/wsl-new-distro-image-20261001/experiment.json +++ b/blueprints/convergence-practice/wsl-new-distro-image-20261001/experiment.json @@ -85,11 +85,11 @@ "sha256": "ff94e93390875a4c6d2baa352fa7075e66acc7a966b33f1ef62338fb9988b7f3" }, { - "path": "adoption/templates/wsl/cloud-init.user-data.template", + "path": "evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/cloud-init.user-data.template.txt", "sha256": "08e6a3f6b078dfa54d78297596d54729d10528d57147b9b9eae063c37160eeb9" }, { - "path": "docs/decisions/2026-10-01-new-wsl-distro-recipe.md", + "path": "evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/2026-10-01-new-wsl-distro-recipe.md.txt", "sha256": "ec03a494aa4d80ac004f74710250bf1ac7cf18e0506758d97a1cf98a0cee42a3" } ], @@ -101,11 +101,11 @@ ], "evaluation": [ { - "path": "adoption/templates/wsl/first-boot-checklist.md", + "path": "evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/first-boot-checklist.md.txt", "sha256": "1a06df0e6d50b07b439317a61a450e450127052589f63254e769d99166e3603b" }, { - "path": "adoption/templates/wsl/stage1-receipt.example.json", + "path": "evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/stage1-receipt.example.json.txt", "sha256": "7c0811c460fe8732f413b954cd7ddadb5cc41a5300f8686b5b83aefcd80a971a" }, { diff --git a/docs/decisions/2026-10-01-new-wsl-distro-recipe.md b/docs/decisions/2026-10-01-new-wsl-distro-recipe.md index 41c58cad2..e95623dcd 100644 --- a/docs/decisions/2026-10-01-new-wsl-distro-recipe.md +++ b/docs/decisions/2026-10-01-new-wsl-distro-recipe.md @@ -453,7 +453,7 @@ The conditions that would overturn these decisions are in Overturn condition, it 2. **Creation path.** - **Path A.** Render `%USERPROFILE%\.cloud-init\.user-data` from the template (W3). The user is ``, uid 1000, in groups `adm, cdrom, sudo, dip, plugdev`, with `sudo: "ALL=(ALL) NOPASSWD:ALL"`, a locked password and - `[user] default` appended to `/etc/wsl.conf`. Then run + `[time] useWindowsTimezone=true` and `[user] default` appended to `/etc/wsl.conf` (amended 2026-10-05). Then run `wsl --install --from-file \ubuntu--wsl-amd64.wsl --name --location Z:\WSL\ --no-launch` (W4), then a first launch with standard input at end of file (W5). - **Rehearsals first (R1).** Each release runs on its own throwaway name and folder through F3, recording W5's schema, @@ -498,7 +498,8 @@ The conditions that would overturn these decisions are in Overturn condition, it `wsl --export Z:\WSL\downloads\-failed.tar` keep its cloud-init logs, `/var/lib/cloud/instance` and its configuration. Its SHA-256 and size go to the receipt's `failed_attempt_export`. A nonzero export exit throws before any deletion. Then `--unregister` the literal new name, check surviving interop, `--import ... --version 2`, then a manual user with - the same groups and NOPASSWD drop-in, `[user] default`, the marker and `wsl --terminate `. + the same groups and NOPASSWD drop-in, `[user] default`, `[time] useWindowsTimezone=true` (amended 2026-10-05), the + marker and `wsl --terminate `. - **Stage-1 receipt.** The private PowerShell transcript, sanitized into the shape of `adoption/templates/wsl/stage1-receipt.example.json`. Its payload keys are those `scripts/validate.py` compares with a `receipts[]` row; `kind` is `native_cli_e2e` and `component_ids` is `systemd` (stack row `255.4-1ubuntu8.17`, the @@ -699,7 +700,7 @@ workstation distribution (Evidence classes). | W5 | powershell | `wsl.exe -d '' -u root --exec cat /var/lib/cloud/data/result.json` | `"datasource": "DataSourceWSL"` and `"errors": []`: the run completed without errors | | W5 | powershell | `wsl.exe -d '' -u root --exec cat /var/lib/cloud/data/status.json` | each of the four stages `finished` with empty `errors`; `recoverable_errors` recorded | | W5 | powershell | `wsl.exe -d '' -u root --exec cloud-init schema --system` | path A: a line matching `^\s*Valid schema user-data$` and exit 0; skipped on path B | -| W5 | powershell | `wsl.exe -d '' -u root --exec cat /etc/wsl.conf` | `[boot]`, `systemd=true`, `[user]`, `default=`, each once | +| W5 | powershell | `wsl.exe -d '' -u root --exec cat /etc/wsl.conf` | `[boot]`, `systemd=true`, `[time]`, `useWindowsTimezone=true`, `[user]`, `default=`, each once | | W5 | powershell | `wsl.exe -d '' -u root --exec ls -l /etc/cloud/cloud-init.disabled` | the marker exists | | W5 | powershell | `wsl.exe -d '' -u root --exec sudo -l -U ''` | `(ALL) NOPASSWD: ALL` | | W5 | powershell | `wsl.exe --list --verbose` | the starred line equals W1's | @@ -749,6 +750,7 @@ workstation distribution (Evidence classes). | W6 | sh | `chmod 0440 /etc/sudoers.d/90-wsl-default-user` | mode 0440 | | W6 | sh | `visudo -cf /etc/sudoers.d/90-wsl-default-user` | `parsed OK` | | W6 | sh | `grep -q '^\[user\]' /etc/wsl.conf \|\| printf '\n[user]\ndefault=%s\n' '' >> /etc/wsl.conf` | one `[user]` section naming the user | +| W6 | sh | `grep -q '^\[time\]' /etc/wsl.conf \|\| printf '\n[time]\nuseWindowsTimezone=true\n' >> /etc/wsl.conf` | one `[time]` section with `useWindowsTimezone=true` | | W6 | sh | `touch /etc/cloud/cloud-init.disabled` | the marker exists | | W6 | powershell | `wsl.exe --terminate ''` | exit 0 | | W6 | powershell | `wsl.exe --list --running` | `` absent | @@ -1423,3 +1425,17 @@ or when the manifest installs a tool whose pieces the map leaves out. Not established: any run of `install.sh`, `accept.sh` or `--apply` on the destination distribution; the after-sign-in checks of `agent-runtime-worker` and `research-harnesses` run upstream model examples (plan README, stage descriptions), and whether the two native sign-ins are enough for them was not read from their sources and not run. + +## Amendment 2026-10-05 (explicit `[time]` in `/etc/wsl.conf`) + +The user-data now writes `[time]` with `useWindowsTimezone=true` before `[user]`, and W6 appends the same key on path +B, so both creation paths make WSL use and sync to the time zone set in Windows. Microsoft documents the key as a +boolean that defaults to `true` (MicrosoftDocs/WSL `7ea1c6f9`, `WSL/wsl-config.md:152-158`, the pin in Sources). The +26.04.1 image's `wsl-setup` 0.6.3 (`73418e32`, `wsl-setup:55-73`) only appends or completes `[user]`, so the section +survives the first run. Writing the key makes W5 read it back. The coordinator measured on 2026-10-05 that +NativeStack2604's `/etc/wsl.conf` held `useWindowsTimezone=false` with no repository record, and set it to `true` on +that host. Only the content listings change here (path A and path B under Decision, the W5 proof and the new W6 row); +"What the image contains" is a historical read and stays. This is a source review and local integration: no first +launch or import ran for it. The experiment's frozen inputs that this amendment changed keep their recorded hashes +under `evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/`. Decision: +[`2026-10-05-user-facing-local-time.md`](2026-10-05-user-facing-local-time.md). diff --git a/docs/decisions/2026-10-02-new-wsl-client-configuration.md b/docs/decisions/2026-10-02-new-wsl-client-configuration.md index e67ed3c34..4a916f120 100644 --- a/docs/decisions/2026-10-02-new-wsl-client-configuration.md +++ b/docs/decisions/2026-10-02-new-wsl-client-configuration.md @@ -1089,3 +1089,17 @@ After the refresh onto main 9e9553277, which carries wave 5's four browser-regis ```text ``` + +## Addendum 2026-10-05: user-facing local time + +The [local-time decision](2026-10-05-user-facing-local-time.md) adds one sentence to the portable Claude block (a Core rule bullet) and to the Codex block's `session-lanes` lines. The piece tables above are unchanged. The current instruction projection follows; earlier dated projections keep their own counts. + +`examples/claude-native/CLAUDE.md`, written to `adoption/new-wsl/claude-user-instructions.md` (0 unit(s) left out; 59 of 59 lines stay): + +```text +``` + +`adoption/templates/codex.AGENTS.template.md`, written to `adoption/new-wsl/codex-user-instructions.md` (0 unit(s) left out; 70 of 70 lines stay): + +```text +``` diff --git a/docs/decisions/2026-10-05-harness-context-budget.md b/docs/decisions/2026-10-05-harness-context-budget.md index 228e1a106..5934702db 100644 --- a/docs/decisions/2026-10-05-harness-context-budget.md +++ b/docs/decisions/2026-10-05-harness-context-budget.md @@ -800,3 +800,18 @@ follow its ordinary merge contract. This is local integration and fixture acceptance, with no full CI suite, host rollout or provider/model trial claimed. The coordinator commits, refreshes the registry last and owns the pending host read-back and subsequent ceiling tightening. + +## Addendum (2026-10-05): user-facing local time + +[The local-time record](2026-10-05-user-facing-local-time.md) adds one 227-byte +sentence to the portable Claude block (+230 bytes with its bullet marker and +newline) and to the Codex `session-lanes` block (+228). `startup_files` measures +Claude 23,566 → 23,796 and Codex 19,332 → 19,560 startup bytes, under the +unchanged 24,458/20,103 constants (662/543 bytes of headroom); the compact Codex +source is 7,535 bytes. For these inputs the post-gate projection above becomes +23,116/18,880-byte scopes and 24,272/19,824-byte ceilings. The gate's reviewed +diff still lowers the constants to the measured post-gate scopes plus 5%. No +constant changes here. The top-rule/lanes pin named above moves from +`568ee365aeef3455fc901e648eb72d28cc3b49c39f1bfba7f5e39beed20479a8` to +`82e23b68466ed8b96566a229582f0c99fa1456a393e635f18cc5e65f601f4d09`: the pinned +segment runs to the RTK marker, so it includes the `session-lanes` lines. diff --git a/docs/decisions/2026-10-05-user-facing-local-time.md b/docs/decisions/2026-10-05-user-facing-local-time.md new file mode 100644 index 000000000..142acb673 --- /dev/null +++ b/docs/decisions/2026-10-05-user-facing-local-time.md @@ -0,0 +1,216 @@ +# Decision: times shown to the user are local first, with UTC beside them; records stay UTC (2026-10-05) + +**Decided by:** the user's request of 2026-10-05, described here rather than quoted. Times that agents show the user +should be in the user's local time, settled at the root of the harness, not by a memory note or by per-host edits. The +coordinator relayed it in a bounded unit brief with the measured facts below; this unit wrote the change in an owned +worktree. + +**North-star action served:** the foundation's work and the US-equities lane report status, schedules, CI runs and +paper-session times to the user. They should read in the user's own zone, while ledgers, receipts and evidence keep one +UTC timeline across hosts and clients. + +## Context + +- The coordination convention writes UTC `Z` times: ledger rows, receipts, and `date -u` in the standing-delegation + checks. Those are records and stay UTC. +- The coordinator measured on 2026-10-05: + - Windows is on "Eastern Standard Time"; + - all three WSL distributions resolve `/etc/localtime` to `America/New_York`. +- Agents relayed record times to the user as they were written, in UTC. No managed instruction told them to convert. +- On NativeStack2604, each client's user-level file holds an interim local-time line dated 2026-10-05, after the + managed block's end marker (read 2026-10-05). This change leaves host files alone. +- NativeStack2604's `/etc/wsl.conf` held `[time] useWindowsTimezone=false` with no repository record, and the coordinator + set it to `true` on that host. The distribution recipe wrote no `[time]` key, so a new host relied on the default and + nothing read the value back. + +## Decision + +1. **One instruction sentence at user level, in both clients, with one wording:** + + > When you tell the user a time, give it first in the host's local time zone (read it with `timedatectl` or + > `date`), with UTC beside it, for example "4:00 PM EDT (20:00Z)"; keep UTC in ledger rows, receipts, evidence and + > commits. + + - **Claude.** [`examples/claude-native/CLAUDE.md`](../../examples/claude-native/CLAUDE.md) carries it as a Core rule + bullet after the concise-handoff bullet, the existing rule about reporting to the user. + - **Codex.** [`adoption/templates/codex.AGENTS.template.md`](../../adoption/templates/codex.AGENTS.template.md) carries + it as the last line of the `session-lanes` block. That keeps it outside the top-rule block, which + `adoption/scaffold/AGENTS.md` copies byte for byte into new repositories. + - **Rendering.** F9 renders both templates into every host's managed block. The regenerated carriers + `adoption/new-wsl/claude-user-instructions.md` and `codex-user-instructions.md` hold the sentence once each. + - **A user-level rule, not a standing clause.** The repository `AGENTS.md` and the scaffold do not repeat it. The + [three-surface record](2026-09-30-rule-text-every-layer.md) requires all three surfaces only for the standing + clauses that `StandingRuleSurfacesTests.SHARED` lists; its one-wording rule still holds here, on both client + templates and both carriers, and a new test in that class checks it. This sentence is a personal display + preference: + - Claude's memory documentation lists `~/.claude/CLAUDE.md` as "Personal preferences for all projects". + - The Codex guide puts persistent defaults in the Codex home "so every repository inherits your working + agreements". + - Both clients load their user-level file in every project, this repository included, so a root copy would load + twice here and count twice against each fixed startup ceiling. +2. **The WSL zone follows Windows, explicitly.** + - **Path A.** The cloud-init user-data appends `[time]` with `useWindowsTimezone=true` to `/etc/wsl.conf`, before + `[user]`. + - **Path B.** W6 appends the same key. + - **Read-back.** W5 reads the key back, and the first-boot checklist and the recipe record's command table follow. + - **The source.** Microsoft documents the key as a boolean that defaults to `true`: "Setting this key will make WSL use + and sync to the timezone set in Windows." + - **Why write it.** Writing the key leaves the default's behavior unchanged. It makes the value part of the recipe and + its proof, so a later `false` shows up as a failed W5 check instead of the silent drift found on NativeStack2604. + - **First run.** The 26.04.1 image's `wsl-setup` 0.6.3 only appends or completes `[user]`, so the section survives its + first run. + - **Claude Code's UI clock.** Claude Code's own UI times follow the system zone by default (below), so this key also + makes them local. +3. **Records stay UTC.** Ledger rows, receipts, evidence, commits and dated records keep UTC, written as RFC 3339 + date-times with `Z` or a numeric offset. + +## Alternatives considered + +- **A memory note.** The user rejected it. Memory is retrieved per project as evidence, not loaded as a standing + instruction, so it would not reach every session or client. +- **Per-host private lines in each client's user-level file.** Kept as the interim only: + - they cannot be reproduced on a new host and can drift from the managed source; + - after the hosts re-render with this rule, they duplicate it. +- **The system zone alone** (`useWindowsTimezone=true`, `/etc/localtime`). It is needed so that `date`, `timedatectl` + and the client UI clocks report the user's zone. It cannot change how an agent words a time: an agent relaying a UTC + ledger row still writes UTC. It is the second half of this decision, not a substitute for the first. +- **Records in local time.** Rejected: + - an Eastern local time repeats an hour when daylight saving ends; + - zones differ between hosts and contributors, while ledgers merge rows from several distributions and Codex lanes; + - RFC 3339 §4.1 gives the reason for UTC. §4.2 defines numeric offsets as local time minus UTC and notes that + alphabetic zone labels have interoperated poorly, so a label such as `EDT` is for people and always has the UTC + time beside it. §4.3 reads `Z` as UTC being the preferred reference point, and §5.6 gives the grammar + `time-offset = "Z" / time-numoffset`. +- **Native client settings instead of a sentence.** + - **Claude Code.** Claude Code 2.1.257 added `timeFormat` and `timeZone` (changelog at v2.1.289). The installed 2.1.289 + describes them as settings "for times shown in the UI", with defaults `auto` and the "system time zone". They do not + reach text the model writes, so the defaults stay. No template sets them, because a fixed IANA zone in a portable + template would be wrong for other users. + - **Model-written times.** No setting that localizes times the model writes was found in `claude --help` (2.1.289), the + changelog through v2.1.289, `codex --help` (0.160.0), the rust-v0.160.0 release notes or the Codex configuration + reference. That reference has `timezone` only in the `location` of `tools.web_search`. + - **A pointer or a skill.** Either would need a startup trigger identical to the rule itself, since any reply may hold + a time. +- **The repository `AGENTS.md` and the scaffold as further surfaces.** Not chosen, for the reasons in Decision 1. The + overturn condition below says when to add them. + +## Startup bytes (the dated comparison the budget record requires) + +These are measured with `PortableTopRuleTests.startup_files`, which uses the renderer's committed carriers. Base is main +`e8c1edec`. + +| File or scope | Before | After | +| --- | ---: | ---: | +| `examples/claude-native/CLAUDE.md` and its carrier | 11,575 | 11,805 (+230) | +| Claude rendered user block | 11,841 | 12,071 | +| `adoption/templates/codex.AGENTS.template.md` (local check: under 8,192) | 7,307 | 7,535 (+228) | +| Codex rendered carrier | 8,373 | 8,601 | +| Claude startup scope (fixed ceiling 24,458) | 23,566 | 23,796; headroom 662 | +| Codex startup scope (fixed ceiling 20,103) | 19,332 | 19,560; headroom 543 | +| Claude post-gate projection (scope − 680; ceiling = ×1.05, rounded up) | 22,886; 24,031 | 23,116; 24,272 | +| Codex post-gate projection | 18,652; 19,585 | 18,880; 19,824 | + +The constants stay at 24,458 and 20,103; nothing is re-baselined. When the routing host gate passes, the +[budget record](2026-10-05-harness-context-budget.md) still requires lowering both constants to the measured post-gate +scopes plus 5%. For these inputs that is 24,272 and 19,824, replacing its earlier projection of 24,031 and 19,585. The +sentence is 227 bytes. The budget record has a dated addendum pointing here. + +## Pins amended in the same change + +- **The local-time wording.** `StandingRuleSurfacesTests` gains a check that the sentence appears once in both + templates and both F9 carriers. It failed first, as the Checks table shows. +- **`TOP_RULE_SHA256`** in `tests/test_codex_worker_lane.py` covers the Codex block up to the RTK marker, the + `session-lanes` lines included. It was re-derived with the module's `template_segments()` as + `82e23b68466ed8b96566a229582f0c99fa1456a393e635f18cc5e65f601f4d09`, and the budget record's addendum names the new + value beside the old one. The size comments there and in `tools/adoption/new_wsl_client_config.py` now give 7,535 + and 8,601 bytes. +- **The cloud-init check.** `tests/test_wsl_new_distro_recipe.py` requires `[time]` and `useWindowsTimezone=true` in + the render. It has two new mutants that the check rejects, a lost section and `false`. +- **The new W6 command.** It has its row in the recipe record's command table and its entry in the receipt example. +- **The client-configuration record.** It takes the tool's regenerated listing (59 and 70 lines) in a dated addendum. +- **The new-WSL handbook.** It is regenerated; it holds the recipe's SHA-256. Its receipt's `outputs` follow, with a + `regenerations` entry. +- **The registry.** `manifests/evidence.json` takes the new hashes of the 20 changed files it lists and the five new + files under `evidence/artifacts/user-facing-local-time-20261005/`. +- **The passwordless-sudo criterion moved from recipe line 624 to 627.** The two live citations of it, + `tools/adoption/host_baseline_probe.py` and `tools/adoption/host-baseline.md`, follow. Dated records keep their own + line citations. +- **Unchanged.** The startup constants (`STARTUP_BUDGET_BYTES`), the scaffold, the moves fixture of the Codex top-rule + section and the RTK block do not change. + +## Overturn condition + +- **A native setting.** A client release adds a setting that localizes times the model writes. Adopt it and drop the + sentence. +- **Sessions without the managed user block.** Cloud sessions, CI agents or a host not yet re-rendered are shown + reporting times to the user in UTC only. Then add the sentence to the repository `AGENTS.md` and the scaffold, under + the three-surface test, with a dated byte comparison. +- **The user changes the format,** for example to local time only or a 24-hour clock. +- **A host's zone is not the user's,** for example a remote or cloud host on UTC. Reword "the host's local time zone" + to name the user's zone. +- **Microsoft changes `useWindowsTimezone`'s meaning or default.** +- **A missed rule at its trigger.** After the hosts re-render, an agent shows the user a UTC-only time. Sharpen the + sentence, as the budget record's reopening rule says. + +## Evidence classes and limits + +- **Evidence classes.** This is source review and local integration: unit tests, the F9 check and render, cloud-init's + own `schema -c` on the rendered user-data, and two synthetic `/etc/wsl.conf` simulations in a scratch directory. No + first boot, import, host re-render or model run happened. No model run measured whether agents follow the sentence. +- **Host work.** The NativeStack2604 change to `useWindowsTimezone=true` was the coordinator's host action and is not + part of this commit. The host re-render that delivers the sentence comes after landing. A re-render replaces only + the text between the managed markers, so it keeps the interim lines; removing them changes the user's own + user-level files and is left to the user. +- **The experiment's frozen inputs.** The WSL image experiment + (`blueprints/convergence-practice/wsl-new-distro-image-20261001/experiment.json`) froze four files that this change + edits. Their recorded bytes moved to `evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/` with + unchanged hashes, following the 2026-10-04 fix-wave precedent. +- **No decision index.** The repository keeps no index of decision records: `ls docs/decisions | grep -v '^20'` + prints nothing. `catalogs/foundation/decisions.json` lists component selections per capability, and + `catalogs/us-equities/decision-index.json` indexes repositories; neither lists decision records. + `manifests/evidence.json` takes the changed files it already lists and the new files under `evidence/` + (`docs/lanes.md`, hot-file protocol); like the context-budget record, this record is not registered there. + +## Checks + +| Command | Exit | Result | +| --- | ---: | --- | +| `python3 -m unittest tests.test_install_claude_profile.StandingRuleSurfacesTests`, before the template edits | 1 | 4 failures: `0 != 1` on each of the four surfaces (fail first) | +| `python3 -m unittest tests.test_wsl_new_distro_recipe.UserDataTemplateTests`, before the user-data edit | 1 | 3 failures: the two new `[time]` patterns, and the two new mutants were not yet mutations (fail first) | +| The same two checks, run in process against main `e8c1edec`'s four instruction files and user-data template | n/a | the same 4 and 3 failures (fail first, repeated) | +| `python3 -m unittest` (the full suite, with every code, test, template and registry change in place; only this record's prose changed afterwards) | 1 | 10,326 tests, 903 skipped, 11 failures and 18 errors, none in a test module this change edits. Every module this change edits, and every module that pins the two instruction templates, their carriers or the WSL recipe files, passes. 28 of the 29 recur unchanged in a clean clone of `e8c1edec`: a missing `exchange_calendars` module (the 18 errors, and the 5 `test_adaptive_paper_credential_race` failures, whose pristine baseline imports it), `dirname: command not found` (3), a systemd scope state (1) and the installed Claude Code's `auth_storage_failure` notification type (1). The 29th, `test_token_e2e_grader` F27c, passes in that clone; here `git clone --local` cannot hard-link from this worktree into `/tmp`, another file system | +| `node test-envelope.mjs` and `node test-contract-mutations.mjs`, in `examples/claude-native/workflows` | 0 | 254 and 74 passed | +| `python3 scripts/validate.py` | 0 | 10,217 hashed files, 208 receipts | +| `python3 scripts/evidence_manifest.py --check` | 0 | passed | +| `python3 scripts/validate_convergence.py blueprints/convergence-practice/wsl-new-distro-image-20261001/experiment.json` | 0 | the record is valid with the relocated frozen inputs | +| `python3 -B tools/adoption/new_wsl_client_config.py --check` | 0 | check passed | +| `python3 -B tools/adoption/new_wsl_client_config.py --render --host nativestack2604 --out /user-times-render` | 0 | each rendered carrier holds the sentence once and equals the committed carrier byte for byte; `managed_block`'s merge of each gives 12,071 and 8,601 bytes with one copy | +| `python3 scripts/build_new_wsl_handbook.py --check` | 0 | current outputs | +| P2's render, then `cloud-init schema -c` (26.1-0ubuntu3~26.04.1, on NativeStack2604) | 0 | `Valid schema` | +| Path A simulation: `[boot]` and `systemd=true`, the user-data's appended content, then the logic of wsl-setup 0.6.3's `set_user_as_default` | 0 | six lines, each once; wsl-setup changes nothing | +| Path B simulation: W6's `[user]` and `[time]` lines, run twice on `[boot]` and `systemd=true` | 0 | six lines, each once | +| `git diff --check` | 0 | clean | + +## Sources + +- **Microsoft wsl-config, "Time settings"** ([learn.microsoft.com](https://learn.microsoft.com/en-us/windows/wsl/wsl-config#time-settings), + read 2026-10-05): + - pinned source MicrosoftDocs/WSL `7ea1c6f9e25f1c89a05a0e97e5325a02a66ac6cd`, `WSL/wsl-config.md:152-158`, which was + still that file's latest commit on 2026-10-05; + - section `[time]`, key `useWindowsTimezone`, boolean, default `true`. +- **ubuntu/wsl-setup** `73418e32bb48d514c2c2853fa7e5cacdcaf3dfe8` (0.6.3), `wsl-setup:55-73` (`set_user_as_default`). +- **[RFC 3339](https://www.rfc-editor.org/rfc/rfc3339)**, read 2026-10-05: §4.1 (UTC, because daylight-saving rules + are convoluted and change by local law), §4.2 (numeric offsets; alphabetic labels interoperated poorly), §4.3 (`Z` + makes UTC the preferred reference point) and §5.6 (`time-offset = "Z" / time-numoffset`). +- **[Claude Code memory](https://code.claude.com/docs/en/memory)**, read 2026-10-05: the user instructions file + `~/.claude/CLAUDE.md`, "Personal preferences for all projects". +- **[Codex AGENTS.md guide](https://developers.openai.com/codex/guides/agents-md)**, read 2026-10-05: + - global scope in the Codex home (`~/.codex`, or `CODEX_HOME`); + - "Create persistent defaults in your Codex home directory so every repository inherits your working agreements". +- **Claude Code** [CHANGELOG at v2.1.289](https://github.com/anthropics/claude-code/blob/v2.1.289/CHANGELOG.md): the + 2.1.257 entry adds `timeFormat` and `timeZone`. The installed 2.1.289 binary holds the setting descriptions quoted + above. +- **Codex** [configuration reference](https://developers.openai.com/codex/config-reference), read 2026-10-05: + `timezone` appears only in `tools.web_search` (`location = { country, region, city, timezone }`). +- **cloud-init 26.1** (installed `26.1-0ubuntu3~26.04.1`): `cloud-init schema -c` on the rendered user-data, P2's + check. diff --git a/evidence/artifacts/user-facing-local-time-20261005/README.md b/evidence/artifacts/user-facing-local-time-20261005/README.md new file mode 100644 index 000000000..cb9582f49 --- /dev/null +++ b/evidence/artifacts/user-facing-local-time-20261005/README.md @@ -0,0 +1,12 @@ +# User-facing local time: frozen-input preservation (2026-10-05) + +The WSL image convergence experiment (`blueprints/convergence-practice/wsl-new-distro-image-20261001/experiment.json`) froze four files that the local-time change edits: the cloud-init user-data template, the new-distro decision record, the first-boot checklist and the stage-1 receipt example. The change adds an explicit `[time]` `useWindowsTimezone=true` to `/etc/wsl.conf` on both creation paths. The files under `frozen-inputs/` are exact bytes from main `e8c1edec` (`git show e8c1edec:`), verified against the experiment's already-recorded SHA-256 values. Only the frozen-input paths are relocated; the existing hashes, attempts, failures, qualification and observations are unchanged. No distribution command or new acceptance ran. The current files remain at their canonical paths and are checked separately by `tests/test_wsl_new_distro_recipe.py`. + +| Frozen copy | Canonical path at `e8c1edec` | SHA-256 | +| --- | --- | --- | +| `frozen-inputs/cloud-init.user-data.template.txt` | `adoption/templates/wsl/cloud-init.user-data.template` | `08e6a3f6b078dfa54d78297596d54729d10528d57147b9b9eae063c37160eeb9` | +| `frozen-inputs/2026-10-01-new-wsl-distro-recipe.md.txt` | `docs/decisions/2026-10-01-new-wsl-distro-recipe.md` | `ec03a494aa4d80ac004f74710250bf1ac7cf18e0506758d97a1cf98a0cee42a3` | +| `frozen-inputs/first-boot-checklist.md.txt` | `adoption/templates/wsl/first-boot-checklist.md` | `1a06df0e6d50b07b439317a61a450e450127052589f63254e769d99166e3603b` | +| `frozen-inputs/stage1-receipt.example.json.txt` | `adoption/templates/wsl/stage1-receipt.example.json` | `7c0811c460fe8732f413b954cd7ddadb5cc41a5300f8686b5b83aefcd80a971a` | + +Decision: `docs/decisions/2026-10-05-user-facing-local-time.md`. Precedent: `evidence/artifacts/new-wsl-fix-wave-20261004/README.md`. Contract: `docs/acceptance-evidence-policy.md`; native verification: `scripts/validate_convergence.py`. diff --git a/evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/2026-10-01-new-wsl-distro-recipe.md.txt b/evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/2026-10-01-new-wsl-distro-recipe.md.txt new file mode 100644 index 000000000..41c58cad2 --- /dev/null +++ b/evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/2026-10-01-new-wsl-distro-recipe.md.txt @@ -0,0 +1,1425 @@ +# Decision: Ubuntu 26.04.1 WSL default and 24.04.5 rollback, provisioned by cloud-init (2026-10-01) + +**Decided by:** coordinator session `native-agent-stack-c5` (decisions 1 to 5 below), on the research unit's findings of +2026-10-01. Unit W2 of that wave wrote the recipe, the templates and the test, and re-read the sources the same day. +A follow-up unit added F11 and the F5 range rule later that day, on the coordinator's brief. A second follow-up unit +then added the completeness critic's pre-stage-1 checks (R1, P1 to P3, W7, W5's schema check and F2's idle observation) +and corrected three facts, on the coordinator's brief and a research packet of the same day. The coordinator then set +P3's rule: the count is a baseline, an error line less than one hour old stops the run, and W5 counts again after the +first launch. A third follow-up took the updated host's first observations into the recipe on 2026-10-02: it names WSL +3.0.1 as the adopted target, adds W5's paired isolation record and F1's one accepted failed unit, and replaces the +interop restart with a stop for review (Updated-host correction). +This record changes nothing on a host. No `wsl.exe` command, import, `.wslconfig` edit or first launch ran for it. The +second follow-up's artifact checks ran P1 to P3's commands in the workstation distribution and changed nothing on the +host (Evidence classes). + +**Scope:** [`adoption/platforms/linux-wsl2-new-distro.md`](../../adoption/platforms/linux-wsl2-new-distro.md) and its +pointer section in `adoption/platforms/linux-wsl2.md`; `adoption/templates/wsl/` (`cloud-init.user-data.template`, +`host.new-distro.json.template`, `first-boot-checklist.md`, `stage1-receipt.example.json`); `tests/test_wsl_new_distro_recipe.py`. + +**Status:** the merged definitive manifest selects Ubuntu 26.04.1 LTS (Canonical WSL image) as the single default. +Ubuntu 24.04.5 is the named rollback, used only on a release-caused 26.04 failure with no in-release remedy. +The 2026-10-02 rehearsal held through stage 1, then stopped at F1 on the host's shared cgroup tree. It does not +trigger the image rollback. The host was updated to WSL 3.0.1.0 on 2026-10-02; full acceptance and the corrected +checks on it, with the two-distribution proof, remain owed. The earlier scoped +[experiment](../../blueprints/convergence-practice/wsl-new-distro-image-20261001/experiment.json) keeps `status: planned`, +decision `trial`, no qualification runs and no usage comparison. Its next test preregisters both R1 rehearsals. The +reported signature exits and image stream hash match retain their bounded artifact scope; local consistency checks and synthetic receipt examples +do not qualify a distribution. Recipe, templates, this decision and tests are pinned by SHA-256 in that experiment. + +## Context + +- **The goal.** The user asked for the definitive WSL distribution for this practice, set up without prompts in the + passwordless way an LLM-native session can follow end to end. Until now the repository had two pieces: + - stage-2 tooling: `adoption/bootstrap-linux.sh` and its full-profile run; + - userspace evidence: a fresh Ubuntu Base 24.04.5 filesystem on the existing WSL kernel + (`docs/portable-userspace-install-20260921.md`). + + It had no procedure that creates the distribution itself. +- **Host** (research unit, 2026-10-01): WSL 2.7.13.0, kernel 6.18.33.2-2 and default version 2, with the workstation's + distribution as the default. `wsl.exe --help` lists these forms: + - `--install --from-file --name --location --no-launch`; + - `--import [--version]`; + - `--manage --set-default-user `; + - `--terminate `. +- **Minimum WSL version for `--install --from-file`.** Microsoft gives 2.4.4 (build-custom-distro, updated 2025-09-12), + Ubuntu's announcement 2.4.8 and Ubuntu's install guide 2.4.10 (Method 1). That install-only minimum remains valid + for a single systemd distribution with no second systemd distribution running. It is insufficient for this page's + second distribution: W1 requires WSL 3.0.1 or later before W4 or any W6 import, for isolated cgroups and their + namespaces (PR #40519 and PR #41512). The research unit read the notes of WSL 2.7.14 (2026-09-11) and 3.0.1 (2026-09-29) and the `microsoft/WSL` + source at the three tags, and wrote that they change nothing about install, import or first run. That was wrong + (completeness critic, item 2). The fix for microsoft/WSL#40941, PR #40977 (after the first-run setup, files created + from Windows are owned by 0:0), first ships in 2.9.8, a pre-release, and in 3.0.1. Its function, + `ConnectionTargetManager::UpdateUid`, is absent from `src/windows/common/Redirector.h` at tags 2.7.13 and 2.7.14 and + present at 2.9.8 and 3.0.1 (research packet, answer 3). W7 terminates `` once after the first launch and probes + a file's owner; updating WSL stays the keys lane's decision. +- **Historical 24.04.5 image artifact check.** `ubuntu-24.04.5-wsl-amd64.wsl` (sha256 `bb415d824822c4b878125729af451a5d18fb13d1cf5cbed9a7393ad64ac6039e`) + is 388,975,696 bytes and published twice: + - in `https://releases.ubuntu.com/24.04.5/SHA256SUMS` (Last-Modified 2026-09-15 19:11:11 GMT; the same line is in + `noble/SHA256SUMS`, with `SHA256SUMS.gpg` beside it); + - as `Ubuntu-24.04` in `microsoft/WSL` `distributions/DistributionInfo.json` at `8bc98bc33b246fe66710eec9eaa1b24c323da987` + (2026-09-14, "Updates Ubuntu latest LTS images (#41465)"), which maps it to + `https://releases.ubuntu.com/24.04.5/ubuntu-24.04.5-wsl-amd64.wsl`. + + On the authoring host on 2026-10-01, a single stream of that URL measured 388,975,696 bytes with exactly that sha256 + (Last-Modified 2026-09-08 00:19:19 GMT, gzip). The gzip size field gives 1,275,596,800 bytes unpacked; that field holds + the size modulo 2^32. +- **What the image contains** (read from that stream): + - `/etc/wsl.conf` is `[boot]` `systemd=true`. + - `/etc/wsl-distribution.conf` has `[oobe] command = /usr/lib/wsl/wsl-setup`, `defaultUid = 1000` and + `defaultName = Ubuntu-24.04`, plus a shortcut icon and a Windows Terminal profile template. + - `/etc/cloud/cloud.cfg.d/99_wsl.cfg` sets `datasource_list: [WSL, NoCloud]`, disables network configuration and creates + no default user. + - `/etc/subuid` and `/etc/subgid` are present and empty (0 bytes). `/etc/login.defs` lines 178-193 set `SUB_UID_MIN` + and `SUB_GID_MIN` to 100000 and `SUB_UID_COUNT` and `SUB_GID_COUNT` to 65536. + - `/etc/skel` has `.bash_logout`, `.bashrc` and `.profile`, and no `.bash_profile`. + - Installed (from the dpkg status): `cloud-init` 26.1-0ubuntu1~24.04.1, `systemd` and `systemd-sysv` 255.4-1ubuntu8.17, + `libpam-systemd`, `dbus-user-session` 1.14.10-4ubuntu4.1, `sudo` 1.9.15p5, `git` 2.43.0, `python3` 3.12.3, `curl`, + `ca-certificates`, `xz-utils`, `iproute2` 6.1.0, `gnupg` 2.4.4 and `wsl-setup` 0.5.10~24.04.2. + - Absent: `jq`, `uidmap`, `bubblewrap`, `socat`, `libatomic1` and `ubuntu-insights`. The upstream `wsl-setup` + repository now calls an Insights consent script that this image does not ship. +- **First run in the image's `/usr/lib/wsl/wsl-setup`** (148 lines, read from the stream): + - It reads the Windows user name through `powershell.exe`, only to prefill a prompt (line 121). + - When the file is missing, it copies the Ubuntu Sans Mono font into `%LOCALAPPDATA%\Microsoft\Windows\Fonts` and + registers it under `HKCU` (lines 91-114, called at 125). + - It sources `wait-for-cloud-init` (lines 127-130). With systemd running and `cloud-init-local.service` enabled, that + script waits with `cloud-init status --wait` and then touches `/etc/cloud/cloud-init.disabled` (lines 9-12), so + cloud-init runs on the first boot only. + - It reuses the first interactive user with uid 1000 or more (lines 132-133). With none, it prompts + `Create a default Unix user account:` (`read -e`, line 30, under `set -euo pipefail`, line 2), runs + `adduser --quiet --gecos ''` (line 39) and adds the groups `adm,cdrom,sudo,dip,plugdev` (lines 20 and 44). + - It appends `[user] default=` to `/etc/wsl.conf` only when no `default` exists under `[user]` (lines 54-72 and 146-148). + - W5's two console lines, `Provisioning the new WSL instance $WSL_DISTRO_NAME` and `This might take a while...`, come + from this script, not from WSL (follow-up research packet, answer 6). At the image's version they are lines 117-118: + Launchpad `ubuntu/+source/wsl-setup` tag `import/0.5.10_24.04.2`, commit + `74bfc89113bc7d46a4d9feb1e69cd6951fbc6908` (also `ubuntu/noble-updates`, changelog `wsl-setup (0.5.10~24.04.2)`), + and GitHub tag 0.5.10 (`f83e4df49b3583272d5ca499ca63b426b934a1f3`). At the GitHub commit this record pins for + `test/systemd-assertions.sh`, `86a561d5`, they sit at lines 122-123. WSL 2.7.13's `Resources.resw` holds neither + string. The image's version, 0.5.10~24.04.2, is the research unit's read of the image's dpkg status; the follow-up + did not re-read the image. +- **When WSL runs that command** (`microsoft/WSL` tag 2.7.13, commit `80697fd42cca3de0c0d5dd1931c36112372a577e`, read + 2026-10-01): + - `WslClient.cpp:321` sets `LXSS_IMPORT_DISTRO_FLAGS_NO_OOBE` for `--import`. `--install --from-file` passes only the + fixed-VHD flag (`:529-535`) and prints `Installing: {}` and + `Distribution successfully installed. It can be launched via 'wsl.exe -d {}'` (`:525`, `:537`; texts from + `localization/strings/en-US/Resources.resw`). + - `LxssUserSession.cpp` registers the distribution with default uid root (`:1501-1511`, uid at `:1508`) and first-run + setup on only when `NO_OOBE` is clear (`:1511`). It refuses a name or install path another registration uses + (`:1475`, `:3798-3811`) and creates a missing install folder (`:1477-1481`). Without `--location` it uses + `%LocalAppData%\wsl` plus a GUID (`:1464-1471`). + - `WslCoreInstance.cpp:204-207` allows the first-run command only for a launch with no file name and no command line. + `:171-178` makes any other launch wait, printing `Waiting for OOBE command to complete for distribution "{}"...`, and + reads the default uid only after the command. `:293-303` clears `RunOOBE` and stores `oobe.defaultUid` only on exit 0; + any other result leaves the setup pending. + - `src/linux/init/init.cpp:623-676` runs the command as uid 0 through `/bin/sh -c`. A nonzero exit prints + `OOBE command "%s" failed, exiting` and exits 1 (`:655-673`); success switches to `oobe.defaultUid` (`:675`). + - `--import` extracts with `bsdtar` (`src/linux/init/main.cpp:1137-1155`), which reads the gzip `.wsl` file too. + - The research unit added `_ProcessImportResultMessage` (`LxssUserSession.cpp:1597`). It creates the Start-menu shortcut + and Windows Terminal profile (`:1682`), and both default to on (`src/linux/init/main.cpp:2656-2669`). +- **cloud-init on WSL.** Per Canonical's how-to and the cloud-init WSL datasource reference, the datasource reads + `%USERPROFILE%\.cloud-init\.user-data` first. A Landscape file at + `%USERPROFILE%\.ubuntupro\.cloud-init\.user-data` takes precedence. The datasource needs interop, + automount and systemd. Both pages' examples create the user with `sudo: ALL=(ALL) NOPASSWD:ALL` and append + `[user] default=` to `/etc/wsl.conf` through `write_files`. Whether cloud-init provisions an imported distribution is + not documented. +- **cloud-init 26.1, the image's version, at tag `26.1`** (commit `8bf3567532b07e2cc15aa4c76c36ebed65ccfaec`; source read + 2026-10-01): + - `cloudinit/sources/DataSourceWSL.py` loads the Ubuntu Pro files first: the Landscape instance file and `agent.yaml`, + both in `%USERPROFILE%\.ubuntupro\.cloud-init` (`:241-270`, `:465-471`). It reads the local file only when no + Landscape file exists (`:474-476`) and merges the result (`:490`). `merge_agent_landscape_data` lets every top-level + `agent.yaml` key replace the user-data key entirely (`:317-336`), keeping only the user-data's Landscape tags + (`:344-350`). When either side is not cloud-config, both go to cloud-init as an `#include` with the agent first + (`:307-315`). + - `cloudinit/cmd/status.py`: with systemd, `/etc/cloud/cloud-init.disabled` makes the boot code + `disabled-by-marker-file` (`:284-286`) and the status `disabled` whatever the run did (`:385-386`). `detail` is the + datasource while the run's `/run/cloud-init/status.json` exists and the marker reason otherwise (`:407-419`, + `:471-481`). `errors` come only from that `/run` file (`:490`). With no datasource found, the generator's code is + `disabled-by-generator` (`:298-300`). After the first launch, `cloud-init status` therefore proves the marker, not + the run. + - `cloudinit/cmd/main.py`: `status_wrapper` keeps `status.json` and `result.json` in the persistent data directory + `/var/lib/cloud/data`, with only symbolic links in `/run/cloud-init` (`:880-888`, `:960-964`). Each stage's + `start`, `finished`, `errors` and `recoverable_errors` go into `status.json` (`:915-929`, `:975-1015`). + `result.json`, with the datasource and the errors of every stage, is written only when the final stage ends + (`:1017-1030`). Single-process boots go through the same wrapper (`:1312-1368`, `:1400-1404`). The datasource + string is the class name, `DataSourceWSL` (`cloudinit/sources/__init__.py:398-399`, + `cloudinit/type_utils.py:21-28`). +- **What stage 2 expects** (`origin/main` 3361b342): + - `adoption/bootstrap-linux.sh:167-178` requires x86_64, a non-root user and an Ubuntu or Debian `ID`. + - `:186-200` refuses `--configure-full-profile` unless the checkout's `HEAD` is `origin/main`. + - `:205-218` installs `ca-certificates curl git tar gzip xz-utils jq`, and `:221` requires + `curl git tar sha256sum realpath flock jq mktemp`. + - `:237-242` exits 1 on an empty `--profile`, and `:154-163` reads `adoption/hosts/.json` for `--host`. + - `adoption/bootstrap.md:227-228` gives the whole-profile run as + `adoption/bootstrap-linux.sh --profile --configure-full-profile --host `, after native sign-in (step 3). + - The guarded runners need a real `systemd --user` bus socket at `/run/user/$UID/bus`, owned by the user + (`adoption/tools/README.md:184-188`). + - Linger appears in the repository only as a draft (`adoption/templates/systemd/credential-boot-receipt.service:11-12`) + and as "no record yet" (`docs/new-workstation-runtime-profile-20260922.md:25`). +- **Shared namespace.** All WSL 2 distributions share one network namespace (`adoption/platforms/linux-wsl2.md`, + "Listeners and ports"). The template's ports collide with the workstation's services: `adoption/hosts/example.json:7-10` + uses 14318, 49374, 16333 and 8231. The observability backend's templates fix 13000, 13100, 14333, 16333, 18080, 18525, + 18888, 18889, 19090, 19093, 20128 and 31415, and the live services also use 20129, 3710, 3800, 49474 and 18231. +- **jCodeMunch on a new distribution** (follow-up, 2026-10-01). The coordinator reported this gap from the cross-family + review of PR #548. Stage 2's Claude profile step copies six SubagentStart carrier blocks from `adoption/hooks/claude/` + into `~/.claude/hooks/` (`tools/adoption/install_claude_profile.py:49-59`): + - `token-lanes-block.md` and `token-lanes-block.researcher.md` name jCodeMunch's `route`, `menu` and `order`; + - `token-lanes-block.builder.md` and `token-lanes-block.reviewer.md` name `route` and `order`; + - `token-lanes-block.scout.md` and `token-lanes-block.verifier.md` name no jCodeMunch tool. + + `adoption/mcp/claude-user.json` registers only `ai-memory` and `serena` at user scope. The 2026-09-25 addendum of + `docs/decisions/2026-09-23-claude-user-profile.md` moved jCodeMunch to a per-project opt-in, + `claude mcp add --scope local` (`adoption/bootstrap.md:458-472`), and no script runs it. No profile of + `adoption/manifest.json` and no entry of `adoption/pins-linux-x86_64.json` carries `jcodemunch-mcp`. Only step 4a's + `uv tool install --python 3.13 jcodemunch-mcp==1.108.319` installs it (`adoption/bootstrap.md:387-395`), and F9 does + not run that line. +- **Local scope** (Claude Code's MCP page). "Local scope is the default. A local-scoped server loads only in the project + where you added it and stays private to you. Claude Code stores it in `~/.claude.json` under that project's path, so + the same server won't appear in your other projects." Its "Server status" section says "`claude mcp add` confirms a + successful add by printing an `Added ...` line, which means the configuration was written". In its example, a + repeated add at the same scope fails with `MCP server sentry already exists in local config`. +- **More than 65,536 subordinate ids.** Docker's rootless troubleshooting page says of + `docker: failed to register layer: Error processing tar file(exit status 1): lchown : invalid argument`: "This + error occurs when the number of available entries in `/etc/subuid` or `/etc/subgid` is not sufficient. The number of + entries required vary across images. However, 65,536 entries are sufficient for most images." The coordinator's + brief calls the workstation's wider range a host observation for one benchmark image set. +- **Facts behind the second follow-up** (research packet of 2026-10-01; each source read that day, quoted as the packet + quotes it): + - *Install-path hashes, WSL 2.7.13* (`80697fd4`). The online `wsl --install ` looks the name up in + DistributionInfo.json (`src/windows/common/WslInstall.cpp:94-146`) and runs `EnforceFileHash(file.get(), + downloadInfo->Sha256)` (`:315`) before registering; a mismatch throws `TRUST_E_BAD_DIGEST` with + `MessageHashMismatch` (`:36-50`; "The distribution hash doesn't match. Expected: {}, actual hash: {}", + `Resources.resw:837-838`). The list comes from + `https://raw.githubusercontent.com/microsoft/WSL/master/distributions/DistributionInfo.json` + (`src/windows/common/Distribution.cpp:20-21`) unless the host-wide `DistributionListUrl` or + `DistributionListUrlAppend` value of the Lxss machine key overrides it (`:223-230`, `Distribution.h:85-86`). + `--install --from-file` goes straight to `RegisterDistribution` with no hash check (`WslClient.cpp:500-537`). + - *File ownership after the first-run setup.* https://github.com/microsoft/WSL/issues/40941 (closed "completed" + 2026-08-07, label `fixinbound`; reported on WSL 2.7.8.0). A contributor's reproduction + (issuecomment-4863506473): "3. Finish the OOBE. 4. Create a file from Windows. The file created will be 0:0 until + the next wsl --shutdown." https://github.com/microsoft/WSL/pull/40977 (merged 2026-08-07, merge commit + `70f6890c83bb5e56b8ea17abdbb834b55e92dc80`): "Currently, after a fresh distro installation, operations in the linux + plan9 share will use the uid 0. Because the uid was cached before the OOBE is complete. And this only recovers after + a distro termination." The 2.9.8 release notes list it; the 2.7.11 to 2.7.14 notes do not. At 2.7.13, + `WslCoreInstance.cpp:293-302` stores the new default uid after the setup without refreshing the plan9 registration. + No source says whether `wsl --terminate` clears the state. + - *Kernel storage errors.* https://github.com/microsoft/WSL/issues/41482 (closed by its reporter 2026-08-31, no linked + fix): continuous `hv_storvsc` errors such as `tag#542 cmd 0x28 status: scsi 0x2 srb 0x4 host 0xc0000001` on WSL + 2.7.12.0, kernel 6.18.33.2-2 and Ubuntu 24.04 LTS, with `/sbin/init failed to start within 10000ms` and + `Timed out waiting for user session`; the reporter counted them with `dmesg | grep -c hv_storvsc`. A contributor: + "This seems to be an issue with the swap.vhdx. The exact cause is not clear yet"; the workaround is `[wsl2]` + `swap=0` and `wsl --shutdown`. A comment of 2026-09-28 reports the same on Fedora 44. + - *Idle shutdown.* wsl-config (pinned in Sources): `[wsl2] vmIdleTimeout`, default 60000, "Only available on Windows + 11"; `[general] instanceIdleTimeout`, default 15000, "Set to -1 to disable auto shutdown" (added to the page by + MicrosoftDocs/WSL#2620; the key came with WSL 2.5.4). WSL 2.7.13 `LxssUserSession.cpp:2658-2676`: "A value of less + than zero indicates that the instance should never be idle-terminated"; `:3583-3599` stops an instance only when + no Windows calling process is registered. https://github.com/microsoft/WSL/issues/13416 (open): on WSL 2.7.3.0, + "`loginctl enable-linger` ... does not prevent the poweroff" (issuecomment-5245052311). The coordinator read this + workstation's global WSL configuration on 2026-10-01 at 10:08Z, values only: `[wsl2] vmIdleTimeout=-1`, + `[general] instanceIdleTimeout=-1`, `networkingMode=mirrored` and `swap=24GB`. + - *cloud-init's schema check.* Canonical's WSL how-to checks the provisioned instance with + `sudo cloud-init schema --system` and expects `Valid schema user-data`. In cloud-init 26.1 + (`cloudinit/config/schema.py`): root is required (`:1388-1393`); a missing file prints + `Error: Config file ... does not exist` (`:1428-1433`); with more than one data part a header comes first and the + line is indented (`:1443-1458`, `:1492`); an invalid schema exits 1 (`:1493-1498`, + `cloudinit/log/log_util.py:68-79`). The `schema` subcommand never reads `/etc/cloud/cloud-init.disabled` + (`cloudinit/cmd/main.py:1240-1241`, `:1286-1293`), and `wait-for-cloud-init` runs no `cloud-init clean`. + - *Signed checksums.* https://ubuntu.com/tutorials/how-to-verify-ubuntu (undated): "Ubuntu and most variants come with + the relevant keys pre-installed"; it verifies with `gpg --keyid-format long --verify SHA256SUMS.gpg SHA256SUMS` + after a keyserver fetch. `ubuntu-keyring` 2023.11.28.1 (Launchpad `import/2023.11.28.1`, + `edc0a0be9a90f364bc39a87d3837ad4c15395950`, `debian/changelog:141-142`) adds "4096R/EFE21092 Ubuntu CD Image + Automatic Signing Key (2012)" to `ubuntu-archive-keyring`, so `/usr/share/keyrings/ubuntu-archive-keyring.gpg` + carries `843938DF228D22F7B3742BC0D94AA3F0EFE21092`. The repository's 2026-09-21 precedent verified Ubuntu's signed + sums with the same key (`docs/portable-userspace-install-20260921.md`). + +## W-IMG preregistration and preserved primary evidence (2026-10-01) + +The north-star action is a recoverable second WSL distribution for native engineering. The accepted unit extends the +existing upstream-command recipe, templates and tests at `798ac445`; it creates no installer or architecture. The +coordinator applied the maintained ECC `search-first` guidance in quick mode and the TDD skill at the already-agreed +public recipe/template/experiment seams. No new dependency or skill installation is needed. + +| Image role | Actual release URL | Exact image SHA-256 | Catalog entry at `8bc98bc33b246fe66710eec9eaa1b24c323da987` | +| --- | --- | --- | --- | +| 26.04.1 default | https://releases.ubuntu.com/26.04.1/ubuntu-26.04.1-wsl-amd64.wsl | `48d56724b5c8e60f24893e83e73bbb58c60b3ca22fba3da977075420acd54104` | `Ubuntu-26.04` (also `Ubuntu`) | +| 24.04.5 rollback | https://releases.ubuntu.com/24.04.5/ubuntu-24.04.5-wsl-amd64.wsl | `bb415d824822c4b878125729af451a5d18fb13d1cf5cbed9a7393ad64ac6039e` | `Ubuntu-24.04` | + +Both arms use the same R1 preregistered criteria with the amendments of 2026-10-02 made before any comparison ran +(recorded below), and native probes in the recipe, on separate throwaway names under the same host kernel, driver, +settings, checkout, user-data and bootstrap profile. R1 keeps its initial run +through F3, then extends successful rehearsals through F9 for the existing toolkit and the bounded comparison probes. +The default-user systemd manager and bus are checked from the workstation's second-instance session. GPU visibility +uses Microsoft's/NVIDIA's native WSL guidance; toolkit runtime checks use the accepted bootstrap and official Node +24.21.0 pin. A version/startup probe or visible GPU does not establish full-stack, CUDA workload or model acceptance. +The clean install uses the single 26.04.1 default from +[`definitive-manifest.json`](../../evidence/artifacts/new-wsl-definitive-defaults-20261001/definitive-manifest.json). +The 24.04.5 rollback returns only for a release-caused 26.04 failure with no in-release remedy. Both arms' full +new-host acceptance remains owed; the comparison distributions do not become clean-install alternatives. + +**Preserved facts, reused rather than rerun.** The retained +[integration receipt](../../evidence/artifacts/new-wsl-dual-image-20261001/receipt.json) reports signed-checksum +verification exit 0 for both releases, signer fingerprint `843938DF228D22F7B3742BC0D94AA3F0EFE21092` and a 26.04.1 +image stream hash match. There is no retained verification output for that 26.04.1 signature check; those reported +exits, fingerprint and match do not establish a retained `Good signature` stream. The earlier 24.04.5 P1 output and +tampered-copy control below retain their historical artifact scope. No 26.04.1 image size was observed in that +authoring stream; the later rehearsal records its own download size separately. + +The inspected 26.04.1 packages are cloud-init 26.1-0ubuntu3~26.04.1, systemd 259.5-0ubuntu3.4, Python interpreter +3.14.4-1ubuntu0.1, sudo-rs 0.2.13-0ubuntu1 and wsl-setup 0.6.3ubuntu~26.04.1. Correction: the earlier Python 3.14.3 +statement was the `python3` metapackage (3.14.3-0ubuntu2); the retained dpkg `python3.14`, `python3.14-minimal` and +`libpython3.14-stdlib` stanzas prove interpreter 3.14.4-1ubuntu0.1. System Python 3.14 is not toolkit CPython 3.13 +migration acceptance; uv selects and installs managed 3.13 separately. Installed uv 0.12.17 `run --help` exited 0 +and lists `--no-project` and `--python`; no managed interpreter run was performed for this unit. + +The retained `wsl-setup` and `wait-for-cloud-init` image extracts match tag `0.6.3`, upstream commit +`73418e32bb48d514c2c2853fa7e5cacdcaf3dfe8`. `wsl-setup:155-156` calls `ubuntu-insights.sh`. Its consent path preserves +existing Linux/Windows settings; `ubuntu-insights.sh:15-19` asks only on terminal stdin (`-t 0`). W5 uses NUL stdin. +No recipe command invents consent or writes the Windows-wide consent registry. Neither source inspection nor the +workstation's P2 schema check qualifies a new image; W5 must record the selected packaged version and its own schema. +Canonical's cloud-init WSL guide explicitly assumes 24.04/22.04 and does not qualify 26.04. + +**Prior failed preparation attempt.** The preceding native Claude worker returned the actual weekly-limit message, +process exit 1, and made no changes. Its native returned usage is preserved in the experiment's usage rule, without +summing cache subsets, estimating missing totals or treating unknown retries as zero. This is a recipe-preparation +failure, not a failed first boot of either image. No provider, GPU, import or first-boot run is claimed by this unit. + +**Completeness critic.** Both signed sums, exact selected-image bytes/hash, packaged schema versus host schema, +second-instance systemd user behavior, Windows GPU visibility, separate uv CPython 3.13 and Node 24 startup now have +explicit symmetric criteria. Preserve the old export guard for failed rehearsals and W6. The remaining gap is native +new-host execution of both arms, plus separate full-stack/GPU-workload/model acceptance. Debian, alternate creation +routes and containers remain the earlier sweep candidates; this bounded unit adds none. The lifecycle-task skills +sweep receives only a future execution gap, since the accepted lifecycle/TDD/search guidance covers parameterization. + +## Rehearsal correction (2026-10-02) + +The retained `coordinator-review.md` and `stage1-receipt.json` on +`origin/foundation/new-wsl-rehearsal-20261002` at `76b7c0b4ee45d3a6f0a60fa68d053e0f2039e8e4`, under +`evidence/artifacts/new-wsl-rehearsal-20261002/`, record the real +Ubuntu 26.04.1 rehearsal on WSL 2.7.13. Stage 1 held, including schema and ownership; F1 returned `degraded`, with +`user@1000.service` failing to spawn its executor. From the already-running distribution, the coordinator read +`0 0` in `system.slice/cron.service/cgroup.procs` and the other distribution's user manager reported the +workstation's own `ControlGroup`. The supplied namespace observation, `cgroup:[4026531835]`, names Linux's initial +cgroup namespace, `PROC_CGROUP_INIT_INO` in `include/linux/proc_ns.h`; it corroborates the diagnosis and is not a gate. + +Correction: command support on 2.7.13 did not establish safe concurrent systemd distributions. Microsoft's release +notes name PR #40519 in 2.9.8 (isolate distro cgroups) and PR #41512 in 2.9.13 (create their namespaces), both +pre-releases; 3.0.1 is the first stable release with both. The 2.7 line has neither. W1 now requires 3.0.1 or later +before any install or import. After launch, W5 must observe no `0` entry in a unit active in both distributions, from +the already-running distribution, and an active new `user@.service`, before F1. A failed observation terminates +only the new distribution, exports, unregisters and checks surviving interop; it never stops or restarts a system unit +in either distribution. When this correction was written, nothing had been observed on 3.0.1 on this host; the +Updated-host correction below records what followed. Rootless Docker/Podman issue 41492 remains open; F3's container +start is owed on the first run after the update. + +After removal, the surviving distribution lost the VM-wide `WSLInterop` registration. The coordinator restored it +with `sudo systemctl restart systemd-binfmt`. Every unregister now checks both that registration and one Windows +executable on the survivor and stops if either observation does not return. The Updated-host correction replaces +the restart, which the adopted release does not support, with a stop for review. + +The rehearsal's storage baseline was 7 and second count 13, with six new lines in the disk-attachment window before +cloud-init. Their placement about 15 seconds before `init-local` is derived in the receipt; the launch exited 0. +W5 and the checklist now record an attachment-only increase with its lines and continue; any later storage error or +provisioning failure with a storage cause stops. The older rule requiring equality was not supported by that run. + +The repair extends the existing Python `unittest` consistency checks with independent stage ids, every selected-image +digest field and in-memory controls restoring the old unsafe wording. These checks inspect source only; they do not +rerun the host rehearsal. A completeness check keeps path B, the export guards, all existing stages and the older +single-distribution install boundary; it also checks the new unregister in cgroup recovery. Public upstream refresh +was unavailable because DNS resolution failed, so the retained review and the supplied upstream references are used +without claiming a fresh fetch or an updated-host result. + +## Updated-host correction (2026-10-02) + +The coordinator's brief of 2026-10-02 reports the first observations of the updated host, WSL 3.0.1.0 with kernel +6.18.40.1-1, made from 06:39Z to 06:57Z in the workstation's distribution, as its default user and without `sudo`. +`id -u` printed `1000`. `systemctl is-system-running` printed `degraded` (exit 1), with `systemd-binfmt.service` as the +only failed unit. `systemctl is-active "user@$(id -u).service"` printed `active`. `readlink /proc/self/ns/cgroup` +printed `cgroup:[4026532183]`, equal to `/proc/1/ns/cgroup`; WSL 2.7.13 had printed the initial namespace, +`cgroup:[4026531835]`. The host also passed its check of a rootless container, interop and the user manager. +`systemd-binfmt.service` failed at each observed boot with `Failed to flush binfmt_misc rules, ignoring: Read-only file system` +and exit 1, while `WSLInterop` and `python3.12` are registered and Windows executables launch. (Qualified on 2026-10-02 +after the Codex lane's bounded review of `46acc1e5`: WSL installs that read-only lock only while `[boot] +protectBinfmt` is on, its default, and as a best-effort step, `init.cpp` L2433 and L2916-L2923 and +`WslDistributionConfig.h` L62 at tag `3.0.1`; "every boot" is this host's observation under the default, not an +upstream guarantee.) Nothing was observed with +two distributions running. + +Three decisions follow; Sources lists what each rests on. + +1. **Adopted target.** The recipe adopts stable WSL 3.0.1 or later for a second systemd distribution. The 2.9.8 and + 2.9.13 pre-release history and the 2.7.13 rehearsal stay as history, and the single-distribution note stays. The + host's update and its check are one sentence in Host-wide rules; the two-distribution proof is still owed. +2. **Paired isolation (W5).** W5 records the same five observations from both running distributions, `uid`, + `system_state`, `failed_units`, `user_manager` and `cgroup_namespace`, under `paired_isolation` in the receipt, after + the cgroup block and before F1. The workstation's values must equal its own baseline from W1, the two namespaces must + differ from each other and from `cgroup:[4026531835]`, and both user managers must be `active`. W6's relaunch repeats + the record, as it repeats the cgroup proof, and the failure recovery is the cgroup-failure recovery, unchanged. +3. **The by-design unit failure (F1) and the interop recovery (R1).** F1 accepts `degraded` only when + `systemctl --failed --no-legend --plain` lists exactly `systemd-binfmt.service` and that unit's log holds the + read-only flush message; any other failed unit stops the run as before. `sudo systemctl restart systemd-binfmt` was + the WSL 2.7.x interop remedy. On the adopted release the registration is protected and that restart itself exits 1, + so R1 keeps its check after every unregister and, when it fails on 3.0.1, stops for review with + `ls /proc/sys/fs/binfmt_misc` and `systemctl status systemd-binfmt.service --no-pager` recorded; nothing is imported + or provisioned. + +Alternatives, each rejected against that evidence: + +- **A one-sided W5 reading**, the new distribution's `user@.service` alone: it cannot show the same uid, distinct + namespaces and two healthy managers, so the record pairs the five observations. +- **Namespace values alone**: a value does not prove isolation (W1 already says so), and two values compare only next to + the workstation's own baseline. +- **Stopping at every `degraded`**, the earlier F1: the unit fails at each boot on the adopted release under its default + `protectBinfmt` setting, so no run on this host could pass F1. +- **Accepting any `degraded`**: it would hide a real failed unit. The pass condition names the one unit and its log + message. +- **Keeping the restart as the interop recovery**: it exited 1 on this host on the adopted release, where PR #40621 + protects the registration on purpose while `protectBinfmt` is on (its default). + +The conditions that would overturn these decisions are in Overturn condition, items 3 and 4. + +## Alternatives + +1. **Images.** Ubuntu 26.04.1 LTS (Canonical WSL image) is the single clean-install default; the parameterized recipe + retains historical 24.04.5 as the named rollback only for a release-caused 26.04 failure with no in-release remedy. + Full new-host compatibility remains owed. The earlier `python3` 3.14.3 statement described its metapackage, not + the 3.14.4 interpreter (correction in W-IMG preregistration). + Other alternatives: + - `wsl --install Ubuntu-24.04` from the online catalog installs the same bytes. At 2.7.13 it does check the download + against the catalog entry's `Sha256` (`WslInstall.cpp:36-50`, `:315`), while `--install --from-file` checks no + hash (`WslClient.cpp:500-537`); this record first had that backwards (completeness critic, item 8). The online path + reads the catalog from `master` at install time unless a host-wide `DistributionListUrl` override is set. The file + path is kept for a pinned file and a hash the operator sees, against a catalog resolved at install time, and W2's + own hash check exists because `--from-file` verifies nothing. + - The `cloud-images.ubuntu.com/wsl` rootfs tarballs: that page says WSL images moved to cdimages and those tarballs are + not general-purpose. + - The other images of WSL's catalog (Fedora, Arch, AlmaLinux, openSUSE, SLE, Kali and eLxr) are out of scope while + stage 2 accepts only `ID=ubuntu` or `ID=debian` (`adoption/bootstrap-linux.sh:175-178`). Debian, whose catalog + file is a salsa.debian.org CI artifact, feeds the layer's next landscape sweep (completeness critic, item 6). + - WSL containers, generally available in 3.0.1, feed the next landscape sweep for disposable clean-install tests; + they are unlikely to host stage 2 (completeness critic, item 7). + - A self-built image is out of scope: it breaks the rule to install upstream artifacts with upstream commands + (completeness critic, item 9). +2. **Creation path.** + - **Interactive first launch.** The prompt of `wsl-setup` line 30 needs a person, which blocks an LLM-native run. + - **`--import` with a manual user (path B).** It starts as root and skips the first-run command + (`WslClient.cpp:321`), so `wait-for-cloud-init` never writes its marker; the cloud-init question stays open. + - **In-place repair after a failed first launch.** Not chosen by the coordinator; recommended by unit W2. Create the user + as root with `wsl.exe -d -u root --exec useradd ...` and relaunch without a command. A failed first run leaves + `RunOOBE` set (`WslCoreInstance.cpp:293-297`), and the next launch reruns `wsl-setup`, which reuses any uid 1000 or + more user (lines 132-133) and writes the marker. The registration, the official first-run path and the marker all + stay as in path A, and no disk is deleted. + - **cloud-init `packages:` in the user-data.** It would fold F4 into the unattended phase, where a failed `apt` would + surface only as cloud-init's recoverable errors. + - **A golden copy**, `wsl --export` of a provisioned distribution and then `--import-in-place` of its disk, feeds the + layer's next landscape sweep. **Ubuntu Pro for WSL** (Landscape) is out of scope: it needs a subscription and a + server (completeness critic, item 9). +3. **First boot.** + - Leave out `libatomic1`. The Node 24.21.0 binary that `adoption/pins-linux-x86_64.json` pins lists no + `libatomic.so.1` among its `NEEDED` libraries (the tarball matched its pin + `fd8e59d5a511510f6a298afb548f18c7d2b1be404d8b4a27d94fbe49f56cb2d6`; read with `readelf -d` on 2026-10-01). + - Append `:100000:65536` to `/etc/subuid` and `/etc/subgid` by hand, as in Docker's example, instead of + `usermod --add-subuids` and `--add-subgids`. + - Allocate a wider subordinate range at first boot, as the workstation has. Docker's troubleshooting page ties the + need to the image set and calls 65,536 sufficient for most images. + - Register jCodeMunch in F11 through step 4a's checked-in `.mcp.json` form. It waits for workspace trust and + approval, and a nested `${ECO_INSTALL_ROOT:-...}` default does not expand there (the addendum's measurement). + - Install `jcodemunch-mcp` in F11 when it is absent, with step 4a's `uv tool install` line. Not chosen in the + coordinator's brief: F11 records `not installed`, and the install stays with step 4a. + - Leave the clone unregistered. Four carrier blocks would then name tools of a server the session lacks. +4. **Host-wide.** + - Proceed with the second systemd distribution on WSL 2.7.13: rejected after the rehearsal proved a shared cgroup + tree and a failed uid-1000 user manager. WSL 3.0.1 or later is now a host prerequisite, for PR #40519 and PR + #41512, and also carries the fix for microsoft/WSL#40941. The keys lane performs the update outside this page. + W7's owner probe remains required. This record's earlier install-only version claim did not establish safe + concurrent systemd distributions; the retained rehearsal and upstream release notes correct it. + - Apply settings with `wsl --shutdown`: it stops every distribution. + - Set `swap=0` for microsoft/WSL#41482, or change an idle key: both change the global WSL configuration, and the + first takes effect only after a WSL shutdown; that is the user's decision, not this recipe's. + - P3 as a bare count that stops on any error line, the second follow-up's first form: rejected by the coordinator. + A count over the current boot cannot tell errors that are happening now from an old burst, and on this host one + burst seven days earlier would have blocked stage 1 for good. P3's count is now a baseline; the newest line's age + and W5's second count decide. + - Judge that age by the journal's wall-clock stamp (`-o short-iso`): rejected, because journald stamps a kernel + line when it reads it, and after a restart of the workstation distribution it reads the ring buffer again and + stamps old lines anew. The kernel time against `/proc/uptime` does not move. + - Make the new distribution the default now: the workstation stays the default until a separate decision. + +## Decision + +1. **Images.** The single clean-install default is Ubuntu 26.04.1 LTS (Canonical WSL image). Select 24.04.5 only as + the named rollback for a release-caused 26.04 failure with no in-release remedy. P1 verifies both signed sums; W2 controls each release-to-file-to-SHA-to-catalog + pairing, downloads the selected release URL and hashes the actual whole image. W4 and W6 use the same selected + filename. Historical 24.04 receipts keep their original evidence class and scope; they do not accept either arm + on a new host. +2. **Creation path.** + - **Path A.** Render `%USERPROFILE%\.cloud-init\.user-data` from the template (W3). The user is ``, + uid 1000, in groups `adm, cdrom, sudo, dip, plugdev`, with `sudo: "ALL=(ALL) NOPASSWD:ALL"`, a locked password and + `[user] default` appended to `/etc/wsl.conf`. Then run + `wsl --install --from-file \ubuntu--wsl-amd64.wsl --name --location Z:\WSL\ --no-launch` (W4), + then a first launch with standard input at end of file (W5). + - **Rehearsals first (R1).** Each release runs on its own throwaway name and folder through F3, recording W5's schema, + W7's probe and F2's idle observation. Successful rehearsals extend through F9 and the same preregistered comparison + probes; per-arm results enter `comparison_arms`. R1 terminates and unregisters only its own name, after W6's export + rule when a rehearsal failed. Both comparison records precede the real explicitly selected ``. + - **Pre-checks (P1 to P3)**, in the workstation distribution before W1. P1 verifies `SHA256SUMS` with `gpgv` + for both releases against the installed archive keyring (key `843938DF228D22F7B3742BC0D94AA3F0EFE21092`) and prints + both signed image lines. P2 runs the workstation's `cloud-init schema -c` on the selected-image render and prints its + SHA-256, which W3's file must match; this does not qualify the selected image's schema, which W5 checks itself. P3 + records the current boot's `hv_storvsc` kernel journal count, without the registration line, as a baseline and + prints the newest error line's kernel time beside `/proc/uptime`. An error line less than one hour old stops the + run (microsoft/WSL#41482); the threshold is this recipe's choice, not an upstream figure. + - **Second storage reading (W5).** Record the baseline before the import and the second count after the first + launch on both paths. An increase confined to disk attachment before cloud-init starts is recorded with its + lines and does not stop the run. Any storage error after that window, or a provisioning failure with a storage + cause, stops the run. An unknown window stops for review; retain the actual cause with microsoft/WSL#41482. + - **Preflight (W1).** Neither Ubuntu Pro file exists, or `agent.yaml`'s top-level keys are recorded and include + neither `users` nor `write_files`. W1 also reads `instanceIdleTimeout` and `vmIdleTimeout` of the global WSL + configuration, without writing it. WSL must be 3.0.1 or later before W4 or any W6 import. Record the + workstation's baseline of five values in the already-running distribution, as its default user and without `sudo`: + the uid, the system state, the failed unit names, the user manager's state and `readlink /proc/self/ns/cgroup`, + with the initial namespace `cgroup:[4026531835]`, Linux's `PROC_CGROUP_INIT_INO`, named as corroboration rather than + the gate. The baseline must already meet W5's rule for one distribution, or the run stops before W4. + - **After the first launch (W5).** `cloud-init status --long` must print `status: disabled` with + `boot_status_code: disabled-by-marker-file`. Completion and errors come from `/var/lib/cloud/data/result.json` + (`DataSourceWSL`, `"errors": []`) and `/var/lib/cloud/data/status.json` (four stages finished without errors). On + path A, `cloud-init schema --system` must print a line matching `^\s*Valid schema user-data$`; path B skips it. + `wsl: Failed to start the systemd user session` stops even when the launch exits 0. While both distributions run, + before F1, the already-running distribution must read no `0` entry in a system unit's `cgroup.procs`, for a unit + active in both, and the new distribution's `user@.service` must be active. Then the paired record reads the + same five values from both distributions (`paired_isolation`): the same uid, active user managers, system managers + `running` or `degraded` with only `systemd-binfmt.service` failed, two namespaces that differ from each other and + from the initial one, and the workstation's values equal to its W1 baseline. Failure of either proof terminates + only the new distribution, never stops or restarts a unit in either, guards the export, unregisters and checks + surviving interop. + - **Terminate once (W7).** After W5 (path A) or W6 (path B): `wsl --terminate `, a relaunch, and an empty file + created from Windows under the user's home must read `1000:1000`. `0:0` stops every write from Windows. + - **How to tell cloud-init did not provision.** The launch prints `Create a default Unix user account:` and + `OOBE command "/usr/lib/wsl/wsl-setup" failed, exiting` and returns nonzero. + - **Fallback, path B (W6).** Re-list, then preserve the failed attempt. `wsl --terminate ` and + `wsl --export Z:\WSL\downloads\-failed.tar` keep its cloud-init logs, `/var/lib/cloud/instance` and its + configuration. Its SHA-256 and size go to the receipt's `failed_attempt_export`. A nonzero export exit throws + before any deletion. Then `--unregister` the literal new name, check surviving interop, `--import ... --version 2`, then a manual user with + the same groups and NOPASSWD drop-in, `[user] default`, the marker and `wsl --terminate `. + - **Stage-1 receipt.** The private PowerShell transcript, sanitized into the shape of + `adoption/templates/wsl/stage1-receipt.example.json`. Its payload keys are those `scripts/validate.py` compares with a + `receipts[]` row; `kind` is `native_cli_e2e` and `component_ids` is `systemd` (stack row `255.4-1ubuntu8.17`, the + image's version). +3. **First boot**, as the user: + - `systemctl is-system-running --wait` must print `running`, as Ubuntu's own setup test asserts, or `degraded` when + `systemctl --failed --no-legend --plain` lists exactly `systemd-binfmt.service` and that unit's log holds the + read-only flush message: the adopted release mounts the binfmt status file read-only (microsoft/WSL#40621) and + upstream calls the unit's error benign (#41226). Any other failed unit stops the run for review. + - `loginctl enable-linger`, then `Linger=yes`; then, with no client attached, `wsl.exe --list --running` every 10 + seconds for two minutes records whether `` stays up (F2). + - The user bus is a socket owned by the user. Right after F3, a rootless container must start on Docker's + `rootless` context, as the user without sudo. Check `name=rootless` and run + `docker --context rootless run --rm hello-world`; a failed start stops with microsoft/WSL#41492. This is owed + on the first run after the update; an absent engine requires F4, F5 and Docker's supported rootless setup, then + a return to this check before F9 or accepting R1. + - `apt-get install` the bootstrap's list plus `libatomic1` and `uidmap`: + - `uidmap` for rootless Docker in the Harbor harness; + - `libatomic1` for parity with the workstation profile (`docs/new-workstation-runtime-profile-20260922.md:34`). The + measurement in Alternatives 3 removes the original reason, so the coordinator may drop it. + - Subordinate ids are checked, and `usermod --add-subuids 100000-165535 --add-subgids 100000-165535` runs only when + none exist. 65,536 stays the stage-1 value. The unit that pulls the images (the evaluation harness unit) widens + the range only when a pull fails with `lchown : invalid argument`, and records the image and the error. + - `~/.bash_profile` gets the hand-off line of `adoption/platforms/linux-wsl2.md` (lines 185-188). + - A clone of `origin/main`; `adoption/hosts/.json` from the host template on ports 24318, 29374, 26333 and 28231, + probed with `ss`. + - Stage 2, changed 2026-10-02 (see "Stage 2 for the new distribution (2026-10-02)" below): the install plan's + `install.sh` and `accept.sh`, then the client-configuration tool's `--check` and `--apply --host `, then the + native sign-ins by hand. Until then this step was the native sign-in followed by + `--profile --configure-full-profile --host `; the brief's command lacked `--profile`, which the script + requires. + - The Windows Terminal fragment with profile names that carry ``, then `type -P claude codex`, with each printed + path tested by `test -f` and `test -x`. + - F11, after stage 2 and F10, in a login shell. `test -x` checks the `jcodemunch-mcp` binary in its own block. When + the binary exists, step 4a's `claude mcp add --scope local jcodemunch` line runs from the clone's root, and + `claude mcp get jcodemunch` proves it. The receipt's `jcodemunch_registration` records `registered`, + `not installed` or `skipped` with the reason. The coordinator's brief gives the reason to record every outcome: + under the Gate A re-aim, the baseline capture freezes whichever state exists. +4. **Host-wide.** + - No `.wslconfig` change, no `wsl --update`, never `wsl --shutdown`. + - `wsl --terminate ` only, and only for the new distribution. + - The default distribution stays the workstation's. + - The generated terminal profile may stay, hidden by the operator if duplicated. + - WSL package updates are the keys lane's decision. + - The rehearsal (R1) names only its own distribution. + - Every unregister checks `/proc/sys/fs/binfmt_misc/WSLInterop` and launches a Windows executable in the surviving + distribution. If registration or Windows execution does not return, the run stops for review after the new + distribution is removed, with `ls /proc/sys/fs/binfmt_misc` and `systemctl status systemd-binfmt.service + --no-pager` recorded; nothing is imported or provisioned. The WSL 2.7.x remedy, restarting `systemd-binfmt`, is + not used on the adopted release, where that restart exits 1. + - W1's `Select-String` read is the only command that names `.wslconfig`; the test rejects any other command naming it. + - The test rejects any recipe command that breaks these rules. +5. **Open questions** stay open; see below. + +## Overturn condition + +1. **Images.** Keep 26.04.1 as the single default; 24.04.5 returns only for a release-caused 26.04 failure with no + in-release remedy. Accept the selected image within the measured host scope only after the required, identically scoped R1 + comparison records for first boot, the systemd user manager from a second instance, WSL GPU visibility, + uv-managed CPython 3.13 and the pinned Node 24. Preserve failures and skips; missing evidence is not a win. + Comparisons stay on throwaway names. Re-pin a release only after Canonical's signed sums and the + pinned Microsoft catalog agree. A W2 mismatch stops the run with nothing installed. +2. **Creation path.** Revisit in two cases: + - a host run with a correct user-data file still shows the W5 markers; + - a WSL release changes when the first-run command runs (`WslCoreInstance.cpp:204`) or what a failure leaves + (`:293-303`). + + The coordinator may instead adopt the in-place repair as the first fallback, ahead of path B. +3. **First boot.** Revisit in four cases: + - F1 reports `degraded` with a failed unit other than `systemd-binfmt.service`, or without that unit's read-only + flush message, on a clean run; + - the bootstrap starts installing `uidmap` or `libatomic1` itself; + - the Harbor lane retires rootless Docker, which removes `uidmap` and F5; + - the user-scope MCP template registers jCodeMunch again (the addendum's overturn condition), or a script runs the + per-project registration, which turns F11 into a check. + + Drop `libatomic1` when the coordinator accepts the `readelf` measurement. +4. **Host-wide.** Revisit on a later WSL update after the completed 3.0.1 update (re-verify install, import and first + run at that tag, including W7's check of the microsoft/WSL#40941 fix already present in 3.0.1) or when a separate + decision makes the new distribution the default. Revisit P3 when microsoft/WSL#41482 names a fixed release, or when a run + shows storage errors that the one-hour threshold, this recipe's choice, misjudges. Revisit the adopted target and + W5's paired record when a stable release after 3.0.1 changes how distributions share cgroups, or when a run shows two + equal namespaces or an unhealthy user manager. Revisit F1's one accepted failed unit when a WSL release stops mounting + the binfmt status file read-only or systemd stops failing on it, and R1's stop for review when a 3.0.1 run loses + `WSLInterop` after an unregister and upstream documents a remedy. + +## Command table + +Every command line of the recipe's `powershell` and `sh` blocks, in order. `tests/test_wsl_new_distro_recipe.py` fails +when a row and the recipe disagree. The proofs are what the next run must print. The earlier 2.7.13 rehearsal is +retained separately; the corrected run on 3.0.1 remains owed. P1 to P3 also have historical artifact checks in the +workstation distribution (Evidence classes). + +| Step | Shell | Command | Proof | +| --- | --- | --- | --- | +| R1 | powershell | `wsl.exe -d '' --exec bash -lc 'stat -c %U,%F /run/user/$(id -u) /run/user/$(id -u)/bus'` | R1: record the returned exit and output required by the recipe | +| R1 | powershell | `wsl.exe -d '' --exec systemctl --user is-system-running --wait` | R1: record the returned exit and output required by the recipe | +| R1 | powershell | `wsl.exe -d '' --exec test -c /dev/dxg` | R1: record the returned exit and output required by the recipe | +| R1 | powershell | `wsl.exe -d '' --exec /usr/lib/wsl/lib/nvidia-smi` | R1: record the returned exit and output required by the recipe | +| R1 | powershell | `wsl.exe -d '' --exec bash -lc 'python3 --version'` | R1: record the returned exit and output required by the recipe | +| R1 | powershell | `wsl.exe -d '' --exec bash -lc 'uv run --no-project --python 3.13 python --version'` | R1: record the returned exit and output required by the recipe | +| R1 | powershell | `wsl.exe -d '' --exec bash -lc 'node --version'` | R1: record the returned exit and output required by the recipe | +| R1 | powershell | `$env:WSL_UTF8 = '1'` | as in W1 | +| R1 | powershell | `wsl.exe --list --quiet` | read first: the throwaway name is listed, and it is not the real `` | +| R1 | powershell | `wsl.exe --terminate ''` | the throwaway distribution stopped; no other distribution stops | +| R1 | powershell | `wsl.exe --unregister ''` | the throwaway distribution removed, nothing else | +| R1 | powershell | `wsl.exe -d '' --exec sh -c 'test -e /proc/sys/fs/binfmt_misc/WSLInterop && /mnt/c/Windows/System32/cmd.exe /d /c ver'` | on the surviving distribution: WSLInterop exists and /mnt/c/Windows/System32/cmd.exe /d /c ver launches with exit 0 | +| R1 | powershell | `if ($LASTEXITCODE -ne 0) { throw 'interop failed: recover in the surviving distribution before continuing' }` | a failure stops the block; R1's recovery records the observations and stops for review | +| R1 | powershell | `wsl.exe --list --verbose` | the throwaway name absent; the starred line equals W1's | +| R1 | powershell | `$env:WSL_UTF8 = '1'` | as in W1 | +| R1 | powershell | `wsl.exe --list --quiet` | read first: the throwaway name is listed, and it is not the real `` | +| R1 | powershell | `wsl.exe --terminate ''` | the throwaway distribution stopped; no other distribution stops | +| R1 | powershell | `wsl.exe --export '' 'Z:\WSL\downloads\-rehearsal.tar'` | the failed rehearsal saved before `--unregister` deletes its disk | +| R1 | powershell | `$LASTEXITCODE` | `0` | +| R1 | powershell | `if ($LASTEXITCODE -ne 0) { throw 'export failed: do not unregister' }` | no `throw`; a `throw` stops R1 with the name still registered | +| R1 | powershell | `(Get-FileHash -Algorithm SHA256 -LiteralPath 'Z:\WSL\downloads\-rehearsal.tar').Hash.ToLowerInvariant()` | the export's SHA-256, recorded in `rehearsal` | +| R1 | powershell | `(Get-Item -LiteralPath 'Z:\WSL\downloads\-rehearsal.tar').Length` | the export's size in bytes, recorded in `rehearsal` | +| R1 | powershell | `wsl.exe --unregister ''` | the throwaway distribution removed, nothing else | +| R1 | powershell | `wsl.exe -d '' --exec sh -c 'test -e /proc/sys/fs/binfmt_misc/WSLInterop && /mnt/c/Windows/System32/cmd.exe /d /c ver'` | on the surviving distribution: WSLInterop exists and /mnt/c/Windows/System32/cmd.exe /d /c ver launches with exit 0 | +| R1 | powershell | `if ($LASTEXITCODE -ne 0) { throw 'interop failed: recover in the surviving distribution before continuing' }` | a failure stops the block; R1's recovery records the observations and stops for review | +| R1 | powershell | `wsl.exe --list --verbose` | the throwaway name absent; the starred line equals W1's | +| R1 | sh | `test -e /proc/sys/fs/binfmt_misc/WSLInterop && /mnt/c/Windows/System32/cmd.exe /d /c ver` | only after a failed removal check, natively on the survivor: the check again, its output recorded | +| R1 | sh | `ls /proc/sys/fs/binfmt_misc` | the registered formats, recorded: whether `WSLInterop` is among them | +| R1 | sh | `systemctl status systemd-binfmt.service --no-pager` | the unit's state and log lines, recorded; then stop for review and import or provision nothing | +| P1 | sh | `for RELEASE in 26.04.1 24.04.5; do` | both signed-sums arms, each in an empty temporary directory | +| P1 | sh | `SUMS_DIR="$(mktemp -d)"` | an empty temporary directory, also `gpgv`'s home | +| P1 | sh | `curl -fsSL -o "$SUMS_DIR/SHA256SUMS" "https://releases.ubuntu.com/$RELEASE/SHA256SUMS" \|\| exit 1` | P1: record the returned exit and output required by the recipe | +| P1 | sh | `curl -fsSL -o "$SUMS_DIR/SHA256SUMS.gpg" "https://releases.ubuntu.com/$RELEASE/SHA256SUMS.gpg" \|\| exit 1` | P1: record the returned exit and output required by the recipe | +| P1 | sh | `gpgv --homedir "$SUMS_DIR" --keyring /usr/share/keyrings/ubuntu-archive-keyring.gpg "$SUMS_DIR/SHA256SUMS.gpg" "$SUMS_DIR/SHA256SUMS"` | exit 0 and `Good signature` by `843938DF228D22F7B3742BC0D94AA3F0EFE21092`; `BAD signature` or a missing key stops the run | +| P1 | sh | `if [ "$?" -ne 0 ]; then exit 1; fi` | nonzero interop verification stops recovery | +| P1 | sh | `grep -F " *ubuntu-$RELEASE-wsl-amd64.wsl" "$SUMS_DIR/SHA256SUMS" \|\| exit 1` | each release's exact signed image line from the controlled pins | +| P1 | sh | `done` | P1: record the returned exit and output required by the recipe | +| P2 | sh | `RENDER_DIR="$(mktemp -d)"` | an empty temporary directory | +| P2 | sh | `python3 -c 'import string, sys; sys.stdout.write(string.Template(open(sys.argv[1], encoding="utf-8").read()).substitute(WSL_USER=sys.argv[2]))' adoption/templates/wsl/cloud-init.user-data.template '' > "$RENDER_DIR/.user-data"` | the bytes W3 writes, rendered in the workstation | +| P2 | sh | `cloud-init --version` | the workstation's cloud-init version, recorded | +| P2 | sh | `cloud-init schema -c "$RENDER_DIR/.user-data"` | `Valid schema` and exit 0; `Invalid user-data` (exit 1) stops the run | +| P2 | sh | `sha256sum "$RENDER_DIR/.user-data"` | the render's SHA-256, equal to W3's | +| P3 | sh | `sudo journalctl -k -b 0 --no-pager \| grep hv_storvsc \| grep -Evc 'registering driver hv_storvsc\|[Cc]ommand line:'` | the count, recorded as the baseline (a count of `0` makes the last `grep` exit 1) | +| P3 | sh | `sudo journalctl -k -b 0 --no-pager -o short-monotonic --no-hostname \| grep hv_storvsc \| grep -Ev 'registering driver hv_storvsc\|[Cc]ommand line:' \| tail -n 1` | the newest error line with its kernel time in brackets, or nothing | +| P3 | sh | `cat /proc/uptime` | the seconds since boot as the first number; minus the line's kernel time, an age under 3600 s stops the run (microsoft/WSL#41482) | +| P3 | sh | `swapon --show` | recorded; active swap is the issue's condition, not a failure by itself | +| W1 | powershell | `New-Item -ItemType Directory -Force -Path 'Z:\WSL\downloads'` | the folder for the transcript and the image exists | +| W1 | powershell | `$env:WSL_UTF8 = '1'` | wsl.exe writes UTF-8 instead of UTF-16 | +| W1 | powershell | `wsl.exe --version` | WSL version: 3.0.1 or later for a second systemd distribution; older stops before W4 or any W6 import | +| W1 | powershell | `wsl.exe --list --verbose` | the starred line is recorded as the default | +| W1 | powershell | `wsl.exe --list --quiet` | `` is not listed | +| W1 | powershell | `Test-Path -LiteralPath 'Z:\WSL\'` | `False` | +| W1 | powershell | `Test-Path -LiteralPath (Join-Path $env:USERPROFILE '.ubuntupro\.cloud-init\.user-data')` | `False` | +| W1 | powershell | `Test-Path -LiteralPath (Join-Path $env:USERPROFILE '.ubuntupro\.cloud-init\agent.yaml')` | `False`; when `True`, the next row decides | +| W1 | powershell | `Select-String -LiteralPath (Join-Path $env:USERPROFILE '.ubuntupro\.cloud-init\agent.yaml') -Pattern '^[A-Za-z_][A-Za-z0-9_-]*:' -ErrorAction SilentlyContinue` | no output, or the top-level keys recorded and neither `users:` nor `write_files:` among them | +| W1 | powershell | `Select-String -LiteralPath (Join-Path $env:USERPROFILE '.wslconfig') -Pattern '^\s*\[', '^\s*instanceIdleTimeout\s*=', '^\s*vmIdleTimeout\s*=' -ErrorAction SilentlyContinue` | a read only: the section headers and the two idle keys, or nothing; recorded as `idle_keys` | +| W1 | powershell | `Get-PSDrive -Name Z \| Select-Object -Property Name, Used, Free` | `Free` recorded | +| W1 | sh | `id -u` | planned default uid 1000; otherwise stop before W4 or any W6 import | +| W1 | sh | `systemctl is-system-running` | running with no failed unit, or degraded with the failed set contained in {systemd-binfmt.service, getty@tty1.service}; otherwise stop | +| W1 | sh | `systemctl --failed --no-legend --plain` | first column of each row, or none; failed getty only as a prior collision with Result=start-limit-hit | +| W1 | sh | `systemctl is-active "user@$(id -u).service"` | active; otherwise stop before W4 or any W6 import | +| W1 | sh | `readlink /proc/self/ns/cgroup` | not cgroup:[4026531835], PROC_CGROUP_INIT_INO; necessary, with W5 required to prove isolation | +| W1 | sh | `systemctl show getty@tty1.service -p Result -p NRestarts` | only when the baseline includes failed getty: Result=start-limit-hit; record NRestarts in optional getty_tty1_result; another result stops | +| W2 | powershell | `$ProgressPreference = 'SilentlyContinue'` | no progress rendering during the download | +| W2 | powershell | `$Release = ''` | W2: record the returned exit and output required by the recipe | +| W2 | powershell | `switch ($Release) {` | W2: record the returned exit and output required by the recipe | +| W2 | powershell | `'26.04.1' { $Expected = '48d56724b5c8e60f24893e83e73bbb58c60b3ca22fba3da977075420acd54104'; $CatalogName = 'Ubuntu-26.04' }` | the exact hash and catalog entry for the single clean-install default | +| W2 | powershell | `'24.04.5' { $Expected = 'bb415d824822c4b878125729af451a5d18fb13d1cf5cbed9a7393ad64ac6039e'; $CatalogName = 'Ubuntu-24.04' }` | the exact hash and catalog entry for the release-caused-failure rollback only | +| W2 | powershell | `default { throw 'unsupported release: do not download or install' }` | W2: record the returned exit and output required by the recipe | +| W2 | powershell | `}` | W2: record the returned exit and output required by the recipe | +| W2 | powershell | `$Image = "ubuntu-$Release-wsl-amd64.wsl"` | W2: record the returned exit and output required by the recipe | +| W2 | powershell | `$ImagePath = "Z:\WSL\downloads\$Image"` | W2: record the returned exit and output required by the recipe | +| W2 | powershell | `$ImageUrl = "https://releases.ubuntu.com/$Release/$Image"` | W2: record the returned exit and output required by the recipe | +| W2 | powershell | `$SumsPath = "Z:\WSL\downloads\$Release-SHA256SUMS"` | W2: record the returned exit and output required by the recipe | +| W2 | powershell | `Invoke-WebRequest -UseBasicParsing -Uri $ImageUrl -OutFile $ImagePath` | W2: record the returned exit and output required by the recipe | +| W2 | powershell | `Invoke-WebRequest -UseBasicParsing -Uri "https://releases.ubuntu.com/$Release/SHA256SUMS" -OutFile $SumsPath` | W2: record the returned exit and output required by the recipe | +| W2 | powershell | `$Published = (Select-String -LiteralPath $SumsPath -Pattern (' \*' + [regex]::Escape($Image) + '$')).Line.Split(' ')[0]` | W2: record the returned exit and output required by the recipe | +| W2 | powershell | `$Listed = ((Invoke-WebRequest -UseBasicParsing -Uri 'https://raw.githubusercontent.com/microsoft/WSL/8bc98bc33b246fe66710eec9eaa1b24c323da987/distributions/DistributionInfo.json').Content \| ConvertFrom-Json).ModernDistributions.Ubuntu \| Where-Object Name -eq $CatalogName` | W2: record the returned exit and output required by the recipe | +| W2 | powershell | `$Actual = (Get-FileHash -Algorithm SHA256 -LiteralPath $ImagePath).Hash.ToLowerInvariant()` | W2: record the returned exit and output required by the recipe | +| W2 | powershell | `"computed $Actual published $Published listed $($Listed.Amd64Url.Sha256) url $($Listed.Amd64Url.Url)"` | the selected release's exact hash three times and its pinned release URL | +| W2 | powershell | `if ($Actual -ne $Expected -or $Published -ne $Expected -or $Listed.Amd64Url.Sha256 -ne $Expected -or $Listed.Amd64Url.Url -ne $ImageUrl) { throw 'sha256 mismatch: do not install' }` | no throw: whole-image, signed-publication, catalog hash and URL match | +| W2 | powershell | `(Get-Item -LiteralPath $ImagePath).Length` | actual selected-image byte count for this host run; run 2 observed 418,495,746 bytes for 26.04.1 | +| W3 | powershell | `New-Item -ItemType Directory -Force -Path (Join-Path $env:USERPROFILE '.cloud-init')` | the folder exists | +| W3 | powershell | `$Text = [System.IO.File]::ReadAllText('\adoption\templates\wsl\cloud-init.user-data.template').Replace('${WSL_USER}', '')` | the template rendered in memory | +| W3 | powershell | `[System.IO.File]::WriteAllText((Join-Path $env:USERPROFILE '.cloud-init\.user-data'), $Text)` | UTF-8 without a byte order mark | +| W3 | powershell | `Get-Content -LiteralPath (Join-Path $env:USERPROFILE '.cloud-init\.user-data') -TotalCount 1` | `#cloud-config` | +| W3 | powershell | `Select-String -LiteralPath (Join-Path $env:USERPROFILE '.cloud-init\.user-data') -Pattern '^- name: ', '^ default='` | two lines ending in `` | +| W3 | powershell | `(Get-FileHash -Algorithm SHA256 -LiteralPath (Join-Path $env:USERPROFILE '.cloud-init\.user-data')).Hash.ToLowerInvariant()` | equal to P2's `sha256sum` value | +| W3 | powershell | `Select-String -LiteralPath (Join-Path $env:USERPROFILE '.cloud-init\.user-data') -SimpleMatch -Pattern '${'` | no output | +| W4 | powershell | `$env:WSL_UTF8 = '1'` | as in W1 | +| W4 | powershell | `wsl.exe --install --from-file 'Z:\WSL\downloads\ubuntu--wsl-amd64.wsl' --name '' --location 'Z:\WSL\' --no-launch` | W4: record the returned exit and output required by the recipe | +| W4 | powershell | `$LASTEXITCODE` | `0` | +| W4 | powershell | `wsl.exe --list --verbose` | `` `Stopped`, version 2; the starred line unchanged | +| W5 | powershell | `$env:WSL_UTF8 = '1'` | as in W1 | +| W5 | powershell | `cmd.exe /d /c "wsl.exe -d < NUL"` | Provisioning the new WSL instance ; no prompt marker; wsl: Failed to start the systemd user session stops even with exit 0 | +| W5 | powershell | `$LASTEXITCODE` | `0`; nonzero with the markers means W6 | +| W5 | powershell | `wsl.exe -d '' --exec id -un` | `` | +| W5 | powershell | `wsl.exe -d '' --exec id -u` | `1000` | +| W5 | powershell | `wsl.exe -d '' --exec systemctl is-enabled getty@tty1.service` | masked; exit 1 is normal for a masked unit; otherwise W5 recovery | +| W5 | powershell | `wsl.exe -d '' --exec systemctl show getty@tty1.service -p LoadState -p ActiveState -p NRestarts` | LoadState=masked, ActiveState=inactive, NRestarts=0; otherwise W5 recovery | +| W5 | powershell | `wsl.exe -d '' -u root --exec cloud-init --version` | selected-image packaged cloud-init version; not the P2 workstation version | +| W5 | powershell | `wsl.exe -d '' -u root --exec cloud-init status --long` | `status: disabled` and `boot_status_code: disabled-by-marker-file`; proves the marker, not the run | +| W5 | powershell | `wsl.exe -d '' -u root --exec cat /var/lib/cloud/data/result.json` | `"datasource": "DataSourceWSL"` and `"errors": []`: the run completed without errors | +| W5 | powershell | `wsl.exe -d '' -u root --exec cat /var/lib/cloud/data/status.json` | each of the four stages `finished` with empty `errors`; `recoverable_errors` recorded | +| W5 | powershell | `wsl.exe -d '' -u root --exec cloud-init schema --system` | path A: a line matching `^\s*Valid schema user-data$` and exit 0; skipped on path B | +| W5 | powershell | `wsl.exe -d '' -u root --exec cat /etc/wsl.conf` | `[boot]`, `systemd=true`, `[user]`, `default=`, each once | +| W5 | powershell | `wsl.exe -d '' -u root --exec ls -l /etc/cloud/cloud-init.disabled` | the marker exists | +| W5 | powershell | `wsl.exe -d '' -u root --exec sudo -l -U ''` | `(ALL) NOPASSWD: ALL` | +| W5 | powershell | `wsl.exe --list --verbose` | the starred line equals W1's | +| W5 | sh | `systemctl is-active ''` | natively on the already-running distribution: active and exit 0 | +| W5 | sh | `/mnt/c/Windows/System32/wsl.exe -d '' --exec systemctl is-active ''` | the same system unit is active in the new distribution, exit 0 | +| W5 | sh | `cat '/sys/fs/cgroup/system.slice//cgroup.procs'` | read from the already-running distribution while both run: local process ids, no exact 0 entry; empty or unreadable stops | +| W5 | sh | `/mnt/c/Windows/System32/wsl.exe -d '' --exec sh -c 'systemctl is-active "user@$(id -u).service"'` | the new default user has uid 1000 and user@1000.service is active, exit 0; otherwise W5 recovery | +| W5 | sh | `id -u` | workstation uid, equal to host.workstation_baseline; record its own exit and printed value | +| W5 | sh | `systemctl is-system-running` | workstation system_state, equal to host.workstation_baseline; record its own exit and printed value | +| W5 | sh | `systemctl --failed --no-legend --plain` | workstation failed_units, equal to host.workstation_baseline; record its own exit and printed value | +| W5 | sh | `systemctl is-active "user@$(id -u).service"` | workstation user_manager, equal to host.workstation_baseline; record its own exit and printed value | +| W5 | sh | `readlink /proc/self/ns/cgroup` | workstation cgroup_namespace, equal to host.workstation_baseline; record its own exit and printed value | +| W5 | sh | `/mnt/c/Windows/System32/wsl.exe -d '' --exec id -u` | same uid as the workstation (1000) | +| W5 | sh | `/mnt/c/Windows/System32/wsl.exe -d '' --exec systemctl is-system-running` | running with no failed unit, or degraded with systemd-binfmt.service as its only failed unit; its message is proved at F1 | +| W5 | sh | `/mnt/c/Windows/System32/wsl.exe -d '' --exec systemctl --failed --no-legend --plain` | first column of each row, or none; getty@tty1.service must not be failed | +| W5 | sh | `/mnt/c/Windows/System32/wsl.exe -d '' --exec sh -c 'systemctl is-active "user@$(id -u).service"'` | new default user manager active; paired observation with its own exit | +| W5 | sh | `/mnt/c/Windows/System32/wsl.exe -d '' --exec readlink /proc/self/ns/cgroup` | different from the workstation namespace and not cgroup:[4026531835] | +| W5 | powershell | `$env:WSL_UTF8 = '1'` | W5: record the returned exit and output required by the recipe | +| W5 | powershell | `wsl.exe --list --quiet` | W5: record the returned exit and output required by the recipe | +| W5 | powershell | `wsl.exe --terminate ''` | any failed W5 proof: terminate the new distribution, never stop or restart a system unit in either distribution | +| W5 | powershell | `wsl.exe --export '' 'Z:\WSL\downloads\-w5-failed.tar'` | the failed W5 state saved before unregister; nonzero export prevents deletion | +| W5 | powershell | `$LASTEXITCODE` | W5: record the returned exit and output required by the recipe | +| W5 | powershell | `if ($LASTEXITCODE -ne 0) { throw 'export failed: do not unregister' }` | export failure stops with the name still registered; otherwise continue to hash, size and unregister | +| W5 | powershell | `(Get-FileHash -Algorithm SHA256 -LiteralPath 'Z:\WSL\downloads\-w5-failed.tar').Hash.ToLowerInvariant()` | record the export hash in w5_failure_export | +| W5 | powershell | `(Get-Item -LiteralPath 'Z:\WSL\downloads\-w5-failed.tar').Length` | record the export size in w5_failure_export | +| W5 | powershell | `wsl.exe --unregister ''` | any failed W5 proof: guarded export succeeded; remove only the new distribution and immediately check surviving interop | +| W5 | powershell | `wsl.exe -d '' --exec sh -c 'test -e /proc/sys/fs/binfmt_misc/WSLInterop && /mnt/c/Windows/System32/cmd.exe /d /c ver'` | on the surviving distribution: WSLInterop exists and /mnt/c/Windows/System32/cmd.exe /d /c ver launches with exit 0 | +| W5 | powershell | `if ($LASTEXITCODE -ne 0) { throw 'interop failed: recover in the surviving distribution before continuing' }` | a failure stops the block; R1's recovery records the observations and stops for review | +| W5 | powershell | `wsl.exe --list --verbose` | W5: record the returned exit and output required by the recipe | +| W5 | sh | `sudo journalctl -k -b 0 --no-pager \| grep hv_storvsc \| grep -Evc 'registering driver hv_storvsc\|[Cc]ommand line:'` | On both paths, record P3's baseline before the import and `second_count` after the first launch. An increase confined to the window in which the new disk is attached, before cloud-init starts, is recorded with the new lines and does not stop the run. Any storage error after that window, or any provisioning step that fails with a storage cause, stops the run. | +| W6 | powershell | `$env:WSL_UTF8 = '1'` | as in W1 | +| W6 | powershell | `wsl.exe --list --quiet` | read before unregistering: `` is the new distribution | +| W6 | powershell | `wsl.exe --terminate ''` | `` stopped before the export; no other distribution stops | +| W6 | powershell | `wsl.exe --export '' 'Z:\WSL\downloads\-failed.tar'` | the failed attempt saved as a tar before `--unregister` deletes its disk | +| W6 | powershell | `$LASTEXITCODE` | `0` | +| W6 | powershell | `if ($LASTEXITCODE -ne 0) { throw 'export failed: do not unregister' }` | no `throw`; a `throw` stops W6 with `` still registered | +| W6 | powershell | `(Get-FileHash -Algorithm SHA256 -LiteralPath 'Z:\WSL\downloads\-failed.tar').Hash.ToLowerInvariant()` | the export's SHA-256, recorded in `failed_attempt_export` | +| W6 | powershell | `(Get-Item -LiteralPath 'Z:\WSL\downloads\-failed.tar').Length` | the export's size in bytes, recorded in `failed_attempt_export` | +| W6 | powershell | `wsl.exe --unregister ''` | `` removed, nothing else | +| W6 | powershell | `wsl.exe -d '' --exec sh -c 'test -e /proc/sys/fs/binfmt_misc/WSLInterop && /mnt/c/Windows/System32/cmd.exe /d /c ver'` | on the surviving distribution: WSLInterop exists and /mnt/c/Windows/System32/cmd.exe /d /c ver launches with exit 0 | +| W6 | powershell | `if ($LASTEXITCODE -ne 0) { throw 'interop failed: recover in the surviving distribution before continuing' }` | a failure stops the block; R1's recovery records the observations and stops for review | +| W6 | powershell | `wsl.exe --import '' 'Z:\WSL\' 'Z:\WSL\downloads\ubuntu--wsl-amd64.wsl' --version 2` | W6: record the returned exit and output required by the recipe | +| W6 | powershell | `wsl.exe -d '' -u root --exec cloud-init status --wait --long` | `done` or `error` when cloud-init ran, `disabled` by `disabled-by-generator` when it found no datasource; recorded (open question 1) | +| W6 | sh | `systemctl mask --now getty@tty1.service` | the imported getty masked; first boot already started it, so W5 paired proof after relaunch must show workstation unchanged | +| W6 | sh | `id -u '' \|\| useradd --create-home --uid 1000 --groups adm,cdrom,sudo,dip,plugdev --shell /bin/bash ''` | uid 1000 exists | +| W6 | sh | `printf '%s ALL=(ALL) NOPASSWD:ALL\n' '' > /etc/sudoers.d/90-wsl-default-user` | the drop-in written | +| W6 | sh | `chmod 0440 /etc/sudoers.d/90-wsl-default-user` | mode 0440 | +| W6 | sh | `visudo -cf /etc/sudoers.d/90-wsl-default-user` | `parsed OK` | +| W6 | sh | `grep -q '^\[user\]' /etc/wsl.conf \|\| printf '\n[user]\ndefault=%s\n' '' >> /etc/wsl.conf` | one `[user]` section naming the user | +| W6 | sh | `touch /etc/cloud/cloud-init.disabled` | the marker exists | +| W6 | powershell | `wsl.exe --terminate ''` | exit 0 | +| W6 | powershell | `wsl.exe --list --running` | `` absent | +| W6 | powershell | `wsl.exe -d '' --exec id -un` | ``; then W5's checks, without `cloud-init schema --system` | +| W7 | powershell | `$env:WSL_UTF8 = '1'` | as in W1 | +| W7 | powershell | `wsl.exe --terminate ''` | `` stopped once after the first launch (microsoft/WSL#40941); no other distribution stops | +| W7 | powershell | `wsl.exe --list --running --quiet` | `` absent | +| W7 | powershell | `wsl.exe -d '' --exec id -un` | ``: relaunched for the F steps | +| W7 | powershell | `New-Item -ItemType File -Path '\\wsl.localhost\\home\\wsl-owner-probe'` | an empty file created from Windows under the user's home | +| W7 | powershell | `wsl.exe -d '' --exec stat -c %u:%g '/home//wsl-owner-probe'` | `1000:1000`; `0:0` is recorded and stops every write from Windows | +| W7 | powershell | `Remove-Item -LiteralPath '\\wsl.localhost\\home\\wsl-owner-probe'` | the probe file deleted | +| F1 | sh | `systemctl is-system-running --wait` | `running` with no failed unit; or `degraded` (exit 1) only when the next two rows show `systemd-binfmt.service` as the only failed unit, with its read-only flush message | +| F1 | sh | `systemctl --failed --no-legend --plain` | no output when `running`; when `degraded`, a list of exactly `systemd-binfmt.service`; any other failed unit stops the run | +| F1 | sh | `journalctl -b 0 -t systemd-binfmt --no-pager -n 4` | when `degraded`: the unit's log holds `Failed to flush binfmt_misc rules, ignoring: Read-only file system`; read without `sudo` through the `adm` group, or repeated once with `sudo` when only the journal's permission notice prints | +| F1 | sh | `systemctl list-unit-files --type=service --no-pager` | the service list prints | +| F1 | sh | `sudo journalctl -b 0 -t systemd-binfmt --no-pager -n 4` | only after an unreadable degraded journal: record both outputs and judge this read-only flush message | +| F2 | sh | `sudo loginctl enable-linger "$(id -un)"` | exit 0 | +| F2 | sh | `loginctl show-user "$(id -un)" --property=Linger --value` | `yes` | +| F2 | powershell | `$env:WSL_UTF8 = '1'` | as in W1 | +| F2 | powershell | `foreach ($Poll in 1..12) { Start-Sleep -Seconds 10; [DateTime]::UtcNow.ToString('HH:mm:ss'); wsl.exe --list --running --quiet }` | twelve times and lists with no client attached; whether `` stays listed, recorded as `idle_observation` | +| F3 | sh | `stat -c '%U %F' "/run/user/$(id -u)" "/run/user/$(id -u)/bus"` | ` directory`, then ` socket` | +| F3 | sh | `systemctl --user is-system-running --wait` | `running` | +| F3 | sh | `docker --context rootless info --format '{{json .SecurityOptions}}'` | exit 0 and name=rootless before attempting the container; otherwise stop | +| F3 | sh | `docker --context rootless run --rm hello-world` | as the user without sudo: Hello from Docker! and exit 0; failure stops with issue 41492; owed on the first run after update | +| F4 | sh | `sudo apt-get update` | exit 0 | +| F4 | sh | `sudo apt-get install -y --no-install-recommends ca-certificates curl git tar gzip xz-utils jq libatomic1 uidmap` | exit 0 | +| F4 | sh | `dpkg-query -W -f='${Package} ${Version}\n' jq libatomic1 uidmap` | three versions | +| F5 | sh | `grep "^$(id -un):" /etc/subuid /etc/subgid` | a range in both files, or nothing | +| F5 | sh | `grep -q "^$(id -un):" /etc/subuid \|\| sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 "$(id -un)"` | only when the first `grep` printed nothing | +| F5 | sh | `grep "^$(id -un):" /etc/subuid /etc/subgid` | 65,536 ids in both files | +| F6 | sh | `test -e ~/.bash_profile \|\| printf '%s\n' 'if [ -r "$HOME/.profile" ]; then . "$HOME/.profile"; fi' > ~/.bash_profile` | written only when absent | +| F6 | sh | `cat ~/.bash_profile` | exactly the hand-off line | +| F7 | sh | `mkdir -p ~/code` | exit 0 | +| F7 | sh | `git clone https://github.com/seathatflowsinourveins/native-agent-stack.git ~/code/native-agent-stack` | exit 0 | +| F7 | sh | `git -C ~/code/native-agent-stack rev-parse HEAD` | a commit | +| F7 | sh | `git -C ~/code/native-agent-stack ls-remote origin refs/heads/main` | the same commit | +| F8 | sh | `cd ~/code/native-agent-stack` | the clone | +| F8 | sh | `ss -ltnH '( sport = :21318 or sport = :29374 or sport = :21633 or sport = :28231 )'` | no output | +| F8 | sh | `python3 -c 'import string, sys; sys.stdout.write(string.Template(open(sys.argv[1], encoding="utf-8").read()).substitute(WSL_USER=sys.argv[2]))' adoption/templates/wsl/host.new-distro.json.template "$(id -un)" > 'adoption/hosts/.json'` | the host file written | +| F8 | sh | `python3 -m json.tool 'adoption/hosts/.json'` | the nine keys | +| F8 | sh | `git check-ignore 'adoption/hosts/.json'` | prints the path | +| F9 | sh | `cd ~/code/native-agent-stack` | the clone | +| F9 | sh | `bash evidence/artifacts/new-wsl-install-plan-20261002/install.sh` | the install plan's default run; exit 0 | +| F9 | sh | `bash evidence/artifacts/new-wsl-install-plan-20261002/accept.sh` | exit 0; a `skipped` line is not a pass | +| F9 | sh | `python3 -B tools/adoption/new_wsl_client_config.py --check` | ends with `check passed` | +| F9 | sh | `python3 -B tools/adoption/new_wsl_client_config.py --apply --host ''` | ends with a `summary:` line in which no step is `failed`; no signed-in client is needed, so the native sign-ins follow | +| F10 | powershell | `wsl.exe -d '' -u '' --exec /bin/bash -lc 'type -P claude codex'` | two absolute paths | +| F10 | powershell | `wsl.exe -d '' -u '' --exec /bin/bash -lc 'for n in claude codex; do p=$(type -P $n); [[ -f $p && -x $p ]] && echo executable: $p; done'` | `executable:` and each path: both are regular executable files | +| F11 | sh | `test -x "${ECO_INSTALL_ROOT:-$HOME/.local/share/codex-ecosystem}/bin/jcodemunch-mcp"` | exit 0; exit 1 means `not installed`: record it and skip the rest of F11 | +| F11 | sh | `cd ~/code/native-agent-stack` | the clone, the project the local scope belongs to | +| F11 | sh | `claude mcp add --scope local jcodemunch -e "CODE_INDEX_PATH=$HOME/.code-index" -e JCODEMUNCH_SHARE_SAVINGS=0 -- "${ECO_INSTALL_ROOT:-$HOME/.local/share/codex-ecosystem}/bin/jcodemunch-mcp"` | an `Added ...` line; on a re-run the name already exists | +| F11 | sh | `claude mcp get jcodemunch` | the local scope, the command and a status, recorded as `jcodemunch_registration` | + +## Open questions + +Each one stays open until a host run records the observation named here. + +1. **cloud-init on an imported distribution.** Whether cloud-init provisions an `--import`ed distribution is not + documented. Path B records `cloud-init status --wait --long` before it creates anything. +2. **What keeps the distribution running with no client attached.** This workstation's global WSL configuration holds + `[general] instanceIdleTimeout=-1` and `[wsl2] vmIdleTimeout=-1` (the coordinator's read of 2026-10-01, 10:08Z, + values only), and the new distribution inherits them. By the WSL 2.7.13 source (`LxssUserSession.cpp:2658-2676`) a + negative value means the instance is never idle-terminated, so on this host the setting keeps `` running, not + linger. Issue reports say linger alone does not: microsoft/WSL#13416 (open; on WSL 2.7.3.0 "`loginctl enable-linger` + ... does not prevent the poweroff") and #9968 (open). On a host without the setting, the distribution stops about + `instanceIdleTimeout` (default 15000 ms, wsl-config) after its last Windows-side client exits, its user services + with it; changing the global file is the user's decision, outside the recipe. W1 records both keys, and F2 lists the + running distributions for two minutes with no client attached and records whether `` stays listed. Whether + `wsl.exe --list --running` itself counts as a client is not verified. +3. **Interop after unregister.** On WSL 2.7.13 the surviving distribution lost the VM-wide `WSLInterop` registration + after the failed rehearsal was unregistered. Restarting its `systemd-binfmt` restored Windows execution there. Every + R1, W5 and W6 unregister now checks the registration and a Windows executable. On the adopted release the + registration is protected (microsoft/WSL#40621) and that restart exits 1, so a failed check stops for review with + R1's records. This is a required observation and a stop, rather than an open binfmt question. +4. **Subordinate ids from `useradd`.** Whether `useradd` allocated subordinate ids on the selected release. cloud-init 26.1 creates + users with `useradd` (`cloudinit/distros/__init__.py:683` at tag 26.1). useradd(8) says it allocates `SUB_UID_COUNT` + ids when `/etc/subuid` exists, and the image ships both; F5 records which tool wrote the range. +5. **Both image comparisons.** The preregistered R1 criteria cover the 26.04.1 default and 24.04.5 rollback on + throwaway names. Full acceptance on the updated host remains owed; the older 24.04.5 receipts do not settle it. +6. **File ownership after a terminate (microsoft/WSL#40941).** Whether `wsl --terminate ` clears the 0:0 owner of + files Windows creates after the first-run setup. The fix's pull request says the state "only recovers after a distro + termination", and the issue's reproduction says it lasts "until the next wsl --shutdown". The 2.7.13 rehearsal + returned `1000:1000` after W7's terminate; the updated host must repeat that observation. +7. **Kernel storage errors on this host (microsoft/WSL#41482).** Whether `hv_storvsc` errors break a first launch on + this kernel. On 2026-10-01 P3's count in the workstation's journal of the current boot was 21 (Evidence classes): + one burst of `cmd 0x2a` write errors (`WRITE_10`) on 2026-09-24 around 03:20Z and none since, where the issue shows + `cmd 0x28` read errors (`READ_10`). Under the coordinator's rule that count is P3's baseline, and its newest line, + about 7.4 days old, did not stop that authoring check. The later rehearsal's baseline 7 and second count 13 retain + their own boot scope; the six new lines preceded cloud-init in the attachment window. Future runs must establish + their own window, retain its lines and stop on later errors or a provisioning failure with a storage cause. +8. **Isolation with two distributions running on 3.0.1.** Whether the two distributions have distinct cgroup + namespaces, healthy managers and the same uid while both run. The updated host's reading covers one distribution + only (Updated-host correction). W5's paired record, `paired_isolation`, is the observation that settles it, and W1's + baseline shows whether the new distribution disturbed the workstation's values. + +## Completeness critic (2026-10-01) + +The unit closed without the critic that the repository rule requires; a review thread on PR #569 pointed it out. An +independent researcher (Claude Opus, read-only, primary sources fetched 2026-10-01) then asked what the unit did not +look at: candidate classes, kinds of evidence and unread sources. It read about 30 sources and found 11 items. None +overturns the selection. Five change what must happen before stage 1. The follow-up of 2026-10-01 adds their checks to +the recipe (last column); do not run stage 1 without the checks of items 1 to 5. + +| # | What the unit missed | Source read 2026-10-01 | Disposition | Outcome in the follow-up (2026-10-01) | +| --- | --- | --- | --- | --- | +| 1 | No dry run. Stage 1 and first boot have never run anywhere; a rehearsal on a throwaway name and location stays inside this record's host-wide rules | this record, "Evidence classes" | Queued before stage 1: a rehearsal that also collects items 2, 3 and 4 | R1: the page runs first on a throwaway name through F3, with W5's schema check, W7's probe and F2's idle observation in the receipt's `rehearsal` block; R1 then removes only that name | +| 2 | The claim that WSL 2.7.14 and 3.0.1 "change nothing about install, import or first run" is broader than what was checked. `2.7.13...3.0.1` is 677 commits ahead, and listed commits touch first run, import and termination. One of them fixes microsoft/WSL#40941: after the first-run setup, a file created from Windows is owned by 0:0 until the next `wsl --shutdown`, which this recipe forbids. That 2.7.13 lacks the fix is now established from source: the fix's function, `ConnectionTargetManager::UpdateUid`, is absent from `src/windows/common/Redirector.h` at tags 2.7.13 and 2.7.14 | https://github.com/microsoft/WSL/compare/2.7.13...3.0.1 ; https://github.com/microsoft/WSL/issues/40941 | Open question; measured in the rehearsal (create a file from Windows under the user's home, read its owner, terminate the distribution, relaunch, read it again) | Context and the recipe's host-wide rules corrected; W7 terminates `` once after the first launch, relaunches it and requires `1000:1000` for a file created from Windows; open question 6 | +| 3 | The issue tracker was not searched for the selected path. microsoft/WSL#41482 reports continuous `hv_storvsc` read errors and systemd boot timeouts on Ubuntu 24.04 with kernel 6.18.33.2, this host's kernel; the maintainer's workaround needs `swap=0` and `wsl --shutdown`, both ruled out here | https://github.com/microsoft/WSL/issues/41482 | Open question; a read-only pre-check in the current distribution (`journalctl -k` for `hv_storvsc`) before stage 1, since both share the kernel | P3 records the current boot's `hv_storvsc` journal count without the registration line as a baseline, stops on an error line less than one hour old by its kernel time and records `swapon --show`; W5 counts again after the first launch. On this host the baseline is 21, from one burst seven days old (Evidence classes, open question 7) | +| 4 | Whether linger keeps the distribution running. microsoft/WSL#13416 (open: WSL shuts down despite an active systemd service, still reproduced on 2.7.3) and #9968 (open) bear on open question 2 | https://github.com/microsoft/WSL/issues/13416 ; https://github.com/microsoft/WSL/issues/9968 | Open question; observed in the rehearsal with no client attached, because stage 2's user services depend on it | W1 reads the two idle keys; F2 lists the running distributions for two minutes with no client attached; open question 2 rewritten with the setting, the source and the issues | +| 5 | Two upstream verification steps are skipped: Canonical's how-to validates the user data with `sudo cloud-init schema --system`, and `SHA256SUMS.gpg` exists and is never verified (the repository's 2026-09-21 precedent verified Ubuntu's signed sums) | https://documentation.ubuntu.com/wsl/stable/howto/cloud-init/ ; https://releases.ubuntu.com/24.04.5/SHA256SUMS.gpg | Queued before stage 1: both become recipe steps in the follow-up | P1 verifies the signed sums with `gpgv`; P2 runs `cloud-init schema -c` before any boot, its hash compared with W3's; W5 runs `cloud-init schema --system` on path A | +| 6 | Other base images were not weighed. Microsoft's distribution list also carries Debian, Fedora, Arch, AlmaLinux, openSUSE, SLE and Kali; stage 2 accepts only `ID=ubuntu` or `debian` (`adoption/bootstrap-linux.sh:175-178`) | https://raw.githubusercontent.com/microsoft/WSL/master/distributions/DistributionInfo.json | Debian feeds the layer's next landscape sweep; the others are out of scope while stage 2 rejects their `ID` | Alternatives 1 states both | +| 7 | WSL containers, generally available with 3.0.1 (one VM, network and disk per session, OCI images), were not weighed for disposable clean-install tests | https://devblogs.microsoft.com/commandline/wslc-architecture-deep-dive/ | Feeds the layer's next landscape sweep; unlikely to host stage 2 | Alternatives 1 states it | +| 8 | The reason given for dismissing `wsl --install Ubuntu-24.04` ("verifies no hash the operator can see") is incomplete: 2.7.13 carries a hash-mismatch message, and a local manifest can be pinned through `DistributionListUrl` | https://raw.githubusercontent.com/microsoft/WSL/2.7.13/localization/strings/en-US/Resources.resw ; https://learn.microsoft.com/en-us/windows/wsl/build-custom-distro | Open question; the selection stands because the online path resolves the list at install time and the registry key is host-wide; the follow-up corrects the stated reason | Alternatives 1 corrected from source (the online path checks the catalog's hash, `--from-file` checks none); the recipe's W2 says why its own check exists | +| 9 | Creation routes not weighed: a golden copy (`wsl --export`, then `--import-in-place` of a provisioned disk), a Docker-exported root file system, a custom first-run command in `/etc/wsl-distribution.conf`, Ubuntu Pro for WSL | https://learn.microsoft.com/en-us/windows/wsl/use-custom-distro ; https://docs.cloud-init.io/en/latest/reference/datasources/wsl.html | The golden copy feeds the next sweep; a self-built image is out of scope (it breaks the upstream-install rule) and Ubuntu Pro needs a subscription and a server | Alternatives 1 and 2 state the exclusions and the sweep item | +| 10 | Package updates between the image build and today were not checked. They are harmless today: `wsl-setup` is unchanged, and cloud-init 26.2 is only proposed | Launchpad, published sources of both packages on noble | Feeds the next sweep | No change | +| 11 | Citation hygiene in a sample of eight: two Microsoft pages, use-custom-distro and systemd, display a date that matches neither of their metadata dates, the cloud-init datasource page is cited at `latest` (26.2 today) while the image runs 26.1, and W5's expected console lines have no cited source | the pages' own metadata | Open question; the follow-up pins each page by its revision and cites the lines | Sources pin the Microsoft Learn pages by `git_commit_id` and the cloud-init CLI page at 26.1; W5's lines are cited at the image's `wsl-setup` (Context) | + +Not checked by the critic: file-level WSL source diffs (the comparison view is truncated), whether `wsl --terminate` +clears the ownership bug, the contents of the other images, upstream test suites and community runbooks. Its issue +searches were samples of eight results per query, not sweeps. + +## Evidence classes + +- **Read today (source review).** The WSL source at tag 2.7.13; `ubuntu/wsl-setup` at + `86a561d5149a9d76ec3c9b3ce2745e7ebca5f2ad`, GitHub main when the research unit read it + (`test/systemd-assertions.sh:6-9` asserts `running`, `:30-33` the marker; these two citations are at that revision + and were not checked at the image's version); + systemd v255 `man/loginctl.xml:186-194` and `man/systemctl.xml:2297-2307`; and the documentation below. For the + follow-up: step 4a and the addendum, the carrier blocks, the MCP template, the profiles and the Linux pins, Claude + Code's MCP page and Docker's rootless troubleshooting page. +- **Artifact checks on the authoring host, 2026-10-01** (not runs of the recipe): + - the image stream's size and sha256, and the files read from it; + - the gzip size field; + - the Node 24.21.0 tarball's sha256 and its binary's `NEEDED` list. +- **Artifact checks of the second follow-up unit, 2026-10-01**, in the workstation distribution (Ubuntu 24.04.5 LTS, + kernel 6.18.33.2): not runs of the recipe on a new distribution, and they changed nothing on the host. The unit ran + P1 to P3 verbatim from 11:50:17Z to 11:50:19Z, and P3 again in its final form at 12:19:29Z, extracted from the page + with `` as `example` and `` as `rehearsal-example`, `TMPDIR` in its scratch area and `sudo` without + a prompt, each command followed by its exit code: + - P1 for 24.04.5 in that earlier recipe: both `curl` lines exited 0. The `gpgv` line exited 0: + `gpgv --homedir "$SUMS_DIR" --keyring /usr/share/keyrings/ubuntu-archive-keyring.gpg "$SUMS_DIR/SHA256SUMS.gpg" "$SUMS_DIR/SHA256SUMS"` + printed `Signature made Tue Sep 15 15:11:12 2026 EDT`, `using RSA key 843938DF228D22F7B3742BC0D94AA3F0EFE21092` and + `Good signature from "Ubuntu CD Image Automatic Signing Key (2012) "`. The `grep` exited 0 and + printed `bb415d824822c4b878125729af451a5d18fb13d1cf5cbed9a7393ad64ac6039e *ubuntu-24.04.5-wsl-amd64.wsl`. Control: + a copy of the sums with that line's first hash digit changed gave `BAD signature from ...`, exit 1. Afterwards the + directory held only the two downloads and the control's copy, so `gpgv` wrote nothing to its home. The sums file + has 9 lines; the SHA-256 of the sums file and of the signature: + `728064ecf411f4ab702d9c3ca0a938ce672771424c028a1b52b2449f7eb5a068` and + `f9be4b4a527b63f14dde0387edcb7a02ba3ac43d537b1674a5aeae19c6f23827`. + `gpgv` 2.4.4-2ubuntu17.6 and `ubuntu-keyring` 2023.11.28.1. + - P2: the render exited 0, and `cloud-init --version` printed cloud-init 26.1-0ubuntu1~24.04.1. + `cloud-init schema -c` on the render printed `Valid schema ` after the warning `datasource not detected, + using default instance-data/user-data paths.`, exit 0. The render's SHA-256 for `example` (another + `` gives another value): + `9ae3b7ba94ba36af78b4b0ce1b088ff92dac48ec9596223a640923dce07550f7`. + Control: `lock_passwd: "maybe"` printed + `Error: Cloud config schema errors: users.0.lock_passwd: 'maybe' is not of type 'boolean'`, + `Error: Invalid schema: user-data` and `Invalid user-data `, exit 1. An earlier run at 11:26Z also found the + render equal to the template with a literal replace, without CR bytes and with `#cloud-config` first. + - P3 under the coordinator's rule, its four lines run verbatim from the page at 12:19:29Z (and the same commands + at 12:13:46Z with the same count and newest line). The count line, + `sudo journalctl -k -b 0 --no-pager | grep hv_storvsc | grep -vc 'registering driver hv_storvsc'`, printed `21` + (exit codes 0, 0 and 0): the baseline. The newest-line command, + `sudo journalctl -k -b 0 --no-pager -o short-monotonic --no-hostname | grep hv_storvsc | grep -v 'registering driver hv_storvsc' | tail -n 1`, + printed `[52879.587119] kernel: hv_storvsc : tag#2687 cmd 0x2a status: scsi 0x0 srb 0x4 host 0xc00000a1` + (exit codes 0, 0, 0 and 0; the device id is masked here). `cat /proc/uptime` printed `690053.56` as its first + number, so the newest error line was about 637,174 s, 7.4 days, old: older than one hour, so P3 does not stop the + run, and 21 is a baseline. `swapon --show` printed one 24G swap partition with 4.2G used (exit 0). W5's count line, + the same command, also printed `21`, but no first launch had run, so that only checks the command: no second reading + exists yet. + - The first form of P3, the count alone with a stop on any count, printed `21` at 11:37:00Z and 11:50:19Z, and the + form the brief first gave, `grep -c hv_storvsc`, printed 24 at 11:26:38Z (exit 0). The unit then split those lines + (`sudo -n`, ids masked): 3 were `hv_vmbus: registering driver hv_storvsc`, one kernel message with one monotonic + stamp that journald stored again after the workstation distribution restarted within this kernel boot. Every boot + logs that line, so the page leaves it out. The other 21 were + `hv_storvsc : tag# cmd 0x2a status: scsi 0x0 srb 0x4 host 0xc00000a1`: 14 distinct monotonic stamps + within 47 ms, about 52,880 s after the kernel booted (2026-09-24 around 03:20Z), and none in the seven days since. + `cmd 0x2a` is `WRITE_10`, where the issue's `cmd 0x28` is `READ_10` (`/usr/include/scsi/scsi.h:59-60`, libc6-dev). + Under a stop on any count, that one old burst would have blocked stage 1 on this host for good; this is why the + coordinator rejected the bare count as the rule (Alternatives 4). + - Why the kernel time decides the age: the registration message, one kernel time, carries three journal reception + stamps from 2026-09-24 03:20:59Z to 04:23:09Z, and the burst's error lines were received at 03:20:59Z and again at + 04:23:09Z (`-o short-iso --no-hostname`, 12:13:46Z). On this host the kernel clock and `/proc/uptime` agree within + 37 s over about 8 days: the newest kernel line's kernel time, 689,242.966 s, against its reception time at 12:13Z. + The one-hour threshold is this recipe's choice, not an upstream figure; microsoft/WSL#41482 names no age. +- **Local integration.** `tests/test_wsl_new_distro_recipe.py`, checks over repository text and an in-memory render; it + runs nothing on a host. +- **Reported artifact verification, W-IMG.** The retained integration receipt contains both signature verification + exit codes, the signer fingerprint and the reported 26.04.1 stream hash match. It retains no verification output + for that 26.04.1 signature check; it does not carry the signature-output evidence of the historical 24.04.5 P1 + check above. +- **Retained rehearsal, 2026-10-02.** The separate stage-1 receipt records Ubuntu 26.04.1's schema, install, launch + and owner results on WSL 2.7.13, then F1's failed user manager. Its coordinator review establishes the shared + cgroup diagnosis. The later unregister/interop observations and the initial namespace observation are retained in + the supplied repair brief; they are not invented as output in the earlier worker receipt. +- **Reported host observation, 2026-10-02.** The coordinator's brief gives the five reads of the Updated-host + correction, taken on WSL 3.0.1.0 with kernel 6.18.40.1-1 in the workstation's distribution from 06:39Z to 06:57Z. They + cover one distribution and are not a run of the recipe. The third follow-up's unit repeated the five commands in the + same distribution afterwards and read the same values: `1000`; `degraded` with exit 1; `systemd-binfmt.service` as + the only failed unit; `active`; `cgroup:[4026532183]`. It also ran + `journalctl -b 0 -t systemd-binfmt --no-pager | tail -n 4` as that distribution's default user, who is not in + `adm`, and read only the notice that other users' and the system's messages are hidden, which is why F1 states its + dependence on `adm` and repeats the line once with `sudo` when only that notice prints. Nothing was observed with two + distributions running. +- **Not run.** The corrected recipe on WSL 3.0.1 with a second distribution, including W5's paired record, F1's accepted + `degraded` and a rootless container in the new distribution, remains owed. + F11 rests on a source read and has not run on a new distribution. The addendum records a run of the same command + form on 2026-09-25, under a temporary `CLAUDE_CONFIG_DIR` on one WSL2 host. +- **Cross-family review.** GPT-6.1 Sol, read-only, 2026-10-01, of PR #569, returned `needs_changes` with three findings. + All three were fixed against the cloud-init 26.1 source above, and `tests/test_wsl_new_distro_recipe.py` + `CrossFamilyReviewTests` keeps them fixed: + - W5 expected `status: done` after the marker exists; it now expects `disabled-by-marker-file` and reads + `result.json` and `status.json`, and W6 does the same. + - W1 did not check `agent.yaml`. + - F10 trusted `type -P` alone. + +## Sources + +Read on 2026-10-01 by the research unit only, and cited here as it reported them: + +- `wsl.exe --version` and `wsl.exe --help` on the Windows host: WSL 2.7.13.0, kernel 6.18.33.2-2, the command forms in + Context. +- https://github.com/microsoft/WSL/releases/tag/2.7.14 (2026-09-11; backports #41524, #41540, #41557 and #41569) and + https://github.com/microsoft/WSL/releases/tag/3.0.1 (2026-09-29; WSL containers generally available; #41657, #41688 + and #41689). Also the `microsoft/WSL` source at tags 2.7.13, 2.7.14 and 3.0.1, including `LxssUserSession.cpp:1597` + and `:1682` and `src/linux/init/main.cpp:2656-2669`. +- https://releases.ubuntu.com/resolute/SHA256SUMS (2026-08-27) and the contents of the Ubuntu 26.04.1 image: + - `python3` 3.14.3; + - uutils coreutils; + - `sudo-rs` beside `sudo`; + - `bubblewrap` and `libatomic1` present. +- https://cloud-images.ubuntu.com/wsl/ (WSL image publication moved to cdimages; those rootfs tarballs are not + general-purpose). + +Read on 2026-10-01 by the research unit or by unit W2. The dates of the Microsoft Learn pages are their displayed "last +updated" values. The second follow-up's researcher read each page's `git_commit_id` that day, and the WSL pages are +pinned to it below; the unit fetched each pinned raw file (HTTP 200). + +- Microsoft: + - https://learn.microsoft.com/en-us/windows/wsl/build-custom-distro (`oobe.command`, `oobe.defaultUid`; the + `uid`/`defaultUid` 1000 recommendation; `--install --from-file`; WSL 2.4.4; displayed 2025-09-12), pinned at + https://github.com/MicrosoftDocs/WSL/blob/7b28cc1ee9b8ff672ada5e1c6c326d3573d703e5/WSL/build-custom-distro.md + - https://learn.microsoft.com/en-us/windows/wsl/basic-commands (`--install` options including `--location` and + `--no-launch`; displayed 2025-12-01), pinned at + https://github.com/MicrosoftDocs/WSL/blob/7b28cc1ee9b8ff672ada5e1c6c326d3573d703e5/WSL/basic-commands.md + - https://learn.microsoft.com/en-us/windows/wsl/use-custom-distro (`--import` starts as root; user and `[user] default`; + `wsl --terminate`; displayed 2024-07-16, which matches neither its `ms.date`, 2021-09-27, nor its `updated_at`, + 2026-06-02), pinned at + https://github.com/MicrosoftDocs/WSL/blob/7b28cc1ee9b8ff672ada5e1c6c326d3573d703e5/WSL/use-custom-distro.md + - https://learn.microsoft.com/en-us/windows/wsl/wsl-config ("The 8 second rule"; `[user] default`; + `instanceIdleTimeout` 15000; `distributionInstallPath`; displayed 2026-09-16), pinned at + https://github.com/MicrosoftDocs/WSL/blob/7ea1c6f9e25f1c89a05a0e97e5325a02a66ac6cd/WSL/wsl-config.md + - https://learn.microsoft.com/en-us/windows/wsl/systemd (`systemctl status` and + `systemctl list-unit-files --type=service`; displayed 2025-03-17, which matches neither its `ms.date`, 2025-01-13, + nor its `updated_at`, 2026-06-02), pinned at + https://github.com/MicrosoftDocs/WSL/blob/7b28cc1ee9b8ff672ada5e1c6c326d3573d703e5/WSL/systemd.md + - https://learn.microsoft.com/en-us/windows/wsl/disk-space (`ext4.vhdx` location; 1 TB default maximum) + - https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.utility/get-filehash (default `SHA256`) + - https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.core/about/about_character_encoding?view=powershell-5.1 + (Windows PowerShell's `UTF8` writes a BOM) + - https://learn.microsoft.com/en-us/dotnet/api/system.io.file.writealltext (UTF-8 without a BOM) + - https://learn.microsoft.com/en-us/powershell/module/microsoft.powershell.host/start-transcript?view=powershell-5.1 + (`-LiteralPath`, `-Append`) + - https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/cmd (`/c`, `/d`) + - https://learn.microsoft.com/en-us/previous-versions/windows/it-pro/windows-xp/bb490982(v=technet.10) (`<` reads input + from a file) + - https://learn.microsoft.com/en-us/windows/terminal/json-fragment-extensions (fragment profile GUIDs from the + application and profile names; displayed 2025-11-12), whose `git_commit_id` is + `eef3334aa62933c168521f13ab2cca28c9d93c47` in `MicrosoftDocs/terminal`; its file path there was not read + - disk-space and the PowerShell, .NET and Windows Server pages were not read again and stay unpinned. +- Canonical and Ubuntu: + - https://documentation.ubuntu.com/wsl/stable/howto/cloud-init/ (`%USERPROFILE%\.cloud-init\.user-data`; the + NOPASSWD user example). It now redirects to https://ubuntu.com/wsl/docs/stable/howto/cloud-init/, "Last updated on + Mar 17, 2026", whose check after provisioning is `sudo cloud-init schema --system` with the output + `Valid schema user-data` (the second follow-up's researcher, 2026-10-01). + - https://documentation.ubuntu.com/wsl/stable/howto/install-ubuntu-wsl2/ (Method 1, WSL 2.4.10) + - https://ubuntu.com/blog/ubuntu-wsl-new-format-available (WSL 2.4.8; `wsl --install --from-file`) + - https://releases.ubuntu.com/24.04.5/SHA256SUMS and https://releases.ubuntu.com/noble/SHA256SUMS + - https://releases.ubuntu.com/24.04.5/ubuntu-24.04.5-wsl-amd64.wsl (streamed and hashed) + - https://manpages.ubuntu.com/manpages/noble/en/man8/useradd.8.html (`SUB_UID_COUNT` allocation) + - https://manpages.ubuntu.com/manpages/noble/en/man8/usermod.8.html (`--add-subuids`, `--add-subgids`) + - https://github.com/ubuntu/wsl-setup at `86a561d5149a9d76ec3c9b3ce2745e7ebca5f2ad`, GitHub main when read: the + revision of the `test/systemd-assertions.sh` citations; W5's console lines sit there at lines 122-123 + - https://git.launchpad.net/ubuntu/+source/wsl-setup/tree/wsl-setup?id=74bfc89113bc7d46a4d9feb1e69cd6951fbc6908#n117, + tag `import/0.5.10_24.04.2`, the image's version: W5's console lines at 117-118 (the second follow-up's researcher) +- cloud-init: + - https://docs.cloud-init.io/en/latest/reference/datasources/wsl.html (lookup order, Landscape precedence, + requirements, the default-user example). `latest` renders the 26.2 documentation today (completeness critic); the + 26.1 page was not fetched, so this citation stays at `latest`. + - https://docs.cloud-init.io/en/latest/reference/modules.html (Users and Groups: `uid`, `lock_passwd` default `true`, + `sudo`; also at `latest`, its 26.1 page not fetched) + - https://docs.cloud-init.io/en/26.1/reference/cli.html, titled "CLI commands - cloud-init 26.1 documentation" + (`status --long`, exit codes 0, 1 and 2; `--system`: "Validate the system cloud-config user-data"; read at 26.1 by + the second follow-up's researcher, after the research unit read it at `latest`) + - https://github.com/canonical/cloud-init at tag `26.1` (`8bf3567532b07e2cc15aa4c76c36ebed65ccfaec`): + - `cloudinit/distros/__init__.py:662-684` (`add_user` runs `useradd`) + - `cloudinit/cmd/status.py:25-68, 83-88, 232-307, 381-392, 407-419, 459-527` (marker and generator boot codes, + `disabled` running status, `detail` and `errors` from the `/run` copy, exit codes) + - `cloudinit/cmd/main.py:880-1032, 1312-1368, 1400-1404` (`status.json` and `result.json` in `/var/lib/cloud/data`) + - `cloudinit/sources/DataSourceWSL.py:241-355, 435-491` (Ubuntu Pro file loading and the `agent.yaml` merge) + - `cloudinit/sources/__init__.py:398-399` and `cloudinit/type_utils.py:21-28` (the datasource's string name) +- Microsoft WSL, Docker and systemd: + - https://github.com/microsoft/WSL at tag 2.7.13 (`80697fd42cca3de0c0d5dd1931c36112372a577e`): + `src/windows/common/WslClient.cpp`, `src/windows/service/exe/LxssUserSession.cpp`, + `src/windows/service/exe/WslCoreInstance.cpp`, `src/linux/init/init.cpp`, `src/linux/init/main.cpp` and + `localization/strings/en-US/Resources.resw` + - https://github.com/microsoft/WSL/blob/8bc98bc33b246fe66710eec9eaa1b24c323da987/distributions/DistributionInfo.json + - https://docs.docker.com/engine/security/rootless/ (`uidmap`; at least 65,536 subordinate ids; + `loginctl enable-linger`) + - https://github.com/systemd/systemd at `v255`: `man/loginctl.xml:186-194` and `man/systemctl.xml:2297-2307`, the + sources of https://www.freedesktop.org/software/systemd/man/255/loginctl.html + - https://nodejs.org/dist/v24.21.0/node-v24.21.0-linux-x64.tar.xz (the pinned tarball, hashed and inspected) +- This repository at `origin/main` 3361b342: + - `adoption/bootstrap-linux.sh` (lines 154-163, 167-178, 186-200, 205-218, 221, 237-242, 1048-1058); + `adoption/bootstrap.md` (lines 223-228, step 3); + - `adoption/platforms/linux-wsl2.md` (lines 52-59, 158-166, 177-205, 211-218, 236-239); `adoption/hosts/example.json`; + `adoption/tools/README.md:184-188`; `adoption/lifecycle.md:215-244`; + - `adoption/templates/systemd/credential-boot-receipt.service:11-12`; `adoption/pins-linux-x86_64.json` (node); + - `docs/new-workstation-runtime-profile-20260922.md:25,34`; `docs/next-host-stages.md:54-77`; + `docs/decisions/2026-09-28-ecosystem-roadmap.md:58,122,205`; `observability/backends/templates`; + - `scripts/validate.py` (`RECEIPT_KINDS`, receipt payload keys); `manifests/stack.json` (`systemd`). + +Read on 2026-10-01 by the follow-up unit (F11 and the F5 range rule): + +- https://code.claude.com/docs/en/mcp: local scope; "Server status", with the `Added ...` line; the repeated-add + failure. The coordinator fetched it at 07:54Z and the unit re-read it at 08:02Z. +- https://docs.docker.com/engine/security/rootless/troubleshoot/: the `lchown : invalid argument` entry. The + coordinator fetched it at 07:47Z and the unit re-read it at 08:02Z. +- This repository at `3361b342`; the files read are unchanged at this branch's head: + - `adoption/bootstrap.md:387-395` (step 4a's `uv tool install` lines) and `:458-472` ("jCodeMunch, per project"); + - `docs/decisions/2026-09-23-claude-user-profile.md:127-210` (the 2026-09-25 addendum); + - `adoption/hooks/claude/token-lanes-block.md` and its five role blocks; + - `tools/adoption/install_claude_profile.py:49-59` (the blocks it copies); + - `adoption/mcp/claude-user.json`, the `profiles` of `adoption/manifest.json` and `adoption/pins-linux-x86_64.json`. + +Read on 2026-10-01 by the review-repair unit (the export before W6's `--unregister`), through Context Mode: + +- https://learn.microsoft.com/en-us/windows/wsl/basic-commands (`ms.date` 2025-12-01), with its source + `WSL/basic-commands.md` of `MicrosoftDocs/WSL` on `main`. "Export a distribution": + `wsl --export ` "Exports a snapshot of the specified distribution as a new distribution + file. Defaults to tar format", with `--vhd` for a `.vhdx` file. "Unregister or uninstall a Linux distribution": all + data, settings and software of the distribution "will be permanently lost". The page's in-place command, + `wsl --import-in-place `, registers an existing `.vhdx` as a new distribution and + repairs nothing. +- https://documentation.ubuntu.com/wsl/latest/howto/cloud-init/ (redirected to + https://ubuntu.com/wsl/docs/latest/howto/cloud-init/, "Last updated on Sep 28, 2026"): no repair of a failed + provisioning is documented. The in-place repair of Alternatives 2 stays a reading of WSL's and `wsl-setup`'s source, + not a documented procedure. + +Read on 2026-10-01 for the second follow-up (the completeness critic's items), by its researcher, whose packet gives each +URL and file:line and is quoted here as it quotes them, and by the unit where noted: + +- microsoft/WSL: https://github.com/microsoft/WSL/issues/40941, https://github.com/microsoft/WSL/pull/40977, + https://github.com/microsoft/WSL/issues/41482, https://github.com/microsoft/WSL/issues/13416, + https://github.com/microsoft/WSL/issues/9968 and the release notes at https://github.com/microsoft/WSL/releases + (2.5.4, 2.7.11 to 2.7.14, 2.9.8); the source at tag 2.7.13 (`80697fd42cca3de0c0d5dd1931c36112372a577e`): + `src/windows/common/WslInstall.cpp:36-50, 94-146, 287-325`, `Distribution.cpp:20-21, 223-230`, + `Distribution.h:85-86`, `WslClient.cpp:500-537`, `Resources.resw:837-838, 1067`, + `LxssUserSession.cpp:2658-2676, 3583-3599` and `WslCoreInstance.cpp:293-302`; `Redirector.h` at tags 2.7.13, 2.7.14, + 2.9.8 and 3.0.1, and `WslCoreInstance.cpp:315` (2.9.8) and `:316` (3.0.1). +- cloud-init at tag 26.1 (`8bf3567532b07e2cc15aa4c76c36ebed65ccfaec`): `cloudinit/config/schema.py:1279-1287, 1388-1498`, + `cloudinit/log/log_util.py:68-79` and `cloudinit/cmd/main.py:1240-1241, 1286-1293`. +- wsl-setup at Launchpad `74bfc89113bc7d46a4d9feb1e69cd6951fbc6908`: `wsl-setup:117-118, 130`, + `wait-for-cloud-init:9-12` and `debian/install`; GitHub tag 0.5.10, `f83e4df49b3583272d5ca499ca63b426b934a1f3`. +- https://ubuntu.com/tutorials/how-to-verify-ubuntu (steps 4 to 6) and the `ubuntu-keyring` `debian/changelog` at + Launchpad `edc0a0be9a90f364bc39a87d3837ad4c15395950` (lines 104-106, 114 and 141-142). +- https://releases.ubuntu.com/24.04.5/SHA256SUMS and https://releases.ubuntu.com/24.04.5/SHA256SUMS.gpg (also + fetched and verified by the unit; Evidence classes). +- The metadata of each Microsoft Learn page above (`ms.date`, `updated_at`, `git_commit_id` and the displayed date). +- By the unit: the pinned raw files of MicrosoftDocs/WSL (HTTP 200 for build-custom-distro, basic-commands, + use-custom-distro, systemd and wsl-config), `adoption/bootstrap-linux.sh:175-178` on this branch, and + `/usr/include/scsi/scsi.h:59-60` (libc6-dev) for the SCSI command codes. + +Primary sources reused for W-IMG parameterization (2026-10-01): + +- https://releases.ubuntu.com/26.04.1/SHA256SUMS and https://releases.ubuntu.com/26.04.1/SHA256SUMS.gpg; + the retained integration receipt's reported signature exits/fingerprint and image stream hash match, with no + retained 26.04.1 signature output and no new download for this unit. +- https://documentation.ubuntu.com/release-notes/26.04/ and the Microsoft creation docs pinned above support creation, + not full-stack compatibility. https://ubuntu.com/wsl/docs/stable/howto/cloud-init/ is explicitly a 24.04/22.04 guide. +- https://github.com/ubuntu/wsl-setup/blob/73418e32bb48d514c2c2853fa7e5cacdcaf3dfe8/wsl-setup and + https://github.com/ubuntu/wsl-setup/blob/73418e32bb48d514c2c2853fa7e5cacdcaf3dfe8/ubuntu-insights.sh, tag `0.6.3`. +- https://learn.microsoft.com/en-us/windows/wsl/tutorials/gpu-compute and + https://docs.nvidia.com/cuda/wsl-user-guide/index.html (preserved CUDA on WSL 13.4 guide): native GPU visibility only; + `nvidia-smi` has limited WSL features and can be invoked at `/usr/lib/wsl/lib/nvidia-smi`. +- Existing `adoption/bootstrap.md`'s Python 3.13 prerequisite and `adoption/pins-linux-x86_64.json`'s official Node + 24.21.0 tarball pin; native uv `run --help` observation recorded above. No replacement installer was written. + +Sources for the rehearsal correction (2026-10-02), retained review and supplied primary references: + +- `origin/foundation/new-wsl-rehearsal-20261002` at `76b7c0b4ee45d3a6f0a60fa68d053e0f2039e8e4`, + `evidence/artifacts/new-wsl-rehearsal-20261002/coordinator-review.md` + and `stage1-receipt.json`; these were read with `git show`. The repair brief adds the interop recovery and + initial-namespace observation after the worker's stop. +- microsoft/WSL release notes at https://github.com/microsoft/WSL/releases/tag/2.9.8, + https://github.com/microsoft/WSL/releases/tag/2.9.13 and https://github.com/microsoft/WSL/releases/tag/3.0.1; + https://github.com/microsoft/WSL/pull/40519 and https://github.com/microsoft/WSL/pull/41512. +- https://github.com/microsoft/WSL/issues/41492, still open in the retained review; no 3.0.1 rootless acceptance. +- Linux `v6.18`, https://github.com/torvalds/linux/blob/v6.18/include/linux/proc_ns.h, `PROC_CGROUP_INIT_INO`. +- https://docs.docker.com/engine/security/rootless/, the supported setup and rootless Docker context already selected + by this recipe; the container check uses its native Docker CLI. +- This worktree's `evidence/artifacts/new-wsl-definitive-defaults-20261001/definitive-manifest.json`, + `cross:wsl-distro` / `base-distribution`: the single Ubuntu 26.04.1 default. The repair brief supplies the later + critics' confirmation and the release-caused-failure rollback condition. + +Sources for the updated-host correction (2026-10-02), read that day by the third follow-up's unit: + +- https://github.com/microsoft/WSL/pull/40621, "protect binfmt_misc from cross-distro wipe at shutdown" (merged): it + bind-mounts a read-only file over `/proc/sys/fs/binfmt_misc/status` in each per-distro mount namespace, so that the + write with which `systemd-shutdown` wiped the shared registry fails with `EROFS`. Read through the GitHub API. +- https://github.com/microsoft/WSL/issues/41226, "`systemd-binfmt.service` fails with "Read-only file system" on boot" + (closed), and a contributor's answer of 2026-08-04: "The systemd error is benign and won't affect registration of user + defined binfmt settings. Please ignore it if it's not affecting any functions." Read through the GitHub API. +- systemd `src/binfmt/binfmt.c`: `Failed to flush binfmt_misc rules, ignoring: %m` is line 253 of tag `v255` and line + 249 of tag `v259`. `tmpfiles.d/systemd.conf.in`: the `adm` group's read access to the system journal, lines 47-53 of + `v255` and 44-50 of `v259`. The 26.04.1 image packages systemd 259.5-0ubuntu3.4 (the experiment record); tag `v259` + was read, not `v259.5`. +- The coordinator's brief of 2026-10-02: the host observations of the Updated-host correction and the contract for the + three changes. + +## Amendment 2026-10-02 (rehearsal run 2 on WSL 3.0.1, and the scoped cross-family read) + +The coordinator accepted run 2 and the scoped cross-family read of `fed1e93c` for PR #593. This amendment records +their source reviews and host observations and changes the next run's commands and proofs; it is not another host +run. Earlier sections retain their dated evidence and planning statements. The observations below supersede their +statements that simultaneous distributions on 3.0.1 were unobserved. No image comparison has run; the criteria are +the preregistered ones with the amendments of 2026-10-02 made before any comparison ran. + +Run 2, 2026-10-02T08:32Z to 08:40Z, used a throwaway name, Ubuntu 26.04.1, path A, WSL 3.0.1.0 and kernel +6.18.40.1-1. P1, P2, W1, W2, W3, W4 and W5's path-A proofs passed: launch exit 0, uid 1000, cloud-init +26.1-0ubuntu3~26.04.1, retained `result.json` and `status.json` without errors, `Valid schema user-data`, +`/etc/wsl.conf`, the marker and sudo. It stopped at W5's paired rule and was removed through W5's recovery block. +The seven findings are: + +1. **P3 counted the kernel's command line.** The baseline of `2` was `kernel: Command line:` at 0.000000 s and + `kernel: Kernel command line:` at 0.018761 s, both naming `hv_storvsc.storvsc_max_hw_queues=4`. Neither is a + storage error. The old age rule would stop a run within one hour of boot on these echoes. P3's two filters and + W5's second count now exclude the registration line and `[Cc]ommand line:` with the same extended-regexp filter. +2. **Cgroup isolation held.** The workstation printed `cgroup:[4026532183]` and the new distribution + `cgroup:[4026532407]`; the common unit's `cgroup.procs` held one local pid and no `0`. Both user managers were + `active`, and the launch printed no user-session warning. This observes two distributions running together on + 3.0.1, while a complete corrected recipe run and F3 remain owed. +3. **The shared console failed the paired rule.** In the second the new distribution booted, both + `getty@tty1.service` units received SIGHUP and restarted five times, then showed `Result=start-limit-hit`. + Both system managers listed that unit and `systemd-binfmt.service` failed. The workstation changed from its W1 + baseline: `1000`, `degraded`, only `systemd-binfmt.service` failed, `active`, `cgroup:[4026532183]`. + Both expose `/dev/tty1` as character device 4,1 of the one VM kernel. Source review of the two running images + (workstation Ubuntu 24.04.5/systemd 255; new Ubuntu 26.04.1/systemd 259) found `TTYPath=/dev/tty1`, + `TTYVHangup=yes`, `Restart=always`, `RestartSec=0` and enablement in `/etc/systemd/system/getty.target.wants/`; + both `console-getty.service` units read `masked-runtime`. The workstation's getty stays failed until the next + WSL start; no unit action is permitted there by this page, and nothing uses the VM console. +4. **One real storage error appeared.** The second count was `3`. The new driver line, `cmd 0x2a`, `srb 0x4`, + host `0xc00000a1`, was at kernel time 7118.5 s in W4's install window. The disk was unmounted at 7126.9 s, + and cloud-init's `init-local` started at 7134.15 s. The written attachment-window rule recorded the line and + did not stop the run; the other two lines were the command-line echoes. +5. **Removal preserved interop.** Export and unregister both exited 0, and the survivor check passed. On 3.0.1 + `WSLInterop` survived both unregisters of 2026-10-02, in run 2 and probe E1, unlike the 2.7.13 rehearsal. + The check after every unregister and its stop-for-review rule remain required. +6. **The image size was observed.** W2 printed `418495746` bytes for `ubuntu-26.04.1-wsl-amd64.wsl`. +7. **Journal access depends on the user's group.** The workstation user is not in `adm`: without `sudo` the + identifier query printed the permission hint and `-- No entries --`; with `sudo` it printed + `Failed to flush binfmt_misc rules, ignoring: Read-only file system`. The new user belongs to `adm` through + the user-data. `journalctl -b 0 -t systemd-binfmt --no-pager -n 4`, without `tail`, printed the same lines. + +The mechanism's upstream sources were reviewed at microsoft/WSL tag `3.0.1`: + +- [`src/linux/init/init.cpp:363-365`](https://github.com/microsoft/WSL/blob/3.0.1/src/linux/init/init.cpp#L363-L365): + "Mask console-getty.service since /dev/tty devices are shared at the VM level across all distros. When multiple + distros are running, the second distro's getty fails because the tty is already held." + [PR #14490](https://github.com/microsoft/WSL/pull/14490), merged 2026-04-09, says "Fixes #13595". +- [`distributions/validate-modern.py:27-40`](https://github.com/microsoft/WSL/blob/3.0.1/distributions/validate-modern.py#L27-L40): + `DISCOURAGED_SYSTEM_UNITS` includes `console-getty.service` and does not include `getty@tty1.service`. +- A maintainer in [issue #13595](https://github.com/microsoft/WSL/issues/13595): "the best way to solve this would + probably to try to get distros to disable the `getty` and adjacent units in WSL". + +**Probe E1, 2026-10-02T08:42Z.** Outside the recipe, a second throwaway name used the same template with the new +`bootcmd`. Its rendered SHA-256 was `c0732303a0004121f9d373f7856454e3e59eeadea395bac46eb17e1671b32af4`. +It returned `Valid schema` on the workstation and `Valid schema user-data` in the image. The getty was `masked` +(`is-enabled` exit 1, normal for a masked unit), with `LoadState=masked`, `ActiveState=inactive` and `NRestarts=0`. +Only `systemd-binfmt.service` was failed. The journal recorded cloud-init creating the mask symlink and, 0.18 s later, +systemd's `Failed to start getty@tty1.service.`: the refused job of a masked unit, not a failed unit. Cloud-init +finished without errors; the user manager was `running`; namespaces were `cgroup:[4026532183]` and +`cgroup:[4026532410]`. The workstation's values did not change, and the probe was removed afterwards. + +Ordering is a source review of the pinned 26.04.1 image's own files: `cloud-init-network.service` has +`Before=systemd-user-sessions.service`, `getty@.service` has `After=systemd-user-sessions.service`, and +`/etc/cloud/cloud.cfg` lists `bootcmd` among `cloud_init_modules`. The workstation's cloud-init +26.1-0ubuntu1~24.04.1 has the same ordering in `cloud-init.service`; that is the version the 24.04.5 image packages, +but the 24.04.5 image's own unit files were not read. + +**Decision.** Mask `getty@tty1.service` in the new distribution through the template's `bootcmd`, during cloud-init's +network stage before systemd starts the unit. W5 proves the mask and retains all five paired observations with +separate exits. The workstation's five values must equal its W1 baseline. W1 accepts a failed workstation getty +only as an earlier collision's leftover with `Result=start-limit-hit`; it does not prove the workstation's unit +messages again, because that distribution is compared with itself and F1 proves the new distribution's message. +Any other baseline stops before W4 or any W6 import, including a uid other than 1000, an inactive user manager or +the initial cgroup namespace. Any failed W5 proof uses `w5_failure_export`, with `cause` (`cgroup`, `tty` or a short +text), and `-w5-failed.tar`; do not take path B for any such cause. Only the OOBE prompt failure permits W6. +On path B the root block masks the getty after its first boot has already started it, so W5's paired proof after +the relaunch decides whether the workstation changed; a difference stops and uses W5's recovery. + +**Run 3 (2026-10-02T10:54Z to 11:01Z, the repaired recipe, a third throwaway name).** P1 to P3, W1 to W5, W7 and F1 +to F5 passed. W5: launch exit 0; `systemctl is-enabled getty@tty1.service` printed `masked`; `LoadState=masked`, +`ActiveState=inactive`, `NRestarts=0`; the paired values were `1000`, `degraded`, {`getty@tty1.service`, +`systemd-binfmt.service`}, `active`, `cgroup:[4026532183]` for the workstation, equal to its W1 baseline, and `1000`, +`degraded`, {`systemd-binfmt.service`}, `active`, `cgroup:[4026532408]` for the new distribution. W7's owner probe +printed `1000:1000` on 3.0.1. F1's journal line printed the read-only flush message without `sudo`. F2: linger `yes`, +and the distribution was listed in all twelve idle polls (`.wslconfig` has `instanceIdleTimeout=-1`). F3: a user-owned +directory and socket, user manager `running`; after F4, F5 and Docker's rootless setup (Docker Engine 29.8.2), +`docker --context rootless info` listed `name=rootless` and `docker --context rootless run --rm hello-world` printed +`Hello from Docker!`: microsoft/WSL#41492 does not affect a new distribution on this host. The second storage count +rose by one line inside W4's install window (15573.8 s), as in run 2 and the probe. Second-instance probes: user +manager `running`, `/dev/dxg` present, `nvidia-smi` exit 0, system `Python 3.14.4`; uv and Node are not installed +before stage 2, so those two probes are owed. + +Run 3 also found three command defects, each corrected and re-run on the same distribution. (1) With the `bootcmd`, +cloud-init records one recoverable error, `Failed to wait for network`, and `cloud-init status --long` exits 2: +cloud-init waits for the network when user-data holds a `bootcmd` (26.1, `cloudinit/cmd/main.py:351-402`), and WSL +masks `systemd-networkd-wait-online.service` (`init.cpp:356-358`). It is harmless and now stated as expected in W5. +(2) Windows PowerShell 5.1 does not escape a double quote inside an argument to a native program, so R1's first probe +returned `stat: missing operand`, and the quoted F10 loop that this repair had introduced returned `unexpected EOF`; +both now carry no double quote (`stat -c %U,%F ...`; `[[ -f $p && -x $p ]]`) and printed the expected lines. (3) F5's +block ran `usermod` unconditionally although its text said otherwise; its second line now guards itself. + +**P3's age rule, one exception (added in the coordinator's review of this repair, 2026-10-02).** With the corrected +filter the workstation's kernel journal holds two real driver errors of the same shape, at 7118.5 s (run 2's install) +and 7678.6 s (probe E1's install): each `wsl.exe --install --from-file` on this host left one line in its install +window. P3 as written would stop a second arm that starts within the hour on its predecessor's line. P3 now lets a +run continue when the newest line's kernel time is one that an earlier run of the page recorded as its own +attachment-window line; a line no earlier receipt explains keeps the stop. Overturned when an install leaves more +than its one line, or lines appear outside an install window: then the count is exposure to microsoft/WSL#41482 and +the stop applies. F10's sentence now renames every profile of the example without naming a count, so it holds for +the example with three profiles and with five (pull request 604 added two resume profiles). + +Alternatives considered, with what would overturn each rejection: + +- **Accept getty as a second by-design failed unit in both distributions.** This hides a real cross-distribution + effect and changes the workstation's state at every import. Reconsider only if upstream evidence eliminates the + effect or a separate decision accepts that workstation change; a failed getty is not part of F1's pass condition. +- **Mask it in the workstation as well.** This changes the owner's running distribution and would also protect it + from other distributions' gettys. It is left to the owner. Reconsider if a run proves the new-distribution mask + takes effect after the unit started, with the owner's decision required for that broader change. +- **Use a kernel command-line or `.wslconfig` setting.** The page never changes `.wslconfig`; a kernel choice also + reaches every distribution. Reconsider only with an upstream-supported setting and a separate host-wide decision. +- **Change the image.** The page installs the official image unmodified. Reconsider if Canonical's official image + stops enabling the getty, or a separate image decision replaces that requirement. + +Drop the `bootcmd` when WSL masks `getty@tty1.service` itself or Canonical's image stops enabling it. If a run shows +the mask taking effect after the unit started, the proof fails and masking the workstation is reconsidered. +A host whose other systemd distributions retain an unmasked getty will see both fail when two of them run; changing +those units belongs to the host owner, outside this page. + +**Not established.** A live workstation getty surviving a new distribution's first boot follows from the masked +unit never starting, but was not observed: the workstation's getty had already failed in run 2 before E1. The +24.04.5 image's own unit files and path B's first boot are unobserved. A complete recipe run with the amended +template through F3 remains owed; E1 is a probe, not a recipe run. Full image comparisons and full-stack acceptance +remain owed. + +The scoped review's points and their repairs are: + +- W1's incomplete stop: W1's four baseline conditions and the checklist now stop every incompatible baseline; + tests remove the stop paragraph, uid, namespace and user-manager conditions separately. +- Receipt equality and abbreviated F1 exceptions: every workstation field requires equality to + `host.workstation_baseline`; the comparison table, receipt and experiment use F1's complete pass condition. +- Linux entry paths and F10 quoting: the launch forms use full Windows paths in Linux shells, and F10 tests each + quoted executable path without word splitting. +- Hidden exits: W1 and both W5 halves have five separate commands and receipt entries; F1 and its sudo fallback + use `journalctl -n 4` directly. +- Test blind spots: the new controls reject every weakened baseline/equality/message instruction, and the full + original quality rule is compared after removing the dated amendments. +- Preregistered label and stale framing: the page, checklist and named record sentence identify the amendments; + the W7 sentence describes the 2.7.13 rehearsal in the past, and overturn now concerns a later WSL update. + +The command table mirrors the amended operational commands. Historical command text in earlier evidence sections +remains as the command actually run then; it is not a command for the next run. The supplied primary reviews, the +official image's files and the installed commands above are the sources for this bounded repair. The existing +Python `unittest` harness tests repository contracts only; its controls do not qualify a host. The completeness +check leaves the same decision-changing gaps: the corrected full run through F3, the rollback image and path B. + +## Stage 2 for the new distribution (2026-10-02) + +Added after `v2026.09.26.2`. The instructions of 2026-10-02 for the client-configuration work (three rounds, the repair +after the cross-family review of the pull request, a repair after the first macOS run of its tests, and a last repair after an +independent read) changed F8 and F9 of the recipe and the places that carried their old text, made `--apply` merge into the +files the plan and the clients leave, made the authorization settings opt-in, and made F9 say, word for word, what the +instruction blocks lose and what the line `--apply` prints about the authorization settings says. This section records what changed and why; it is not a host run, and no step below was run on the +destination distribution. Earlier sections keep their dated text: the reading of +`adoption/bootstrap-linux.sh` under "What stage 2 expects" and the ports that Decision 3 names describe the state of +2026-10-01. + +What changed: + +1. **F9 is no longer the bootstrap's profile.** It runs the install plan's `install.sh` and `accept.sh`, then + `tools/adoption/new_wsl_client_config.py` with `--check` and `--apply --host ''`, then the two native sign-ins by + hand, then the plan's after-sign-in checks one owner at a time (`accept.sh --only --stage after_sign_in`, for + `codex`, `claude-agent-sdk`, `codex-sdk-and-codex-exec-app-server`, `local-model-server`, `agent-runtime-worker` and + `research-harnesses`, the six owners whose plan row has that stage). Why: every profile of `adoption/manifest.json` + installs tools that the merged + [definitive manifest](../../evidence/artifacts/new-wsl-definitive-defaults-20261001/definitive-manifest.json) does not + install, and wires the clients to them. The + [install plan](../../evidence/artifacts/new-wsl-install-plan-20261002/README.md) installs the 36 owners the manifest + does install, and the tool wires each client only to those + ([record](2026-10-02-new-wsl-client-configuration.md)). +2. **The configuration comes before the sign-ins.** The guard hooks are then active in the first session a person + starts. (An earlier version of this item also gave `tools/adoption/codex_home.py`'s refusal to rewrite an existing + Codex `config.toml`, which a login or a first start can leave behind, as a reason. That was incomplete: the install + plan itself leaves one, because `codex plugin marketplace add trailofbits/skills --ref ` writes a + `[marketplaces.trailofbits]` table, so the file exists before `--apply` in either order. Observed on the destination + after the plan and the sign-ins: 192 bytes, that table and a `[tui]` table. `--apply` therefore merges into an + existing `config.toml`; see the next item.) No step of `--apply` needs a signed-in client. Observed 2026-10-02 with the native + Claude Code 2.1.287 and a Codex 0.159.x binary against temporary homes that held no credential file: every step but + one applied and no credential file appeared. The exception, `codex-config`, was refused on the workstation because + other Codex processes run there, a guard of `codex_home.py` that belongs to this host and not to a sign-in. Its one + Codex call, `codex features disable daemon_auto_start` for an existing `config.toml`, was run with that guard's + process name pointed at nothing and exited 0 and wrote the key. On the destination that call is made once, because the + plan leaves a `config.toml` without the key; an empty Codex home starts Codex not at all, because the render carries + it. +3. **`--apply` merges into the files that exist.** A `~/.codex/config.toml` that exists is merged, never rewritten: every + key and table it has stays as it is; each top-level key and each table the render has and it lacks is added; where a + table exists on both sides only the missing keys are added; a value that differs stays as the file has it, is printed + beside the render's, and the step ends `merged with conflicts kept` (exit 0). The file is backed up first, read back + with `tomllib` after the write, and put back from the backup when it is not the merge; `features.daemon_auto_start` + goes through Codex's own writer, as `codex_home.py` does; a running Codex stops either write of `config.toml`, the merge and the creation of an absent file, and the message says +to close the sessions and run again; so does a `pgrep` that cannot run or exits with a status other than 0 or 1, because +whether a Codex runs is then not known. `~/.claude/settings.json` keeps the keys it has (on the destination + `extraKnownMarketplaces` and `theme`); the template's values win except the theme, which stays as the file has it. +4. **One port truth.** Observed 2026-10-02 in the first real run of the recipe: the template's ports 24318 and 26333 had + listeners on the workstation (its own collector and its Qdrant, in the shared network namespace), so F8's proof failed + and the page's fallback rule was used. The template's `OTEL_ENDPOINT` is now `127.0.0.1:21318`, the OTLP/HTTP port + of the plan's collector (`config/otel.yaml`), and `QDRANT_URL` is `127.0.0.1:21633`, a port no service of the plan uses. + F8's `ss` probe checks `21318`, `29374`, `21633` and `28231`, and its exclusion set gains `24318` and `26333`. The + tool takes the collector port from the plan, so the render and the host file agree. +5. **The authorization settings are written only on request.** Claude Code's `permissions.defaultMode` + (`bypassPermissions`) and `skipDangerousModePermissionPrompt` and Codex's `approval_policy` (`never`) and `sandbox_mode` + (`danger-full-access`) grant permissions and suppress confirmations, so the plain `--apply` of F9 writes none of them and + leaves any value a file has; `--with-authorization-settings`, added to that line only on a host whose owner asked for the + repository's permission practice, adds the ones a file lacks and still keeps a differing value, printed beside the + render's. The receipt's `authorization_settings` records who asked. The cross-family review of the pull request asked + for this; the first rounds had classed the four as practice. +6. **F9's sentence about the instruction blocks.** It said every unit that names a tool the manifest does not install is + left out; the fourth round's repair said the filter goes by the names the map declares as not wired, which the + filter's own dropped list contradicts: the Promptfoo unit names no tool of any map entry (only the former default of + the manifest row `promptfoo`) and also names `skill-creator`, and it is left out of both blocks. F9 now says: a unit + is left out when it names a tool that is not wired, which is a name the map lists for an unwired piece or the former + default of a manifest row that installs nothing; a unit is not left out merely for naming a skill or timer that + neither lists, and whether those skills exist on the host is not established by this step. +7. **F9 says what the line `--apply` prints about the authorization settings says.** It names every outcome of the line, + in the words the tool prints: a line before the summary that starts `authorization settings:` and says `left to the + clients' own defaults` when the option was not given and, when it was, `applied`, `partly applied`, `kept` or `not + applied` (`would be applied` or `would be partly applied` in a dry run), followed by what it added, kept, found + already the same and did not reach, a skipped step and a failed step told apart. +8. **Four places that carried the old text.** F7's first sentence gave `--configure-full-profile`'s refusal of a checkout + that is not `origin/main` as the reason to clone `main`; it now says the plan and the tool are on `main`. The + placeholder table loses ``, which only the bootstrap's `--profile` took. F11's opening no longer says stage 2 copies + the carrier blocks that name jCodeMunch tools. This record's command table, the receipt example's F8 and F9 rows and the + checklist's F9 line carry the new commands, and the two comparisons in `tests/test_wsl_new_distro_recipe.py` read F9 + again, with a test that the template's collector port equals the plan's. + +Alternatives, each rejected against that evidence: + +- **Keep `--profile --configure-full-profile`.** It installs and wires tools the manifest does not install; the + client-configuration record lists them. +- **Sign in first, then configure.** The first session would start without the guard hooks. (The plan leaves a Codex + `config.toml` whichever order is run, so that file does not decide the order; the merge handles it.) +- **Keep the template's ports and add 24318 and 26333 to the fallback rule.** The page would keep a port that fails on the + workstation the page is written for, and the collector port would differ from the plan's. + +Overturned when the plan and the tool run on the destination distribution and a step fails for a reason this section +did not name (a client that needs a sign-in before `--apply`, or a listener on `21318` that is not the plan's collector), +or when the manifest installs a tool whose pieces the map leaves out. + +Not established: any run of `install.sh`, `accept.sh` or `--apply` on the destination distribution; the after-sign-in +checks of `agent-runtime-worker` and `research-harnesses` run upstream model examples (plan README, stage descriptions), and whether the two +native sign-ins are enough for them was not read from their sources and not run. diff --git a/evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/cloud-init.user-data.template.txt b/evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/cloud-init.user-data.template.txt new file mode 100644 index 000000000..cf5ac78e7 --- /dev/null +++ b/evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/cloud-init.user-data.template.txt @@ -0,0 +1,30 @@ +#cloud-config +# User-data for Ubuntu 26.04.1 default or 24.04.5 rollback WSL image: adoption/platforms/linux-wsl2-new-distro.md, step W3, renders this +# file to %USERPROFILE%\.cloud-init\.user-data, where is the --name of the install, before the instance +# first boots. The cloud-init WSL datasource reads that file at the first boot; the image's /usr/lib/wsl/wsl-setup then +# waits for cloud-init, reuses this user (the first one with uid 1000 or more), keeps the [user] default written below +# and disables cloud-init for later boots. +# +# Keys follow Canonical's "Automatic setup of Ubuntu on WSL with cloud-init" example and the cloud-init WSL datasource +# "Default user" example. The groups are the ones the image's wsl-setup gives a user it creates interactively. uid 1000 +# matches defaultUid in the image's /etc/wsl-distribution.conf. The password stays locked; sudo needs none. +# P2 checks the workstation schema; W5 must validate the selected image's own schema. Full new-host acceptance is owed. +users: +- name: ${WSL_USER} + uid: 1000 + groups: [adm, cdrom, sudo, dip, plugdev] + sudo: "ALL=(ALL) NOPASSWD:ALL" + shell: /bin/bash + lock_passwd: true + +write_files: +- path: /etc/wsl.conf + append: true + content: | + [user] + default=${WSL_USER} + +# /dev/tty1 is shared across distributions in the WSL VM. Mask its getty in cloud-init's network stage, +# before systemd starts the unit. The source review, ordering and proof are in the page's W5. +bootcmd: +- [systemctl, mask, --now, getty@tty1.service] diff --git a/evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/first-boot-checklist.md.txt b/evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/first-boot-checklist.md.txt new file mode 100644 index 000000000..cf32faeb7 --- /dev/null +++ b/evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/first-boot-checklist.md.txt @@ -0,0 +1,74 @@ +# Stage-1 and first-boot checklist + +Copy this list into the private run log, not into the checkout, and tick a step only when its proof in +[the recipe](../../platforms/linux-wsl2-new-distro.md) holds. The recipe gives every command; this list repeats the five +baseline and paired observations so each has its own record. A step that shows anything else stops the run. W6 runs only after the W5 marker. +The ticked list and transcript feed the synthetic [receipt example](stage1-receipt.example.json). Tick a separate copy +for each image arm's throwaway rehearsal, through F3 and the R1 comparisons, then for the real run, which skips R1. +Ubuntu 26.04.1 LTS (Canonical WSL image) is the single default for the clean install. Ubuntu 24.04.5 is the named +rollback, used only on a release-caused 26.04 failure with no in-release remedy. The 26.04.1 rehearsal stopped at F1 +on WSL 2.7.13. Run 2 on WSL 3.0.1.0 (2026-10-02) passed stage 1 and W5's path-A proofs and observed distinct cgroup +namespaces, then stopped at the paired rule on the console collision. Probe E1 tested the getty mask outside the +recipe; a complete run with it through F3 and full acceptance remain owed. + +| Release | Selected image | Exact SHA-256 | +| --- | --- | --- | +| 26.04.1 default | `ubuntu-26.04.1-wsl-amd64.wsl` | `48d56724b5c8e60f24893e83e73bbb58c60b3ca22fba3da977075420acd54104` | +| 24.04.5 rollback | `ubuntu-24.04.5-wsl-amd64.wsl` | `bb415d824822c4b878125729af451a5d18fb13d1cf5cbed9a7393ad64ac6039e` | + +## Rehearsals (both image arms, before the real run) + +- [ ] **R1** Each image arm ran on its own throwaway name and its own folder through F3, with P3's baseline and W5's second storage count, W5's `cloud-init schema --system`, W7's owner and F2's idle observation recorded in the receipt's `rehearsal` block; then that name alone was terminated and unregistered (a failed rehearsal exported first, with exit 0 and its SHA-256 and size recorded); after each unregister, the surviving distribution's `WSLInterop` registration exists and a Windows executable launches, and when either check fails R1's records (`ls /proc/sys/fs/binfmt_misc` and `systemctl status systemd-binfmt.service --no-pager`) are kept and the run stops for review, importing or provisioning nothing; the last `--list --verbose` no longer lists it and the starred line is unchanged. A name already removed by W5 is not removed again. Both arms have the preregistered criteria with the amendments of 2026-10-02 made before any comparison ran ([decision record](../../../docs/decisions/2026-10-01-new-wsl-distro-recipe.md)): first boot with F1's pass condition, schema, systemd user from the second instance, WSL GPU visibility, uv-managed CPython 3.13 and Node 24.21.0, recorded in `comparison_arms`; failures and skips stay explicit, and F3's rootless check must pass before acceptance. The real install uses the single default unless the release-caused rollback condition holds. + +## Workstation distribution (sh), before stage 1 + +- [ ] **P1** `gpgv` exits 0 with `Good signature` by `843938DF228D22F7B3742BC0D94AA3F0EFE21092`, with no key import; both 26.04.1 and 24.04.5 signed `SHA256SUMS` lines carry the exact release hashes above. +- [ ] **P2** `cloud-init schema -c` prints `Valid schema` for the render of `` and exits 0; the workstation cloud-init version and render's SHA-256 are recorded for the selected image. A host schema check is not image qualification; W5 must record and validate the selected packaged version on path A. +- [ ] **P3** The `hv_storvsc` count without the registration line or the kernel's command-line echo is recorded as the baseline; the newest error line, by its kernel time against `/proc/uptime`, is at least one hour old or absent (younger: errors are current, the run stops; microsoft/WSL#41482), unless its kernel time is a line an earlier run of the page recorded as its own attachment-window line, which is noted and does not stop the run; `swapon --show` is recorded. + +## Windows host (PowerShell 7) + +- [ ] **PowerShell 7 prerequisite** The MSI installation provides `C:\Program Files\PowerShell\7\pwsh.exe`, visible from the workstation distribution at `/mnt/c/Program Files/PowerShell/7/pwsh.exe`; before any Windows-side step, the recipe's [version gate](../../platforms/linux-wsl2-new-distro.md#powershell-version-gate-before-w1) runs from bash in the workstation distribution and its private log records `$PSVersionTable.PSVersion` as 7.6.6 or a later stable 7.6 patch, with exit 0. A missing executable takes `winget install --id Microsoft.PowerShell --source winget --installer-type wix --version 7.6.6`; an older version takes `winget upgrade --id Microsoft.PowerShell --source winget --installer-type wix --version 7.6.6`, or the recipe's 7.6.6 MSI fallback when WinGet is unavailable. Rerun the gate and retain its new version log before resuming; do not fall back to Windows PowerShell 5.1. + +- [ ] **W1** `Z:\WSL\downloads` exists and the transcript is open; `wsl.exe --version` records WSL 3.0.1 or later before installing or importing a second systemd distribution (PR #40519 and PR #41512); an older version stops before W4 or W6; the workstation's baseline is recorded from the already-running distribution without `sudo`, with the five commands below each on its own line and with its own receipt entry and exit. Its uid equals the new default uid `1000`; its system state is `running` with no failed unit, or `degraded` with its failed set contained in {`systemd-binfmt.service`, `getty@tty1.service`}; its user manager is `active`; its namespace is not `cgroup:[4026531835]` (`PROC_CGROUP_INIT_INO`, the initial namespace). Anything else stops before W4 and before any W6 import. A failed getty is allowed only as an earlier console collision's leftover: `systemctl show getty@tty1.service -p Result -p NRestarts` is then recorded as optional `getty_tty1_result` and must show `Result=start-limit-hit`, or the run stops. The default (starred) distribution is recorded; `` is unregistered; `Z:\WSL\` does not exist; no Landscape user-data exists for ``; no `agent.yaml` exists, or its recorded top-level keys include neither `users` nor `write_files`; the `.wslconfig` read shows `instanceIdleTimeout` and `vmIdleTimeout` or their absence, recorded as `idle_keys`; free space on `Z:` is recorded. The older 2.4.10 install-only minimum applies to a single systemd distribution with no second systemd distribution running. + +```sh +id -u +systemctl is-system-running +systemctl --failed --no-legend --plain +systemctl is-active "user@$(id -u).service" +readlink /proc/self/ns/cgroup +``` +- [ ] **W2** Explicit `` is one of the two controlled arms; download URL, filename and catalog entry match that release. The actual whole-image, signed Canonical publication and pinned DistributionInfo.json SHA-256 all equal that release's exact hash above; actual file size recorded (run 2 observed 418,495,746 bytes for 26.04.1); no `throw`. +- [ ] **W3** `%USERPROFILE%\.cloud-init\.user-data` exists, starts with `#cloud-config`, names `` twice and contains no `${`; its SHA-256 equals P2's. +- [ ] **W4** The selected `ubuntu--wsl-amd64.wsl` installs with exit 0; `Distribution successfully installed`; `` is `Stopped` at version 2; the starred line is unchanged. +- [ ] **W5** First launch exit 0, without the prompt marker; `wsl: Failed to start the systemd user session` stops even with exit 0; `id -un` is `` and `id -u` is `1000`; getty `is-enabled` prints `masked` (normal exit 1), and `show` prints `LoadState=masked`, `ActiveState=inactive`, `NRestarts=0`, recorded as `getty_mask`; selected-image `cloud-init --version` recorded; `cloud-init status --long` prints `status: disabled` with `boot_status_code: disabled-by-marker-file` (with the template's `bootcmd` it exits 2 on one expected recoverable error, `Failed to wait for network`, because WSL masks `systemd-networkd-wait-online.service`; any other recoverable error is recorded and reviewed); `/var/lib/cloud/data/result.json` names `DataSourceWSL` with `"errors": []`; `/var/lib/cloud/data/status.json` shows the four stages finished without errors; `cloud-init schema --system` prints `Valid schema user-data` and exits 0; `/etc/wsl.conf` names the user once; `/etc/cloud/cloud-init.disabled` exists; sudo is NOPASSWD; the starred line is unchanged. After the first launch, while both distributions run and before F1, the already-running distribution reads a system unit active in both: its `cgroup.procs` has local process ids and no `0` entry, and the new distribution's `user@.service` is active. An empty or unreadable file is inconclusive and stops. The paired block below records each of the five commands on each side in its own receipt entry with its own exit, in `paired_isolation`: the same uid, both user managers `active`, the new distribution `running` with no failed unit or `degraded` with `systemd-binfmt.service` as its only failed unit (message proved at F1), two `cgroup:[...]` values that differ from each other and are not `cgroup:[4026531835]`, and all five workstation values equal to its W1 baseline. A getty failed in the new distribution or newly failed in the workstation fails the proof. Any failed W5 proof terminates only the new distribution, never stops or restarts a unit in either, exports `-w5-failed.tar` with the exit guard and its cause (`cgroup`, `tty` or a short text) in `w5_failure_export`, unregisters and checks surviving `WSLInterop` and a Windows executable, with R1's records and stop for review. Do not take path B for any such failure. On both paths, record P3's baseline before the import and `second_count` after the first launch. An increase confined to the window in which the new disk is attached, before cloud-init starts, is recorded with the new lines and does not stop the run. Any storage error after that window, or any provisioning step that fails with a storage cause, stops the run. An unknown attachment window stops for review; storage failures retain microsoft/WSL#41482 and their own cause. + +```sh +id -u +systemctl is-system-running +systemctl --failed --no-legend --plain +systemctl is-active "user@$(id -u).service" +readlink /proc/self/ns/cgroup +/mnt/c/Windows/System32/wsl.exe -d '' --exec id -u +/mnt/c/Windows/System32/wsl.exe -d '' --exec systemctl is-system-running +/mnt/c/Windows/System32/wsl.exe -d '' --exec systemctl --failed --no-legend --plain +/mnt/c/Windows/System32/wsl.exe -d '' --exec sh -c 'systemctl is-active "user@$(id -u).service"' +/mnt/c/Windows/System32/wsl.exe -d '' --exec readlink /proc/self/ns/cgroup +``` +- [ ] **W6** Only after the W5 OOBE marker, never for another failed W5 proof: re-listed before `--unregister`; before `--unregister`, `` terminated and exported to `Z:\WSL\downloads\-failed.tar` with exit 0, and its SHA-256 and size recorded in `failed_attempt_export`; immediately after unregister, the surviving distribution's `WSLInterop` exists and a Windows executable launches before any import, and when either check fails R1's records are kept and the run stops for review with no import; W1's version precondition holds; the same selected image imports with exit 0; `cloud-init status` recorded; the root block starts with `systemctl mask --now getty@tty1.service`, after the import's first boot has already started the getty; `visudo` `parsed OK`; after `--terminate`, `id -un` is ``; W5's cgroup proof, paired record and checks pass before W7 or F1, or a workstation value differing from its baseline stops and uses W5's recovery, with `result.json` and `status.json` checked only if cloud-init ran on the import (otherwise their absence is recorded) and `cloud-init schema --system` skipped; `creation_path` is `B`. +- [ ] **W7** Both paths: `` terminated once and absent from the running list, then relaunched with `id -un` printing ``; the file created from Windows under the user's home reads `1000:1000` and is deleted; `0:0` is recorded and stops every write from Windows into the distribution. + +## Inside the new distribution (as ``) + +- [ ] **F1** `systemctl is-system-running --wait` prints `running` with no failed unit, or `degraded` when `systemctl --failed --no-legend --plain` lists exactly `systemd-binfmt.service` and its log, from `journalctl -b 0 -t systemd-binfmt --no-pager -n 4`, holds `Failed to flush binfmt_misc rules, ignoring: Read-only file system` (microsoft/WSL#40621 and #41226); any other failed unit stops for review. `getty@tty1.service` must not appear there. +- [ ] **F2** `Linger` is `yes`; with no client attached, `` is in every list of the two minutes of polls, or the time it left is recorded beside W1's idle keys. +- [ ] **F3** `/run/user/` is a directory and its `bus` a socket, both owned by ``; the user manager is `running`. Right afterwards, as that user without sudo, Docker's `rootless` context reports `name=rootless` and `docker --context rootless run --rm hello-world` exits 0 with `Hello from Docker!`; a failed start or missing rootless mode stops with microsoft/WSL#41492. This check is owed on the first run after the WSL update; if the engine is absent, record `owed`, complete F4, F5 and Docker's supported rootless setup, then return here before F9 or accepting R1. +- [ ] **F4** apt exits 0; `jq`, `libatomic1` and `uidmap` print versions. +- [ ] **F5** `/etc/subuid` and `/etc/subgid` hold 65,536 ids for ``; whether `useradd` or `usermod` wrote them is recorded. +- [ ] **F6** `~/.bash_profile` is exactly the hand-off line. +- [ ] **F7** The clone's `HEAD` equals `origin` `main`; the commit is recorded. +- [ ] **F8** `ss` shows no listener on the four ports; the host file parses with the nine keys and is git-ignored. +- [ ] **F9** Stage 2 runs from this clone, before either sign-in: the install plan's `install.sh` and `accept.sh` exit 0, the client-configuration tool's `--check` ends with `check passed`, and its `--apply` ends with a `summary:` line in which no step is `failed` (a step that says `merged with conflicts kept` lists the values it left as the file has them). `--apply` is the plain line unless the host's owner asked for the repository's permission practice: then `--with-authorization-settings` is added and the receipt's `authorization_settings` names who asked. Then `codex login` and `claude` by hand, then `accept.sh --only --stage after_sign_in` for each owner whose plan row has that stage. +- [ ] **F10** The fragment profiles carry `` in their names; `type -P claude codex` prints two absolute paths, and the per-path `[[ -f ... && -x ... ]]` line prints `executable:` for both (no double quote inside a command that PowerShell hands to `wsl.exe`). +- [ ] **F11** In a login shell, after F9 and F10: when `test -x` finds `jcodemunch-mcp`, `claude mcp add` run from the clone's root prints `Added ...` and `claude mcp get jcodemunch` names the local scope and the command; when it does not, `not installed` is recorded, and a step not run is recorded as `skipped` with the reason. `jcodemunch_registration` holds the outcome. diff --git a/evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/stage1-receipt.example.json.txt b/evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/stage1-receipt.example.json.txt new file mode 100644 index 000000000..94a42e8b5 --- /dev/null +++ b/evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/stage1-receipt.example.json.txt @@ -0,0 +1,1404 @@ +{ + "schema_version": 1, + "artifact_class": "synthetic_fixture", + "id": "wsl-new-distro-stage1--", + "kind": "native_cli_e2e", + "component_ids": [ + "systemd" + ], + "claim": " (uid 1000) as the default user, W7 read 1000:1000, W5's paired record showed the same uid, distinct namespaces and two active user managers, and F1 met F1's pass condition, F2 printed Linger yes and a user-owned bus socket.>", + "limitations": [ + "One run on one Windows host: a new distribution on the existing WSL kernel and Windows installation, not a new physical machine.", + "Stage 1 and first boot only: stage 2 (the bootstrap), native sign-in, services and the Windows Terminal PATH proof are not covered.", + "Sanitized: Windows and Linux user names, home and profile paths and the checkout path are replaced by placeholders; the raw transcript stays private on the host.", + "jcodemunch_registration records F11's state only: a registration is not evidence that a session or subagent uses jCodeMunch.", + "The rehearsal block records a separate run on a throwaway name, removed by R1; its observations sit beside this run's, not in place of them.", + "Synthetic fixture only: placeholders and unrun comparison arms are not upstream tests, new-host acceptance or fresh model/GPU runs." + ], + "recorded_at_utc": "", + "recipe": { + "path": "adoption/platforms/linux-wsl2-new-distro.md", + "catalog_revision": "", + "creation_path": "" + }, + "supported_images": { + "26.04.1": { + "file": "ubuntu-26.04.1-wsl-amd64.wsl", + "url": "https://releases.ubuntu.com/26.04.1/ubuntu-26.04.1-wsl-amd64.wsl", + "sha256": "48d56724b5c8e60f24893e83e73bbb58c60b3ca22fba3da977075420acd54104", + "catalog_name": "Ubuntu-26.04", + "bytes": 418495746 + }, + "24.04.5": { + "file": "ubuntu-24.04.5-wsl-amd64.wsl", + "url": "https://releases.ubuntu.com/24.04.5/ubuntu-24.04.5-wsl-amd64.wsl", + "sha256": "bb415d824822c4b878125729af451a5d18fb13d1cf5cbed9a7393ad64ac6039e", + "catalog_name": "Ubuntu-24.04", + "bytes": 388975696 + } + }, + "rehearsal": { + "name": " or the workstation's>", + "result": "", + "creation_path": "", + "schema_system": "", + "ownership_probe": "", + "idle_observation": "", + "storage_readings": "", + "export": { + "file": "-rehearsal.tar; null when it passed>", + "sha256": "", + "bytes": "" + } + }, + "pre_checks": { + "signed_sums": "", + "user_data_schema": "" + }, + "storage_errors": { + "baseline": "", + "newest_line_time": "", + "swap": "", + "second_count": "", + "new_lines": "" + }, + "host": { + "wsl_version": [ + "" + ], + "default_distribution_before": "", + "default_distribution_after": "", + "z_free_bytes": "", + "idle_keys": "", + "workstation_baseline": { + "uid": "", + "system_state": "", + "failed_units": "", + "user_manager": ".service; active>", + "cgroup_namespace": "", + "getty_tty1_result": "" + } + }, + "image": { + "release": "", + "file": "ubuntu--wsl-amd64.wsl", + "url": "https://releases.ubuntu.com//ubuntu--wsl-amd64.wsl", + "bytes": "", + "sha256_published": "", + "sha256_distributioninfo": "", + "sha256_computed": "" + }, + "first_launch": { + "user_prompt_marker": "", + "oobe_failed_marker": "", + "schema_system": "", + "cloud_init_version": "", + "cgroup_isolation": ".service; the paired five-value record is paired_isolation; any failed W5 proof uses the guarded w5_failure_export/removal>", + "systemd_user_session_warning": "", + "getty_mask": { + "is_enabled": "", + "load_state": "", + "active_state": "", + "n_restarts": "" + } + }, + "paired_isolation": { + "recorded_with": "", + "workstation": { + "uid": "", + "system_state": "", + "failed_units": "", + "user_manager": ".service; active; equal to host.workstation_baseline>", + "cgroup_namespace": "" + }, + "new_distribution": { + "uid": " through wsl.exe; equal to the workstation's>", + "system_state": "", + "failed_units": ", or none; getty@tty1.service must not be failed>", + "user_manager": ".service inside ; active>", + "cgroup_namespace": "; differs from the workstation's and is not cgroup:[4026531835]>" + } + }, + "ownership_probe": "", + "idle_observation": " was in every list, or the time of the first list without it>", + "subordinate_ids": "", + "jcodemunch_registration": "", + "authorization_settings": "", + "failed_attempt_export": { + "file": "-failed.tar; null on path A>", + "sha256": "", + "bytes": "" + }, + "steps": [ + { + "step": "R1", + "cmd": "wsl.exe -d '' --exec bash -lc 'stat -c %U,%F /run/user/$(id -u) /run/user/$(id -u)/bus'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "wsl.exe -d '' --exec systemctl --user is-system-running --wait", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "wsl.exe -d '' --exec test -c /dev/dxg", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "wsl.exe -d '' --exec /usr/lib/wsl/lib/nvidia-smi", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "wsl.exe -d '' --exec bash -lc 'python3 --version'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "wsl.exe -d '' --exec bash -lc 'uv run --no-project --python 3.13 python --version'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "wsl.exe -d '' --exec bash -lc 'node --version'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "$env:WSL_UTF8 = '1'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "wsl.exe --list --quiet", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "wsl.exe --terminate ''", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "wsl.exe --unregister ''", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "wsl.exe -d '' --exec sh -c 'test -e /proc/sys/fs/binfmt_misc/WSLInterop && /mnt/c/Windows/System32/cmd.exe /d /c ver'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "if ($LASTEXITCODE -ne 0) { throw 'interop failed: recover in the surviving distribution before continuing' }", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "wsl.exe --list --verbose", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "$env:WSL_UTF8 = '1'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "wsl.exe --list --quiet", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "wsl.exe --terminate ''", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "wsl.exe --export '' 'Z:\\WSL\\downloads\\-rehearsal.tar'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "$LASTEXITCODE", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "if ($LASTEXITCODE -ne 0) { throw 'export failed: do not unregister' }", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "(Get-FileHash -Algorithm SHA256 -LiteralPath 'Z:\\WSL\\downloads\\-rehearsal.tar').Hash.ToLowerInvariant()", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "(Get-Item -LiteralPath 'Z:\\WSL\\downloads\\-rehearsal.tar').Length", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "wsl.exe --unregister ''", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "wsl.exe -d '' --exec sh -c 'test -e /proc/sys/fs/binfmt_misc/WSLInterop && /mnt/c/Windows/System32/cmd.exe /d /c ver'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "if ($LASTEXITCODE -ne 0) { throw 'interop failed: recover in the surviving distribution before continuing' }", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "wsl.exe --list --verbose", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "test -e /proc/sys/fs/binfmt_misc/WSLInterop && /mnt/c/Windows/System32/cmd.exe /d /c ver", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "ls /proc/sys/fs/binfmt_misc", + "exit": "", + "output_excerpt": "" + }, + { + "step": "R1", + "cmd": "systemctl status systemd-binfmt.service --no-pager", + "exit": "", + "output_excerpt": "" + }, + { + "step": "P1", + "cmd": "for RELEASE in 26.04.1 24.04.5; do", + "exit": "", + "output_excerpt": "" + }, + { + "step": "P1", + "cmd": "SUMS_DIR=\"$(mktemp -d)\"", + "exit": "", + "output_excerpt": "" + }, + { + "step": "P1", + "cmd": "curl -fsSL -o \"$SUMS_DIR/SHA256SUMS\" \"https://releases.ubuntu.com/$RELEASE/SHA256SUMS\" || exit 1", + "exit": "", + "output_excerpt": "" + }, + { + "step": "P1", + "cmd": "curl -fsSL -o \"$SUMS_DIR/SHA256SUMS.gpg\" \"https://releases.ubuntu.com/$RELEASE/SHA256SUMS.gpg\" || exit 1", + "exit": "", + "output_excerpt": "" + }, + { + "step": "P1", + "cmd": "gpgv --homedir \"$SUMS_DIR\" --keyring /usr/share/keyrings/ubuntu-archive-keyring.gpg \"$SUMS_DIR/SHA256SUMS.gpg\" \"$SUMS_DIR/SHA256SUMS\"", + "exit": 0, + "output_excerpt": "" + }, + { + "step": "P1", + "cmd": "if [ \"$?\" -ne 0 ]; then exit 1; fi", + "exit": "", + "output_excerpt": "" + }, + { + "step": "P1", + "cmd": "grep -F \" *ubuntu-$RELEASE-wsl-amd64.wsl\" \"$SUMS_DIR/SHA256SUMS\" || exit 1", + "exit": "", + "output_excerpt": "" + }, + { + "step": "P1", + "cmd": "done", + "exit": "", + "output_excerpt": "" + }, + { + "step": "P2", + "cmd": "RENDER_DIR=\"$(mktemp -d)\"", + "exit": "", + "output_excerpt": "" + }, + { + "step": "P2", + "cmd": "python3 -c 'import string, sys; sys.stdout.write(string.Template(open(sys.argv[1], encoding=\"utf-8\").read()).substitute(WSL_USER=sys.argv[2]))' adoption/templates/wsl/cloud-init.user-data.template '' > \"$RENDER_DIR/.user-data\"", + "exit": "", + "output_excerpt": "" + }, + { + "step": "P2", + "cmd": "cloud-init --version", + "exit": "", + "output_excerpt": "" + }, + { + "step": "P2", + "cmd": "cloud-init schema -c \"$RENDER_DIR/.user-data\"", + "exit": 0, + "output_excerpt": "" + }, + { + "step": "P2", + "cmd": "sha256sum \"$RENDER_DIR/.user-data\"", + "exit": "", + "output_excerpt": "" + }, + { + "step": "P3", + "cmd": "sudo journalctl -k -b 0 --no-pager | grep hv_storvsc | grep -Evc 'registering driver hv_storvsc|[Cc]ommand line:'", + "exit": "<0, or 1 when the count is 0>", + "output_excerpt": "" + }, + { + "step": "P3", + "cmd": "sudo journalctl -k -b 0 --no-pager -o short-monotonic --no-hostname | grep hv_storvsc | grep -Ev 'registering driver hv_storvsc|[Cc]ommand line:' | tail -n 1", + "exit": "", + "output_excerpt": "" + }, + { + "step": "P3", + "cmd": "cat /proc/uptime", + "exit": "", + "output_excerpt": "" + }, + { + "step": "P3", + "cmd": "swapon --show", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W1", + "cmd": "New-Item -ItemType Directory -Force -Path 'Z:\\WSL\\downloads'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W1", + "cmd": "$env:WSL_UTF8 = '1'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W1", + "cmd": "wsl.exe --version", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W1", + "cmd": "wsl.exe --list --verbose", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W1", + "cmd": "wsl.exe --list --quiet", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W1", + "cmd": "Test-Path -LiteralPath 'Z:\\WSL\\'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W1", + "cmd": "Test-Path -LiteralPath (Join-Path $env:USERPROFILE '.ubuntupro\\.cloud-init\\.user-data')", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W1", + "cmd": "Test-Path -LiteralPath (Join-Path $env:USERPROFILE '.ubuntupro\\.cloud-init\\agent.yaml')", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W1", + "cmd": "Select-String -LiteralPath (Join-Path $env:USERPROFILE '.ubuntupro\\.cloud-init\\agent.yaml') -Pattern '^[A-Za-z_][A-Za-z0-9_-]*:' -ErrorAction SilentlyContinue", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W1", + "cmd": "Select-String -LiteralPath (Join-Path $env:USERPROFILE '.wslconfig') -Pattern '^\\s*\\[', '^\\s*instanceIdleTimeout\\s*=', '^\\s*vmIdleTimeout\\s*=' -ErrorAction SilentlyContinue", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W1", + "cmd": "Get-PSDrive -Name Z | Select-Object -Property Name, Used, Free", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W1", + "cmd": "id -u", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W1", + "cmd": "systemctl is-system-running", + "exit": "<0 for running; 1 for degraded, expected and recorded>", + "output_excerpt": "" + }, + { + "step": "W1", + "cmd": "systemctl --failed --no-legend --plain", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W1", + "cmd": "systemctl is-active \"user@$(id -u).service\"", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W1", + "cmd": "readlink /proc/self/ns/cgroup", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W1", + "cmd": "systemctl show getty@tty1.service -p Result -p NRestarts", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W2", + "cmd": "$ProgressPreference = 'SilentlyContinue'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W2", + "cmd": "$Release = ''", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W2", + "cmd": "switch ($Release) {", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W2", + "cmd": "'26.04.1' { $Expected = '48d56724b5c8e60f24893e83e73bbb58c60b3ca22fba3da977075420acd54104'; $CatalogName = 'Ubuntu-26.04' }", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W2", + "cmd": "'24.04.5' { $Expected = 'bb415d824822c4b878125729af451a5d18fb13d1cf5cbed9a7393ad64ac6039e'; $CatalogName = 'Ubuntu-24.04' }", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W2", + "cmd": "default { throw 'unsupported release: do not download or install' }", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W2", + "cmd": "}", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W2", + "cmd": "$Image = \"ubuntu-$Release-wsl-amd64.wsl\"", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W2", + "cmd": "$ImagePath = \"Z:\\WSL\\downloads\\$Image\"", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W2", + "cmd": "$ImageUrl = \"https://releases.ubuntu.com/$Release/$Image\"", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W2", + "cmd": "$SumsPath = \"Z:\\WSL\\downloads\\$Release-SHA256SUMS\"", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W2", + "cmd": "Invoke-WebRequest -UseBasicParsing -Uri $ImageUrl -OutFile $ImagePath", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W2", + "cmd": "Invoke-WebRequest -UseBasicParsing -Uri \"https://releases.ubuntu.com/$Release/SHA256SUMS\" -OutFile $SumsPath", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W2", + "cmd": "$Published = (Select-String -LiteralPath $SumsPath -Pattern (' \\*' + [regex]::Escape($Image) + '$')).Line.Split(' ')[0]", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W2", + "cmd": "$Listed = ((Invoke-WebRequest -UseBasicParsing -Uri 'https://raw.githubusercontent.com/microsoft/WSL/8bc98bc33b246fe66710eec9eaa1b24c323da987/distributions/DistributionInfo.json').Content | ConvertFrom-Json).ModernDistributions.Ubuntu | Where-Object Name -eq $CatalogName", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W2", + "cmd": "$Actual = (Get-FileHash -Algorithm SHA256 -LiteralPath $ImagePath).Hash.ToLowerInvariant()", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W2", + "cmd": "\"computed $Actual published $Published listed $($Listed.Amd64Url.Sha256) url $($Listed.Amd64Url.Url)\"", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W2", + "cmd": "if ($Actual -ne $Expected -or $Published -ne $Expected -or $Listed.Amd64Url.Sha256 -ne $Expected -or $Listed.Amd64Url.Url -ne $ImageUrl) { throw 'sha256 mismatch: do not install' }", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W2", + "cmd": "(Get-Item -LiteralPath $ImagePath).Length", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W3", + "cmd": "New-Item -ItemType Directory -Force -Path (Join-Path $env:USERPROFILE '.cloud-init')", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W3", + "cmd": "$Text = [System.IO.File]::ReadAllText('\\adoption\\templates\\wsl\\cloud-init.user-data.template').Replace('${WSL_USER}', '')", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W3", + "cmd": "[System.IO.File]::WriteAllText((Join-Path $env:USERPROFILE '.cloud-init\\.user-data'), $Text)", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W3", + "cmd": "Get-Content -LiteralPath (Join-Path $env:USERPROFILE '.cloud-init\\.user-data') -TotalCount 1", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W3", + "cmd": "Select-String -LiteralPath (Join-Path $env:USERPROFILE '.cloud-init\\.user-data') -Pattern '^- name: ', '^ default='", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W3", + "cmd": "(Get-FileHash -Algorithm SHA256 -LiteralPath (Join-Path $env:USERPROFILE '.cloud-init\\.user-data')).Hash.ToLowerInvariant()", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W3", + "cmd": "Select-String -LiteralPath (Join-Path $env:USERPROFILE '.cloud-init\\.user-data') -SimpleMatch -Pattern '${'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W4", + "cmd": "$env:WSL_UTF8 = '1'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W4", + "cmd": "wsl.exe --install --from-file 'Z:\\WSL\\downloads\\ubuntu--wsl-amd64.wsl' --name '' --location 'Z:\\WSL\\' --no-launch", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W4", + "cmd": "$LASTEXITCODE", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W4", + "cmd": "wsl.exe --list --verbose", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "$env:WSL_UTF8 = '1'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "cmd.exe /d /c \"wsl.exe -d < NUL\"", + "exit": 0, + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "$LASTEXITCODE", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "wsl.exe -d '' --exec id -un", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "wsl.exe -d '' --exec id -u", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "wsl.exe -d '' --exec systemctl is-enabled getty@tty1.service", + "exit": "<1 for masked, this command's normal exit>", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "wsl.exe -d '' --exec systemctl show getty@tty1.service -p LoadState -p ActiveState -p NRestarts", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "wsl.exe -d '' -u root --exec cloud-init --version", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "wsl.exe -d '' -u root --exec cloud-init status --long", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "wsl.exe -d '' -u root --exec cat /var/lib/cloud/data/result.json", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "wsl.exe -d '' -u root --exec cat /var/lib/cloud/data/status.json", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "wsl.exe -d '' -u root --exec cloud-init schema --system", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "wsl.exe -d '' -u root --exec cat /etc/wsl.conf", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "wsl.exe -d '' -u root --exec ls -l /etc/cloud/cloud-init.disabled", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "wsl.exe -d '' -u root --exec sudo -l -U ''", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "wsl.exe --list --verbose", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "systemctl is-active ''", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "/mnt/c/Windows/System32/wsl.exe -d '' --exec systemctl is-active ''", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "cat '/sys/fs/cgroup/system.slice//cgroup.procs'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "/mnt/c/Windows/System32/wsl.exe -d '' --exec sh -c 'systemctl is-active \"user@$(id -u).service\"'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "id -u", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "systemctl is-system-running", + "exit": "<0 for running; 1 for degraded, expected and recorded>", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "systemctl --failed --no-legend --plain", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "systemctl is-active \"user@$(id -u).service\"", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "readlink /proc/self/ns/cgroup", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "/mnt/c/Windows/System32/wsl.exe -d '' --exec id -u", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "/mnt/c/Windows/System32/wsl.exe -d '' --exec systemctl is-system-running", + "exit": "<0 for running; 1 for degraded, expected and recorded>", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "/mnt/c/Windows/System32/wsl.exe -d '' --exec systemctl --failed --no-legend --plain", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "/mnt/c/Windows/System32/wsl.exe -d '' --exec sh -c 'systemctl is-active \"user@$(id -u).service\"'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "/mnt/c/Windows/System32/wsl.exe -d '' --exec readlink /proc/self/ns/cgroup", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "$env:WSL_UTF8 = '1'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "wsl.exe --list --quiet", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "wsl.exe --terminate ''", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "wsl.exe --export '' 'Z:\\WSL\\downloads\\-w5-failed.tar'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "$LASTEXITCODE", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "if ($LASTEXITCODE -ne 0) { throw 'export failed: do not unregister' }", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "(Get-FileHash -Algorithm SHA256 -LiteralPath 'Z:\\WSL\\downloads\\-w5-failed.tar').Hash.ToLowerInvariant()", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "(Get-Item -LiteralPath 'Z:\\WSL\\downloads\\-w5-failed.tar').Length", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "wsl.exe --unregister ''", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "wsl.exe -d '' --exec sh -c 'test -e /proc/sys/fs/binfmt_misc/WSLInterop && /mnt/c/Windows/System32/cmd.exe /d /c ver'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "if ($LASTEXITCODE -ne 0) { throw 'interop failed: recover in the surviving distribution before continuing' }", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "wsl.exe --list --verbose", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "sudo journalctl -k -b 0 --no-pager | grep hv_storvsc | grep -Evc 'registering driver hv_storvsc|[Cc]ommand line:'", + "exit": "<0, or 1 when the count is 0>", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "$env:WSL_UTF8 = '1'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "wsl.exe --list --quiet", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "wsl.exe --terminate ''", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "wsl.exe --export '' 'Z:\\WSL\\downloads\\-failed.tar'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "$LASTEXITCODE", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "if ($LASTEXITCODE -ne 0) { throw 'export failed: do not unregister' }", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "(Get-FileHash -Algorithm SHA256 -LiteralPath 'Z:\\WSL\\downloads\\-failed.tar').Hash.ToLowerInvariant()", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "(Get-Item -LiteralPath 'Z:\\WSL\\downloads\\-failed.tar').Length", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "wsl.exe --unregister ''", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "wsl.exe -d '' --exec sh -c 'test -e /proc/sys/fs/binfmt_misc/WSLInterop && /mnt/c/Windows/System32/cmd.exe /d /c ver'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "if ($LASTEXITCODE -ne 0) { throw 'interop failed: recover in the surviving distribution before continuing' }", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "wsl.exe --import '' 'Z:\\WSL\\' 'Z:\\WSL\\downloads\\ubuntu--wsl-amd64.wsl' --version 2", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "wsl.exe -d '' -u root --exec cloud-init status --wait --long", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "systemctl mask --now getty@tty1.service", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "id -u '' || useradd --create-home --uid 1000 --groups adm,cdrom,sudo,dip,plugdev --shell /bin/bash ''", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "printf '%s ALL=(ALL) NOPASSWD:ALL\\n' '' > /etc/sudoers.d/90-wsl-default-user", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "chmod 0440 /etc/sudoers.d/90-wsl-default-user", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "visudo -cf /etc/sudoers.d/90-wsl-default-user", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "grep -q '^\\[user\\]' /etc/wsl.conf || printf '\\n[user]\\ndefault=%s\\n' '' >> /etc/wsl.conf", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "touch /etc/cloud/cloud-init.disabled", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "wsl.exe --terminate ''", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "wsl.exe --list --running", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W6", + "cmd": "wsl.exe -d '' --exec id -un", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W7", + "cmd": "$env:WSL_UTF8 = '1'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W7", + "cmd": "wsl.exe --terminate ''", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W7", + "cmd": "wsl.exe --list --running --quiet", + "exit": "", + "output_excerpt": " absent>" + }, + { + "step": "W7", + "cmd": "wsl.exe -d '' --exec id -un", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W7", + "cmd": "New-Item -ItemType File -Path '\\\\wsl.localhost\\\\home\\\\wsl-owner-probe'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W7", + "cmd": "wsl.exe -d '' --exec stat -c %u:%g '/home//wsl-owner-probe'", + "exit": 0, + "output_excerpt": "1000:1000" + }, + { + "step": "W7", + "cmd": "Remove-Item -LiteralPath '\\\\wsl.localhost\\\\home\\\\wsl-owner-probe'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F1", + "cmd": "systemctl is-system-running --wait", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F1", + "cmd": "systemctl --failed --no-legend --plain", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F1", + "cmd": "journalctl -b 0 -t systemd-binfmt --no-pager -n 4", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F1", + "cmd": "systemctl list-unit-files --type=service --no-pager", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F1", + "cmd": "sudo journalctl -b 0 -t systemd-binfmt --no-pager -n 4", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F2", + "cmd": "sudo loginctl enable-linger \"$(id -un)\"", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F2", + "cmd": "loginctl show-user \"$(id -un)\" --property=Linger --value", + "exit": 0, + "output_excerpt": "yes" + }, + { + "step": "F2", + "cmd": "$env:WSL_UTF8 = '1'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F2", + "cmd": "foreach ($Poll in 1..12) { Start-Sleep -Seconds 10; [DateTime]::UtcNow.ToString('HH:mm:ss'); wsl.exe --list --running --quiet }", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F3", + "cmd": "stat -c '%U %F' \"/run/user/$(id -u)\" \"/run/user/$(id -u)/bus\"", + "exit": 0, + "output_excerpt": " directory\n socket" + }, + { + "step": "F3", + "cmd": "systemctl --user is-system-running --wait", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F3", + "cmd": "docker --context rootless info --format '{{json .SecurityOptions}}'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F3", + "cmd": "docker --context rootless run --rm hello-world", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F4", + "cmd": "sudo apt-get update", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F4", + "cmd": "sudo apt-get install -y --no-install-recommends ca-certificates curl git tar gzip xz-utils jq libatomic1 uidmap", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F4", + "cmd": "dpkg-query -W -f='${Package} ${Version}\\n' jq libatomic1 uidmap", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F5", + "cmd": "grep \"^$(id -un):\" /etc/subuid /etc/subgid", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F5", + "cmd": "grep -q \"^$(id -un):\" /etc/subuid || sudo usermod --add-subuids 100000-165535 --add-subgids 100000-165535 \"$(id -un)\"", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F5", + "cmd": "grep \"^$(id -un):\" /etc/subuid /etc/subgid", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F6", + "cmd": "test -e ~/.bash_profile || printf '%s\\n' 'if [ -r \"$HOME/.profile\" ]; then . \"$HOME/.profile\"; fi' > ~/.bash_profile", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F6", + "cmd": "cat ~/.bash_profile", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F7", + "cmd": "mkdir -p ~/code", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F7", + "cmd": "git clone https://github.com/seathatflowsinourveins/native-agent-stack.git ~/code/native-agent-stack", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F7", + "cmd": "git -C ~/code/native-agent-stack rev-parse HEAD", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F7", + "cmd": "git -C ~/code/native-agent-stack ls-remote origin refs/heads/main", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F8", + "cmd": "cd ~/code/native-agent-stack", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F8", + "cmd": "ss -ltnH '( sport = :21318 or sport = :29374 or sport = :21633 or sport = :28231 )'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F8", + "cmd": "python3 -c 'import string, sys; sys.stdout.write(string.Template(open(sys.argv[1], encoding=\"utf-8\").read()).substitute(WSL_USER=sys.argv[2]))' adoption/templates/wsl/host.new-distro.json.template \"$(id -un)\" > 'adoption/hosts/.json'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F8", + "cmd": "python3 -m json.tool 'adoption/hosts/.json'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F8", + "cmd": "git check-ignore 'adoption/hosts/.json'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F9", + "cmd": "cd ~/code/native-agent-stack", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F9", + "cmd": "bash evidence/artifacts/new-wsl-install-plan-20261002/install.sh", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F9", + "cmd": "bash evidence/artifacts/new-wsl-install-plan-20261002/accept.sh", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F9", + "cmd": "python3 -B tools/adoption/new_wsl_client_config.py --check", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F9", + "cmd": "python3 -B tools/adoption/new_wsl_client_config.py --apply --host ''", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F10", + "cmd": "wsl.exe -d '' -u '' --exec /bin/bash -lc 'type -P claude codex'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F10", + "cmd": "wsl.exe -d '' -u '' --exec /bin/bash -lc 'for n in claude codex; do p=$(type -P $n); [[ -f $p && -x $p ]] && echo executable: $p; done'", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F11", + "cmd": "test -x \"${ECO_INSTALL_ROOT:-$HOME/.local/share/codex-ecosystem}/bin/jcodemunch-mcp\"", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F11", + "cmd": "cd ~/code/native-agent-stack", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F11", + "cmd": "claude mcp add --scope local jcodemunch -e \"CODE_INDEX_PATH=$HOME/.code-index\" -e JCODEMUNCH_SHARE_SAVINGS=0 -- \"${ECO_INSTALL_ROOT:-$HOME/.local/share/codex-ecosystem}/bin/jcodemunch-mcp\"", + "exit": "", + "output_excerpt": "" + }, + { + "step": "F11", + "cmd": "claude mcp get jcodemunch", + "exit": "", + "output_excerpt": "" + } + ], + "comparison_arms": { + "26.04.1": { + "status": "unrun", + "name": "", + "first_boot": "", + "systemd_user_from_second_instance": "", + "wsl_gpu": "", + "uv_cpython_3_13": "", + "node_24": "" + }, + "24.04.5": { + "status": "unrun", + "name": "", + "first_boot": "", + "systemd_user_from_second_instance": "", + "wsl_gpu": "", + "uv_cpython_3_13": "", + "node_24": "" + } + }, + "w5_failure_export": { + "file": "-w5-failed.tar; null otherwise>", + "sha256": "", + "bytes": "", + "cause": "" + }, + "interop_after_unregister": "", + "rootless_container": "" +} diff --git a/examples/claude-native/CLAUDE.md b/examples/claude-native/CLAUDE.md index 4128a034f..c9017bd81 100644 --- a/examples/claude-native/CLAUDE.md +++ b/examples/claude-native/CLAUDE.md @@ -21,6 +21,7 @@ Decide by evidence and research convergence: a choice stands when current primar - Resolve supported findings before claiming completion. Distinguish measured results, simulations and untested boundaries; unchanged upstream tests, local integration checks, synthetic fixtures and actual provider execution are different evidence classes. New machines collect their own evidence; historical receipts do not certify the new host. - Settle a discoverable harness capability by step 2 before asking the user; compare community alternatives against demonstrated gaps, retain source pins and reasons, and run the selected native path through returned results. Dated exclusions are not current availability evidence. Research only the relevant layers. - Carry the user's authorized work through implementation, relevant verification and a concise handoff. Use a short plan for bounded work; do not add intake, repeated approvals, diagnostic campaigns or restarts without a concrete need. Preserve existing edits, native accounts, model choices and project scope. +- When you tell the user a time, give it first in the host's local time zone (read it with `timedatectl` or `date`), with UTC beside it, for example "4:00 PM EDT (20:00Z)"; keep UTC in ledger rows, receipts, evidence and commits. - Use the project's canonical instructions for memory scope, tests and domain rules, and keep durable memory and indexes scoped to that project. ## Token practice (base layer) diff --git a/tests/test_codex_worker_lane.py b/tests/test_codex_worker_lane.py index 8f8081a40..7331776fe 100644 --- a/tests/test_codex_worker_lane.py +++ b/tests/test_codex_worker_lane.py @@ -47,9 +47,10 @@ TEMPLATES = ROOT / "adoption" / "templates" FIXTURES = ROOT / "tests" / "fixtures" / "codex-worker-lane" # The canonical routing move is recorded in 2026-10-05-harness-context-budget.md. +# The session-lanes local-time line is recorded in 2026-10-05-user-facing-local-time.md. # RTK's unchanged 0.51.0 awareness fixture is pinned to e001f773 and checked # byte for byte in the rendered block, with local exceptions kept separately. -TOP_RULE_SHA256 = "568ee365aeef3455fc901e648eb72d28cc3b49c39f1bfba7f5e39beed20479a8" +TOP_RULE_SHA256 = "82e23b68466ed8b96566a229582f0c99fa1456a393e635f18cc5e65f601f4d09" RTK_AWARENESS_SHA256 = "278274ef3d08c858d4247cc91419c4d74ef922b95719e987b22e896aef10e1fc" UPSTREAM_MARKER = '\n' @@ -434,8 +435,8 @@ def test_agents_template_is_one_managed_block(self): self.assertNotIn(lane.managed_block.RTK_INCLUDE, rendered) # Codex expands no @ reference (codex-rs/core/src/agents_md.rs at rust-v0.157.1): the text is inline. self.assertFalse([line for line in rendered.splitlines() if line.startswith("@")]) - # This local 8,192-byte check covers the compact source (7,307 bytes). - # The rendered Codex carrier is 8,373 bytes, counted by the startup budget. + # This local 8,192-byte check covers the compact source (7,535 bytes). + # The rendered Codex carrier is 8,601 bytes, counted by the startup budget. self.assertLess(len(text.encode("utf-8")), 8192) def test_top_rule_is_pinned_and_rendered_rtk_is_the_unchanged_pinned_source(self): diff --git a/tests/test_install_claude_profile.py b/tests/test_install_claude_profile.py index 2105c7fb7..f1df6fd9d 100644 --- a/tests/test_install_claude_profile.py +++ b/tests/test_install_claude_profile.py @@ -1781,6 +1781,20 @@ def test_the_three_surfaces_carry_the_same_standing_sentences(self): with self.subTest(surface=name, dropped=phrase): self.assertNotIn(phrase, text) + # A user-level reporting rule (docs/decisions/2026-10-05-user-facing-local-time.md): one wording in the two client + # blocks and in the carriers F9 renders from them, once each. It is not a standing clause of the repository + # AGENTS.md, so SHARED does not hold it. + LOCAL_TIME = ("When you tell the user a time, give it first in the host's local time zone (read it with " + "`timedatectl` or `date`), with UTC beside it, for example \"4:00 PM EDT (20:00Z)\"; keep UTC in " + "ledger rows, receipts, evidence and commits.") + LOCAL_TIME_SURFACES = ("examples/claude-native/CLAUDE.md", "adoption/templates/codex.AGENTS.template.md", + "adoption/new-wsl/claude-user-instructions.md", "adoption/new-wsl/codex-user-instructions.md") + + def test_the_user_level_blocks_and_their_carriers_carry_the_local_time_rule_once(self): + for relative in self.LOCAL_TIME_SURFACES: + with self.subTest(surface=relative): + self.assertEqual((ROOT / relative).read_text(encoding="utf-8").count(self.LOCAL_TIME), 1) + class PortableTopRuleTests(unittest.TestCase): """Portable procedure and fixed rendered startup bytes (2026-10-05). diff --git a/tests/test_wsl_new_distro_recipe.py b/tests/test_wsl_new_distro_recipe.py index 960e5ffa5..21c9416f9 100644 --- a/tests/test_wsl_new_distro_recipe.py +++ b/tests/test_wsl_new_distro_recipe.py @@ -7,7 +7,8 @@ ``${WSL_USER}`` placeholder, so the recipe's PowerShell ``.Replace`` writes the same bytes; the render starts with the ``#cloud-config`` header (no byte order mark, LF line ends) and creates the user the recipe describes: uid 1000, the groups the image's wsl-setup gives an interactive user, - passwordless sudo, a locked password and ``[user] default`` appended to /etc/wsl.conf; + passwordless sudo, a locked password and ``[user] default`` appended to /etc/wsl.conf, with + ``[time] useWindowsTimezone=true`` before it so the zone follows Windows (2026-10-05); - the host value template renders to JSON with exactly adoption/hosts/example.json's keys, every service on 127.0.0.1 at a port outside the workstation distribution's ports (WSL 2 distributions share one network namespace), and the recipe's ``ss`` probe checks exactly those ports; @@ -430,7 +431,8 @@ def user_data_errors(template: str, user: str = "example") -> list[str]: required = (r"^users:$", rf"^- name: {re.escape(user)}$", r"^ uid: 1000$", r"^ groups: \[adm, cdrom, sudo, dip, plugdev\]$", r'^ sudo: "ALL=\(ALL\) NOPASSWD:ALL"$', r"^ shell: /bin/bash$", r"^ lock_passwd: true$", r"^write_files:$", r"^- path: /etc/wsl\.conf$", - r"^ append: true$", r"^ content: \|$", r"^ \[user\]$", rf"^ default={re.escape(user)}$") + r"^ append: true$", r"^ content: \|$", r"^ \[time\]$", r"^ useWindowsTimezone=true$", + r"^ \[user\]$", rf"^ default={re.escape(user)}$") errors += [f"the render lacks a line matching {pattern}" for pattern in required if not re.search(pattern, rendered, re.M)] if template.count(GETTY_BOOTCMD) != 1: @@ -1289,6 +1291,8 @@ def test_the_check_rejects_a_bom_a_stray_dollar_a_password_and_a_lost_line(self) "sudo with a password": good.replace("NOPASSWD:ALL", "ALL"), "no default user": good.replace("default=", "default_user="), "header": good.replace("#cloud-config", "# cloud-config", 1), + "no time section": good.replace(" [time]\n", ""), + "zone not from Windows": good.replace("useWindowsTimezone=true", "useWindowsTimezone=false"), } for name, mutant in mutants.items(): with self.subTest(mutant=name): diff --git a/tools/adoption/host-baseline.md b/tools/adoption/host-baseline.md index db8de8f95..eb9711ff0 100644 --- a/tools/adoption/host-baseline.md +++ b/tools/adoption/host-baseline.md @@ -48,7 +48,7 @@ attestation. The passwd record retains field 7 only, and the sudoers marker retains existence, mode and size only. Its owner column is discarded. Passwordless sudo remains -`unknown`: `adoption/platforms/linux-wsl2-new-distro.md:624` is a recipe criterion, +`unknown`: `adoption/platforms/linux-wsl2-new-distro.md:627` is a recipe criterion, not a per-host acceptance record. No such acceptance is linked by these receipts. PR-1 must label root rows `status-unknown` until it has that separate evidence; neither an absent marker nor a denied stat proves those rows unreachable. diff --git a/tools/adoption/host_baseline_probe.py b/tools/adoption/host_baseline_probe.py index 815438a0d..c75824d35 100644 --- a/tools/adoption/host_baseline_probe.py +++ b/tools/adoption/host_baseline_probe.py @@ -338,7 +338,7 @@ def collect(home): "passwordless_sudo": { "marker": marker_observation(), "status": "unknown", - "other_source": "adoption/platforms/linux-wsl2-new-distro.md:624 (recipe criterion only; per-host acceptance not linked)", + "other_source": "adoption/platforms/linux-wsl2-new-distro.md:627 (recipe criterion only; per-host acceptance not linked)", }, "launchers": {name: binary_observation(name) for name in LAUNCHERS}, "launcher_packages": {name: package_observation(name) for name in ("time", "util-linux", "procps", "systemd", "gdb")}, diff --git a/tools/adoption/new_wsl_client_config.py b/tools/adoption/new_wsl_client_config.py index 60000c252..f2fa38f39 100644 --- a/tools/adoption/new_wsl_client_config.py +++ b/tools/adoption/new_wsl_client_config.py @@ -206,8 +206,8 @@ SENTENCE_BREAK = re.compile(r"(?<=[.!?])(\s+)(?=[A-Z`\[(<\"'*_])") SENTENCE_END = re.compile(r"[.!?][\"')\]`*_]*$") # Wrap width of the verbatim rtk-ai/rtk v0.51.0 hooks/rtk-awareness-full.md. -# The rendered Codex carrier is 8,373 bytes; the local 8,192-byte test covers only -# adoption/templates/codex.AGENTS.template.md's compact source (7,307 bytes). +# The rendered Codex carrier is 8,601 bytes; the local 8,192-byte test covers only +# adoption/templates/codex.AGENTS.template.md's compact source (7,535 bytes). # A run of lines this short with a sentence running into the next line is one # wrapped paragraph; longer lines are one statement each. WRAP_WIDTH = 80 From 2a76c419438ff64ad05e071142d3f113e6ab14df Mon Sep 17 00:00:00 2001 From: seathatflowsinourveins <234074349+seathatflowsinourveins@users.noreply.github.com> Date: Mon, 5 Oct 2026 20:20:34 -0400 Subject: [PATCH 02/10] Review fixes for #758: the user's rule stays theirs, UTC records with Z, what W5 detects, a section-aware [time] check, the W5 zone pair The command center's review of 8dfc91e4 (changes requested), applied on the rebased tree: - The record no longer calls the user's own dated local-time rule on NativeStack2604 interim, and nothing plans to remove it. A re-render keeps every byte outside the managed markers (managed_block.py:106-113), so the rule loads twice there until the user decides. - Decision 3 now reads: "Ledger rows, receipts, evidence and commits keep UTC, written as RFC 3339 date-times with `Z`." Whether git's own author and committer dates count as commits is recorded as open for the user. - The useWindowsTimezone rationale says what holds. The key restates WSL's documented default (MicrosoftDocs/WSL 7ea1c6f9; microsoft/WSL 91f161fa, tag 3.0.1, WslDistributionConfig.h:58) and records the intent. W5 fails at the first boot on another value or a missing [time] section. A later change is not detected. - The same correction is made in the 2026-10-01 amendment, the W5 proof, the user-data comment and the test docstring. - The W6 row's proof says what its guard shows. - W5 pairs `timedatectl show -p Timezone --value` with `tzutil /g`. - The pair has command-table rows, receipt-example entries, the checklist line and TimeZonePairTests (seven mutants). - Its proof cites WSL's mapping (helpers.cpp:358-413, timezone.cpp:22-110) and CLDR windowsZones. - The user-data check reads the appended /etc/wsl.conf text by section. - A key moved under [user] now fails; the new mutant keeps every line. - [user] before [time] also fails. - Smaller fixes: - the Learn citations are pinned and dated; - the two flagged overturn conditions go to the user; - the sentence is described as a display rule plus a records rule, with a records trigger; - the WSL condition also watches the source default. - Measurements and records: - startup bytes were re-measured in code at base ecfa1127 and are unchanged; - the Checks table reports post-rebase results; - the passwordless-sudo criterion moved to recipe line 636, and its two live citations follow. The regenerated handbook, its receipt and the registry are in the next commit (hot-file protocol, docs/lanes.md). Co-Authored-By: Claude Opus 5.5 --- adoption/platforms/linux-wsl2-new-distro.md | 15 ++- .../wsl/cloud-init.user-data.template | 5 +- .../templates/wsl/first-boot-checklist.md | 2 +- .../templates/wsl/stage1-receipt.example.json | 12 ++ .../2026-10-01-new-wsl-distro-recipe.md | 25 ++-- .../2026-10-05-user-facing-local-time.md | 123 +++++++++++++----- tests/test_wsl_new_distro_recipe.py | 117 ++++++++++++++++- tools/adoption/host-baseline.md | 2 +- tools/adoption/host_baseline_probe.py | 2 +- 9 files changed, 252 insertions(+), 51 deletions(-) diff --git a/adoption/platforms/linux-wsl2-new-distro.md b/adoption/platforms/linux-wsl2-new-distro.md index d7f435ead..7add8a97a 100644 --- a/adoption/platforms/linux-wsl2-new-distro.md +++ b/adoption/platforms/linux-wsl2-new-distro.md @@ -560,6 +560,8 @@ wsl.exe -d '' -u root --exec cat /var/lib/cloud/data/result.json wsl.exe -d '' -u root --exec cat /var/lib/cloud/data/status.json wsl.exe -d '' -u root --exec cloud-init schema --system wsl.exe -d '' -u root --exec cat /etc/wsl.conf +wsl.exe -d '' --exec timedatectl show -p Timezone --value +tzutil /g wsl.exe -d '' -u root --exec ls -l /etc/cloud/cloud-init.disabled wsl.exe -d '' -u root --exec sudo -l -U '' wsl.exe --list --verbose @@ -621,9 +623,16 @@ Proof (path A, cloud-init provisioned the instance): `:1286-1293`). The 26.04.1 rehearsal returned `Valid schema user-data` after the marker on both host releases; each run must repeat that observation. Record the output (`schema_system`). - `/etc/wsl.conf` holds `[boot]`, `systemd=true`, `[time]`, `useWindowsTimezone=true`, `[user]` and - `default=`, each once. The explicit `[time]` key keeps the distribution on the time zone set in Windows - ([Microsoft wsl-config, "Time settings"](https://learn.microsoft.com/en-us/windows/wsl/wsl-config#time-settings): - `useWindowsTimezone`, default `true`); W6 writes the same key on path B. + `default=`, each once. The `[time]` key restates WSL's default and records the intent + ([Microsoft wsl-config, "Time settings"](https://learn.microsoft.com/en-us/windows/wsl/wsl-config#time-settings), read + 2026-10-05: MicrosoftDocs/WSL `7ea1c6f9`, `WSL/wsl-config.md:152-158`, `useWindowsTimezone`, default `true`); W6 + writes the same key on path B. This read-back proves the file's text at this boot only; nothing reads it later. +- `timedatectl show -p Timezone --value` prints the IANA zone that CLDR's `windowsZones` mapping gives for the Windows + zone that `tzutil /g` prints ([tzutil](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/tzutil) + `/g`, read 2026-10-05) and the Windows region: the first zone of the region's row, else the `001` row, for example + `America/New_York` for `Eastern Standard Time` in the US. WSL maps the zone with the ICU that Windows ships and, while + `useWindowsTimezone` is true, links `/etc/localtime` to it at each instance start (microsoft/WSL `91f161fa`, tag + 3.0.1: `src/windows/common/helpers.cpp:358-413`, `src/linux/init/timezone.cpp:22-110`). Record both outputs. - The marker file exists, and `sudo -l` lists `(ALL) NOPASSWD: ALL`. - The starred line of `--list --verbose` is still W1's (`default_distribution_after`). diff --git a/adoption/templates/wsl/cloud-init.user-data.template b/adoption/templates/wsl/cloud-init.user-data.template index ff67b0c4c..c7c7caa0f 100644 --- a/adoption/templates/wsl/cloud-init.user-data.template +++ b/adoption/templates/wsl/cloud-init.user-data.template @@ -8,8 +8,9 @@ # Keys follow Canonical's "Automatic setup of Ubuntu on WSL with cloud-init" example and the cloud-init WSL datasource # "Default user" example. The groups are the ones the image's wsl-setup gives a user it creates interactively. uid 1000 # matches defaultUid in the image's /etc/wsl-distribution.conf. The password stays locked; sudo needs none. -# [time] useWindowsTimezone=true makes WSL use and sync to the time zone set in Windows (Microsoft wsl-config, "Time -# settings"; default true). It is written explicitly so W5 reads it back: docs/decisions/2026-10-05-user-facing-local-time.md. +# [time] useWindowsTimezone=true restates WSL's default (Microsoft wsl-config, "Time settings", read 2026-10-05: +# MicrosoftDocs/WSL@7ea1c6f9 WSL/wsl-config.md:152-158; default true). It records the intent, and W5 reads it back: +# docs/decisions/2026-10-05-user-facing-local-time.md. # P2 checks the workstation schema; W5 must validate the selected image's own schema. Full new-host acceptance is owed. users: - name: ${WSL_USER} diff --git a/adoption/templates/wsl/first-boot-checklist.md b/adoption/templates/wsl/first-boot-checklist.md index 1760abae1..255abbce6 100644 --- a/adoption/templates/wsl/first-boot-checklist.md +++ b/adoption/templates/wsl/first-boot-checklist.md @@ -42,7 +42,7 @@ readlink /proc/self/ns/cgroup - [ ] **W2** Explicit `` is one of the two controlled arms; download URL, filename and catalog entry match that release. The actual whole-image, signed Canonical publication and pinned DistributionInfo.json SHA-256 all equal that release's exact hash above; actual file size recorded (run 2 observed 418,495,746 bytes for 26.04.1); no `throw`. - [ ] **W3** `%USERPROFILE%\.cloud-init\.user-data` exists, starts with `#cloud-config`, names `` twice and contains no `${`; its SHA-256 equals P2's. - [ ] **W4** The selected `ubuntu--wsl-amd64.wsl` installs with exit 0; `Distribution successfully installed`; `` is `Stopped` at version 2; the starred line is unchanged. -- [ ] **W5** First launch exit 0, without the prompt marker; `wsl: Failed to start the systemd user session` stops even with exit 0; `id -un` is `` and `id -u` is `1000`; getty `is-enabled` prints `masked` (normal exit 1), and `show` prints `LoadState=masked`, `ActiveState=inactive`, `NRestarts=0`, recorded as `getty_mask`; selected-image `cloud-init --version` recorded; `cloud-init status --long` prints `status: disabled` with `boot_status_code: disabled-by-marker-file` (with the template's `bootcmd` it exits 2 on one expected recoverable error, `Failed to wait for network`, because WSL masks `systemd-networkd-wait-online.service`; any other recoverable error is recorded and reviewed); `/var/lib/cloud/data/result.json` names `DataSourceWSL` with `"errors": []`; `/var/lib/cloud/data/status.json` shows the four stages finished without errors; `cloud-init schema --system` prints `Valid schema user-data` and exits 0; `/etc/wsl.conf` names the user once and sets `useWindowsTimezone=true` under `[time]`; `/etc/cloud/cloud-init.disabled` exists; sudo is NOPASSWD; the starred line is unchanged. After the first launch, while both distributions run and before F1, the already-running distribution reads a system unit active in both: its `cgroup.procs` has local process ids and no `0` entry, and the new distribution's `user@.service` is active. An empty or unreadable file is inconclusive and stops. The paired block below records each of the five commands on each side in its own receipt entry with its own exit, in `paired_isolation`: the same uid, both user managers `active`, the new distribution `running` with no failed unit or `degraded` with `systemd-binfmt.service` as its only failed unit (message proved at F1), two `cgroup:[...]` values that differ from each other and are not `cgroup:[4026531835]`, and all five workstation values equal to its W1 baseline. A getty failed in the new distribution or newly failed in the workstation fails the proof. Any failed W5 proof terminates only the new distribution, never stops or restarts a unit in either, exports `-w5-failed.tar` with the exit guard and its cause (`cgroup`, `tty` or a short text) in `w5_failure_export`, unregisters and checks surviving `WSLInterop` and a Windows executable, with R1's records and stop for review. Do not take path B for any such failure. On both paths, record P3's baseline before the import and `second_count` after the first launch. An increase confined to the window in which the new disk is attached, before cloud-init starts, is recorded with the new lines and does not stop the run. Any storage error after that window, or any provisioning step that fails with a storage cause, stops the run. An unknown attachment window stops for review; storage failures retain microsoft/WSL#41482 and their own cause. +- [ ] **W5** First launch exit 0, without the prompt marker; `wsl: Failed to start the systemd user session` stops even with exit 0; `id -un` is `` and `id -u` is `1000`; getty `is-enabled` prints `masked` (normal exit 1), and `show` prints `LoadState=masked`, `ActiveState=inactive`, `NRestarts=0`, recorded as `getty_mask`; selected-image `cloud-init --version` recorded; `cloud-init status --long` prints `status: disabled` with `boot_status_code: disabled-by-marker-file` (with the template's `bootcmd` it exits 2 on one expected recoverable error, `Failed to wait for network`, because WSL masks `systemd-networkd-wait-online.service`; any other recoverable error is recorded and reviewed); `/var/lib/cloud/data/result.json` names `DataSourceWSL` with `"errors": []`; `/var/lib/cloud/data/status.json` shows the four stages finished without errors; `cloud-init schema --system` prints `Valid schema user-data` and exits 0; `/etc/wsl.conf` names the user once and sets `useWindowsTimezone=true` under `[time]`; `timedatectl show -p Timezone --value` prints the IANA zone that CLDR's `windowsZones` mapping gives for the zone `tzutil /g` prints and the Windows region, both recorded; `/etc/cloud/cloud-init.disabled` exists; sudo is NOPASSWD; the starred line is unchanged. After the first launch, while both distributions run and before F1, the already-running distribution reads a system unit active in both: its `cgroup.procs` has local process ids and no `0` entry, and the new distribution's `user@.service` is active. An empty or unreadable file is inconclusive and stops. The paired block below records each of the five commands on each side in its own receipt entry with its own exit, in `paired_isolation`: the same uid, both user managers `active`, the new distribution `running` with no failed unit or `degraded` with `systemd-binfmt.service` as its only failed unit (message proved at F1), two `cgroup:[...]` values that differ from each other and are not `cgroup:[4026531835]`, and all five workstation values equal to its W1 baseline. A getty failed in the new distribution or newly failed in the workstation fails the proof. Any failed W5 proof terminates only the new distribution, never stops or restarts a unit in either, exports `-w5-failed.tar` with the exit guard and its cause (`cgroup`, `tty` or a short text) in `w5_failure_export`, unregisters and checks surviving `WSLInterop` and a Windows executable, with R1's records and stop for review. Do not take path B for any such failure. On both paths, record P3's baseline before the import and `second_count` after the first launch. An increase confined to the window in which the new disk is attached, before cloud-init starts, is recorded with the new lines and does not stop the run. Any storage error after that window, or any provisioning step that fails with a storage cause, stops the run. An unknown attachment window stops for review; storage failures retain microsoft/WSL#41482 and their own cause. ```sh id -u diff --git a/adoption/templates/wsl/stage1-receipt.example.json b/adoption/templates/wsl/stage1-receipt.example.json index b7486f3d3..e5b6afbb2 100644 --- a/adoption/templates/wsl/stage1-receipt.example.json +++ b/adoption/templates/wsl/stage1-receipt.example.json @@ -766,6 +766,18 @@ "exit": "", "output_excerpt": "" }, + { + "step": "W5", + "cmd": "wsl.exe -d '' --exec timedatectl show -p Timezone --value", + "exit": "", + "output_excerpt": "" + }, + { + "step": "W5", + "cmd": "tzutil /g", + "exit": "", + "output_excerpt": "" + }, { "step": "W5", "cmd": "wsl.exe -d '' -u root --exec ls -l /etc/cloud/cloud-init.disabled", diff --git a/docs/decisions/2026-10-01-new-wsl-distro-recipe.md b/docs/decisions/2026-10-01-new-wsl-distro-recipe.md index e95623dcd..f12424ad9 100644 --- a/docs/decisions/2026-10-01-new-wsl-distro-recipe.md +++ b/docs/decisions/2026-10-01-new-wsl-distro-recipe.md @@ -701,6 +701,8 @@ workstation distribution (Evidence classes). | W5 | powershell | `wsl.exe -d '' -u root --exec cat /var/lib/cloud/data/status.json` | each of the four stages `finished` with empty `errors`; `recoverable_errors` recorded | | W5 | powershell | `wsl.exe -d '' -u root --exec cloud-init schema --system` | path A: a line matching `^\s*Valid schema user-data$` and exit 0; skipped on path B | | W5 | powershell | `wsl.exe -d '' -u root --exec cat /etc/wsl.conf` | `[boot]`, `systemd=true`, `[time]`, `useWindowsTimezone=true`, `[user]`, `default=`, each once | +| W5 | powershell | `wsl.exe -d '' --exec timedatectl show -p Timezone --value` | the IANA zone that CLDR's `windowsZones` mapping gives for `tzutil /g`'s zone and the Windows region, for example `America/New_York`; recorded | +| W5 | powershell | `tzutil /g` | the Windows time zone ID, for example `Eastern Standard Time`; recorded | | W5 | powershell | `wsl.exe -d '' -u root --exec ls -l /etc/cloud/cloud-init.disabled` | the marker exists | | W5 | powershell | `wsl.exe -d '' -u root --exec sudo -l -U ''` | `(ALL) NOPASSWD: ALL` | | W5 | powershell | `wsl.exe --list --verbose` | the starred line equals W1's | @@ -750,7 +752,7 @@ workstation distribution (Evidence classes). | W6 | sh | `chmod 0440 /etc/sudoers.d/90-wsl-default-user` | mode 0440 | | W6 | sh | `visudo -cf /etc/sudoers.d/90-wsl-default-user` | `parsed OK` | | W6 | sh | `grep -q '^\[user\]' /etc/wsl.conf \|\| printf '\n[user]\ndefault=%s\n' '' >> /etc/wsl.conf` | one `[user]` section naming the user | -| W6 | sh | `grep -q '^\[time\]' /etc/wsl.conf \|\| printf '\n[time]\nuseWindowsTimezone=true\n' >> /etc/wsl.conf` | one `[time]` section with `useWindowsTimezone=true` | +| W6 | sh | `grep -q '^\[time\]' /etc/wsl.conf \|\| printf '\n[time]\nuseWindowsTimezone=true\n' >> /etc/wsl.conf` | a `[time]` section exists (the guard reads only the header); W5's repeated read-back confirms `useWindowsTimezone=true` | | W6 | sh | `touch /etc/cloud/cloud-init.disabled` | the marker exists | | W6 | powershell | `wsl.exe --terminate ''` | exit 0 | | W6 | powershell | `wsl.exe --list --running` | `` absent | @@ -1429,13 +1431,18 @@ native sign-ins are enough for them was not read from their sources and not run. ## Amendment 2026-10-05 (explicit `[time]` in `/etc/wsl.conf`) The user-data now writes `[time]` with `useWindowsTimezone=true` before `[user]`, and W6 appends the same key on path -B, so both creation paths make WSL use and sync to the time zone set in Windows. Microsoft documents the key as a -boolean that defaults to `true` (MicrosoftDocs/WSL `7ea1c6f9`, `WSL/wsl-config.md:152-158`, the pin in Sources). The -26.04.1 image's `wsl-setup` 0.6.3 (`73418e32`, `wsl-setup:55-73`) only appends or completes `[user]`, so the section -survives the first run. Writing the key makes W5 read it back. The coordinator measured on 2026-10-05 that -NativeStack2604's `/etc/wsl.conf` held `useWindowsTimezone=false` with no repository record, and set it to `true` on -that host. Only the content listings change here (path A and path B under Decision, the W5 proof and the new W6 row); -"What the image contains" is a historical read and stays. This is a source review and local integration: no first -launch or import ran for it. The experiment's frozen inputs that this amendment changed keep their recorded hashes +B when `/etc/wsl.conf` has no `[time]` section. The key restates WSL's default: Microsoft documents it as a boolean that +defaults to `true` (MicrosoftDocs/WSL `7ea1c6f9`, `WSL/wsl-config.md:152-158`, the pin in Sources), and the WSL 3.0.1 +source holds the same default (microsoft/WSL `91f161fa`, `src/linux/init/WslDistributionConfig.h:58`). Writing it +leaves WSL's behavior unchanged and records the intent. W5 reads it back, so the first boot fails W5 when it finds +another value or no `[time]` section; path B repeats W5's checks once after W6. Nothing reads `/etc/wsl.conf` after +provisioning, so a later change of the key is not detected. W5 also pairs `timedatectl show -p Timezone --value` with +`tzutil /g`, so the receipt holds the zone the distribution reports beside the Windows zone, not only the file's text. +The 26.04.1 image's `wsl-setup` 0.6.3 (`73418e32`, `wsl-setup:55-73`) only appends or completes `[user]`, so the +section survives the first run. The coordinator measured on 2026-10-05 that NativeStack2604's `/etc/wsl.conf` held +`useWindowsTimezone=false` with no repository record, and set it to `true` on that host. Only the content listings +change here (path A and path B under Decision, the W5 commands and proof, the new W5 and W6 rows); "What the image +contains" is a historical read and stays. This is a source review and local integration: no first launch or import +ran for it. The experiment's frozen inputs that this amendment changed keep their recorded hashes under `evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/`. Decision: [`2026-10-05-user-facing-local-time.md`](2026-10-05-user-facing-local-time.md). diff --git a/docs/decisions/2026-10-05-user-facing-local-time.md b/docs/decisions/2026-10-05-user-facing-local-time.md index 142acb673..a2e2903fb 100644 --- a/docs/decisions/2026-10-05-user-facing-local-time.md +++ b/docs/decisions/2026-10-05-user-facing-local-time.md @@ -17,7 +17,7 @@ UTC timeline across hosts and clients. - Windows is on "Eastern Standard Time"; - all three WSL distributions resolve `/etc/localtime` to `America/New_York`. - Agents relayed record times to the user as they were written, in UTC. No managed instruction told them to convert. -- On NativeStack2604, each client's user-level file holds an interim local-time line dated 2026-10-05, after the +- On NativeStack2604, each client's user-level file holds the user's own local-time rule, dated 2026-10-05, after the managed block's end marker (read 2026-10-05). This change leaves host files alone. - NativeStack2604's `/etc/wsl.conf` held `[time] useWindowsTimezone=false` with no repository record, and the coordinator set it to `true` on that host. The distribution recipe wrote no `[time]` key, so a new host relied on the default and @@ -41,36 +41,49 @@ UTC timeline across hosts and clients. - **A user-level rule, not a standing clause.** The repository `AGENTS.md` and the scaffold do not repeat it. The [three-surface record](2026-09-30-rule-text-every-layer.md) requires all three surfaces only for the standing clauses that `StandingRuleSurfacesTests.SHARED` lists; its one-wording rule still holds here, on both client - templates and both carriers, and a new test in that class checks it. This sentence is a personal display - preference: + templates and both carriers, and a new test in that class checks it. The sentence is a display rule (how an agent + words a time it tells the user) plus a records rule (UTC in ledger rows, receipts, evidence and commits). The user + keeps both at user level, the level both clients document for a person's standing working agreements: - Claude's memory documentation lists `~/.claude/CLAUDE.md` as "Personal preferences for all projects". - The Codex guide puts persistent defaults in the Codex home "so every repository inherits your working agreements". - Both clients load their user-level file in every project, this repository included, so a root copy would load twice here and count twice against each fixed startup ceiling. -2. **The WSL zone follows Windows, explicitly.** + - The records rule also governs this repository's artifacts, which a session without the managed block can write; + the overturn conditions below watch both halves. +2. **The recipe writes WSL's time-zone default explicitly.** - **Path A.** The cloud-init user-data appends `[time]` with `useWindowsTimezone=true` to `/etc/wsl.conf`, before `[user]`. - - **Path B.** W6 appends the same key. + - **Path B.** W6 appends the same key when `/etc/wsl.conf` has no `[time]` section. - **Read-back.** W5 reads the key back, and the first-boot checklist and the recipe record's command table follow. - **The source.** Microsoft documents the key as a boolean that defaults to `true`: "Setting this key will make WSL use - and sync to the timezone set in Windows." - - **Why write it.** Writing the key leaves the default's behavior unchanged. It makes the value part of the recipe and - its proof, so a later `false` shows up as a failed W5 check instead of the silent drift found on NativeStack2604. + and sync to the timezone set in Windows." The WSL 3.0.1 source holds the same default (Sources). + - **What writing it does.** The key restates WSL's default, so the distribution's behavior does not change. It records + the intent in the recipe, and W5 fails when the first boot finds a value other than `true` or no `[time]` section. + W5 runs once, at the first boot; path B repeats its checks once, after W6. Nothing in the repository reads + `/etc/wsl.conf` after provisioning, so a later change of the key, like the `false` found on NativeStack2604, is not + detected. Detecting it would need a recurring read in a host probe. + - **The zone itself.** The read-back proves the file's text, not the zone. W5 therefore also pairs the zone the + distribution reports, `timedatectl show -p Timezone --value`, with the Windows zone, `tzutil /g`. They agree when + the IANA zone is CLDR's `windowsZones` mapping of the Windows zone for the Windows region: WSL maps the zone with + the ICU that Windows ships and, while the key is `true`, links `/etc/localtime` to it at each instance start + (Sources). Both outputs go to the receipt. - **First run.** The 26.04.1 image's `wsl-setup` 0.6.3 only appends or completes `[user]`, so the section survives its first run. - - **Claude Code's UI clock.** Claude Code's own UI times follow the system zone by default (below), so this key also - makes them local. -3. **Records stay UTC.** Ledger rows, receipts, evidence, commits and dated records keep UTC, written as RFC 3339 - date-times with `Z` or a numeric offset. + - **Claude Code's UI clock.** Claude Code's own UI times follow the system zone by default (below), so they show the + Windows zone while the distribution follows it. +3. **Records stay UTC.** Ledger rows, receipts, evidence and commits keep UTC, written as RFC 3339 date-times with `Z`. ## Alternatives considered - **A memory note.** The user rejected it. Memory is retrieved per project as evidence, not loaded as a standing instruction, so it would not reach every session or client. -- **Per-host private lines in each client's user-level file.** Kept as the interim only: +- **Per-host lines in each client's user-level file, outside the managed block,** as the harness mechanism. Not + chosen: - they cannot be reproduced on a new host and can drift from the managed source; - - after the hosts re-render with this rule, they duplicate it. + - NativeStack2604 holds such a line, the user's own dated rule. After that host re-renders with this rule, the rule + loads there twice, in the managed block and in the user's line, until the user decides (Evidence classes and + limits). - **The system zone alone** (`useWindowsTimezone=true`, `/etc/localtime`). It is needed so that `date`, `timedatectl` and the client UI clocks report the user's zone. It cannot change how an agent words a time: an agent relaying a UTC ledger row still writes UTC. It is the second half of this decision, not a substitute for the first. @@ -97,7 +110,8 @@ UTC timeline across hosts and clients. ## Startup bytes (the dated comparison the budget record requires) These are measured with `PortableTopRuleTests.startup_files`, which uses the renderer's committed carriers. Base is main -`e8c1edec`. +`ecfa1127`. After the rebase onto it, both columns were measured again in code (the before column from main's startup +files, extracted to a scratch directory), and every value equals the first measurement, made on main `e8c1edec`. | File or scope | Before | After | | --- | ---: | ---: | @@ -125,14 +139,20 @@ sentence is 227 bytes. The budget record has a dated addendum pointing here. value beside the old one. The size comments there and in `tools/adoption/new_wsl_client_config.py` now give 7,535 and 8,601 bytes. - **The cloud-init check.** `tests/test_wsl_new_distro_recipe.py` requires `[time]` and `useWindowsTimezone=true` in - the render. It has two new mutants that the check rejects, a lost section and `false`. -- **The new W6 command.** It has its row in the recipe record's command table and its entry in the receipt example. + the render, and reads the appended `/etc/wsl.conf` text by section: `[time]` holds only that key and comes before + `[user]`, which holds only the default user. Its new mutants are a lost section, `false`, the key moved under + `[user]` (every line still present, so only the section reading rejects it) and `[user]` before `[time]`. +- **The new W6 command.** It has its row in the recipe record's command table and its entry in the receipt example. The + row's proof says what the guard shows: a `[time]` section exists, and W5's repeated read-back confirms the value. +- **The W5 zone pair.** `timedatectl show -p Timezone --value` and `tzutil /g` have their rows in the command table, + their entries in the receipt example and their place in the checklist's W5 line. `TimeZonePairTests` checks them + with seven mutants. - **The client-configuration record.** It takes the tool's regenerated listing (59 and 70 lines) in a dated addendum. -- **The new-WSL handbook.** It is regenerated; it holds the recipe's SHA-256. Its receipt's `outputs` follow, with a - `regenerations` entry. +- **The new-WSL handbook.** It is regenerated on the rebased tree; it holds the recipe's SHA-256. Its receipt's + `outputs` follow, with this change's `regenerations` entry after the one #703 added on main. - **The registry.** `manifests/evidence.json` takes the new hashes of the 20 changed files it lists and the five new files under `evidence/artifacts/user-facing-local-time-20261005/`. -- **The passwordless-sudo criterion moved from recipe line 624 to 627.** The two live citations of it, +- **The passwordless-sudo criterion moved from recipe line 624 to 636.** The two live citations of it, `tools/adoption/host_baseline_probe.py` and `tools/adoption/host-baseline.md`, follow. Dated records keep their own line citations. - **Unchanged.** The startup constants (`STARTUP_BUDGET_BYTES`), the scaffold, the moves fixture of the Codex top-rule @@ -140,15 +160,20 @@ sentence is 227 bytes. The budget record has a dated addendum pointing here. ## Overturn condition -- **A native setting.** A client release adds a setting that localizes times the model writes. Adopt it and drop the - sentence. +- **A native setting.** A client release adds a setting that localizes times the model writes. Raise it to the user, + who decides whether it replaces the display half. The records half stays in force whatever display setting is + adopted, because no display setting keeps records in UTC. - **Sessions without the managed user block.** Cloud sessions, CI agents or a host not yet re-rendered are shown reporting times to the user in UTC only. Then add the sentence to the repository `AGENTS.md` and the scaffold, under the three-surface test, with a dated byte comparison. +- **A non-UTC record.** A ledger row, receipt or evidence file in this repository carries a timestamp that is not UTC, + written by a session without the managed block. Then add the records half to the repository `AGENTS.md`, under the + three-surface test. - **The user changes the format,** for example to local time only or a 24-hour clock. -- **A host's zone is not the user's,** for example a remote or cloud host on UTC. Reword "the host's local time zone" - to name the user's zone. -- **Microsoft changes `useWindowsTimezone`'s meaning or default.** +- **A host's zone is not the user's,** for example a remote or cloud host on UTC. Raise it to the user, who decides the + wording; a fixed zone in the portable templates would be wrong for other users (Alternatives). +- **WSL changes `useWindowsTimezone`'s meaning or default,** in Microsoft's documentation or in the WSL source + (`src/linux/init/WslDistributionConfig.h` at the installed release). - **A missed rule at its trigger.** After the hosts re-render, an agent shows the user a UTC-only time. Sharpen the sentence, as the budget record's reopening rule says. @@ -157,10 +182,17 @@ sentence is 227 bytes. The budget record has a dated addendum pointing here. - **Evidence classes.** This is source review and local integration: unit tests, the F9 check and render, cloud-init's own `schema -c` on the rendered user-data, and two synthetic `/etc/wsl.conf` simulations in a scratch directory. No first boot, import, host re-render or model run happened. No model run measured whether agents follow the sentence. +- **The zone pair has no W5 output yet.** No first boot ran for this change. A host read on NativeStack2604 on + 2026-10-05, not a W5 run, gave `America/New_York` from `timedatectl show -p Timezone --value` and + `Eastern Standard Time` from `tzutil /g`, both with exit 0; `/etc/localtime` links to the same zone. - **Host work.** The NativeStack2604 change to `useWindowsTimezone=true` was the coordinator's host action and is not - part of this commit. The host re-render that delivers the sentence comes after landing. A re-render replaces only - the text between the managed markers, so it keeps the interim lines; removing them changes the user's own - user-level files and is left to the user. + part of this change. The host re-render that delivers the sentence comes after landing. A re-render replaces only + the text between the managed markers and keeps every byte outside them (`tools/adoption/managed_block.py:106-113`), + so the user's own dated rule after the end markers stays, and the rule loads twice on NativeStack2604 until the user + decides. Whether to remove the user's copy is the user's call; this change does not touch it. +- **Open for the user.** Whether "commits" in the records half covers git's own author and committer dates, which these + hosts write with the local numeric offset (for example `-0400`), is the user's decision. This record does not + decide it. - **The experiment's frozen inputs.** The WSL image experiment (`blueprints/convergence-practice/wsl-new-distro-image-20261001/experiment.json`) froze four files that this change edits. Their recorded bytes moved to `evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/` with @@ -173,22 +205,30 @@ sentence is 227 bytes. The budget record has a dated addendum pointing here. ## Checks +The first three rows are the first round's fail-first checks, on main `e8c1edec`. Every other row was run after the +rebase, on the final tree at base `ecfa1127`, on NativeStack2604 (2026-10-05 and 2026-10-06 UTC); only this record's +prose changed afterwards. + | Command | Exit | Result | | --- | ---: | --- | | `python3 -m unittest tests.test_install_claude_profile.StandingRuleSurfacesTests`, before the template edits | 1 | 4 failures: `0 != 1` on each of the four surfaces (fail first) | | `python3 -m unittest tests.test_wsl_new_distro_recipe.UserDataTemplateTests`, before the user-data edit | 1 | 3 failures: the two new `[time]` patterns, and the two new mutants were not yet mutations (fail first) | | The same two checks, run in process against main `e8c1edec`'s four instruction files and user-data template | n/a | the same 4 and 3 failures (fail first, repeated) | -| `python3 -m unittest` (the full suite, with every code, test, template and registry change in place; only this record's prose changed afterwards) | 1 | 10,326 tests, 903 skipped, 11 failures and 18 errors, none in a test module this change edits. Every module this change edits, and every module that pins the two instruction templates, their carriers or the WSL recipe files, passes. 28 of the 29 recur unchanged in a clean clone of `e8c1edec`: a missing `exchange_calendars` module (the 18 errors, and the 5 `test_adaptive_paper_credential_race` failures, whose pristine baseline imports it), `dirname: command not found` (3), a systemd scope state (1) and the installed Claude Code's `auth_storage_failure` notification type (1). The 29th, `test_token_e2e_grader` F27c, passes in that clone; here `git clone --local` cannot hard-link from this worktree into `/tmp`, another file system | +| `python3 -m unittest` (the full suite) | 1 | 10,347 tests in 1,719 s, 903 skipped, 11 failures and 18 errors, none in a test module this change edits. All 29 are the classes the first round found recurring unchanged in a clean clone of `e8c1edec`: a missing `exchange_calendars` module (the 18 errors, and the 5 `test_adaptive_paper_credential_race` failures, whose pristine baseline imports it), `dirname: command not found` (3), a systemd scope state (1), the installed Claude Code's `auth_storage_failure` notification type (1), and `test_token_e2e_grader` F27c (1), whose `git clone --local` cannot hard-link from this worktree into `/tmp`, another file system (it passed in that clone) | +| `python3 -m unittest tests.test_install_claude_profile tests.test_codex_worker_lane tests.test_wsl_new_distro_recipe` | 0 | 333 tests, 13 skipped | +| `python3 -m unittest` over the 19 other modules that pin the touched files (`test_new_wsl_handbook`, `test_new_wsl_client_config`, `test_new_wsl_profile`, `test_host_baseline_probe`, `test_managed_block`, `test_codex_agents`, `test_codex_roles`, `test_scaffold_repo`, `test_bootstrap_full_profile`, `test_token_e2e_grader`, `test_landscape_sweep_harness`, `test_landscape_sweep_skills`, `test_freeze_snapshot`, `test_blind_checkout`, `test_runtime_worker_openhands_push_gate`, `test_runtime_worker_openhands_resolver`, `test_upstream_surface_watch`, `test_catalog_freshness_runtime`, `test_new_wsl_definitive_defaults`) | 1 | 2,237 tests, 15 skipped, 1 failure: F27c, as above (`Invalid cross-device link` at the clone, before any check; the suite's `TMPDIR` guard refuses a directory inside the repository) | | `node test-envelope.mjs` and `node test-contract-mutations.mjs`, in `examples/claude-native/workflows` | 0 | 254 and 74 passed | -| `python3 scripts/validate.py` | 0 | 10,217 hashed files, 208 receipts | +| `python3 scripts/validate.py` | 0 | 10,277 hashed files, 212 receipts | | `python3 scripts/evidence_manifest.py --check` | 0 | passed | | `python3 scripts/validate_convergence.py blueprints/convergence-practice/wsl-new-distro-image-20261001/experiment.json` | 0 | the record is valid with the relocated frozen inputs | -| `python3 -B tools/adoption/new_wsl_client_config.py --check` | 0 | check passed | +| `python3 -B tools/adoption/new_wsl_client_config.py --check` | 0 | check passed, with one warning about slot `mcp-inspector`, from inputs this change does not touch | | `python3 -B tools/adoption/new_wsl_client_config.py --render --host nativestack2604 --out /user-times-render` | 0 | each rendered carrier holds the sentence once and equals the committed carrier byte for byte; `managed_block`'s merge of each gives 12,071 and 8,601 bytes with one copy | | `python3 scripts/build_new_wsl_handbook.py --check` | 0 | current outputs | +| Startup bytes: `PortableTopRuleTests.startup_files` on this tree, and on main `ecfa1127`'s startup files in a scratch directory | 0 | the Startup bytes table, unchanged from the first round | | P2's render, then `cloud-init schema -c` (26.1-0ubuntu3~26.04.1, on NativeStack2604) | 0 | `Valid schema` | -| Path A simulation: `[boot]` and `systemd=true`, the user-data's appended content, then the logic of wsl-setup 0.6.3's `set_user_as_default` | 0 | six lines, each once; wsl-setup changes nothing | +| Path A simulation: `[boot]` and `systemd=true`, then the user-data's `write_files` content as cloud-init's YAML gives it, then wsl-setup 0.6.3's own `set_user_as_default` with only its file path changed | 0 | six lines, each once; wsl-setup changes nothing | | Path B simulation: W6's `[user]` and `[time]` lines, run twice on `[boot]` and `systemd=true` | 0 | six lines, each once | +| Host read: `timedatectl show -p Timezone --value`; `tzutil.exe /g` (not a W5 run) | 0; 0 | `America/New_York`; `Eastern Standard Time` | | `git diff --check` | 0 | clean | ## Sources @@ -198,6 +238,25 @@ sentence is 227 bytes. The budget record has a dated addendum pointing here. - pinned source MicrosoftDocs/WSL `7ea1c6f9e25f1c89a05a0e97e5325a02a66ac6cd`, `WSL/wsl-config.md:152-158`, which was still that file's latest commit on 2026-10-05; - section `[time]`, key `useWindowsTimezone`, boolean, default `true`. +- **microsoft/WSL** `91f161fa240dc355c1a88daabc8aac4273e35ba5` (tag 3.0.1, the release W1 requires), read 2026-10-05: + - `src/linux/init/WslDistributionConfig.h:25` (`time.useWindowsTimezone`) and `:58` (`bool AutoUpdateTimezone = + true;`), registered at `WslDistributionConfig.cpp:42`; + - `src/linux/init/timezone.cpp:22-110` (`UpdateTimezone`): no change when the key is off (`:49`), the mapping is + empty (`:54`) or the zoneinfo file is missing (`:68`); otherwise it links `/etc/localtime` (`:82`) and writes + `/etc/timezone` (`:92`). `src/linux/init/config.cpp:882` calls it at instance start; + - `src/windows/common/helpers.cpp:358-413` (`GetLinuxTimezone`): the Windows zone from `GetDynamicTimeZoneInformation` + and the region from `GetUserDefaultGeoName`, mapped by `ucal_getTimeZoneIDForWindowsID`, with Windows' own ICU + (`src/windows/common/precomp.h:40` includes ``; `CMakeLists.txt:386` links `icu.lib`). +- **ICU** `release-78.3` (`21d1eb0f306e1141c10931e914dfc038c06121da`), read 2026-10-05, for the API's meaning only (the + data version Windows ships is not established here): `icu4c/source/i18n/unicode/ucal.h:1644-1674` + (`ucal_getTimeZoneIDForWindowsID`, a Windows zone ID to a system zone ID for a region) and + `icu4c/source/i18n/timezone.cpp:1698-1719` (the first zone of the region's row, else the `001` row). +- **CLDR** `release-48-2` (`11299982335beb974c1c63c45265184e759c0f41`), `common/supplemental/windowsZones.xml:130` and + `:133`, read 2026-10-05: `Eastern Standard Time` maps to `America/New_York` for `001` and first for `US`. +- **Microsoft tzutil** ([learn.microsoft.com](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/tzutil), + read 2026-10-05; MicrosoftDocs/windowsserverdocs `48fd05321fd0fe328b1977597b554d884ca5e35d`, + `WindowsServerDocs/administration/windows-commands/tzutil.md:18` and `:25`): `/g` "Displays the current time zone + ID." - **ubuntu/wsl-setup** `73418e32bb48d514c2c2853fa7e5cacdcaf3dfe8` (0.6.3), `wsl-setup:55-73` (`set_user_as_default`). - **[RFC 3339](https://www.rfc-editor.org/rfc/rfc3339)**, read 2026-10-05: §4.1 (UTC, because daylight-saving rules are convoluted and change by local law), §4.2 (numeric offsets; alphabetic labels interoperated poorly), §4.3 (`Z` diff --git a/tests/test_wsl_new_distro_recipe.py b/tests/test_wsl_new_distro_recipe.py index 21c9416f9..419084ae7 100644 --- a/tests/test_wsl_new_distro_recipe.py +++ b/tests/test_wsl_new_distro_recipe.py @@ -7,8 +7,9 @@ ``${WSL_USER}`` placeholder, so the recipe's PowerShell ``.Replace`` writes the same bytes; the render starts with the ``#cloud-config`` header (no byte order mark, LF line ends) and creates the user the recipe describes: uid 1000, the groups the image's wsl-setup gives an interactive user, - passwordless sudo, a locked password and ``[user] default`` appended to /etc/wsl.conf, with - ``[time] useWindowsTimezone=true`` before it so the zone follows Windows (2026-10-05); + passwordless sudo, a locked password and ``[user] default`` appended to /etc/wsl.conf, after a ``[time]`` + section whose one key is ``useWindowsTimezone=true``, WSL's default, written so W5 reads it back (2026-10-05); the + check reads the appended text by section, so a key moved to another section or ``[user]`` ahead of ``[time]`` fails; - the host value template renders to JSON with exactly adoption/hosts/example.json's keys, every service on 127.0.0.1 at a port outside the workstation distribution's ports (WSL 2 distributions share one network namespace), and the recipe's ``ss`` probe checks exactly those ports; @@ -89,6 +90,10 @@ for naming a skill or timer that neither lists, and that this step does not establish those skills; - F9 says what the line ``--apply`` prints about the authorization settings says: every outcome, in the words the tool prints; +- the review of the 2026-10-05 local-time change: W5 reads the zone the distribution reports, + ``timedatectl show -p Timezone --value``, beside the Windows zone, ``tzutil /g``, once each and after ``id -u`` (so + path B's repeat covers them); its proof names CLDR's ``windowsZones`` mapping, and the record's command table, the + checklist and the receipt example carry the pair. These are local consistency checks over repository text and an in-memory render of the templates. Nothing here runs wsl.exe, PowerShell, gpgv, journalctl or cloud-init; a pass is not a host run. @@ -292,6 +297,11 @@ SCHEMA_SYSTEM = "wsl.exe -d '' -u root --exec cloud-init schema --system" SCHEMA_LINE = "`^\\s*Valid schema user-data$`" CLOUD_INIT_HOWTO = "https://ubuntu.com/wsl/docs/stable/howto/cloud-init/" +# The review of the 2026-10-05 local-time change: W5 pairs the zone the distribution reports with the Windows zone. +ZONE_DISTRIBUTION = "wsl.exe -d '' --exec timedatectl show -p Timezone --value" +ZONE_WINDOWS = "tzutil /g" +WSL_CONF_SECTIONS_ERROR = ("the appended /etc/wsl.conf text is not [time] holding only useWindowsTimezone=true, then [user] " + "holding only the default user") STORVSC_COUNT = ("sudo journalctl -k -b 0 --no-pager | grep hv_storvsc | " "grep -Evc 'registering driver hv_storvsc|[Cc]ommand line:'") # The coordinator's P3 rule (2026-10-01): the count is a baseline; the newest error line's kernel time against the @@ -411,6 +421,22 @@ def command_table_errors(recipe: str, record: str) -> list[str]: return errors +def wsl_conf_sections(rendered: str) -> list[tuple[str, list[str]]]: + """The /etc/wsl.conf text the user-data appends, its write_files ``content: |`` block of four-space lines, as + (section header, key lines) in order, blank lines left out; a key line before any header has the header ``""``.""" + match = re.search(r"^ content: \|\n((?: [^\n]*\n|\n)+)", rendered, re.M) + sections: list[tuple[str, list[str]]] = [] + for line in (match[1].splitlines() if match else []): + line = line.strip() + if line.startswith("["): + sections.append((line, [])) + elif line: + if not sections: + sections.append(("", [])) + sections[-1][1].append(line) + return sections + + def user_data_errors(template: str, user: str = "example") -> list[str]: errors = [] if "$" in template.replace(PLACEHOLDER, ""): @@ -435,6 +461,10 @@ def user_data_errors(template: str, user: str = "example") -> list[str]: r"^ \[user\]$", rf"^ default={re.escape(user)}$") errors += [f"the render lacks a line matching {pattern}" for pattern in required if not re.search(pattern, rendered, re.M)] + # Each line above may sit anywhere, so the appended text is also read by section: the key belongs to [time], which + # comes before [user] (the review of the 2026-10-05 local-time change). + if wsl_conf_sections(rendered) != [("[time]", ["useWindowsTimezone=true"]), ("[user]", [f"default={user}"])]: + errors.append(WSL_CONF_SECTIONS_ERROR) if template.count(GETTY_BOOTCMD) != 1: errors.append("the user-data must hold the exact bootcmd getty mask once") if not re.search(r"^ default=\$\{WSL_USER\}\n\n(?:#[^\n]*\n){1,4}" + re.escape(GETTY_BOOTCMD), template, re.M): @@ -1091,6 +1121,36 @@ def schema_system_errors(recipe: str, record: str, checklist: str, receipt: dict return errors +def time_zone_pair_errors(recipe: str, record: str, checklist: str, receipt: dict) -> list[str]: + """The review of the 2026-10-05 local-time change. The /etc/wsl.conf read-back proves the key's text only, so W5 + also reads the zone the distribution reports beside the Windows zone, once each and after ``id -u`` (path B + repeats W5's checks from ``id -u`` on). Its proof says how the two agree: CLDR's ``windowsZones`` mapping for the + Windows region, which WSL applies through the ICU that Windows ships (microsoft/WSL 3.0.1, + src/windows/common/helpers.cpp:358-413).""" + errors = [] + commands = [command for shell, command in step_commands(recipe, "W5") if shell == "powershell"] + for command in (ZONE_DISTRIBUTION, ZONE_WINDOWS): + if commands.count(command) != 1: + errors.append(f"W5 does not run `{command}` once") + first_id = "wsl.exe -d '' --exec id -u" + if all(command in commands for command in (ZONE_DISTRIBUTION, ZONE_WINDOWS, first_id)) and min( + commands.index(ZONE_DISTRIBUTION), commands.index(ZONE_WINDOWS)) < commands.index(first_id): + errors.append("the zone pair runs before `id -u`, so path B's repeat of W5's checks skips it") + w5 = prose(section(recipe, "W5")) + if "windowsZones" not in w5 or "`timedatectl show -p Timezone --value`" not in w5: + errors.append("W5's proof does not say how the two zones agree (CLDR's windowsZones mapping)") + rows = [command for step, _, command, _ in command_table_rows(record) if step == "W5"] + if rows.count(ZONE_DISTRIBUTION) != 1 or rows.count(ZONE_WINDOWS) != 1: + errors.append("the record's command table does not hold each W5 zone row once") + line = checklist_line(checklist, "W5") + if "`timedatectl show -p Timezone --value`" not in line or f"`{ZONE_WINDOWS}`" not in line: + errors.append("the checklist's W5 line does not name both zone commands") + listed = [(entry.get("step"), entry.get("cmd")) for entry in receipt.get("steps", []) if isinstance(entry, dict)] + if listed.count(("W5", ZONE_DISTRIBUTION)) != 1 or listed.count(("W5", ZONE_WINDOWS)) != 1: + errors.append("the receipt example does not record each zone command once under W5") + return errors + + def kernel_storage_errors(recipe: str, record: str, checklist: str, receipt: dict) -> list[str]: """Item D (critic item 3), under the coordinator's rule of 2026-10-01. The workstation distribution shares the kernel. Before stage 1, P3 counts the current boot's ``hv_storvsc`` kernel journal lines (sudo: the user cannot read @@ -1279,6 +1339,11 @@ def rehearsal_errors(recipe: str, record: str, checklist: str, receipt: dict, ex class UserDataTemplateTests(unittest.TestCase): + # The template's appended /etc/wsl.conf text, and two rearrangements that keep every one of its lines. + WSL_CONF_BLOCK = " [time]\n useWindowsTimezone=true\n\n [user]\n default=${WSL_USER}\n" + KEY_UNDER_USER = " [time]\n\n [user]\n useWindowsTimezone=true\n default=${WSL_USER}\n" + USER_FIRST = " [user]\n default=${WSL_USER}\n\n [time]\n useWindowsTimezone=true\n" + def test_the_template_renders_to_the_recipe_user(self): self.assertEqual(user_data_errors(read(USER_DATA)), []) @@ -1293,12 +1358,25 @@ def test_the_check_rejects_a_bom_a_stray_dollar_a_password_and_a_lost_line(self) "header": good.replace("#cloud-config", "# cloud-config", 1), "no time section": good.replace(" [time]\n", ""), "zone not from Windows": good.replace("useWindowsTimezone=true", "useWindowsTimezone=false"), + "time key under [user]": good.replace(self.WSL_CONF_BLOCK, self.KEY_UNDER_USER), + "[user] before [time]": good.replace(self.WSL_CONF_BLOCK, self.USER_FIRST), } for name, mutant in mutants.items(): with self.subTest(mutant=name): self.assertNotEqual(mutant, good) self.assertTrue(user_data_errors(mutant)) + def test_a_key_in_another_section_fails_the_section_reading(self): + """The moved key keeps every required line and the bootcmd's place, so only the section reading rejects it; with + [user] ahead of [time] the section reading rejects it too (the default user also leaves the bootcmd's side).""" + good = read(USER_DATA) + self.assertIn(self.WSL_CONF_BLOCK, good) + self.assertEqual(user_data_errors(good.replace(self.WSL_CONF_BLOCK, self.KEY_UNDER_USER)), + [WSL_CONF_SECTIONS_ERROR]) + self.assertIn(WSL_CONF_SECTIONS_ERROR, user_data_errors(good.replace(self.WSL_CONF_BLOCK, self.USER_FIRST))) + self.assertEqual(wsl_conf_sections(string.Template(good).substitute(WSL_USER="example")), + [("[time]", ["useWindowsTimezone=true"]), ("[user]", ["default=example"])]) + class HostTemplateTests(unittest.TestCase): def test_the_host_template_renders_with_example_keys_and_free_ports(self): @@ -1929,6 +2007,41 @@ def test_the_check_rejects_a_missing_check_a_user_run_a_path_b_run_and_a_lost_pr }) +class TimeZonePairTests(FollowUpCase): + """The review of the 2026-10-05 local-time change: W5 pairs the zone the distribution reports with the Windows zone.""" + + def test_w5_reads_the_distribution_zone_beside_the_windows_zone(self): + self.assertEqual(time_zone_pair_errors(*self.inputs()), []) + + def test_the_check_rejects_a_lost_command_an_early_pair_a_lost_mapping_and_lost_records(self): + recipe, record, checklist, receipt = self.inputs() + w5 = section(recipe, "W5") + pair, first_launch_user = ZONE_DISTRIBUTION + "\n" + ZONE_WINDOWS + "\n", "wsl.exe -d '' --exec id -un\n" + self.assertIn(pair, w5) + self.assertIn(first_launch_user, w5) + rows = [line for line in record.splitlines() if line.startswith("| W5 |") + and (f"`{ZONE_DISTRIBUTION}`" in line or f"`{ZONE_WINDOWS}`" in line)] + self.assertEqual(len(rows), 2) + steps = [entry for entry in receipt["steps"] + if (entry.get("step"), entry.get("cmd")) not in {("W5", ZONE_DISTRIBUTION), ("W5", ZONE_WINDOWS)}] + self.assert_mutants_fail(time_zone_pair_errors, { + "no distribution zone": (recipe.replace(w5, w5.replace(ZONE_DISTRIBUTION + "\n", "")), record, checklist, + receipt), + "no Windows zone": (recipe.replace(w5, w5.replace("\n" + ZONE_WINDOWS + "\n", "\n")), record, checklist, + receipt), + "pair before id -u": (recipe.replace(w5, w5.replace(pair, "").replace(first_launch_user, + pair + first_launch_user)), + record, checklist, receipt), + "no mapping in the proof": (recipe.replace(w5, w5.replace("windowsZones", "zone table")), record, checklist, + receipt), + "record without the rows": (recipe, "\n".join(line for line in record.splitlines() if line not in rows), + checklist, receipt), + "checklist without the pair": (recipe, record, checklist.replace(f"`{ZONE_WINDOWS}`", "the Windows zone"), + receipt), + "receipt without the entries": (recipe, record, checklist, dict(receipt, steps=steps)), + }) + + class KernelStorageTests(FollowUpCase): """Item D (critic item 3), under the coordinator's rule: P3 records a baseline and stops only on errors less than one hour old; W5 counts again after the first launch on both paths.""" diff --git a/tools/adoption/host-baseline.md b/tools/adoption/host-baseline.md index eb9711ff0..83ccda815 100644 --- a/tools/adoption/host-baseline.md +++ b/tools/adoption/host-baseline.md @@ -48,7 +48,7 @@ attestation. The passwd record retains field 7 only, and the sudoers marker retains existence, mode and size only. Its owner column is discarded. Passwordless sudo remains -`unknown`: `adoption/platforms/linux-wsl2-new-distro.md:627` is a recipe criterion, +`unknown`: `adoption/platforms/linux-wsl2-new-distro.md:636` is a recipe criterion, not a per-host acceptance record. No such acceptance is linked by these receipts. PR-1 must label root rows `status-unknown` until it has that separate evidence; neither an absent marker nor a denied stat proves those rows unreachable. diff --git a/tools/adoption/host_baseline_probe.py b/tools/adoption/host_baseline_probe.py index c75824d35..c48b6dda8 100644 --- a/tools/adoption/host_baseline_probe.py +++ b/tools/adoption/host_baseline_probe.py @@ -338,7 +338,7 @@ def collect(home): "passwordless_sudo": { "marker": marker_observation(), "status": "unknown", - "other_source": "adoption/platforms/linux-wsl2-new-distro.md:627 (recipe criterion only; per-host acceptance not linked)", + "other_source": "adoption/platforms/linux-wsl2-new-distro.md:636 (recipe criterion only; per-host acceptance not linked)", }, "launchers": {name: binary_observation(name) for name in LAUNCHERS}, "launcher_packages": {name: package_observation(name) for name in ("time", "util-linux", "procps", "systemd", "gdb")}, From 34a720a972db3a66772460e28dd336f51da26b7f Mon Sep 17 00:00:00 2001 From: seathatflowsinourveins <234074349+seathatflowsinourveins@users.noreply.github.com> Date: Mon, 5 Oct 2026 21:47:55 -0400 Subject: [PATCH 03/10] Round-3 fixes for #758: both WSL zone paths, a stop for review on a W5 zone mismatch, pinned mapping sources The command center's round-3 ruling on 608c5266 (ACK_AFTER): - Fold-in from the clock adjudication: the record now states both paths that set the zone while useWindowsTimezone was true at an instance's start. - Instance start: ConfigInitializeCommon reads /etc/wsl.conf once (config.cpp:532), and config.cpp:882 calls UpdateTimezone. - A running instance: the service's window receives the broadcast WM_TIMECHANGE (LxssUserSession.cpp:607-647, :4294-4305) and updates every running instance (:3835-3846, WslCoreInstance.cpp:349-362). Init's loops call UpdateTimezone with the configuration read at start (init.cpp :2559-2561, :2745-2747; timezone.cpp:49-52 and :76-106). - Each of these lines was read at microsoft/WSL 91f161fa (tag 3.0.1). That a Windows time-zone change raises WM_TIMECHANGE rests on WSL's own comment and log text. - The recipe's W5 bullet and the 2026-10-01 amendment say the same. - A W5 zone mismatch, on either path, is recorded with both outputs and stops the run for review, without the failed-proof export and unregister. - WSL leaves /etc/localtime unchanged on an empty mapping or a missing zone file (timezone.cpp:54-58, :66-70). So the recipe names the remedy (the Windows region and zone, the image's tzdata), and unregistering would change neither. - The failed-proof rule, the checklist and the record's row carry the exception. - The bullet pins CLDR release-48-2 windowsZones.xml and the tzutil page (windowsserverdocs 48fd0532). - TimeZonePairTests gains seven mutants for the disposition, the exception, the remedy and both pins. - From the clock adjudication's P3: the record's context adds that a private host plan had pinned the false value because Windows' automatic time zone setting was on. No private path is named. - The passwordless-sudo criterion moved to recipe line 645, and its two live citations follow. - The Checks table marks the rows re-run on this tree. The regenerated handbook, its receipt and the registry are in the next commit (hot-file protocol, docs/lanes.md). Co-Authored-By: Claude Opus 5.5 --- adoption/platforms/linux-wsl2-new-distro.md | 25 ++++-- .../templates/wsl/first-boot-checklist.md | 2 +- .../2026-10-01-new-wsl-distro-recipe.md | 6 +- .../2026-10-05-user-facing-local-time.md | 89 ++++++++++++------- tests/test_wsl_new_distro_recipe.py | 44 ++++++++- tools/adoption/host-baseline.md | 2 +- tools/adoption/host_baseline_probe.py | 2 +- 7 files changed, 127 insertions(+), 43 deletions(-) diff --git a/adoption/platforms/linux-wsl2-new-distro.md b/adoption/platforms/linux-wsl2-new-distro.md index 7add8a97a..20284975d 100644 --- a/adoption/platforms/linux-wsl2-new-distro.md +++ b/adoption/platforms/linux-wsl2-new-distro.md @@ -628,11 +628,20 @@ Proof (path A, cloud-init provisioned the instance): 2026-10-05: MicrosoftDocs/WSL `7ea1c6f9`, `WSL/wsl-config.md:152-158`, `useWindowsTimezone`, default `true`); W6 writes the same key on path B. This read-back proves the file's text at this boot only; nothing reads it later. - `timedatectl show -p Timezone --value` prints the IANA zone that CLDR's `windowsZones` mapping gives for the Windows - zone that `tzutil /g` prints ([tzutil](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/tzutil) - `/g`, read 2026-10-05) and the Windows region: the first zone of the region's row, else the `001` row, for example - `America/New_York` for `Eastern Standard Time` in the US. WSL maps the zone with the ICU that Windows ships and, while - `useWindowsTimezone` is true, links `/etc/localtime` to it at each instance start (microsoft/WSL `91f161fa`, tag - 3.0.1: `src/windows/common/helpers.cpp:358-413`, `src/linux/init/timezone.cpp:22-110`). Record both outputs. + zone that `tzutil /g` prints and the Windows region: the first zone of the region's row, else the `001` row, for + example `America/New_York` for `Eastern Standard Time` in the US (unicode-org/cldr `release-48-2`, `11299982`, + `common/supplemental/windowsZones.xml:130` and `:133`). `tzutil /g` displays the current time zone ID + ([tzutil](https://learn.microsoft.com/en-us/windows-server/administration/windows-commands/tzutil), read 2026-10-05: + MicrosoftDocs/windowsserverdocs `48fd0532`, `tzutil.md:18` and `:25`). WSL maps the zone with the ICU that Windows + ships and, while `useWindowsTimezone` was true when the instance started, links `/etc/localtime` to it at each + instance start and again in a running instance when Windows' time zone changes (microsoft/WSL `91f161fa`, tag 3.0.1: + `src/windows/common/helpers.cpp:358-413`, `src/linux/init/timezone.cpp:22-110`; the decision record cites both + paths). Record both outputs. On either path, a mismatch is recorded with both outputs and stops the run for review, + without the failed-proof recovery below: WSL leaves `/etc/localtime` unchanged when the Windows-to-IANA mapping is + empty (`Windows to Linux timezone mapping was not possible.`, `timezone.cpp:54-58`) or the image lacks the zone's + file (`... not found. Is the tzdata package installed?`, `:66-70`). The remedy is to check the Windows region and + zone against that mapping and the image's `tzdata` (`/usr/share/zoneinfo/`); unregistering the distribution + would not change either. - The marker file exists, and `sudo -l` lists `(ALL) NOPASSWD: ALL`. - The starred line of `--list --verbose` is still W1's (`default_distribution_after`). @@ -682,8 +691,10 @@ proof: the mask did not take effect before the unit started. Record all of it, w kernel from W1 and the image revision from W2, in the receipt's `paired_isolation`: one object for the workstation and one for ``, each with `uid`, `system_state`, `failed_units`, `user_manager` and `cgroup_namespace`. -When any W5 proof fails, or the user-session warning appears, stop the run and never stop or restart a unit in either -distribution. Terminate only the new distribution, export its failed state, and unregister only that name. Do not +The time-zone pair is the one exception, on path A and in path B's repeat: a mismatch is recorded and stops the run for +review, as its proof above says, and the recovery below does not run for it. When any W5 proof fails, or the +user-session warning appears, stop the run and never stop or restart a unit in either distribution. Terminate only the +new distribution, export its failed state, and unregister only that name. Do not take path B for any such failure. Record its cause (`cgroup`, `tty` or a short text); this recovery block serves every failed W5 proof: diff --git a/adoption/templates/wsl/first-boot-checklist.md b/adoption/templates/wsl/first-boot-checklist.md index 255abbce6..073962cac 100644 --- a/adoption/templates/wsl/first-boot-checklist.md +++ b/adoption/templates/wsl/first-boot-checklist.md @@ -42,7 +42,7 @@ readlink /proc/self/ns/cgroup - [ ] **W2** Explicit `` is one of the two controlled arms; download URL, filename and catalog entry match that release. The actual whole-image, signed Canonical publication and pinned DistributionInfo.json SHA-256 all equal that release's exact hash above; actual file size recorded (run 2 observed 418,495,746 bytes for 26.04.1); no `throw`. - [ ] **W3** `%USERPROFILE%\.cloud-init\.user-data` exists, starts with `#cloud-config`, names `` twice and contains no `${`; its SHA-256 equals P2's. - [ ] **W4** The selected `ubuntu--wsl-amd64.wsl` installs with exit 0; `Distribution successfully installed`; `` is `Stopped` at version 2; the starred line is unchanged. -- [ ] **W5** First launch exit 0, without the prompt marker; `wsl: Failed to start the systemd user session` stops even with exit 0; `id -un` is `` and `id -u` is `1000`; getty `is-enabled` prints `masked` (normal exit 1), and `show` prints `LoadState=masked`, `ActiveState=inactive`, `NRestarts=0`, recorded as `getty_mask`; selected-image `cloud-init --version` recorded; `cloud-init status --long` prints `status: disabled` with `boot_status_code: disabled-by-marker-file` (with the template's `bootcmd` it exits 2 on one expected recoverable error, `Failed to wait for network`, because WSL masks `systemd-networkd-wait-online.service`; any other recoverable error is recorded and reviewed); `/var/lib/cloud/data/result.json` names `DataSourceWSL` with `"errors": []`; `/var/lib/cloud/data/status.json` shows the four stages finished without errors; `cloud-init schema --system` prints `Valid schema user-data` and exits 0; `/etc/wsl.conf` names the user once and sets `useWindowsTimezone=true` under `[time]`; `timedatectl show -p Timezone --value` prints the IANA zone that CLDR's `windowsZones` mapping gives for the zone `tzutil /g` prints and the Windows region, both recorded; `/etc/cloud/cloud-init.disabled` exists; sudo is NOPASSWD; the starred line is unchanged. After the first launch, while both distributions run and before F1, the already-running distribution reads a system unit active in both: its `cgroup.procs` has local process ids and no `0` entry, and the new distribution's `user@.service` is active. An empty or unreadable file is inconclusive and stops. The paired block below records each of the five commands on each side in its own receipt entry with its own exit, in `paired_isolation`: the same uid, both user managers `active`, the new distribution `running` with no failed unit or `degraded` with `systemd-binfmt.service` as its only failed unit (message proved at F1), two `cgroup:[...]` values that differ from each other and are not `cgroup:[4026531835]`, and all five workstation values equal to its W1 baseline. A getty failed in the new distribution or newly failed in the workstation fails the proof. Any failed W5 proof terminates only the new distribution, never stops or restarts a unit in either, exports `-w5-failed.tar` with the exit guard and its cause (`cgroup`, `tty` or a short text) in `w5_failure_export`, unregisters and checks surviving `WSLInterop` and a Windows executable, with R1's records and stop for review. Do not take path B for any such failure. On both paths, record P3's baseline before the import and `second_count` after the first launch. An increase confined to the window in which the new disk is attached, before cloud-init starts, is recorded with the new lines and does not stop the run. Any storage error after that window, or any provisioning step that fails with a storage cause, stops the run. An unknown attachment window stops for review; storage failures retain microsoft/WSL#41482 and their own cause. +- [ ] **W5** First launch exit 0, without the prompt marker; `wsl: Failed to start the systemd user session` stops even with exit 0; `id -un` is `` and `id -u` is `1000`; getty `is-enabled` prints `masked` (normal exit 1), and `show` prints `LoadState=masked`, `ActiveState=inactive`, `NRestarts=0`, recorded as `getty_mask`; selected-image `cloud-init --version` recorded; `cloud-init status --long` prints `status: disabled` with `boot_status_code: disabled-by-marker-file` (with the template's `bootcmd` it exits 2 on one expected recoverable error, `Failed to wait for network`, because WSL masks `systemd-networkd-wait-online.service`; any other recoverable error is recorded and reviewed); `/var/lib/cloud/data/result.json` names `DataSourceWSL` with `"errors": []`; `/var/lib/cloud/data/status.json` shows the four stages finished without errors; `cloud-init schema --system` prints `Valid schema user-data` and exits 0; `/etc/wsl.conf` names the user once and sets `useWindowsTimezone=true` under `[time]`; `timedatectl show -p Timezone --value` prints the IANA zone that CLDR's `windowsZones` mapping gives for the zone `tzutil /g` prints and the Windows region, both recorded; a mismatch, on either path, is recorded and stops for review without the export and unregister: check the Windows region and zone against that mapping and the image's `tzdata`; `/etc/cloud/cloud-init.disabled` exists; sudo is NOPASSWD; the starred line is unchanged. After the first launch, while both distributions run and before F1, the already-running distribution reads a system unit active in both: its `cgroup.procs` has local process ids and no `0` entry, and the new distribution's `user@.service` is active. An empty or unreadable file is inconclusive and stops. The paired block below records each of the five commands on each side in its own receipt entry with its own exit, in `paired_isolation`: the same uid, both user managers `active`, the new distribution `running` with no failed unit or `degraded` with `systemd-binfmt.service` as its only failed unit (message proved at F1), two `cgroup:[...]` values that differ from each other and are not `cgroup:[4026531835]`, and all five workstation values equal to its W1 baseline. A getty failed in the new distribution or newly failed in the workstation fails the proof. Any other failed W5 proof terminates only the new distribution, never stops or restarts a unit in either, exports `-w5-failed.tar` with the exit guard and its cause (`cgroup`, `tty` or a short text) in `w5_failure_export`, unregisters and checks surviving `WSLInterop` and a Windows executable, with R1's records and stop for review. Do not take path B for any such failure. On both paths, record P3's baseline before the import and `second_count` after the first launch. An increase confined to the window in which the new disk is attached, before cloud-init starts, is recorded with the new lines and does not stop the run. Any storage error after that window, or any provisioning step that fails with a storage cause, stops the run. An unknown attachment window stops for review; storage failures retain microsoft/WSL#41482 and their own cause. ```sh id -u diff --git a/docs/decisions/2026-10-01-new-wsl-distro-recipe.md b/docs/decisions/2026-10-01-new-wsl-distro-recipe.md index f12424ad9..b92233c8d 100644 --- a/docs/decisions/2026-10-01-new-wsl-distro-recipe.md +++ b/docs/decisions/2026-10-01-new-wsl-distro-recipe.md @@ -701,7 +701,7 @@ workstation distribution (Evidence classes). | W5 | powershell | `wsl.exe -d '' -u root --exec cat /var/lib/cloud/data/status.json` | each of the four stages `finished` with empty `errors`; `recoverable_errors` recorded | | W5 | powershell | `wsl.exe -d '' -u root --exec cloud-init schema --system` | path A: a line matching `^\s*Valid schema user-data$` and exit 0; skipped on path B | | W5 | powershell | `wsl.exe -d '' -u root --exec cat /etc/wsl.conf` | `[boot]`, `systemd=true`, `[time]`, `useWindowsTimezone=true`, `[user]`, `default=`, each once | -| W5 | powershell | `wsl.exe -d '' --exec timedatectl show -p Timezone --value` | the IANA zone that CLDR's `windowsZones` mapping gives for `tzutil /g`'s zone and the Windows region, for example `America/New_York`; recorded | +| W5 | powershell | `wsl.exe -d '' --exec timedatectl show -p Timezone --value` | the IANA zone that CLDR's `windowsZones` mapping gives for `tzutil /g`'s zone and the Windows region, for example `America/New_York`; recorded; a mismatch stops the run for review without the failed-proof export and unregister | | W5 | powershell | `tzutil /g` | the Windows time zone ID, for example `Eastern Standard Time`; recorded | | W5 | powershell | `wsl.exe -d '' -u root --exec ls -l /etc/cloud/cloud-init.disabled` | the marker exists | | W5 | powershell | `wsl.exe -d '' -u root --exec sudo -l -U ''` | `(ALL) NOPASSWD: ALL` | @@ -1438,6 +1438,10 @@ leaves WSL's behavior unchanged and records the intent. W5 reads it back, so the another value or no `[time]` section; path B repeats W5's checks once after W6. Nothing reads `/etc/wsl.conf` after provisioning, so a later change of the key is not detected. W5 also pairs `timedatectl show -p Timezone --value` with `tzutil /g`, so the receipt holds the zone the distribution reports beside the Windows zone, not only the file's text. +A mismatch is recorded and stops the run for review without the failed-proof export and unregister, because WSL leaves +`/etc/localtime` unchanged when the mapping is empty or the zone's file is missing; the recipe names the remedy. While +the key was true at an instance's start, WSL relinks the zone at that start and again whenever Windows' time zone +changes during the run (the decision record cites both paths). The 26.04.1 image's `wsl-setup` 0.6.3 (`73418e32`, `wsl-setup:55-73`) only appends or completes `[user]`, so the section survives the first run. The coordinator measured on 2026-10-05 that NativeStack2604's `/etc/wsl.conf` held `useWindowsTimezone=false` with no repository record, and set it to `true` on that host. Only the content listings diff --git a/docs/decisions/2026-10-05-user-facing-local-time.md b/docs/decisions/2026-10-05-user-facing-local-time.md index a2e2903fb..360f5cc3f 100644 --- a/docs/decisions/2026-10-05-user-facing-local-time.md +++ b/docs/decisions/2026-10-05-user-facing-local-time.md @@ -19,9 +19,9 @@ UTC timeline across hosts and clients. - Agents relayed record times to the user as they were written, in UTC. No managed instruction told them to convert. - On NativeStack2604, each client's user-level file holds the user's own local-time rule, dated 2026-10-05, after the managed block's end marker (read 2026-10-05). This change leaves host files alone. -- NativeStack2604's `/etc/wsl.conf` held `[time] useWindowsTimezone=false` with no repository record, and the coordinator - set it to `true` on that host. The distribution recipe wrote no `[time]` key, so a new host relied on the default and - nothing read the value back. +- NativeStack2604's `/etc/wsl.conf` held `[time] useWindowsTimezone=false` with no repository record; a private host plan + had pinned it because Windows' automatic time zone setting was on. The coordinator set it to `true` on that host. The + distribution recipe wrote no `[time]` key, so a new host relied on the default and nothing read the value back. ## Decision @@ -65,9 +65,16 @@ UTC timeline across hosts and clients. detected. Detecting it would need a recurring read in a host probe. - **The zone itself.** The read-back proves the file's text, not the zone. W5 therefore also pairs the zone the distribution reports, `timedatectl show -p Timezone --value`, with the Windows zone, `tzutil /g`. They agree when - the IANA zone is CLDR's `windowsZones` mapping of the Windows zone for the Windows region: WSL maps the zone with - the ICU that Windows ships and, while the key is `true`, links `/etc/localtime` to it at each instance start - (Sources). Both outputs go to the receipt. + the IANA zone is CLDR's `windowsZones` mapping of the Windows zone for the Windows region; WSL maps the zone with + the ICU that Windows ships. Both outputs go to the receipt. A mismatch is recorded and stops the run for review + without the failed-proof export and unregister: WSL leaves `/etc/localtime` unchanged when the mapping is empty or + the zone's file is missing, so the remedy lies in the Windows region and zone or the image's `tzdata`, and + unregistering the new distribution would change neither. + - **Two paths set the zone.** While the key was `true` when an instance started, WSL links `/etc/localtime` to the + mapped zone on two paths (Sources): at each instance start, and in every running instance when the WSL service + receives a Windows `WM_TIMECHANGE` broadcast. A Windows time-zone change therefore reaches a running distribution + mid-session, not only at its next start. A change of the key itself takes effect only at the next start, because + init reads `/etc/wsl.conf` once, when the instance starts. - **First run.** The 26.04.1 image's `wsl-setup` 0.6.3 only appends or completes `[user]`, so the section survives its first run. - **Claude Code's UI clock.** Claude Code's own UI times follow the system zone by default (below), so they show the @@ -146,13 +153,15 @@ sentence is 227 bytes. The budget record has a dated addendum pointing here. row's proof says what the guard shows: a `[time]` section exists, and W5's repeated read-back confirms the value. - **The W5 zone pair.** `timedatectl show -p Timezone --value` and `tzutil /g` have their rows in the command table, their entries in the receipt example and their place in the checklist's W5 line. `TimeZonePairTests` checks them - with seven mutants. + with seven mutants, and with seven more checks what a mismatch does (recorded, a stop for review without the + failed-proof export and unregister, on both paths), the failure rule's exception, the remedy, and the CLDR and + tzutil pins. - **The client-configuration record.** It takes the tool's regenerated listing (59 and 70 lines) in a dated addendum. - **The new-WSL handbook.** It is regenerated on the rebased tree; it holds the recipe's SHA-256. Its receipt's `outputs` follow, with this change's `regenerations` entry after the one #703 added on main. - **The registry.** `manifests/evidence.json` takes the new hashes of the 20 changed files it lists and the five new files under `evidence/artifacts/user-facing-local-time-20261005/`. -- **The passwordless-sudo criterion moved from recipe line 624 to 636.** The two live citations of it, +- **The passwordless-sudo criterion moved from recipe line 624 to 645.** The two live citations of it, `tools/adoption/host_baseline_probe.py` and `tools/adoption/host-baseline.md`, follow. Dated records keep their own line citations. - **Unchanged.** The startup constants (`STARTUP_BUDGET_BYTES`), the scaffold, the moves fixture of the Codex top-rule @@ -206,30 +215,39 @@ sentence is 227 bytes. The budget record has a dated addendum pointing here. ## Checks The first three rows are the first round's fail-first checks, on main `e8c1edec`. Every other row was run after the -rebase, on the final tree at base `ecfa1127`, on NativeStack2604 (2026-10-05 and 2026-10-06 UTC); only this record's -prose changed afterwards. +rebase, at base `ecfa1127`, on NativeStack2604 (2026-10-05 and 2026-10-06 UTC). The review's third round changed: +- the recipe's and the checklist's prose; +- the 2026-10-01 record and this record; +- `tests/test_wsl_new_distro_recipe.py`; +- the probe's citation of the recipe; +- the regenerated handbook, receipt and registry. + +Rows whose result starts "round 3" ran again on that final tree. The others ran on the round-2 tree, `608c5266`. The +third round changed no template, carrier, user-data or startup file. Only this record's prose changed after the last +checks. | Command | Exit | Result | | --- | ---: | --- | | `python3 -m unittest tests.test_install_claude_profile.StandingRuleSurfacesTests`, before the template edits | 1 | 4 failures: `0 != 1` on each of the four surfaces (fail first) | | `python3 -m unittest tests.test_wsl_new_distro_recipe.UserDataTemplateTests`, before the user-data edit | 1 | 3 failures: the two new `[time]` patterns, and the two new mutants were not yet mutations (fail first) | | The same two checks, run in process against main `e8c1edec`'s four instruction files and user-data template | n/a | the same 4 and 3 failures (fail first, repeated) | -| `python3 -m unittest` (the full suite) | 1 | 10,347 tests in 1,719 s, 903 skipped, 11 failures and 18 errors, none in a test module this change edits. All 29 are the classes the first round found recurring unchanged in a clean clone of `e8c1edec`: a missing `exchange_calendars` module (the 18 errors, and the 5 `test_adaptive_paper_credential_race` failures, whose pristine baseline imports it), `dirname: command not found` (3), a systemd scope state (1), the installed Claude Code's `auth_storage_failure` notification type (1), and `test_token_e2e_grader` F27c (1), whose `git clone --local` cannot hard-link from this worktree into `/tmp`, another file system (it passed in that clone) | -| `python3 -m unittest tests.test_install_claude_profile tests.test_codex_worker_lane tests.test_wsl_new_distro_recipe` | 0 | 333 tests, 13 skipped | -| `python3 -m unittest` over the 19 other modules that pin the touched files (`test_new_wsl_handbook`, `test_new_wsl_client_config`, `test_new_wsl_profile`, `test_host_baseline_probe`, `test_managed_block`, `test_codex_agents`, `test_codex_roles`, `test_scaffold_repo`, `test_bootstrap_full_profile`, `test_token_e2e_grader`, `test_landscape_sweep_harness`, `test_landscape_sweep_skills`, `test_freeze_snapshot`, `test_blind_checkout`, `test_runtime_worker_openhands_push_gate`, `test_runtime_worker_openhands_resolver`, `test_upstream_surface_watch`, `test_catalog_freshness_runtime`, `test_new_wsl_definitive_defaults`) | 1 | 2,237 tests, 15 skipped, 1 failure: F27c, as above (`Invalid cross-device link` at the clone, before any check; the suite's `TMPDIR` guard refuses a directory inside the repository) | -| `node test-envelope.mjs` and `node test-contract-mutations.mjs`, in `examples/claude-native/workflows` | 0 | 254 and 74 passed | -| `python3 scripts/validate.py` | 0 | 10,277 hashed files, 212 receipts | -| `python3 scripts/evidence_manifest.py --check` | 0 | passed | -| `python3 scripts/validate_convergence.py blueprints/convergence-practice/wsl-new-distro-image-20261001/experiment.json` | 0 | the record is valid with the relocated frozen inputs | -| `python3 -B tools/adoption/new_wsl_client_config.py --check` | 0 | check passed, with one warning about slot `mcp-inspector`, from inputs this change does not touch | -| `python3 -B tools/adoption/new_wsl_client_config.py --render --host nativestack2604 --out /user-times-render` | 0 | each rendered carrier holds the sentence once and equals the committed carrier byte for byte; `managed_block`'s merge of each gives 12,071 and 8,601 bytes with one copy | -| `python3 scripts/build_new_wsl_handbook.py --check` | 0 | current outputs | -| Startup bytes: `PortableTopRuleTests.startup_files` on this tree, and on main `ecfa1127`'s startup files in a scratch directory | 0 | the Startup bytes table, unchanged from the first round | -| P2's render, then `cloud-init schema -c` (26.1-0ubuntu3~26.04.1, on NativeStack2604) | 0 | `Valid schema` | -| Path A simulation: `[boot]` and `systemd=true`, then the user-data's `write_files` content as cloud-init's YAML gives it, then wsl-setup 0.6.3's own `set_user_as_default` with only its file path changed | 0 | six lines, each once; wsl-setup changes nothing | -| Path B simulation: W6's `[user]` and `[time]` lines, run twice on `[boot]` and `systemd=true` | 0 | six lines, each once | -| Host read: `timedatectl show -p Timezone --value`; `tzutil.exe /g` (not a W5 run) | 0; 0 | `America/New_York`; `Eastern Standard Time` | -| `git diff --check` | 0 | clean | +| `python3 -m unittest` (the full suite) | 1 | round 2: 10,347 tests in 1,719 s, 903 skipped, 11 failures and 18 errors, none in a test module this change edits. All 29 are the classes the first round found recurring unchanged in a clean clone of `e8c1edec`: a missing `exchange_calendars` module (the 18 errors, and the 5 `test_adaptive_paper_credential_race` failures, whose pristine baseline imports it), `dirname: command not found` (3), a systemd scope state (1), the installed Claude Code's `auth_storage_failure` notification type (1), and `test_token_e2e_grader` F27c (1), whose `git clone --local` cannot hard-link from this worktree into `/tmp`, another file system (it passed in that clone) | +| `python3 -m unittest tests.test_install_claude_profile tests.test_codex_worker_lane tests.test_wsl_new_distro_recipe` | 0 | round 3: 334 tests, 13 skipped (round 2: 333) | +| `python3 -m unittest tests.test_new_wsl_handbook tests.test_new_wsl_client_config tests.test_new_wsl_profile tests.test_host_baseline_probe` | 0 | round 3: 309 tests, the modules that read the recipe, the receipt or the probe | +| `python3 -m unittest` over the 19 other modules that pin the touched files (`test_new_wsl_handbook`, `test_new_wsl_client_config`, `test_new_wsl_profile`, `test_host_baseline_probe`, `test_managed_block`, `test_codex_agents`, `test_codex_roles`, `test_scaffold_repo`, `test_bootstrap_full_profile`, `test_token_e2e_grader`, `test_landscape_sweep_harness`, `test_landscape_sweep_skills`, `test_freeze_snapshot`, `test_blind_checkout`, `test_runtime_worker_openhands_push_gate`, `test_runtime_worker_openhands_resolver`, `test_upstream_surface_watch`, `test_catalog_freshness_runtime`, `test_new_wsl_definitive_defaults`) | 1 | round 2: 2,237 tests, 15 skipped, 1 failure: F27c, as above (`Invalid cross-device link` at the clone, before any check; the suite's `TMPDIR` guard refuses a directory inside the repository) | +| `node test-envelope.mjs` and `node test-contract-mutations.mjs`, in `examples/claude-native/workflows` | 0 | round 2: 254 and 74 passed | +| `python3 scripts/validate.py` | 0 | round 3: 10,277 hashed files, 212 receipts | +| `python3 scripts/evidence_manifest.py --check` | 0 | round 3: passed | +| `python3 scripts/validate_convergence.py blueprints/convergence-practice/wsl-new-distro-image-20261001/experiment.json` | 0 | round 3: the record is valid with the relocated frozen inputs | +| `python3 -B tools/adoption/new_wsl_client_config.py --check` | 0 | round 3: check passed, with two warnings, both about slot `mcp-inspector`, from inputs this change does not touch (round 2 reported one: it read only the output's last two lines) | +| `python3 -B tools/adoption/new_wsl_client_config.py --render --host nativestack2604 --out /user-times-render` | 0 | round 2: each rendered carrier holds the sentence once and equals the committed carrier byte for byte; `managed_block`'s merge of each gives 12,071 and 8,601 bytes with one copy | +| `python3 scripts/build_new_wsl_handbook.py --check` | 0 | round 3: current outputs | +| Startup bytes: `PortableTopRuleTests.startup_files` on this tree, and on main `ecfa1127`'s startup files in a scratch directory | 0 | round 2: the Startup bytes table, unchanged from the first round | +| P2's render, then `cloud-init schema -c` (26.1-0ubuntu3~26.04.1, on NativeStack2604) | 0 | round 2: `Valid schema` | +| Path A simulation: `[boot]` and `systemd=true`, then the user-data's `write_files` content as cloud-init's YAML gives it, then wsl-setup 0.6.3's own `set_user_as_default` with only its file path changed | 0 | round 2: six lines, each once; wsl-setup changes nothing | +| Path B simulation: W6's `[user]` and `[time]` lines, run twice on `[boot]` and `systemd=true` | 0 | round 2: six lines, each once | +| Host read: `timedatectl show -p Timezone --value`; `tzutil.exe /g` (not a W5 run) | 0; 0 | round 2: `America/New_York`; `Eastern Standard Time` | +| `git diff --check` | 0 | round 3: clean | ## Sources @@ -238,12 +256,23 @@ prose changed afterwards. - pinned source MicrosoftDocs/WSL `7ea1c6f9e25f1c89a05a0e97e5325a02a66ac6cd`, `WSL/wsl-config.md:152-158`, which was still that file's latest commit on 2026-10-05; - section `[time]`, key `useWindowsTimezone`, boolean, default `true`. -- **microsoft/WSL** `91f161fa240dc355c1a88daabc8aac4273e35ba5` (tag 3.0.1, the release W1 requires), read 2026-10-05: +- **microsoft/WSL** `91f161fa240dc355c1a88daabc8aac4273e35ba5` (tag 3.0.1, the release W1 requires), read 2026-10-05; + the running-instance lines re-read 2026-10-06 UTC: - `src/linux/init/WslDistributionConfig.h:25` (`time.useWindowsTimezone`) and `:58` (`bool AutoUpdateTimezone = true;`), registered at `WslDistributionConfig.cpp:42`; - - `src/linux/init/timezone.cpp:22-110` (`UpdateTimezone`): no change when the key is off (`:49`), the mapping is - empty (`:54`) or the zoneinfo file is missing (`:68`); otherwise it links `/etc/localtime` (`:82`) and writes - `/etc/timezone` (`:92`). `src/linux/init/config.cpp:882` calls it at instance start; + - `src/linux/init/timezone.cpp:22-110` (`UpdateTimezone`): no change when the key is off (`:49-52`), the mapping is + empty (`:54-58`) or the zoneinfo file is missing (`:66-70`); otherwise it relinks `/etc/localtime` and rewrites + `/etc/timezone` (`:76-106`); + - at instance start: `ConfigInitializeCommon` reads `/etc/wsl.conf` once (`src/linux/init/config.cpp:532`), and + `config.cpp:882` calls `UpdateTimezone`; + - in a running instance: the service's window receives broadcast messages + (`src/windows/service/exe/LxssUserSession.cpp:607-647`, whose comment at `:619` names `WM_TIMECHANGE`). On + `WM_TIMECHANGE` (`:4294-4305`) it calls `_TimezoneUpdated`, which logs "Received timezone change notification" and + updates every running instance (`:3835-3846`). Each instance sends the mapped zone to its init + (`src/windows/service/exe/WslCoreInstance.cpp:349-362`), whose message loops in `InitEntryUtilityVm` and + `InitEntryWsl` call `UpdateTimezone` with the configuration read at start (`src/linux/init/init.cpp:2559-2561` and + `:2745-2747`). That a Windows time-zone change raises `WM_TIMECHANGE` rests on WSL's own comment and log text; no + Microsoft document was read for it; - `src/windows/common/helpers.cpp:358-413` (`GetLinuxTimezone`): the Windows zone from `GetDynamicTimeZoneInformation` and the region from `GetUserDefaultGeoName`, mapped by `ucal_getTimeZoneIDForWindowsID`, with Windows' own ICU (`src/windows/common/precomp.h:40` includes ``; `CMakeLists.txt:386` links `icu.lib`). diff --git a/tests/test_wsl_new_distro_recipe.py b/tests/test_wsl_new_distro_recipe.py index 419084ae7..455d2685e 100644 --- a/tests/test_wsl_new_distro_recipe.py +++ b/tests/test_wsl_new_distro_recipe.py @@ -93,7 +93,8 @@ - the review of the 2026-10-05 local-time change: W5 reads the zone the distribution reports, ``timedatectl show -p Timezone --value``, beside the Windows zone, ``tzutil /g``, once each and after ``id -u`` (so path B's repeat covers them); its proof names CLDR's ``windowsZones`` mapping, and the record's command table, the - checklist and the receipt example carry the pair. + checklist and the receipt example carry the pair. A mismatch is recorded and stops for review without the + failed-proof export and unregister, on both paths, and the proof names the remedy and the CLDR and tzutil pins. These are local consistency checks over repository text and an in-memory render of the templates. Nothing here runs wsl.exe, PowerShell, gpgv, journalctl or cloud-init; a pass is not a host run. @@ -300,6 +301,15 @@ # The review of the 2026-10-05 local-time change: W5 pairs the zone the distribution reports with the Windows zone. ZONE_DISTRIBUTION = "wsl.exe -d '' --exec timedatectl show -p Timezone --value" ZONE_WINDOWS = "tzutil /g" +# Its round-3 review: a mismatch is recorded and stops for review without the failed-proof export and unregister, since +# WSL leaves /etc/localtime alone when the mapping is empty or the zone's file is missing (timezone.cpp:54-58, :66-70); +# the proof names the remedy and pins the CLDR mapping and the tzutil page it compares against. +ZONE_MISMATCH_RULE = ("a mismatch is recorded with both outputs and stops the run for review, without the failed-proof " + "recovery below") +ZONE_EXCEPTION = "The time-zone pair is the one exception" +ZONE_REMEDY = "the image's `tzdata`" +ZONE_PINS = ("`release-48-2`", "`48fd0532`") +CHECKLIST_ZONE_RULE = "stops for review without the export and unregister" WSL_CONF_SECTIONS_ERROR = ("the appended /etc/wsl.conf text is not [time] holding only useWindowsTimezone=true, then [user] " "holding only the default user") STORVSC_COUNT = ("sudo journalctl -k -b 0 --no-pager | grep hv_storvsc | " @@ -1126,7 +1136,9 @@ def time_zone_pair_errors(recipe: str, record: str, checklist: str, receipt: dic also reads the zone the distribution reports beside the Windows zone, once each and after ``id -u`` (path B repeats W5's checks from ``id -u`` on). Its proof says how the two agree: CLDR's ``windowsZones`` mapping for the Windows region, which WSL applies through the ICU that Windows ships (microsoft/WSL 3.0.1, - src/windows/common/helpers.cpp:358-413).""" + src/windows/common/helpers.cpp:358-413). Round 3 of the review: a mismatch is recorded and stops for review + without the failed-proof export and unregister, on both paths; the proof names the remedy and pins the CLDR + release and the tzutil page, and the record's row and the checklist say the same.""" errors = [] commands = [command for shell, command in step_commands(recipe, "W5") if shell == "powershell"] for command in (ZONE_DISTRIBUTION, ZONE_WINDOWS): @@ -1139,12 +1151,21 @@ def time_zone_pair_errors(recipe: str, record: str, checklist: str, receipt: dic w5 = prose(section(recipe, "W5")) if "windowsZones" not in w5 or "`timedatectl show -p Timezone --value`" not in w5: errors.append("W5's proof does not say how the two zones agree (CLDR's windowsZones mapping)") + for phrase, what in ((ZONE_MISMATCH_RULE, "what a mismatch does"), (ZONE_EXCEPTION, "the failed-proof rule's exception"), + (ZONE_REMEDY, "the remedy"), *((pin, "the CLDR or tzutil pin") for pin in ZONE_PINS)): + if phrase not in w5: + errors.append(f"W5 lacks {what}: {phrase}") rows = [command for step, _, command, _ in command_table_rows(record) if step == "W5"] if rows.count(ZONE_DISTRIBUTION) != 1 or rows.count(ZONE_WINDOWS) != 1: errors.append("the record's command table does not hold each W5 zone row once") + proofs = [proof for step, _, command, proof in command_table_rows(record) if step == "W5" and command == ZONE_DISTRIBUTION] + if len(proofs) != 1 or "stops the run for review" not in proofs[0]: + errors.append("the record's W5 zone row does not say that a mismatch stops for review") line = checklist_line(checklist, "W5") if "`timedatectl show -p Timezone --value`" not in line or f"`{ZONE_WINDOWS}`" not in line: errors.append("the checklist's W5 line does not name both zone commands") + if CHECKLIST_ZONE_RULE not in line: + errors.append("the checklist's W5 line does not say what a mismatch does") listed = [(entry.get("step"), entry.get("cmd")) for entry in receipt.get("steps", []) if isinstance(entry, dict)] if listed.count(("W5", ZONE_DISTRIBUTION)) != 1 or listed.count(("W5", ZONE_WINDOWS)) != 1: errors.append("the receipt example does not record each zone command once under W5") @@ -2041,6 +2062,25 @@ def test_the_check_rejects_a_lost_command_an_early_pair_a_lost_mapping_and_lost_ "receipt without the entries": (recipe, record, checklist, dict(receipt, steps=steps)), }) + def test_the_check_rejects_a_mismatch_that_unregisters_a_lost_remedy_and_lost_pins(self): + recipe, record, checklist, receipt = self.inputs() + w5 = section(recipe, "W5") + row_rule = "; a mismatch stops the run for review without the failed-proof export and unregister |" + self.assertIn(row_rule, record) + self.assert_mutants_fail(time_zone_pair_errors, { + "the mismatch runs the recovery": (recipe.replace(w5, reword(w5, "without the failed-proof recovery below", + "with the failed-proof recovery below")), + record, checklist, receipt), + "no exception in the failure rule": (recipe.replace(w5, w5.replace(ZONE_EXCEPTION, + "The time-zone pair is no exception")), + record, checklist, receipt), + "no remedy": (recipe.replace(w5, w5.replace(ZONE_REMEDY, "the image")), record, checklist, receipt), + "no CLDR pin": (recipe.replace(w5, w5.replace(ZONE_PINS[0], "a CLDR release")), record, checklist, receipt), + "no tzutil pin": (recipe.replace(w5, w5.replace(ZONE_PINS[1], "the docs")), record, checklist, receipt), + "record row without the rule": (recipe, record.replace(row_rule, " |"), checklist, receipt), + "checklist without the rule": (recipe, record, checklist.replace(CHECKLIST_ZONE_RULE, "fails W5"), receipt), + }) + class KernelStorageTests(FollowUpCase): """Item D (critic item 3), under the coordinator's rule: P3 records a baseline and stops only on errors less than diff --git a/tools/adoption/host-baseline.md b/tools/adoption/host-baseline.md index 83ccda815..7d8ad6d39 100644 --- a/tools/adoption/host-baseline.md +++ b/tools/adoption/host-baseline.md @@ -48,7 +48,7 @@ attestation. The passwd record retains field 7 only, and the sudoers marker retains existence, mode and size only. Its owner column is discarded. Passwordless sudo remains -`unknown`: `adoption/platforms/linux-wsl2-new-distro.md:636` is a recipe criterion, +`unknown`: `adoption/platforms/linux-wsl2-new-distro.md:645` is a recipe criterion, not a per-host acceptance record. No such acceptance is linked by these receipts. PR-1 must label root rows `status-unknown` until it has that separate evidence; neither an absent marker nor a denied stat proves those rows unreachable. diff --git a/tools/adoption/host_baseline_probe.py b/tools/adoption/host_baseline_probe.py index c48b6dda8..d143188c2 100644 --- a/tools/adoption/host_baseline_probe.py +++ b/tools/adoption/host_baseline_probe.py @@ -338,7 +338,7 @@ def collect(home): "passwordless_sudo": { "marker": marker_observation(), "status": "unknown", - "other_source": "adoption/platforms/linux-wsl2-new-distro.md:636 (recipe criterion only; per-host acceptance not linked)", + "other_source": "adoption/platforms/linux-wsl2-new-distro.md:645 (recipe criterion only; per-host acceptance not linked)", }, "launchers": {name: binary_observation(name) for name in LAUNCHERS}, "launcher_packages": {name: package_observation(name) for name in ("time", "util-linux", "procps", "systemd", "gdb")}, From 418a3d9e01aa7ce69d786ae0ce53c3e40b800f27 Mon Sep 17 00:00:00 2001 From: seathatflowsinourveins <234074349+seathatflowsinourveins@users.noreply.github.com> Date: Mon, 5 Oct 2026 21:47:55 -0400 Subject: [PATCH 04/10] Hot files for #758 on main ecfa1127: regenerated new-WSL handbook, its receipt and the registry Hot-file protocol (docs/lanes.md): 1. take main's manifests/evidence.json; 2. run python3 scripts/build_new_wsl_handbook.py --write on the final tree; 3. re-register last, through host_receipts.register_file. Results: - docs/new-wsl-handbook.{md,json} are regenerated. They carry #703's catalog digest (8420f58e...) and the edited recipe's digest (3d616445...). - evidence/artifacts/new-wsl-handbook-20261001/receipt.json: - outputs are 50585de8... and 70cf495c...; - this change's one 2026-10-05 regenerations entry follows #703's, with previous_outputs b1dd8bdb... and 32da2ef7... (main's). - manifests/evidence.json is main's copy plus 20 changed and 5 new file entries. receipts[] and convergence_records[] are unchanged. - component_matrix.py --write and new_host_grand_list.py --write changed nothing. Co-Authored-By: Claude Opus 5.5 --- docs/new-wsl-handbook.json | 2 +- docs/new-wsl-handbook.md | 2 +- .../new-wsl-handbook-20261001/receipt.json | 15 ++- manifests/evidence.json | 95 ++++++++++++------- 4 files changed, 75 insertions(+), 39 deletions(-) diff --git a/docs/new-wsl-handbook.json b/docs/new-wsl-handbook.json index 8303ec164..2d5645dca 100644 --- a/docs/new-wsl-handbook.json +++ b/docs/new-wsl-handbook.json @@ -7799,7 +7799,7 @@ }, { "path": "adoption/platforms/linux-wsl2-new-distro.md", - "sha256": "4e505c4e7ca64d769bb0f9be4e506526dfaed79231e4a30a26bb811bcb967abe" + "sha256": "3d6164455a92300664d8b62870f2078a8163397aa8973131d037d7e474638026" }, { "path": "catalogs/foundation/new-wsl-architecture-20261001.json", diff --git a/docs/new-wsl-handbook.md b/docs/new-wsl-handbook.md index 61e0aabb7..945a6efb1 100644 --- a/docs/new-wsl-handbook.md +++ b/docs/new-wsl-handbook.md @@ -2129,7 +2129,7 @@ Reference edition: [catalogs/foundation/new-wsl-architecture-20261001.json](../c | --- | --- | | [adoption/manifest.json](../adoption/manifest.json) | `159ad1f56a9a654951eb16a4a66645378d225d8b924eede282815f49342add55` (without source.release_tag, source.release_commit, updated_at) | | [adoption/new-wsl-profile.json](../adoption/new-wsl-profile.json) | `7504e55c2e66730d5e0b0ead2be5abef39f3989afc69e1c3640e6709bdb3db04` | -| [adoption/platforms/linux-wsl2-new-distro.md](../adoption/platforms/linux-wsl2-new-distro.md) | `4e505c4e7ca64d769bb0f9be4e506526dfaed79231e4a30a26bb811bcb967abe` | +| [adoption/platforms/linux-wsl2-new-distro.md](../adoption/platforms/linux-wsl2-new-distro.md) | `3d6164455a92300664d8b62870f2078a8163397aa8973131d037d7e474638026` | | [catalogs/foundation/new-wsl-architecture-20261001.json](../catalogs/foundation/new-wsl-architecture-20261001.json) | `8420f58ef2f613bab8da7a4486e907a9118b178d9d8e5eb8c70e4d84950c6c73` | | [catalogs/landscape/research-state.json](../catalogs/landscape/research-state.json) | `fe142c1b8b8ec8b92d69ec5971d68028225899c101ffc43cea618883b119332a` | | [catalogs/landscape/us-equities.json](../catalogs/landscape/us-equities.json) | `cf441d393b1a1ee49c57d62592e2c7477de2003ca40aa9e1933a6c9b28d4a618` | diff --git a/evidence/artifacts/new-wsl-handbook-20261001/receipt.json b/evidence/artifacts/new-wsl-handbook-20261001/receipt.json index 6fae320ec..50b6bc28e 100644 --- a/evidence/artifacts/new-wsl-handbook-20261001/receipt.json +++ b/evidence/artifacts/new-wsl-handbook-20261001/receipt.json @@ -6,8 +6,8 @@ "generator_sha256": "6e6ee9cfb4d697b3bea2d7684e042e638be20634792f43e517b205463b1967f9", "profile_sha256": "7504e55c2e66730d5e0b0ead2be5abef39f3989afc69e1c3640e6709bdb3db04", "outputs": { - "docs/new-wsl-handbook.md": "b1dd8bdb72f32d3ab449aa90014b9621f779a5a7b3db590e4253120ba6c0bdfe", - "docs/new-wsl-handbook.json": "32da2ef7bbb47cbd8e75a1674b19c9dd1ee8e3cc855a05efee322765186e079e" + "docs/new-wsl-handbook.md": "50585de8165cddcc4e0b115fb2eb6e066796ca66ef6fefc3e1f16d0095c89fb3", + "docs/new-wsl-handbook.json": "70cf495c8d914442db92d938c987fcba39a99382ad28463fb0641c4fa96bc96a" }, "inventory": { "layers": 37, @@ -249,6 +249,17 @@ "docs/new-wsl-handbook.json": "c49d7d2457692e6d3895cd0896ed601d81d55a90b2f3dd9ff27cec6116da7a16" }, "check": "python3 scripts/build_new_wsl_handbook.py --check" + }, + { + "date": "2026-10-05", + "command": "python3 scripts/build_new_wsl_handbook.py --write", + "native_exit_code": 0, + "cause": "The user-facing local-time change (docs/decisions/2026-10-05-user-facing-local-time.md), with its review fixes, edits adoption/platforms/linux-wsl2-new-distro.md, whose SHA-256 the handbook's source table holds; rebased onto main ecfa1127 (#703, the regeneration above) and regenerated there, so the outputs carry #703's catalog digest and the edited recipe's digest.", + "previous_outputs": { + "docs/new-wsl-handbook.md": "b1dd8bdb72f32d3ab449aa90014b9621f779a5a7b3db590e4253120ba6c0bdfe", + "docs/new-wsl-handbook.json": "32da2ef7bbb47cbd8e75a1674b19c9dd1ee8e3cc855a05efee322765186e079e" + }, + "check": "python3 scripts/build_new_wsl_handbook.py --check" } ] } diff --git a/manifests/evidence.json b/manifests/evidence.json index 9dfe095f7..b350e408d 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -4877,8 +4877,8 @@ }, { "path": "adoption/new-wsl/claude-user-instructions.md", - "sha256": "9bc0f41208ce30ec46a1f08fa7d0b6680f14b8e56266a414389d7e69d2ea36c2", - "bytes": 11575 + "sha256": "585b88f2c381f966ffd42803a6c17d9630a8ff285369c386242d41638c86258f", + "bytes": 11805 }, { "path": "adoption/new-wsl/client-config-map.json", @@ -4887,8 +4887,8 @@ }, { "path": "adoption/new-wsl/codex-user-instructions.md", - "sha256": "f8874e406be3e7cc5a43bf9747eed5e6923ab5a997ac2f384694b6cc9f6bf539", - "bytes": 8373 + "sha256": "c5961b3ff4625d51e5ccc6c2ebc6329c5d0f5d7274b86a7c2f23f7e7c7d0357d", + "bytes": 8601 }, { "path": "adoption/new-wsl/templates/claude-user.mcp.additions.json", @@ -4947,8 +4947,8 @@ }, { "path": "adoption/platforms/linux-wsl2-new-distro.md", - "sha256": "4e505c4e7ca64d769bb0f9be4e506526dfaed79231e4a30a26bb811bcb967abe", - "bytes": 92982 + "sha256": "3d6164455a92300664d8b62870f2078a8163397aa8973131d037d7e474638026", + "bytes": 95459 }, { "path": "adoption/platforms/linux-wsl2.md", @@ -5017,8 +5017,8 @@ }, { "path": "adoption/templates/codex.AGENTS.template.md", - "sha256": "453173835cb0aec0d4e7396dc21fcc8e1e4bf7c0429078b3d2766b74dceb290f", - "bytes": 7307 + "sha256": "2d62d6c73dbbfeb2c6d7632da0f76e77686ef0f422c6b7a845ce2065e8b6e52b", + "bytes": 7535 }, { "path": "adoption/templates/codex.config.template.toml", @@ -5072,18 +5072,18 @@ }, { "path": "adoption/templates/wsl/cloud-init.user-data.template", - "sha256": "08e6a3f6b078dfa54d78297596d54729d10528d57147b9b9eae063c37160eeb9", - "bytes": 1527 + "sha256": "fea33774ce5989fef58465e226a1982e905e39fa8f8e4029e678d4700668afba", + "bytes": 1851 }, { "path": "adoption/templates/wsl/first-boot-checklist.md", - "sha256": "1a06df0e6d50b07b439317a61a450e450127052589f63254e769d99166e3603b", - "bytes": 15192 + "sha256": "12170643cc9fdf8ba4503762fd61147f902bb85c94f0f73b7be0522b31fd1014", + "bytes": 15597 }, { "path": "adoption/templates/wsl/stage1-receipt.example.json", - "sha256": "7c0811c460fe8732f413b954cd7ddadb5cc41a5300f8686b5b83aefcd80a971a", - "bytes": 60963 + "sha256": "20c5ef6d34a9de45473be73e2bdf2114eb45c7d8e9389f15c754445d0cb8e398", + "bytes": 61751 }, { "path": "adoption/tools/README.md", @@ -7173,8 +7173,8 @@ }, { "path": "blueprints/convergence-practice/wsl-new-distro-image-20261001/experiment.json", - "sha256": "822da49b85f1645c09887f27bf24268e554f2f27594719620e5cc8b28530853e", - "bytes": 23166 + "sha256": "ffbbd654c88cb8c78ddff1174dd2e15f4478cee78fa4d126a7fd16a3661f7c31", + "bytes": 23358 }, { "path": "blueprints/convergence-practice/wsl-restore/README.md", @@ -16848,8 +16848,8 @@ }, { "path": "docs/decisions/2026-10-01-new-wsl-distro-recipe.md", - "sha256": "ec03a494aa4d80ac004f74710250bf1ac7cf18e0506758d97a1cf98a0cee42a3", - "bytes": 147654 + "sha256": "753f01a78f820933eb8e2fe3be7f9cd7582710e0907c815e3274537be797ab83", + "bytes": 150728 }, { "path": "docs/decisions/2026-10-01-new-wsl-handbook-generator.md", @@ -16868,8 +16868,8 @@ }, { "path": "docs/decisions/2026-10-02-new-wsl-client-configuration.md", - "sha256": "549a70f992717568f6fe8811b3f4d7ffbe3299015458686643fffe54a6f86d7b", - "bytes": 134812 + "sha256": "7a1666642e8ce5aa70901d114f09dc718a43861e29c461457949e3005814e266", + "bytes": 135484 }, { "path": "docs/decisions/2026-10-02-omniroute-mac-rebuild.md", @@ -17358,12 +17358,12 @@ }, { "path": "docs/new-wsl-handbook.json", - "sha256": "32da2ef7bbb47cbd8e75a1674b19c9dd1ee8e3cc855a05efee322765186e079e", + "sha256": "70cf495c8d914442db92d938c987fcba39a99382ad28463fb0641c4fa96bc96a", "bytes": 685881 }, { "path": "docs/new-wsl-handbook.md", - "sha256": "b1dd8bdb72f32d3ab449aa90014b9621f779a5a7b3db590e4253120ba6c0bdfe", + "sha256": "50585de8165cddcc4e0b115fb2eb6e066796ca66ef6fefc3e1f16d0095c89fb3", "bytes": 362513 }, { @@ -39498,8 +39498,8 @@ }, { "path": "evidence/artifacts/new-wsl-handbook-20261001/receipt.json", - "sha256": "36385bc3af6e6cfdaac4dc6fbdfad33eb8f149a80ec726dcc8a130c8689b8749", - "bytes": 12145 + "sha256": "ef808e29ee3fe8914d4757504897cd892e0996b09fc8a6cf96279db0d0e96fcc", + "bytes": 12984 }, { "path": "evidence/artifacts/new-wsl-handbook-20261001/slot-state-and-core-recipes.json", @@ -50446,6 +50446,31 @@ "sha256": "e5ba2fc9e397b97888a4929069434321e4ea17ea298a536ae20360638a304d50", "bytes": 11763 }, + { + "path": "evidence/artifacts/user-facing-local-time-20261005/README.md", + "sha256": "28476281a102651a8413997b69597d4d7195ee425e2d596772c42d8b09bb370f", + "bytes": 1955 + }, + { + "path": "evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/2026-10-01-new-wsl-distro-recipe.md.txt", + "sha256": "ec03a494aa4d80ac004f74710250bf1ac7cf18e0506758d97a1cf98a0cee42a3", + "bytes": 147654 + }, + { + "path": "evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/cloud-init.user-data.template.txt", + "sha256": "08e6a3f6b078dfa54d78297596d54729d10528d57147b9b9eae063c37160eeb9", + "bytes": 1527 + }, + { + "path": "evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/first-boot-checklist.md.txt", + "sha256": "1a06df0e6d50b07b439317a61a450e450127052589f63254e769d99166e3603b", + "bytes": 15192 + }, + { + "path": "evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/stage1-receipt.example.json.txt", + "sha256": "7c0811c460fe8732f413b954cd7ddadb5cc41a5300f8686b5b83aefcd80a971a", + "bytes": 60963 + }, { "path": "evidence/artifacts/workstation-lane-20260925/nemotron-qualification/model_card_check.py", "sha256": "c6ecc30a169b4af802f7e8497c7ebfc7c9373114bdd8589ef4ee4db102e516c3", @@ -52933,8 +52958,8 @@ }, { "path": "examples/claude-native/CLAUDE.md", - "sha256": "9bc0f41208ce30ec46a1f08fa7d0b6680f14b8e56266a414389d7e69d2ea36c2", - "bytes": 11575 + "sha256": "585b88f2c381f966ffd42803a6c17d9630a8ff285369c386242d41638c86258f", + "bytes": 11805 }, { "path": "examples/claude-native/agents/blind-adjudicator.md", @@ -54393,8 +54418,8 @@ }, { "path": "tests/test_codex_worker_lane.py", - "sha256": "d35f474cbdfda1e7666ee84479047de04f3666913d15110f7736f8c074bd8d41", - "bytes": 191406 + "sha256": "8ad69ae5015c4d212001d7eedca10a35fa3fbfc45201d336c57de010067375fc", + "bytes": 191495 }, { "path": "tests/test_codex_worker_skill.py", @@ -54608,8 +54633,8 @@ }, { "path": "tests/test_install_claude_profile.py", - "sha256": "66225f2f62892b5600883e15ac362c0a62ef0d858e856ed00b86f418620699bc", - "bytes": 131331 + "sha256": "1649fe42c58014d3b5b00290fd2f4723bef248489f92aae425cbdab29fab5f66", + "bytes": 132431 }, { "path": "tests/test_install_skills.py", @@ -55188,8 +55213,8 @@ }, { "path": "tests/test_wsl_new_distro_recipe.py", - "sha256": "9dab36806887d763e9df4cb8af472c223470c23ac133ebff5948ce64d13b898d", - "bytes": 221208 + "sha256": "14c09b86ffb9db8f3c565da498c8035edec7e223079881c9ab51e1a94f632ee3", + "bytes": 233146 }, { "path": "tests/test_wsl_restore.py", @@ -55238,12 +55263,12 @@ }, { "path": "tools/adoption/host-baseline.md", - "sha256": "478e7f061e964606cf4f7209155f596b76bf04bc5bd6a67fdaba7a5a0cbc4c4c", + "sha256": "e7527052bd6c4f36bc7449185a978406bc796acf0f30c328f9ee6ec495a75a86", "bytes": 16770 }, { "path": "tools/adoption/host_baseline_probe.py", - "sha256": "f99aaa0e8e3b6ef057dc47fb5b0f9fbf281bf07686cc5f2d57d287753c554c78", + "sha256": "e58d2797f72d298b10d4e501e709afc524b09180805c9bf9cf3c11505e7f33a0", "bytes": 17992 }, { @@ -55263,7 +55288,7 @@ }, { "path": "tools/adoption/new_wsl_client_config.py", - "sha256": "ae8f67fd2ed4028998bbf7524dc758491ba98c2b9c2b6ed82312c50b9faa4127", + "sha256": "a4a3b5fd21037fbffe1f96cf1c3b55cdf429ffa8ae3a680c3b48bbab7f5bcd96", "bytes": 153006 }, { From 0df3f0dc1187adddba92026061f4e2584aed172e Mon Sep 17 00:00:00 2001 From: seathatflowsinourveins <234074349+seathatflowsinourveins@users.noreply.github.com> Date: Tue, 6 Oct 2026 08:08:38 -0400 Subject: [PATCH 05/10] fix: repair Windows time proof and scope UTC commit records --- adoption/new-wsl/claude-user-instructions.md | 2 +- adoption/new-wsl/codex-user-instructions.md | 2 +- adoption/platforms/linux-wsl2-new-distro.md | 22 ++++++- adoption/templates/codex.AGENTS.template.md | 2 +- .../templates/wsl/first-boot-checklist.md | 2 +- .../templates/wsl/stage1-receipt.example.json | 9 ++- .../2026-10-01-new-wsl-distro-recipe.md | 3 +- .../2026-10-05-user-facing-local-time.md | 26 +++++--- examples/claude-native/CLAUDE.md | 2 +- tests/test_install_claude_profile.py | 5 +- tests/test_wsl_new_distro_recipe.py | 60 +++++++++++++++++++ 11 files changed, 115 insertions(+), 20 deletions(-) diff --git a/adoption/new-wsl/claude-user-instructions.md b/adoption/new-wsl/claude-user-instructions.md index c9017bd81..cb14a82b9 100644 --- a/adoption/new-wsl/claude-user-instructions.md +++ b/adoption/new-wsl/claude-user-instructions.md @@ -21,7 +21,7 @@ Decide by evidence and research convergence: a choice stands when current primar - Resolve supported findings before claiming completion. Distinguish measured results, simulations and untested boundaries; unchanged upstream tests, local integration checks, synthetic fixtures and actual provider execution are different evidence classes. New machines collect their own evidence; historical receipts do not certify the new host. - Settle a discoverable harness capability by step 2 before asking the user; compare community alternatives against demonstrated gaps, retain source pins and reasons, and run the selected native path through returned results. Dated exclusions are not current availability evidence. Research only the relevant layers. - Carry the user's authorized work through implementation, relevant verification and a concise handoff. Use a short plan for bounded work; do not add intake, repeated approvals, diagnostic campaigns or restarts without a concrete need. Preserve existing edits, native accounts, model choices and project scope. -- When you tell the user a time, give it first in the host's local time zone (read it with `timedatectl` or `date`), with UTC beside it, for example "4:00 PM EDT (20:00Z)"; keep UTC in ledger rows, receipts, evidence and commits. +- When you tell the user a time, give it first in the host's local time zone (read it with `timedatectl` or `date`), with UTC beside it, for example "4:00 PM EDT (20:00Z)". Write timestamps in ledger rows, receipts, evidence and commit messages in UTC (RFC 3339 with `Z`); Git author/committer metadata retains its native format. - Use the project's canonical instructions for memory scope, tests and domain rules, and keep durable memory and indexes scoped to that project. ## Token practice (base layer) diff --git a/adoption/new-wsl/codex-user-instructions.md b/adoption/new-wsl/codex-user-instructions.md index 5d1e28af8..d50073894 100644 --- a/adoption/new-wsl/codex-user-instructions.md +++ b/adoption/new-wsl/codex-user-instructions.md @@ -26,7 +26,7 @@ Long commands: set `yield_time_ms` 30000; while a `session_id` returns, poll `wr Web research: if the stack installs GPT Researcher, run `bash ~/code/native-agent-stack/tools/research/gpt_researcher.sh ""` as a long command (stops at 1,500 s); use short, unseeded current-month queries; reports are leads: re-read facts in primary sources. Message Claude Code in one long command: set `msg` via a quoted heredoc (`msg=$(cat <<'MSG'`, text, `MSG`, `)` each on its own line), then `printf '%s\n\nreply: codex queue --thread %s\n' "$msg" "$CODEX_THREAD_ID" | claude -p -n "codex-$(printf '%.8s' "$CODEX_THREAD_ID")" --permission-mode bypassPermissions --max-turns 3 --output-format stream-json --verbose "Send the text on stdin, complete and verbatim, to the session named with exactly one SendMessage call, then stop."`. Codex receives queued messages only between turns; idle delay is up to ~20 s. -When you tell the user a time, give it first in the host's local time zone (read it with `timedatectl` or `date`), with UTC beside it, for example "4:00 PM EDT (20:00Z)"; keep UTC in ledger rows, receipts, evidence and commits. +When you tell the user a time, give it first in the host's local time zone (read it with `timedatectl` or `date`), with UTC beside it, for example "4:00 PM EDT (20:00Z)". Write timestamps in ledger rows, receipts, evidence and commit messages in UTC (RFC 3339 with `Z`); Git author/committer metadata retains its native format. # RTK diff --git a/adoption/platforms/linux-wsl2-new-distro.md b/adoption/platforms/linux-wsl2-new-distro.md index 20284975d..a68031d76 100644 --- a/adoption/platforms/linux-wsl2-new-distro.md +++ b/adoption/platforms/linux-wsl2-new-distro.md @@ -562,6 +562,7 @@ wsl.exe -d '' -u root --exec cloud-init schema --system wsl.exe -d '' -u root --exec cat /etc/wsl.conf wsl.exe -d '' --exec timedatectl show -p Timezone --value tzutil /g +if (-not ('WslRegionProof.Native' -as [type])) { Add-Type -Namespace WslRegionProof -Name Native -MemberDefinition '[System.Runtime.InteropServices.DllImport("kernel32.dll", CharSet=System.Runtime.InteropServices.CharSet.Unicode, ExactSpelling=true, SetLastError=true)] public static extern int GetUserDefaultGeoName(System.Text.StringBuilder geoName, int geoNameCount);' -ErrorAction Stop }; $wslRegionBuffer = [System.Text.StringBuilder]::new(16); if ([WslRegionProof.Native]::GetUserDefaultGeoName($wslRegionBuffer, $wslRegionBuffer.Capacity) -eq 0) { throw [System.ComponentModel.Win32Exception]::new([System.Runtime.InteropServices.Marshal]::GetLastWin32Error()) }; $wslRegionBuffer.ToString() wsl.exe -d '' -u root --exec ls -l /etc/cloud/cloud-init.disabled wsl.exe -d '' -u root --exec sudo -l -U '' wsl.exe --list --verbose @@ -636,7 +637,15 @@ Proof (path A, cloud-init provisioned the instance): ships and, while `useWindowsTimezone` was true when the instance started, links `/etc/localtime` to it at each instance start and again in a running instance when Windows' time zone changes (microsoft/WSL `91f161fa`, tag 3.0.1: `src/windows/common/helpers.cpp:358-413`, `src/linux/init/timezone.cpp:22-110`; the decision record cites both - paths). Record both outputs. On either path, a mismatch is recorded with both outputs and stops the run for review, + paths). The region command calls the same `GetUserDefaultGeoName` API under the Windows account that launches + ``; record its exact ISO 3166-1 alpha-2 or numeric UN M.49 result separately from both zone outputs, as + `windows_region`. A culture name or `Get-WinHomeLocation`'s numeric GeoID is not this mapping input. API failure + throws and stops W5 rather than substituting a region. This small PowerShell bridge follows Microsoft's + [native-API Add-Type pattern](https://github.com/MicrosoftDocs/PowerShell-Docs/blob/5b129a73fbe761fc12516c5f9776700daa0aa8a4/reference/5.1/Microsoft.PowerShell.Utility/Add-Type.md#L200) + and [GetUserDefaultGeoName contract](https://github.com/MicrosoftDocs/sdk-api/blob/7b93b54da4b144f042bef2ed25aa902d58916dc4/sdk-api-src/content/winnls/nf-winnls-getuserdefaultgeoname.md#L54). + The CLDR pin is a reference mapping; Windows' shipped ICU data version is not established here. These sequential + reads must be repeated if the zone or region changes during the proof. On either path, a mismatch is recorded with + both outputs and stops the run for review, without the failed-proof recovery below: WSL leaves `/etc/localtime` unchanged when the Windows-to-IANA mapping is empty (`Windows to Linux timezone mapping was not possible.`, `timezone.cpp:54-58`) or the image lacks the zone's file (`... not found. Is the tzdata package installed?`, `:66-70`). The remedy is to check the Windows region and @@ -801,11 +810,18 @@ printf '%s ALL=(ALL) NOPASSWD:ALL\n' '' > /etc/sudoers.d/90-wsl-defaul chmod 0440 /etc/sudoers.d/90-wsl-default-user visudo -cf /etc/sudoers.d/90-wsl-default-user grep -q '^\[user\]' /etc/wsl.conf || printf '\n[user]\ndefault=%s\n' '' >> /etc/wsl.conf -grep -q '^\[time\]' /etc/wsl.conf || printf '\n[time]\nuseWindowsTimezone=true\n' >> /etc/wsl.conf +python3 -c 'import configparser, io; from pathlib import Path; p = Path("/etc/wsl.conf"); c = configparser.ConfigParser(interpolation=None); c.optionxform = str; c.read_string(p.read_text() if p.exists() else ""); c.has_section("time") or c.add_section("time"); c.set("time", "useWindowsTimezone", "true"); out = io.StringIO(); c.write(out, space_around_delimiters=False); p.write_text(out.getvalue())' || exit "$?" touch /etc/cloud/cloud-init.disabled ``` -The last line writes the marker the official first-run command writes after cloud-init. `/etc/wsl.conf` takes effect at +The Python command updates the actual key when `[time]` already exists, including `false` or a missing key, and adds +the section only when absent. It uses the maintained standard-library +[ConfigParser read/set/write API](https://github.com/python/cpython/blob/v3.13.16/Doc/library/configparser.rst), +preserves other section values and key case, and writes compact `key=value` text. Its INI serialization normalizes +formatting and drops comments; malformed or duplicate-section input fails before writing and exits the root block +before the completion marker. This is a provisioning +step, not a host command to run during a paper session. The last line writes the marker the official first-run +command writes after cloud-init. `/etc/wsl.conf` takes effect at the next start, so terminate the new distribution only and wait until it is no longer running: ```powershell diff --git a/adoption/templates/codex.AGENTS.template.md b/adoption/templates/codex.AGENTS.template.md index 9feccea69..5710431e5 100644 --- a/adoption/templates/codex.AGENTS.template.md +++ b/adoption/templates/codex.AGENTS.template.md @@ -26,7 +26,7 @@ Long commands: set `yield_time_ms` 30000; while a `session_id` returns, poll `wr Web research: if the stack installs GPT Researcher, run `bash ~/code/native-agent-stack/tools/research/gpt_researcher.sh ""` as a long command (stops at 1,500 s); use short, unseeded current-month queries; reports are leads: re-read facts in primary sources. Message Claude Code in one long command: set `msg` via a quoted heredoc (`msg=$(cat <<'MSG'`, text, `MSG`, `)` each on its own line), then `printf '%s\n\nreply: codex queue --thread %s\n' "$msg" "$CODEX_THREAD_ID" | claude -p -n "codex-$(printf '%.8s' "$CODEX_THREAD_ID")" --permission-mode bypassPermissions --max-turns 3 --output-format stream-json --verbose "Send the text on stdin, complete and verbatim, to the session named with exactly one SendMessage call, then stop."`. Codex receives queued messages only between turns; idle delay is up to ~20 s. -When you tell the user a time, give it first in the host's local time zone (read it with `timedatectl` or `date`), with UTC beside it, for example "4:00 PM EDT (20:00Z)"; keep UTC in ledger rows, receipts, evidence and commits. +When you tell the user a time, give it first in the host's local time zone (read it with `timedatectl` or `date`), with UTC beside it, for example "4:00 PM EDT (20:00Z)". Write timestamps in ledger rows, receipts, evidence and commit messages in UTC (RFC 3339 with `Z`); Git author/committer metadata retains its native format. diff --git a/adoption/templates/wsl/first-boot-checklist.md b/adoption/templates/wsl/first-boot-checklist.md index 073962cac..4a1bb4499 100644 --- a/adoption/templates/wsl/first-boot-checklist.md +++ b/adoption/templates/wsl/first-boot-checklist.md @@ -42,7 +42,7 @@ readlink /proc/self/ns/cgroup - [ ] **W2** Explicit `` is one of the two controlled arms; download URL, filename and catalog entry match that release. The actual whole-image, signed Canonical publication and pinned DistributionInfo.json SHA-256 all equal that release's exact hash above; actual file size recorded (run 2 observed 418,495,746 bytes for 26.04.1); no `throw`. - [ ] **W3** `%USERPROFILE%\.cloud-init\.user-data` exists, starts with `#cloud-config`, names `` twice and contains no `${`; its SHA-256 equals P2's. - [ ] **W4** The selected `ubuntu--wsl-amd64.wsl` installs with exit 0; `Distribution successfully installed`; `` is `Stopped` at version 2; the starred line is unchanged. -- [ ] **W5** First launch exit 0, without the prompt marker; `wsl: Failed to start the systemd user session` stops even with exit 0; `id -un` is `` and `id -u` is `1000`; getty `is-enabled` prints `masked` (normal exit 1), and `show` prints `LoadState=masked`, `ActiveState=inactive`, `NRestarts=0`, recorded as `getty_mask`; selected-image `cloud-init --version` recorded; `cloud-init status --long` prints `status: disabled` with `boot_status_code: disabled-by-marker-file` (with the template's `bootcmd` it exits 2 on one expected recoverable error, `Failed to wait for network`, because WSL masks `systemd-networkd-wait-online.service`; any other recoverable error is recorded and reviewed); `/var/lib/cloud/data/result.json` names `DataSourceWSL` with `"errors": []`; `/var/lib/cloud/data/status.json` shows the four stages finished without errors; `cloud-init schema --system` prints `Valid schema user-data` and exits 0; `/etc/wsl.conf` names the user once and sets `useWindowsTimezone=true` under `[time]`; `timedatectl show -p Timezone --value` prints the IANA zone that CLDR's `windowsZones` mapping gives for the zone `tzutil /g` prints and the Windows region, both recorded; a mismatch, on either path, is recorded and stops for review without the export and unregister: check the Windows region and zone against that mapping and the image's `tzdata`; `/etc/cloud/cloud-init.disabled` exists; sudo is NOPASSWD; the starred line is unchanged. After the first launch, while both distributions run and before F1, the already-running distribution reads a system unit active in both: its `cgroup.procs` has local process ids and no `0` entry, and the new distribution's `user@.service` is active. An empty or unreadable file is inconclusive and stops. The paired block below records each of the five commands on each side in its own receipt entry with its own exit, in `paired_isolation`: the same uid, both user managers `active`, the new distribution `running` with no failed unit or `degraded` with `systemd-binfmt.service` as its only failed unit (message proved at F1), two `cgroup:[...]` values that differ from each other and are not `cgroup:[4026531835]`, and all five workstation values equal to its W1 baseline. A getty failed in the new distribution or newly failed in the workstation fails the proof. Any other failed W5 proof terminates only the new distribution, never stops or restarts a unit in either, exports `-w5-failed.tar` with the exit guard and its cause (`cgroup`, `tty` or a short text) in `w5_failure_export`, unregisters and checks surviving `WSLInterop` and a Windows executable, with R1's records and stop for review. Do not take path B for any such failure. On both paths, record P3's baseline before the import and `second_count` after the first launch. An increase confined to the window in which the new disk is attached, before cloud-init starts, is recorded with the new lines and does not stop the run. Any storage error after that window, or any provisioning step that fails with a storage cause, stops the run. An unknown attachment window stops for review; storage failures retain microsoft/WSL#41482 and their own cause. +- [ ] **W5** First launch exit 0, without the prompt marker; `wsl: Failed to start the systemd user session` stops even with exit 0; `id -un` is `` and `id -u` is `1000`; getty `is-enabled` prints `masked` (normal exit 1), and `show` prints `LoadState=masked`, `ActiveState=inactive`, `NRestarts=0`, recorded as `getty_mask`; selected-image `cloud-init --version` recorded; `cloud-init status --long` prints `status: disabled` with `boot_status_code: disabled-by-marker-file` (with the template's `bootcmd` it exits 2 on one expected recoverable error, `Failed to wait for network`, because WSL masks `systemd-networkd-wait-online.service`; any other recoverable error is recorded and reviewed); `/var/lib/cloud/data/result.json` names `DataSourceWSL` with `"errors": []`; `/var/lib/cloud/data/status.json` shows the four stages finished without errors; `cloud-init schema --system` prints `Valid schema user-data` and exits 0; `/etc/wsl.conf` names the user once and sets `useWindowsTimezone=true` under `[time]`; `timedatectl show -p Timezone --value` prints the IANA zone that CLDR's `windowsZones` mapping gives for the zone `tzutil /g` prints and the exact Windows GetUserDefaultGeoName region, all three recorded separately as windows_region and both zone outputs; a mismatch, on either path, is recorded and stops for review without the export and unregister: check the Windows region and zone against that mapping and the image's `tzdata`; `/etc/cloud/cloud-init.disabled` exists; sudo is NOPASSWD; the starred line is unchanged. After the first launch, while both distributions run and before F1, the already-running distribution reads a system unit active in both: its `cgroup.procs` has local process ids and no `0` entry, and the new distribution's `user@.service` is active. An empty or unreadable file is inconclusive and stops. The paired block below records each of the five commands on each side in its own receipt entry with its own exit, in `paired_isolation`: the same uid, both user managers `active`, the new distribution `running` with no failed unit or `degraded` with `systemd-binfmt.service` as its only failed unit (message proved at F1), two `cgroup:[...]` values that differ from each other and are not `cgroup:[4026531835]`, and all five workstation values equal to its W1 baseline. A getty failed in the new distribution or newly failed in the workstation fails the proof. Any other failed W5 proof terminates only the new distribution, never stops or restarts a unit in either, exports `-w5-failed.tar` with the exit guard and its cause (`cgroup`, `tty` or a short text) in `w5_failure_export`, unregisters and checks surviving `WSLInterop` and a Windows executable, with R1's records and stop for review. Do not take path B for any such failure. On both paths, record P3's baseline before the import and `second_count` after the first launch. An increase confined to the window in which the new disk is attached, before cloud-init starts, is recorded with the new lines and does not stop the run. Any storage error after that window, or any provisioning step that fails with a storage cause, stops the run. An unknown attachment window stops for review; storage failures retain microsoft/WSL#41482 and their own cause. ```sh id -u diff --git a/adoption/templates/wsl/stage1-receipt.example.json b/adoption/templates/wsl/stage1-receipt.example.json index e5b6afbb2..ae369a101 100644 --- a/adoption/templates/wsl/stage1-receipt.example.json +++ b/adoption/templates/wsl/stage1-receipt.example.json @@ -93,6 +93,7 @@ "oobe_failed_marker": "", "schema_system": "", "cloud_init_version": "", + "windows_region": "", "cgroup_isolation": ".service; the paired five-value record is paired_isolation; any failed W5 proof uses the guarded w5_failure_export/removal>", "systemd_user_session_warning": "", "getty_mask": { @@ -778,6 +779,12 @@ "exit": "", "output_excerpt": "" }, + { + "step": "W5", + "cmd": "if (-not ('WslRegionProof.Native' -as [type])) { Add-Type -Namespace WslRegionProof -Name Native -MemberDefinition '[System.Runtime.InteropServices.DllImport(\"kernel32.dll\", CharSet=System.Runtime.InteropServices.CharSet.Unicode, ExactSpelling=true, SetLastError=true)] public static extern int GetUserDefaultGeoName(System.Text.StringBuilder geoName, int geoNameCount);' -ErrorAction Stop }; $wslRegionBuffer = [System.Text.StringBuilder]::new(16); if ([WslRegionProof.Native]::GetUserDefaultGeoName($wslRegionBuffer, $wslRegionBuffer.Capacity) -eq 0) { throw [System.ComponentModel.Win32Exception]::new([System.Runtime.InteropServices.Marshal]::GetLastWin32Error()) }; $wslRegionBuffer.ToString()", + "exit": "", + "output_excerpt": "" + }, { "step": "W5", "cmd": "wsl.exe -d '' -u root --exec ls -l /etc/cloud/cloud-init.disabled", @@ -1074,7 +1081,7 @@ }, { "step": "W6", - "cmd": "grep -q '^\\[time\\]' /etc/wsl.conf || printf '\\n[time]\\nuseWindowsTimezone=true\\n' >> /etc/wsl.conf", + "cmd": "python3 -c 'import configparser, io; from pathlib import Path; p = Path(\"/etc/wsl.conf\"); c = configparser.ConfigParser(interpolation=None); c.optionxform = str; c.read_string(p.read_text() if p.exists() else \"\"); c.has_section(\"time\") or c.add_section(\"time\"); c.set(\"time\", \"useWindowsTimezone\", \"true\"); out = io.StringIO(); c.write(out, space_around_delimiters=False); p.write_text(out.getvalue())' || exit \"$?\"", "exit": "", "output_excerpt": "" }, diff --git a/docs/decisions/2026-10-01-new-wsl-distro-recipe.md b/docs/decisions/2026-10-01-new-wsl-distro-recipe.md index b92233c8d..3444e96cf 100644 --- a/docs/decisions/2026-10-01-new-wsl-distro-recipe.md +++ b/docs/decisions/2026-10-01-new-wsl-distro-recipe.md @@ -703,6 +703,7 @@ workstation distribution (Evidence classes). | W5 | powershell | `wsl.exe -d '' -u root --exec cat /etc/wsl.conf` | `[boot]`, `systemd=true`, `[time]`, `useWindowsTimezone=true`, `[user]`, `default=`, each once | | W5 | powershell | `wsl.exe -d '' --exec timedatectl show -p Timezone --value` | the IANA zone that CLDR's `windowsZones` mapping gives for `tzutil /g`'s zone and the Windows region, for example `America/New_York`; recorded; a mismatch stops the run for review without the failed-proof export and unregister | | W5 | powershell | `tzutil /g` | the Windows time zone ID, for example `Eastern Standard Time`; recorded | +| W5 | powershell | `if (-not ('WslRegionProof.Native' -as [type])) { Add-Type -Namespace WslRegionProof -Name Native -MemberDefinition '[System.Runtime.InteropServices.DllImport("kernel32.dll", CharSet=System.Runtime.InteropServices.CharSet.Unicode, ExactSpelling=true, SetLastError=true)] public static extern int GetUserDefaultGeoName(System.Text.StringBuilder geoName, int geoNameCount);' -ErrorAction Stop }; $wslRegionBuffer = [System.Text.StringBuilder]::new(16); if ([WslRegionProof.Native]::GetUserDefaultGeoName($wslRegionBuffer, $wslRegionBuffer.Capacity) -eq 0) { throw [System.ComponentModel.Win32Exception]::new([System.Runtime.InteropServices.Marshal]::GetLastWin32Error()) }; $wslRegionBuffer.ToString()` | exact ISO alpha-2 or UN M.49 GeoName under the Windows account launching WSL, recorded separately as `windows_region`; native API failure stops W5 | | W5 | powershell | `wsl.exe -d '' -u root --exec ls -l /etc/cloud/cloud-init.disabled` | the marker exists | | W5 | powershell | `wsl.exe -d '' -u root --exec sudo -l -U ''` | `(ALL) NOPASSWD: ALL` | | W5 | powershell | `wsl.exe --list --verbose` | the starred line equals W1's | @@ -752,7 +753,7 @@ workstation distribution (Evidence classes). | W6 | sh | `chmod 0440 /etc/sudoers.d/90-wsl-default-user` | mode 0440 | | W6 | sh | `visudo -cf /etc/sudoers.d/90-wsl-default-user` | `parsed OK` | | W6 | sh | `grep -q '^\[user\]' /etc/wsl.conf \|\| printf '\n[user]\ndefault=%s\n' '' >> /etc/wsl.conf` | one `[user]` section naming the user | -| W6 | sh | `grep -q '^\[time\]' /etc/wsl.conf \|\| printf '\n[time]\nuseWindowsTimezone=true\n' >> /etc/wsl.conf` | a `[time]` section exists (the guard reads only the header); W5's repeated read-back confirms `useWindowsTimezone=true` | +| W6 | sh | `python3 -c 'import configparser, io; from pathlib import Path; p = Path("/etc/wsl.conf"); c = configparser.ConfigParser(interpolation=None); c.optionxform = str; c.read_string(p.read_text() if p.exists() else ""); c.has_section("time") or c.add_section("time"); c.set("time", "useWindowsTimezone", "true"); out = io.StringIO(); c.write(out, space_around_delimiters=False); p.write_text(out.getvalue())' \|\| exit "$?"` | updates false/missing key or adds [time]; preserves other values; setter failure exits before the marker; W5 read-back/relaunch proves resulting zone | | W6 | sh | `touch /etc/cloud/cloud-init.disabled` | the marker exists | | W6 | powershell | `wsl.exe --terminate ''` | exit 0 | | W6 | powershell | `wsl.exe --list --running` | `` absent | diff --git a/docs/decisions/2026-10-05-user-facing-local-time.md b/docs/decisions/2026-10-05-user-facing-local-time.md index 360f5cc3f..1f433b03b 100644 --- a/docs/decisions/2026-10-05-user-facing-local-time.md +++ b/docs/decisions/2026-10-05-user-facing-local-time.md @@ -28,8 +28,8 @@ UTC timeline across hosts and clients. 1. **One instruction sentence at user level, in both clients, with one wording:** > When you tell the user a time, give it first in the host's local time zone (read it with `timedatectl` or - > `date`), with UTC beside it, for example "4:00 PM EDT (20:00Z)"; keep UTC in ledger rows, receipts, evidence and - > commits. + > `date`), with UTC beside it, for example "4:00 PM EDT (20:00Z)". Write timestamps in ledger rows, receipts, evidence + > and commit messages in UTC (RFC 3339 with `Z`); Git author/committer metadata retains its native format. - **Claude.** [`examples/claude-native/CLAUDE.md`](../../examples/claude-native/CLAUDE.md) carries it as a Core rule bullet after the concise-handoff bullet, the existing rule about reporting to the user. @@ -42,7 +42,8 @@ UTC timeline across hosts and clients. [three-surface record](2026-09-30-rule-text-every-layer.md) requires all three surfaces only for the standing clauses that `StandingRuleSurfacesTests.SHARED` lists; its one-wording rule still holds here, on both client templates and both carriers, and a new test in that class checks it. The sentence is a display rule (how an agent - words a time it tells the user) plus a records rule (UTC in ledger rows, receipts, evidence and commits). The user + words a time it tells the user) plus a records rule (UTC timestamps in ledger rows, receipts, evidence and commit + messages; Git metadata keeps its native format). The user keeps both at user level, the level both clients document for a person's standing working agreements: - Claude's memory documentation lists `~/.claude/CLAUDE.md` as "Personal preferences for all projects". - The Codex guide puts persistent defaults in the Codex home "so every repository inherits your working @@ -54,7 +55,9 @@ UTC timeline across hosts and clients. 2. **The recipe writes WSL's time-zone default explicitly.** - **Path A.** The cloud-init user-data appends `[time]` with `useWindowsTimezone=true` to `/etc/wsl.conf`, before `[user]`. - - **Path B.** W6 appends the same key when `/etc/wsl.conf` has no `[time]` section. + - **Path B.** W6 uses Python's maintained ConfigParser read/set/write API to set the actual key to `true`, updating + an existing `false` value or missing key and adding `[time]` only if absent. It preserves other section values and + key case, with INI formatting/comments normalized on serialization. - **Read-back.** W5 reads the key back, and the first-boot checklist and the recipe record's command table follow. - **The source.** Microsoft documents the key as a boolean that defaults to `true`: "Setting this key will make WSL use and sync to the timezone set in Windows." The WSL 3.0.1 source holds the same default (Sources). @@ -79,7 +82,12 @@ UTC timeline across hosts and clients. first run. - **Claude Code's UI clock.** Claude Code's own UI times follow the system zone by default (below), so they show the Windows zone while the distribution follows it. -3. **Records stay UTC.** Ledger rows, receipts, evidence and commits keep UTC, written as RFC 3339 date-times with `Z`. +3. **Recorded timestamps stay UTC.** Timestamps written in ledger rows, receipts, evidence and commit messages use + RFC 3339 date-times with `Z`. This implementation scope does not alter Git author/committer dates: Git stores those + as Unix seconds plus a numeric UTC offset, and preserves original authorship when reusing commits. It is not a + claim that a Git commit object stores RFC 3339 text. See Git 2.53.0's + [date formats](https://github.com/git/git/blob/v2.53.0/Documentation/date-formats.adoc#L4) and + [authorship reuse](https://github.com/git/git/blob/v2.53.0/Documentation/git-commit.adoc#L81). ## Alternatives considered @@ -199,9 +207,11 @@ sentence is 227 bytes. The budget record has a dated addendum pointing here. the text between the managed markers and keeps every byte outside them (`tools/adoption/managed_block.py:106-113`), so the user's own dated rule after the end markers stays, and the rule loads twice on NativeStack2604 until the user decides. Whether to remove the user's copy is the user's call; this change does not touch it. -- **Open for the user.** Whether "commits" in the records half covers git's own author and committer dates, which these - hosts write with the local numeric offset (for example `-0400`), is the user's decision. This record does not - decide it. +- **Commit scope clarified in the review repair.** The managed records sentence covers timestamps written in commit + messages/text. Native Git author/committer metadata keeps its Unix-time-plus-offset format (for example `-0400`); + this patch supplies no metadata override or history rewrite. A future requirement to normalize metadata offsets + needs a separate user decision and a mechanism that preserves original author instants. This narrows the + implementation's previously ambiguous term; it is not a new assertion of the user's intent about metadata. - **The experiment's frozen inputs.** The WSL image experiment (`blueprints/convergence-practice/wsl-new-distro-image-20261001/experiment.json`) froze four files that this change edits. Their recorded bytes moved to `evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/` with diff --git a/examples/claude-native/CLAUDE.md b/examples/claude-native/CLAUDE.md index c9017bd81..cb14a82b9 100644 --- a/examples/claude-native/CLAUDE.md +++ b/examples/claude-native/CLAUDE.md @@ -21,7 +21,7 @@ Decide by evidence and research convergence: a choice stands when current primar - Resolve supported findings before claiming completion. Distinguish measured results, simulations and untested boundaries; unchanged upstream tests, local integration checks, synthetic fixtures and actual provider execution are different evidence classes. New machines collect their own evidence; historical receipts do not certify the new host. - Settle a discoverable harness capability by step 2 before asking the user; compare community alternatives against demonstrated gaps, retain source pins and reasons, and run the selected native path through returned results. Dated exclusions are not current availability evidence. Research only the relevant layers. - Carry the user's authorized work through implementation, relevant verification and a concise handoff. Use a short plan for bounded work; do not add intake, repeated approvals, diagnostic campaigns or restarts without a concrete need. Preserve existing edits, native accounts, model choices and project scope. -- When you tell the user a time, give it first in the host's local time zone (read it with `timedatectl` or `date`), with UTC beside it, for example "4:00 PM EDT (20:00Z)"; keep UTC in ledger rows, receipts, evidence and commits. +- When you tell the user a time, give it first in the host's local time zone (read it with `timedatectl` or `date`), with UTC beside it, for example "4:00 PM EDT (20:00Z)". Write timestamps in ledger rows, receipts, evidence and commit messages in UTC (RFC 3339 with `Z`); Git author/committer metadata retains its native format. - Use the project's canonical instructions for memory scope, tests and domain rules, and keep durable memory and indexes scoped to that project. ## Token practice (base layer) diff --git a/tests/test_install_claude_profile.py b/tests/test_install_claude_profile.py index f1df6fd9d..c1f96b744 100644 --- a/tests/test_install_claude_profile.py +++ b/tests/test_install_claude_profile.py @@ -1785,8 +1785,9 @@ def test_the_three_surfaces_carry_the_same_standing_sentences(self): # blocks and in the carriers F9 renders from them, once each. It is not a standing clause of the repository # AGENTS.md, so SHARED does not hold it. LOCAL_TIME = ("When you tell the user a time, give it first in the host's local time zone (read it with " - "`timedatectl` or `date`), with UTC beside it, for example \"4:00 PM EDT (20:00Z)\"; keep UTC in " - "ledger rows, receipts, evidence and commits.") + "`timedatectl` or `date`), with UTC beside it, for example \"4:00 PM EDT (20:00Z)\". Write timestamps in " + "ledger rows, receipts, evidence and commit messages in UTC (RFC 3339 with `Z`); " + "Git author/committer metadata retains its native format.") LOCAL_TIME_SURFACES = ("examples/claude-native/CLAUDE.md", "adoption/templates/codex.AGENTS.template.md", "adoption/new-wsl/claude-user-instructions.md", "adoption/new-wsl/codex-user-instructions.md") diff --git a/tests/test_wsl_new_distro_recipe.py b/tests/test_wsl_new_distro_recipe.py index 455d2685e..4a196753c 100644 --- a/tests/test_wsl_new_distro_recipe.py +++ b/tests/test_wsl_new_distro_recipe.py @@ -103,8 +103,13 @@ from __future__ import annotations import json +import configparser import re +import shlex import string +import subprocess +import sys +import tempfile import unittest from collections import Counter from pathlib import Path @@ -1169,6 +1174,15 @@ def time_zone_pair_errors(recipe: str, record: str, checklist: str, receipt: dic listed = [(entry.get("step"), entry.get("cmd")) for entry in receipt.get("steps", []) if isinstance(entry, dict)] if listed.count(("W5", ZONE_DISTRIBUTION)) != 1 or listed.count(("W5", ZONE_WINDOWS)) != 1: errors.append("the receipt example does not record each zone command once under W5") + region_commands = [c for c in commands if "GetUserDefaultGeoName" in c and "$wslRegionBuffer.ToString()" in c] + if len(region_commands) != 1: + errors.append("W5 lacks the exact Windows GeoName API readback") + elif rows.count(region_commands[0]) != 1 or listed.count(("W5", region_commands[0])) != 1: + errors.append("the Windows GeoName readback is not recorded in both the command table and receipt") + if not is_placeholder(field(receipt, "first_launch", "windows_region"), " Date: Tue, 6 Oct 2026 08:08:38 -0400 Subject: [PATCH 06/10] chore: regenerate WSL handbook and register P2 repairs --- docs/new-wsl-handbook.json | 2 +- docs/new-wsl-handbook.md | 2 +- .../new-wsl-handbook-20261001/receipt.json | 18 ++++++- manifests/evidence.json | 52 +++++++++---------- 4 files changed, 44 insertions(+), 30 deletions(-) diff --git a/docs/new-wsl-handbook.json b/docs/new-wsl-handbook.json index 2d5645dca..76cafbcf1 100644 --- a/docs/new-wsl-handbook.json +++ b/docs/new-wsl-handbook.json @@ -7799,7 +7799,7 @@ }, { "path": "adoption/platforms/linux-wsl2-new-distro.md", - "sha256": "3d6164455a92300664d8b62870f2078a8163397aa8973131d037d7e474638026" + "sha256": "65318f13f0fa3b92edd8566191ea8077988ff0909ac37594eb0ea08118512e58" }, { "path": "catalogs/foundation/new-wsl-architecture-20261001.json", diff --git a/docs/new-wsl-handbook.md b/docs/new-wsl-handbook.md index 945a6efb1..1d2e9323f 100644 --- a/docs/new-wsl-handbook.md +++ b/docs/new-wsl-handbook.md @@ -2129,7 +2129,7 @@ Reference edition: [catalogs/foundation/new-wsl-architecture-20261001.json](../c | --- | --- | | [adoption/manifest.json](../adoption/manifest.json) | `159ad1f56a9a654951eb16a4a66645378d225d8b924eede282815f49342add55` (without source.release_tag, source.release_commit, updated_at) | | [adoption/new-wsl-profile.json](../adoption/new-wsl-profile.json) | `7504e55c2e66730d5e0b0ead2be5abef39f3989afc69e1c3640e6709bdb3db04` | -| [adoption/platforms/linux-wsl2-new-distro.md](../adoption/platforms/linux-wsl2-new-distro.md) | `3d6164455a92300664d8b62870f2078a8163397aa8973131d037d7e474638026` | +| [adoption/platforms/linux-wsl2-new-distro.md](../adoption/platforms/linux-wsl2-new-distro.md) | `65318f13f0fa3b92edd8566191ea8077988ff0909ac37594eb0ea08118512e58` | | [catalogs/foundation/new-wsl-architecture-20261001.json](../catalogs/foundation/new-wsl-architecture-20261001.json) | `8420f58ef2f613bab8da7a4486e907a9118b178d9d8e5eb8c70e4d84950c6c73` | | [catalogs/landscape/research-state.json](../catalogs/landscape/research-state.json) | `fe142c1b8b8ec8b92d69ec5971d68028225899c101ffc43cea618883b119332a` | | [catalogs/landscape/us-equities.json](../catalogs/landscape/us-equities.json) | `cf441d393b1a1ee49c57d62592e2c7477de2003ca40aa9e1933a6c9b28d4a618` | diff --git a/evidence/artifacts/new-wsl-handbook-20261001/receipt.json b/evidence/artifacts/new-wsl-handbook-20261001/receipt.json index 50b6bc28e..25272bb9c 100644 --- a/evidence/artifacts/new-wsl-handbook-20261001/receipt.json +++ b/evidence/artifacts/new-wsl-handbook-20261001/receipt.json @@ -6,8 +6,8 @@ "generator_sha256": "6e6ee9cfb4d697b3bea2d7684e042e638be20634792f43e517b205463b1967f9", "profile_sha256": "7504e55c2e66730d5e0b0ead2be5abef39f3989afc69e1c3640e6709bdb3db04", "outputs": { - "docs/new-wsl-handbook.md": "50585de8165cddcc4e0b115fb2eb6e066796ca66ef6fefc3e1f16d0095c89fb3", - "docs/new-wsl-handbook.json": "70cf495c8d914442db92d938c987fcba39a99382ad28463fb0641c4fa96bc96a" + "docs/new-wsl-handbook.md": "4480fe88eeed33c961c3457b7947fcd870721690d491e27d80ccff8fc921a7d6", + "docs/new-wsl-handbook.json": "0bf0583e75e866736c106157e14162f85b954522e7e55d65bba584d3b2d02af9" }, "inventory": { "layers": 37, @@ -260,6 +260,20 @@ "docs/new-wsl-handbook.json": "32da2ef7bbb47cbd8e75a1674b19c9dd1ee8e3cc855a05efee322765186e079e" }, "check": "python3 scripts/build_new_wsl_handbook.py --check" + }, + { + "date": "2026-10-06", + "command": "python3 scripts/build_new_wsl_handbook.py --write", + "native_exit_code": 0, + "input_parent": "a10ad2c019b54ccd83abdd0a831aeca536e846d6", + "cause": "PR758 Codex P2 repair: record the exact Windows GeoName mapping input, upsert the path-B time key and propagate parser failure, and scope UTC commit records to message text. The guide digest changed; the maintained generator refreshed both provenance outputs.", + "previous_outputs": { + "docs/new-wsl-handbook.md": "50585de8165cddcc4e0b115fb2eb6e066796ca66ef6fefc3e1f16d0095c89fb3", + "docs/new-wsl-handbook.json": "70cf495c8d914442db92d938c987fcba39a99382ad28463fb0641c4fa96bc96a" + }, + "check": "python3 scripts/build_new_wsl_handbook.py --check", + "evidence_class": "local_integration", + "windows_geoname_api_execution_or_first_boot": false } ] } diff --git a/manifests/evidence.json b/manifests/evidence.json index 370a2027b..2d813fccc 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -4877,8 +4877,8 @@ }, { "path": "adoption/new-wsl/claude-user-instructions.md", - "sha256": "585b88f2c381f966ffd42803a6c17d9630a8ff285369c386242d41638c86258f", - "bytes": 11805 + "sha256": "2f1d38a12e5714c6178e33f7ade258cf7b5751f174afd059c37d2947ae6261ed", + "bytes": 11905 }, { "path": "adoption/new-wsl/client-config-map.json", @@ -4887,8 +4887,8 @@ }, { "path": "adoption/new-wsl/codex-user-instructions.md", - "sha256": "c5961b3ff4625d51e5ccc6c2ebc6329c5d0f5d7274b86a7c2f23f7e7c7d0357d", - "bytes": 8601 + "sha256": "26474ab5025772b05ed8bf5ba393d49da61e902f003ad427bf7b245c56b91d51", + "bytes": 8701 }, { "path": "adoption/new-wsl/templates/claude-user.mcp.additions.json", @@ -4947,8 +4947,8 @@ }, { "path": "adoption/platforms/linux-wsl2-new-distro.md", - "sha256": "3d6164455a92300664d8b62870f2078a8163397aa8973131d037d7e474638026", - "bytes": 95459 + "sha256": "65318f13f0fa3b92edd8566191ea8077988ff0909ac37594eb0ea08118512e58", + "bytes": 98099 }, { "path": "adoption/platforms/linux-wsl2.md", @@ -5017,8 +5017,8 @@ }, { "path": "adoption/templates/codex.AGENTS.template.md", - "sha256": "2d62d6c73dbbfeb2c6d7632da0f76e77686ef0f422c6b7a845ce2065e8b6e52b", - "bytes": 7535 + "sha256": "16dc351855e8c8cb27a0d0258636a91dee1db92b6f57adc7f541b764b327e140", + "bytes": 7635 }, { "path": "adoption/templates/codex.config.template.toml", @@ -5077,13 +5077,13 @@ }, { "path": "adoption/templates/wsl/first-boot-checklist.md", - "sha256": "12170643cc9fdf8ba4503762fd61147f902bb85c94f0f73b7be0522b31fd1014", - "bytes": 15597 + "sha256": "523dd2ed0fe38dfab70061056cdabbf6a2df23257958ced6646fe0a624163c0f", + "bytes": 15681 }, { "path": "adoption/templates/wsl/stage1-receipt.example.json", - "sha256": "20c5ef6d34a9de45473be73e2bdf2114eb45c7d8e9389f15c754445d0cb8e398", - "bytes": 61751 + "sha256": "b8e35b2791d47c57f69f086cba55ff15558c951ef6a3c3d50fc64aa4d60728a2", + "bytes": 63208 }, { "path": "adoption/tools/README.md", @@ -17018,8 +17018,8 @@ }, { "path": "docs/decisions/2026-10-01-new-wsl-distro-recipe.md", - "sha256": "753f01a78f820933eb8e2fe3be7f9cd7582710e0907c815e3274537be797ab83", - "bytes": 150728 + "sha256": "8df3f18af352be4f1dd1a2a2dc5a4d718804582b8a6281e6d461dddd73e2647c", + "bytes": 151947 }, { "path": "docs/decisions/2026-10-01-new-wsl-handbook-generator.md", @@ -17273,8 +17273,8 @@ }, { "path": "docs/decisions/2026-10-05-user-facing-local-time.md", - "sha256": "d906951e9dcf27bad23e2f599fa9a74aeb2796ab9cd6d5315072ecce69d21427", - "bytes": 27979 + "sha256": "4f85e48639c929983409d441a655c82d269c4cf6e6ffaf518861c49dc7227eab", + "bytes": 29182 }, { "path": "docs/decisions/2026-10-06-application-input-binding-amendment.md", @@ -17543,12 +17543,12 @@ }, { "path": "docs/new-wsl-handbook.json", - "sha256": "70cf495c8d914442db92d938c987fcba39a99382ad28463fb0641c4fa96bc96a", + "sha256": "0bf0583e75e866736c106157e14162f85b954522e7e55d65bba584d3b2d02af9", "bytes": 685881 }, { "path": "docs/new-wsl-handbook.md", - "sha256": "50585de8165cddcc4e0b115fb2eb6e066796ca66ef6fefc3e1f16d0095c89fb3", + "sha256": "4480fe88eeed33c961c3457b7947fcd870721690d491e27d80ccff8fc921a7d6", "bytes": 362513 }, { @@ -39683,8 +39683,8 @@ }, { "path": "evidence/artifacts/new-wsl-handbook-20261001/receipt.json", - "sha256": "ef808e29ee3fe8914d4757504897cd892e0996b09fc8a6cf96279db0d0e96fcc", - "bytes": 12984 + "sha256": "9fb0af56bf21e3fc2c5d8d7932ab23f9daa8b97d15751e85e8e134a7bdaa19b6", + "bytes": 13886 }, { "path": "evidence/artifacts/new-wsl-handbook-20261001/slot-state-and-core-recipes.json", @@ -53183,8 +53183,8 @@ }, { "path": "examples/claude-native/CLAUDE.md", - "sha256": "585b88f2c381f966ffd42803a6c17d9630a8ff285369c386242d41638c86258f", - "bytes": 11805 + "sha256": "2f1d38a12e5714c6178e33f7ade258cf7b5751f174afd059c37d2947ae6261ed", + "bytes": 11905 }, { "path": "examples/claude-native/agents/blind-adjudicator.md", @@ -54858,8 +54858,8 @@ }, { "path": "tests/test_install_claude_profile.py", - "sha256": "1649fe42c58014d3b5b00290fd2f4723bef248489f92aae425cbdab29fab5f66", - "bytes": 132431 + "sha256": "c4b2815e0e51086aeeb9da24a88bba918b81812bf33c433f473e3619bdfdec80", + "bytes": 132552 }, { "path": "tests/test_install_skills.py", @@ -55438,8 +55438,8 @@ }, { "path": "tests/test_wsl_new_distro_recipe.py", - "sha256": "14c09b86ffb9db8f3c565da498c8035edec7e223079881c9ab51e1a94f632ee3", - "bytes": 233146 + "sha256": "3604c05b04921fbc6c4493a1678885386f1ef3f05a7995592b9d04157ce5fa72", + "bytes": 237414 }, { "path": "tests/test_wsl_restore.py", From 039b7776c9c8df2f70ee0487ea4ef77087b415e1 Mon Sep 17 00:00:00 2001 From: seathatflowsinourveins <234074349+seathatflowsinourveins@users.noreply.github.com> Date: Tue, 6 Oct 2026 12:24:28 -0400 Subject: [PATCH 07/10] fix: re-pin the Codex top-rule digest and date the repair's measurements The review repair (0df3f0dc) grew the local-time sentence from 227 to 327 bytes inside the segment that tests/test_codex_worker_lane.py pins, so TOP_RULE_SHA256 was stale (GPT verdict on 563eedab7, finding 1, P2). It is re-derived with the module's template_segments() as d21bb3bc0a2e68fb362af1d085da3761a08cc5ccec18ebd7ed16dd83d80bb3cd. The sentence is byte-identical on the four surfaces StandingRuleSurfacesTests checks, so only the pin moves. Finding 2 (P3): the size comments in the test and in tools/adoption/new_wsl_client_config.py now give 7,635 and 8,701 bytes. The local-time record keeps its earlier measurements, labels them as preceding the repair, and appends a 2026-10-06 addendum: measured sizes, startup scopes (Claude 23,896, Codex 19,660), post-gate projections, the new pin, the checks run and an erratum on registry membership. The budget record's 2026-10-05 addendum, which named the old pin and figures, gets a matching dated addendum. Co-Authored-By: Claude Opus 5.5 --- .../2026-10-05-harness-context-budget.md | 15 +++++ .../2026-10-05-user-facing-local-time.md | 62 ++++++++++++++++++- tests/test_codex_worker_lane.py | 6 +- tools/adoption/new_wsl_client_config.py | 4 +- 4 files changed, 80 insertions(+), 7 deletions(-) diff --git a/docs/decisions/2026-10-05-harness-context-budget.md b/docs/decisions/2026-10-05-harness-context-budget.md index 5934702db..c29625796 100644 --- a/docs/decisions/2026-10-05-harness-context-budget.md +++ b/docs/decisions/2026-10-05-harness-context-budget.md @@ -815,3 +815,18 @@ constant changes here. The top-rule/lanes pin named above moves from `568ee365aeef3455fc901e648eb72d28cc3b49c39f1bfba7f5e39beed20479a8` to `82e23b68466ed8b96566a229582f0c99fa1456a393e635f18cc5e65f601f4d09`: the pinned segment runs to the RTK marker, so it includes the `session-lanes` lines. + +## Addendum (2026-10-06): local-time review repair + +The 2026-10-05 addendum's figures precede the local-time record's review +repair, which grew its sentence from 227 to 327 bytes. Against main, the +portable Claude block now gains 330 bytes and the Codex `session-lanes` block +328. `startup_files` measures Claude 23,896 and Codex 19,660 startup bytes, +under the unchanged 24,458/20,103 constants (562/443 bytes of headroom); the +compact Codex source is 7,635 bytes. For these inputs the post-gate projection +becomes 23,216/18,980-byte scopes and 24,377/19,929-byte ceilings. No constant +changes here. The top-rule/lanes pin moves from +`82e23b68466ed8b96566a229582f0c99fa1456a393e635f18cc5e65f601f4d09` to +`d21bb3bc0a2e68fb362af1d085da3761a08cc5ccec18ebd7ed16dd83d80bb3cd`. The +[local-time record](2026-10-05-user-facing-local-time.md)'s 2026-10-06 +addendum holds the measurement table and the checks. diff --git a/docs/decisions/2026-10-05-user-facing-local-time.md b/docs/decisions/2026-10-05-user-facing-local-time.md index 1f433b03b..48c211cff 100644 --- a/docs/decisions/2026-10-05-user-facing-local-time.md +++ b/docs/decisions/2026-10-05-user-facing-local-time.md @@ -127,6 +127,8 @@ UTC timeline across hosts and clients. These are measured with `PortableTopRuleTests.startup_files`, which uses the renderer's committed carriers. Base is main `ecfa1127`. After the rebase onto it, both columns were measured again in code (the before column from main's startup files, extracted to a scratch directory), and every value equals the first measurement, made on main `e8c1edec`. +This table and the sentence size below precede the review repair (`0df3f0dc`), which added 100 bytes to the +sentence; the 2026-10-06 addendum at the end of this record gives the measurements after it. | File or scope | Before | After | | --- | ---: | ---: | @@ -152,7 +154,7 @@ sentence is 227 bytes. The budget record has a dated addendum pointing here. `session-lanes` lines included. It was re-derived with the module's `template_segments()` as `82e23b68466ed8b96566a229582f0c99fa1456a393e635f18cc5e65f601f4d09`, and the budget record's addendum names the new value beside the old one. The size comments there and in `tools/adoption/new_wsl_client_config.py` now give 7,535 - and 8,601 bytes. + and 8,601 bytes. These values precede the review repair; the 2026-10-06 addendum gives the current pin and sizes. - **The cloud-init check.** `tests/test_wsl_new_distro_recipe.py` requires `[time]` and `useWindowsTimezone=true` in the render, and reads the appended `/etc/wsl.conf` text by section: `[time]` holds only that key and comes before `[user]`, which holds only the default user. Its new mutants are a lost section, `false`, the key moved under @@ -234,7 +236,7 @@ rebase, at base `ecfa1127`, on NativeStack2604 (2026-10-05 and 2026-10-06 UTC). Rows whose result starts "round 3" ran again on that final tree. The others ran on the round-2 tree, `608c5266`. The third round changed no template, carrier, user-data or startup file. Only this record's prose changed after the last -checks. +checks. Every row precedes the review repair (`0df3f0dc`); the 2026-10-06 addendum lists the checks run after it. | Command | Exit | Result | | --- | ---: | --- | @@ -312,3 +314,59 @@ checks. `timezone` appears only in `tools.web_search` (`location = { country, region, city, timezone }`). - **cloud-init 26.1** (installed `26.1-0ubuntu3~26.04.1`): `cloud-init schema -c` on the rendered user-data, P2's check. + +## Addendum (2026-10-06): measurements after the review repair + +The review repair (`0df3f0dc`) gave the sentence the records half that Decision 1 quotes, in both templates and both +F9 carriers. The sentence grew from 227 to 327 bytes, and each of those four files by 100 bytes. The Startup bytes +table, the sentence size, the pin and size comments under "Pins amended in the same change" and every Checks row above +precede that repair; they stay as the record of the earlier tree. The repair left `TOP_RULE_SHA256` and those comments +stale (a review finding of 2026-10-06), and this addendum's change repairs them. + +These values were measured on 2026-10-06 UTC on NativeStack2604 with `PortableTopRuleTests.startup_files` on the +repaired tree. The two earlier columns were computed again with the same file set, reading the startup files and +carriers from `ecfa1127` and from `a10ad2c0`, the branch head before the repair; both reproduce the table above. No +startup file other than the two carriers changed between `ecfa1127` and the repaired tree. + +| File or scope | Main `ecfa1127` | Before the repair, `a10ad2c0` | After the repair | +| --- | ---: | ---: | ---: | +| `examples/claude-native/CLAUDE.md` and its carrier | 11,575 | 11,805 | 11,905 (+330 against main) | +| Claude rendered user block | 11,841 | 12,071 | 12,171 | +| `adoption/templates/codex.AGENTS.template.md` (local check: under 8,192) | 7,307 | 7,535 | 7,635 (+328) | +| Codex rendered carrier | 8,373 | 8,601 | 8,701 | +| Claude startup scope (fixed ceiling 24,458) | 23,566 | 23,796; headroom 662 | 23,896; headroom 562 | +| Codex startup scope (fixed ceiling 20,103) | 19,332 | 19,560; headroom 543 | 19,660; headroom 443 | +| Claude post-gate projection (scope − 680; ceiling = ×1.05, rounded up) | 22,886; 24,031 | 23,116; 24,272 | 23,216; 24,377 | +| Codex post-gate projection | 18,652; 19,585 | 18,880; 19,824 | 18,980; 19,929 | + +- **The constants.** They stay at 24,458 and 20,103; nothing is re-baselined. When the routing host gate passes, + lowering both constants to the measured post-gate scopes plus 5% gives 24,377 and 19,929 for these inputs, replacing + 24,272 and 19,824 above. The budget record has a matching dated addendum. +- **The pin.** `TOP_RULE_SHA256` moves from `82e23b68466ed8b96566a229582f0c99fa1456a393e635f18cc5e65f601f4d09` to + `d21bb3bc0a2e68fb362af1d085da3761a08cc5ccec18ebd7ed16dd83d80bb3cd`. It was re-derived with the module's + `template_segments()`, whose top-rule segment, ending at the RTK marker, is now 6,360 bytes, and it equals the value + the review computed. +- **The size comments.** Those in `tests/test_codex_worker_lane.py` and `tools/adoption/new_wsl_client_config.py` now + give 7,635 and 8,701 bytes. +- **One wording.** The sentence is byte-identical in both templates and both F9 carriers. The repair changed all four + together with `StandingRuleSurfacesTests.LOCAL_TIME`, which checks that wording once on each, so no surface besides + the pin and these records needed a change. +- **Erratum.** "No decision index" above says this record, like the context-budget record, is not registered in + `manifests/evidence.json`. This branch lists both in its `files[]` (main does not), so their edits here are + re-registered, with the test and the renderer, in the branch's last commit. + +These checks ran on 2026-10-06 UTC on NativeStack2604. The tests ran before this table was written; `validate.py`, the +manifest order check and `git diff --check` ran again after it. + +| Command | Exit | Result | +| --- | ---: | --- | +| `python3 -B -m unittest tests.test_codex_worker_lane.TemplateTests.test_top_rule_is_pinned_and_rendered_rtk_is_the_unchanged_pinned_source`, before the pin repair (`563eedab`) | 1 | the segment hashed to `d21bb3bc…` against the pinned `82e23b68…` (the review's finding, reproduced) | +| The same test, after it | 0 | 1 test | +| `python3 -B -m unittest` on 13 modules (the first 13 below), before the pin repair | 1 | 1,297 tests; the only failure is the test above | +| `python3 -B -m unittest`, one run per module, after the repair: `test_codex_worker_lane`, `test_install_claude_profile`, `test_managed_block`, `test_new_wsl_client_config`, `test_codex_agents`, `test_codex_roles`, `test_scaffold_repo`, `test_bootstrap_full_profile`, `test_wsl_new_distro_recipe`, `test_new_wsl_handbook`, `test_landscape_sweep_harness`, `test_runtime_worker_openhands_push_gate`, `test_runtime_worker_openhands_resolver`, `test_catalog_freshness_runtime`, `test_freeze_snapshot`, `test_new_wsl_definitive_defaults`, `test_upstream_surface_watch` | 0 each | 1,754 tests, 26 skipped: every module that names the two templates or carriers, or reads the test, the renderer or these two records | +| `python3 -B tools/adoption/new_wsl_client_config.py --check` | 0 | check passed, with the same two warnings about slot `mcp-inspector` | +| `python3 -B tools/adoption/new_wsl_client_config.py --render --host nativestack2604 --out ` | 1 | stopped before rendering: this worktree has no host value file (`adoption/hosts/nativestack2604.json` is not tracked); the block sizes above come from `startup_files`' own `managed_block` merge of the committed carriers | +| `python3 -B scripts/build_new_wsl_handbook.py --check` | 0 | current outputs (MD `4480fe88…`, JSON `0bf0583e…`); none of the four changed files is a handbook source | +| `python3 scripts/validate.py` | 0 | 10,322 hashed files, 212 receipts | +| `python3 scripts/evidence_manifest.py --check` | 0 | passed | +| `git diff --check` | 0 | clean | diff --git a/tests/test_codex_worker_lane.py b/tests/test_codex_worker_lane.py index 7331776fe..f3a224199 100644 --- a/tests/test_codex_worker_lane.py +++ b/tests/test_codex_worker_lane.py @@ -50,7 +50,7 @@ # The session-lanes local-time line is recorded in 2026-10-05-user-facing-local-time.md. # RTK's unchanged 0.51.0 awareness fixture is pinned to e001f773 and checked # byte for byte in the rendered block, with local exceptions kept separately. -TOP_RULE_SHA256 = "82e23b68466ed8b96566a229582f0c99fa1456a393e635f18cc5e65f601f4d09" +TOP_RULE_SHA256 = "d21bb3bc0a2e68fb362af1d085da3761a08cc5ccec18ebd7ed16dd83d80bb3cd" RTK_AWARENESS_SHA256 = "278274ef3d08c858d4247cc91419c4d74ef922b95719e987b22e896aef10e1fc" UPSTREAM_MARKER = '\n' @@ -435,8 +435,8 @@ def test_agents_template_is_one_managed_block(self): self.assertNotIn(lane.managed_block.RTK_INCLUDE, rendered) # Codex expands no @ reference (codex-rs/core/src/agents_md.rs at rust-v0.157.1): the text is inline. self.assertFalse([line for line in rendered.splitlines() if line.startswith("@")]) - # This local 8,192-byte check covers the compact source (7,535 bytes). - # The rendered Codex carrier is 8,601 bytes, counted by the startup budget. + # This local 8,192-byte check covers the compact source (7,635 bytes). + # The rendered Codex carrier is 8,701 bytes, counted by the startup budget. self.assertLess(len(text.encode("utf-8")), 8192) def test_top_rule_is_pinned_and_rendered_rtk_is_the_unchanged_pinned_source(self): diff --git a/tools/adoption/new_wsl_client_config.py b/tools/adoption/new_wsl_client_config.py index f2fa38f39..ab2acfc6c 100644 --- a/tools/adoption/new_wsl_client_config.py +++ b/tools/adoption/new_wsl_client_config.py @@ -206,8 +206,8 @@ SENTENCE_BREAK = re.compile(r"(?<=[.!?])(\s+)(?=[A-Z`\[(<\"'*_])") SENTENCE_END = re.compile(r"[.!?][\"')\]`*_]*$") # Wrap width of the verbatim rtk-ai/rtk v0.51.0 hooks/rtk-awareness-full.md. -# The rendered Codex carrier is 8,601 bytes; the local 8,192-byte test covers only -# adoption/templates/codex.AGENTS.template.md's compact source (7,535 bytes). +# The rendered Codex carrier is 8,701 bytes; the local 8,192-byte test covers only +# adoption/templates/codex.AGENTS.template.md's compact source (7,635 bytes). # A run of lines this short with a sentence running into the next line is one # wrapped paragraph; longer lines are one statement each. WRAP_WIDTH = 80 From 360987990fad136dbc3823b276cc6fe578d91214 Mon Sep 17 00:00:00 2001 From: seathatflowsinourveins <234074349+seathatflowsinourveins@users.noreply.github.com> Date: Tue, 6 Oct 2026 12:24:28 -0400 Subject: [PATCH 08/10] chore: register the pin and measurement repair Re-registers exactly the four files the previous commit changed (sha256 and bytes in manifests/evidence.json files[]), last, per the docs/lanes.md hot-file protocol. validate.py passes: 10,322 hashed files, 212 receipts. Co-Authored-By: Claude Opus 5.5 --- manifests/evidence.json | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/manifests/evidence.json b/manifests/evidence.json index 2d813fccc..7eaab3ac0 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -17238,8 +17238,8 @@ }, { "path": "docs/decisions/2026-10-05-harness-context-budget.md", - "sha256": "d11e199975995de0b328cc4603c6a3a158eeba43c627e92d74d2f1f256cd272a", - "bytes": 54584 + "sha256": "9f1ddd367361a6f9c88b78d18e4f230b80935e966c9d1e3e91aacd578024c38e", + "bytes": 55486 }, { "path": "docs/decisions/2026-10-05-main-checkout-backup-disposition.md", @@ -17273,8 +17273,8 @@ }, { "path": "docs/decisions/2026-10-05-user-facing-local-time.md", - "sha256": "4f85e48639c929983409d441a655c82d269c4cf6e6ffaf518861c49dc7227eab", - "bytes": 29182 + "sha256": "8bc7cb99e2b415fff9a1c4a7784c21291f3cacefac97107277a11988af9b1a34", + "bytes": 35121 }, { "path": "docs/decisions/2026-10-06-application-input-binding-amendment.md", @@ -54643,7 +54643,7 @@ }, { "path": "tests/test_codex_worker_lane.py", - "sha256": "8ad69ae5015c4d212001d7eedca10a35fa3fbfc45201d336c57de010067375fc", + "sha256": "558925fea22815575e0dc5856bafba739d06bf0d4b3f40f8fb4f49960c322c0b", "bytes": 191495 }, { @@ -55513,7 +55513,7 @@ }, { "path": "tools/adoption/new_wsl_client_config.py", - "sha256": "a4a3b5fd21037fbffe1f96cf1c3b55cdf429ffa8ae3a680c3b48bbab7f5bcd96", + "sha256": "0df4d00a9ca66f4f8b3ce3da9d27d0c6bbfec9b2045ead14dfdcf21911dd845c", "bytes": 153006 }, { From eedac4bea82f5131a4f35102d2875cb87b33594a Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 19:31:57 -0400 Subject: [PATCH 09/10] Hot-file protocol: reset manifests/evidence.json to the merge base before the final hot-file commit Co-Authored-By: Claude Opus 5.5 --- manifests/evidence.json | 105 ++++++++++++++-------------------------- 1 file changed, 35 insertions(+), 70 deletions(-) diff --git a/manifests/evidence.json b/manifests/evidence.json index 210786ba2..a73fb7053 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -5076,8 +5076,8 @@ }, { "path": "adoption/new-wsl/claude-user-instructions.md", - "sha256": "2f1d38a12e5714c6178e33f7ade258cf7b5751f174afd059c37d2947ae6261ed", - "bytes": 11905 + "sha256": "9bc0f41208ce30ec46a1f08fa7d0b6680f14b8e56266a414389d7e69d2ea36c2", + "bytes": 11575 }, { "path": "adoption/new-wsl/client-config-map.json", @@ -5086,8 +5086,8 @@ }, { "path": "adoption/new-wsl/codex-user-instructions.md", - "sha256": "26474ab5025772b05ed8bf5ba393d49da61e902f003ad427bf7b245c56b91d51", - "bytes": 8701 + "sha256": "f8874e406be3e7cc5a43bf9747eed5e6923ab5a997ac2f384694b6cc9f6bf539", + "bytes": 8373 }, { "path": "adoption/new-wsl/templates/claude-user.mcp.additions.json", @@ -5146,8 +5146,8 @@ }, { "path": "adoption/platforms/linux-wsl2-new-distro.md", - "sha256": "da18d556933993c2308e68b7cdefa63b2f32e875a80a97c047482b750cfd1b34", - "bytes": 98823 + "sha256": "bc37a2c3fde395818f1ffcaae01e9578fa222367cac5b36f92bcbc013e5f491a", + "bytes": 93706 }, { "path": "adoption/platforms/linux-wsl2.md", @@ -5216,8 +5216,8 @@ }, { "path": "adoption/templates/codex.AGENTS.template.md", - "sha256": "16dc351855e8c8cb27a0d0258636a91dee1db92b6f57adc7f541b764b327e140", - "bytes": 7635 + "sha256": "453173835cb0aec0d4e7396dc21fcc8e1e4bf7c0429078b3d2766b74dceb290f", + "bytes": 7307 }, { "path": "adoption/templates/codex.config.template.toml", @@ -5271,18 +5271,18 @@ }, { "path": "adoption/templates/wsl/cloud-init.user-data.template", - "sha256": "fea33774ce5989fef58465e226a1982e905e39fa8f8e4029e678d4700668afba", - "bytes": 1851 + "sha256": "08e6a3f6b078dfa54d78297596d54729d10528d57147b9b9eae063c37160eeb9", + "bytes": 1527 }, { "path": "adoption/templates/wsl/first-boot-checklist.md", - "sha256": "523dd2ed0fe38dfab70061056cdabbf6a2df23257958ced6646fe0a624163c0f", - "bytes": 15681 + "sha256": "1a06df0e6d50b07b439317a61a450e450127052589f63254e769d99166e3603b", + "bytes": 15192 }, { "path": "adoption/templates/wsl/stage1-receipt.example.json", - "sha256": "b8e35b2791d47c57f69f086cba55ff15558c951ef6a3c3d50fc64aa4d60728a2", - "bytes": 63208 + "sha256": "7c0811c460fe8732f413b954cd7ddadb5cc41a5300f8686b5b83aefcd80a971a", + "bytes": 60963 }, { "path": "adoption/tools/README.md", @@ -7517,8 +7517,8 @@ }, { "path": "blueprints/convergence-practice/wsl-new-distro-image-20261001/experiment.json", - "sha256": "ffbbd654c88cb8c78ddff1174dd2e15f4478cee78fa4d126a7fd16a3661f7c31", - "bytes": 23358 + "sha256": "822da49b85f1645c09887f27bf24268e554f2f27594719620e5cc8b28530853e", + "bytes": 23166 }, { "path": "blueprints/convergence-practice/wsl-restore/README.md", @@ -17317,8 +17317,8 @@ }, { "path": "docs/decisions/2026-10-01-new-wsl-distro-recipe.md", - "sha256": "8df3f18af352be4f1dd1a2a2dc5a4d718804582b8a6281e6d461dddd73e2647c", - "bytes": 151947 + "sha256": "ec03a494aa4d80ac004f74710250bf1ac7cf18e0506758d97a1cf98a0cee42a3", + "bytes": 147654 }, { "path": "docs/decisions/2026-10-01-new-wsl-handbook-generator.md", @@ -17337,8 +17337,8 @@ }, { "path": "docs/decisions/2026-10-02-new-wsl-client-configuration.md", - "sha256": "908789292c7d5919e1d4cff2cf552a0d254dea5af363d8c00cfee908b7583a24", - "bytes": 136295 + "sha256": "2bcc4e1c173e1780cd6f48f863e6e4fcb134d9374971f95d1217e755e7f336d4", + "bytes": 135623 }, { "path": "docs/decisions/2026-10-02-omniroute-mac-rebuild.md", @@ -17550,11 +17550,6 @@ "sha256": "c1eaaffe72e840c7655e05a44e71116174dc1701675d2e08730ba54a8c9a6e87", "bytes": 135389 }, - { - "path": "docs/decisions/2026-10-05-harness-context-budget.md", - "sha256": "9f1ddd367361a6f9c88b78d18e4f230b80935e966c9d1e3e91aacd578024c38e", - "bytes": 55486 - }, { "path": "docs/decisions/2026-10-05-main-checkout-backup-disposition.md", "sha256": "22839061d0826b5272f5b840f1765e5b175845b25b8f0e3c9a8ce8ccc8dfedba", @@ -17590,11 +17585,6 @@ "sha256": "d197ab297186fe55e6c83624c78a06ead264e00d66a7fe2d5e62cfc1b653dbd6", "bytes": 9226 }, - { - "path": "docs/decisions/2026-10-05-user-facing-local-time.md", - "sha256": "8bc7cb99e2b415fff9a1c4a7784c21291f3cacefac97107277a11988af9b1a34", - "bytes": 35121 - }, { "path": "docs/decisions/2026-10-06-application-input-binding-amendment.md", "sha256": "c12885d5d078f88cf6784d539e5b8261545a759b7aba8ff1eba566fb5d6366fb", @@ -17877,12 +17867,12 @@ }, { "path": "docs/new-wsl-handbook.json", - "sha256": "e85827c513b80cf075792edbcaf0c1b03c59ca89ca0c31e8fe846f2f3d1fa174", + "sha256": "efd48eae032b0556f8679b3177c6f03fcae364bb6d353836e5254a2edd1722b1", "bytes": 692584 }, { "path": "docs/new-wsl-handbook.md", - "sha256": "c6a357756080c7651a3890f5ff2c9bcd4d5c14a0e596d7a7fa7372d2e08c2303", + "sha256": "193d6560d9ec3e20284bfce36fefdd0363b524edcd3498f613f0770999812056", "bytes": 368585 }, { @@ -40077,8 +40067,8 @@ }, { "path": "evidence/artifacts/new-wsl-handbook-20261001/receipt.json", - "sha256": "ef3cc8d69fe92ae8a50c97dd2b5fa43d028edd8b0f751dbd4ab497136b3eeedf", - "bytes": 40332 + "sha256": "084eb71632e71f16643ed54a166e6da44d615fa9f016e96aa53d666765cb27cd", + "bytes": 37656 }, { "path": "evidence/artifacts/new-wsl-handbook-20261001/slot-state-and-core-recipes.json", @@ -51075,31 +51065,6 @@ "sha256": "e5ba2fc9e397b97888a4929069434321e4ea17ea298a536ae20360638a304d50", "bytes": 11763 }, - { - "path": "evidence/artifacts/user-facing-local-time-20261005/README.md", - "sha256": "28476281a102651a8413997b69597d4d7195ee425e2d596772c42d8b09bb370f", - "bytes": 1955 - }, - { - "path": "evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/2026-10-01-new-wsl-distro-recipe.md.txt", - "sha256": "ec03a494aa4d80ac004f74710250bf1ac7cf18e0506758d97a1cf98a0cee42a3", - "bytes": 147654 - }, - { - "path": "evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/cloud-init.user-data.template.txt", - "sha256": "08e6a3f6b078dfa54d78297596d54729d10528d57147b9b9eae063c37160eeb9", - "bytes": 1527 - }, - { - "path": "evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/first-boot-checklist.md.txt", - "sha256": "1a06df0e6d50b07b439317a61a450e450127052589f63254e769d99166e3603b", - "bytes": 15192 - }, - { - "path": "evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/stage1-receipt.example.json.txt", - "sha256": "7c0811c460fe8732f413b954cd7ddadb5cc41a5300f8686b5b83aefcd80a971a", - "bytes": 60963 - }, { "path": "evidence/artifacts/workstation-lane-20260925/nemotron-qualification/model_card_check.py", "sha256": "c6ecc30a169b4af802f7e8497c7ebfc7c9373114bdd8589ef4ee4db102e516c3", @@ -53687,8 +53652,8 @@ }, { "path": "examples/claude-native/CLAUDE.md", - "sha256": "2f1d38a12e5714c6178e33f7ade258cf7b5751f174afd059c37d2947ae6261ed", - "bytes": 11905 + "sha256": "9bc0f41208ce30ec46a1f08fa7d0b6680f14b8e56266a414389d7e69d2ea36c2", + "bytes": 11575 }, { "path": "examples/claude-native/agents/blind-adjudicator.md", @@ -55162,8 +55127,8 @@ }, { "path": "tests/test_codex_worker_lane.py", - "sha256": "558925fea22815575e0dc5856bafba739d06bf0d4b3f40f8fb4f49960c322c0b", - "bytes": 191495 + "sha256": "d35f474cbdfda1e7666ee84479047de04f3666913d15110f7736f8c074bd8d41", + "bytes": 191406 }, { "path": "tests/test_codex_worker_skill.py", @@ -55377,8 +55342,8 @@ }, { "path": "tests/test_install_claude_profile.py", - "sha256": "c4b2815e0e51086aeeb9da24a88bba918b81812bf33c433f473e3619bdfdec80", - "bytes": 132552 + "sha256": "66225f2f62892b5600883e15ac362c0a62ef0d858e856ed00b86f418620699bc", + "bytes": 131331 }, { "path": "tests/test_install_skills.py", @@ -55957,8 +55922,8 @@ }, { "path": "tests/test_wsl_new_distro_recipe.py", - "sha256": "3604c05b04921fbc6c4493a1678885386f1ef3f05a7995592b9d04157ce5fa72", - "bytes": 237414 + "sha256": "9dab36806887d763e9df4cb8af472c223470c23ac133ebff5948ce64d13b898d", + "bytes": 221208 }, { "path": "tests/test_wsl_restore.py", @@ -56007,12 +55972,12 @@ }, { "path": "tools/adoption/host-baseline.md", - "sha256": "e7527052bd6c4f36bc7449185a978406bc796acf0f30c328f9ee6ec495a75a86", + "sha256": "478e7f061e964606cf4f7209155f596b76bf04bc5bd6a67fdaba7a5a0cbc4c4c", "bytes": 16770 }, { "path": "tools/adoption/host_baseline_probe.py", - "sha256": "e58d2797f72d298b10d4e501e709afc524b09180805c9bf9cf3c11505e7f33a0", + "sha256": "f99aaa0e8e3b6ef057dc47fb5b0f9fbf281bf07686cc5f2d57d287753c554c78", "bytes": 17992 }, { @@ -56032,7 +55997,7 @@ }, { "path": "tools/adoption/new_wsl_client_config.py", - "sha256": "31ef4d049e9a34ba7013a668179117b2d89ae1d46bf60f2299992987a870df5c", + "sha256": "08639afa0c43d537a9071055fbae81b219bff9550be76b2afaba50d386c2e147", "bytes": 160807 }, { From 4b828cbc2793bbcfd3c1749c550e48aa97de7247 Mon Sep 17 00:00:00 2001 From: Scout Date: Tue, 6 Oct 2026 19:31:58 -0400 Subject: [PATCH 10/10] Reapply this branch's manifests/evidence.json edits on the merge base (hot-file protocol: every hot-file edit in the last commit) Co-Authored-By: Claude Opus 5.5 --- manifests/evidence.json | 105 ++++++++++++++++++++++++++-------------- 1 file changed, 70 insertions(+), 35 deletions(-) diff --git a/manifests/evidence.json b/manifests/evidence.json index a73fb7053..210786ba2 100644 --- a/manifests/evidence.json +++ b/manifests/evidence.json @@ -5076,8 +5076,8 @@ }, { "path": "adoption/new-wsl/claude-user-instructions.md", - "sha256": "9bc0f41208ce30ec46a1f08fa7d0b6680f14b8e56266a414389d7e69d2ea36c2", - "bytes": 11575 + "sha256": "2f1d38a12e5714c6178e33f7ade258cf7b5751f174afd059c37d2947ae6261ed", + "bytes": 11905 }, { "path": "adoption/new-wsl/client-config-map.json", @@ -5086,8 +5086,8 @@ }, { "path": "adoption/new-wsl/codex-user-instructions.md", - "sha256": "f8874e406be3e7cc5a43bf9747eed5e6923ab5a997ac2f384694b6cc9f6bf539", - "bytes": 8373 + "sha256": "26474ab5025772b05ed8bf5ba393d49da61e902f003ad427bf7b245c56b91d51", + "bytes": 8701 }, { "path": "adoption/new-wsl/templates/claude-user.mcp.additions.json", @@ -5146,8 +5146,8 @@ }, { "path": "adoption/platforms/linux-wsl2-new-distro.md", - "sha256": "bc37a2c3fde395818f1ffcaae01e9578fa222367cac5b36f92bcbc013e5f491a", - "bytes": 93706 + "sha256": "da18d556933993c2308e68b7cdefa63b2f32e875a80a97c047482b750cfd1b34", + "bytes": 98823 }, { "path": "adoption/platforms/linux-wsl2.md", @@ -5216,8 +5216,8 @@ }, { "path": "adoption/templates/codex.AGENTS.template.md", - "sha256": "453173835cb0aec0d4e7396dc21fcc8e1e4bf7c0429078b3d2766b74dceb290f", - "bytes": 7307 + "sha256": "16dc351855e8c8cb27a0d0258636a91dee1db92b6f57adc7f541b764b327e140", + "bytes": 7635 }, { "path": "adoption/templates/codex.config.template.toml", @@ -5271,18 +5271,18 @@ }, { "path": "adoption/templates/wsl/cloud-init.user-data.template", - "sha256": "08e6a3f6b078dfa54d78297596d54729d10528d57147b9b9eae063c37160eeb9", - "bytes": 1527 + "sha256": "fea33774ce5989fef58465e226a1982e905e39fa8f8e4029e678d4700668afba", + "bytes": 1851 }, { "path": "adoption/templates/wsl/first-boot-checklist.md", - "sha256": "1a06df0e6d50b07b439317a61a450e450127052589f63254e769d99166e3603b", - "bytes": 15192 + "sha256": "523dd2ed0fe38dfab70061056cdabbf6a2df23257958ced6646fe0a624163c0f", + "bytes": 15681 }, { "path": "adoption/templates/wsl/stage1-receipt.example.json", - "sha256": "7c0811c460fe8732f413b954cd7ddadb5cc41a5300f8686b5b83aefcd80a971a", - "bytes": 60963 + "sha256": "b8e35b2791d47c57f69f086cba55ff15558c951ef6a3c3d50fc64aa4d60728a2", + "bytes": 63208 }, { "path": "adoption/tools/README.md", @@ -7517,8 +7517,8 @@ }, { "path": "blueprints/convergence-practice/wsl-new-distro-image-20261001/experiment.json", - "sha256": "822da49b85f1645c09887f27bf24268e554f2f27594719620e5cc8b28530853e", - "bytes": 23166 + "sha256": "ffbbd654c88cb8c78ddff1174dd2e15f4478cee78fa4d126a7fd16a3661f7c31", + "bytes": 23358 }, { "path": "blueprints/convergence-practice/wsl-restore/README.md", @@ -17317,8 +17317,8 @@ }, { "path": "docs/decisions/2026-10-01-new-wsl-distro-recipe.md", - "sha256": "ec03a494aa4d80ac004f74710250bf1ac7cf18e0506758d97a1cf98a0cee42a3", - "bytes": 147654 + "sha256": "8df3f18af352be4f1dd1a2a2dc5a4d718804582b8a6281e6d461dddd73e2647c", + "bytes": 151947 }, { "path": "docs/decisions/2026-10-01-new-wsl-handbook-generator.md", @@ -17337,8 +17337,8 @@ }, { "path": "docs/decisions/2026-10-02-new-wsl-client-configuration.md", - "sha256": "2bcc4e1c173e1780cd6f48f863e6e4fcb134d9374971f95d1217e755e7f336d4", - "bytes": 135623 + "sha256": "908789292c7d5919e1d4cff2cf552a0d254dea5af363d8c00cfee908b7583a24", + "bytes": 136295 }, { "path": "docs/decisions/2026-10-02-omniroute-mac-rebuild.md", @@ -17550,6 +17550,11 @@ "sha256": "c1eaaffe72e840c7655e05a44e71116174dc1701675d2e08730ba54a8c9a6e87", "bytes": 135389 }, + { + "path": "docs/decisions/2026-10-05-harness-context-budget.md", + "sha256": "9f1ddd367361a6f9c88b78d18e4f230b80935e966c9d1e3e91aacd578024c38e", + "bytes": 55486 + }, { "path": "docs/decisions/2026-10-05-main-checkout-backup-disposition.md", "sha256": "22839061d0826b5272f5b840f1765e5b175845b25b8f0e3c9a8ce8ccc8dfedba", @@ -17585,6 +17590,11 @@ "sha256": "d197ab297186fe55e6c83624c78a06ead264e00d66a7fe2d5e62cfc1b653dbd6", "bytes": 9226 }, + { + "path": "docs/decisions/2026-10-05-user-facing-local-time.md", + "sha256": "8bc7cb99e2b415fff9a1c4a7784c21291f3cacefac97107277a11988af9b1a34", + "bytes": 35121 + }, { "path": "docs/decisions/2026-10-06-application-input-binding-amendment.md", "sha256": "c12885d5d078f88cf6784d539e5b8261545a759b7aba8ff1eba566fb5d6366fb", @@ -17867,12 +17877,12 @@ }, { "path": "docs/new-wsl-handbook.json", - "sha256": "efd48eae032b0556f8679b3177c6f03fcae364bb6d353836e5254a2edd1722b1", + "sha256": "e85827c513b80cf075792edbcaf0c1b03c59ca89ca0c31e8fe846f2f3d1fa174", "bytes": 692584 }, { "path": "docs/new-wsl-handbook.md", - "sha256": "193d6560d9ec3e20284bfce36fefdd0363b524edcd3498f613f0770999812056", + "sha256": "c6a357756080c7651a3890f5ff2c9bcd4d5c14a0e596d7a7fa7372d2e08c2303", "bytes": 368585 }, { @@ -40067,8 +40077,8 @@ }, { "path": "evidence/artifacts/new-wsl-handbook-20261001/receipt.json", - "sha256": "084eb71632e71f16643ed54a166e6da44d615fa9f016e96aa53d666765cb27cd", - "bytes": 37656 + "sha256": "ef3cc8d69fe92ae8a50c97dd2b5fa43d028edd8b0f751dbd4ab497136b3eeedf", + "bytes": 40332 }, { "path": "evidence/artifacts/new-wsl-handbook-20261001/slot-state-and-core-recipes.json", @@ -51065,6 +51075,31 @@ "sha256": "e5ba2fc9e397b97888a4929069434321e4ea17ea298a536ae20360638a304d50", "bytes": 11763 }, + { + "path": "evidence/artifacts/user-facing-local-time-20261005/README.md", + "sha256": "28476281a102651a8413997b69597d4d7195ee425e2d596772c42d8b09bb370f", + "bytes": 1955 + }, + { + "path": "evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/2026-10-01-new-wsl-distro-recipe.md.txt", + "sha256": "ec03a494aa4d80ac004f74710250bf1ac7cf18e0506758d97a1cf98a0cee42a3", + "bytes": 147654 + }, + { + "path": "evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/cloud-init.user-data.template.txt", + "sha256": "08e6a3f6b078dfa54d78297596d54729d10528d57147b9b9eae063c37160eeb9", + "bytes": 1527 + }, + { + "path": "evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/first-boot-checklist.md.txt", + "sha256": "1a06df0e6d50b07b439317a61a450e450127052589f63254e769d99166e3603b", + "bytes": 15192 + }, + { + "path": "evidence/artifacts/user-facing-local-time-20261005/frozen-inputs/stage1-receipt.example.json.txt", + "sha256": "7c0811c460fe8732f413b954cd7ddadb5cc41a5300f8686b5b83aefcd80a971a", + "bytes": 60963 + }, { "path": "evidence/artifacts/workstation-lane-20260925/nemotron-qualification/model_card_check.py", "sha256": "c6ecc30a169b4af802f7e8497c7ebfc7c9373114bdd8589ef4ee4db102e516c3", @@ -53652,8 +53687,8 @@ }, { "path": "examples/claude-native/CLAUDE.md", - "sha256": "9bc0f41208ce30ec46a1f08fa7d0b6680f14b8e56266a414389d7e69d2ea36c2", - "bytes": 11575 + "sha256": "2f1d38a12e5714c6178e33f7ade258cf7b5751f174afd059c37d2947ae6261ed", + "bytes": 11905 }, { "path": "examples/claude-native/agents/blind-adjudicator.md", @@ -55127,8 +55162,8 @@ }, { "path": "tests/test_codex_worker_lane.py", - "sha256": "d35f474cbdfda1e7666ee84479047de04f3666913d15110f7736f8c074bd8d41", - "bytes": 191406 + "sha256": "558925fea22815575e0dc5856bafba739d06bf0d4b3f40f8fb4f49960c322c0b", + "bytes": 191495 }, { "path": "tests/test_codex_worker_skill.py", @@ -55342,8 +55377,8 @@ }, { "path": "tests/test_install_claude_profile.py", - "sha256": "66225f2f62892b5600883e15ac362c0a62ef0d858e856ed00b86f418620699bc", - "bytes": 131331 + "sha256": "c4b2815e0e51086aeeb9da24a88bba918b81812bf33c433f473e3619bdfdec80", + "bytes": 132552 }, { "path": "tests/test_install_skills.py", @@ -55922,8 +55957,8 @@ }, { "path": "tests/test_wsl_new_distro_recipe.py", - "sha256": "9dab36806887d763e9df4cb8af472c223470c23ac133ebff5948ce64d13b898d", - "bytes": 221208 + "sha256": "3604c05b04921fbc6c4493a1678885386f1ef3f05a7995592b9d04157ce5fa72", + "bytes": 237414 }, { "path": "tests/test_wsl_restore.py", @@ -55972,12 +56007,12 @@ }, { "path": "tools/adoption/host-baseline.md", - "sha256": "478e7f061e964606cf4f7209155f596b76bf04bc5bd6a67fdaba7a5a0cbc4c4c", + "sha256": "e7527052bd6c4f36bc7449185a978406bc796acf0f30c328f9ee6ec495a75a86", "bytes": 16770 }, { "path": "tools/adoption/host_baseline_probe.py", - "sha256": "f99aaa0e8e3b6ef057dc47fb5b0f9fbf281bf07686cc5f2d57d287753c554c78", + "sha256": "e58d2797f72d298b10d4e501e709afc524b09180805c9bf9cf3c11505e7f33a0", "bytes": 17992 }, { @@ -55997,7 +56032,7 @@ }, { "path": "tools/adoption/new_wsl_client_config.py", - "sha256": "08639afa0c43d537a9071055fbae81b219bff9550be76b2afaba50d386c2e147", + "sha256": "31ef4d049e9a34ba7013a668179117b2d89ae1d46bf60f2299992987a870df5c", "bytes": 160807 }, {