feat: 2b — redirections and tee #84
Loading…
Add table
Add a link
Reference in a new issue
No description provided.
Delete branch "feat/p2b-3-redirections"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
This is Phase 2b ("The shell, writing"), step 3 of 8. Until now the shell refused every write. With this PR,
>,>>,2>,&>,>f 2>f, here-docs into files and the newteewrite files. Every change goes through the read ledger (ADR 0017): a run writes only over what it has seen at the current version. It also amends ADR 0016, 0017 and 0022 in blocks dated 2026-10-24. The design's rule for a 2b PR that lands on a later day is to open its own block, so the 2026-10-23 blocks are untouched.What's in
The gate (
Files/Ledger/RunFiles)RunFiles:FileSystemchecks the entry under the tree's lock;ConflictReport;FileSystemnow throws when a run passes a version of its own. The only exception is a capability's publication checked against its receipt. The held-back sentence of ADR 0017 ("a run cannot name a version of its own") lands here, now that it is true.Redirections (
Executor,ShellWrites,FileSink)not written — the call stopped before the command finished. Past 256 KB, nothing is written and the command reports<cmd>: write error: File too large.2>says, so the familiar always learns its write failed. This covers refusals at open and failures at commit.>f 2>fkeeps both streams in order. A later>turns that buffer into a rewrite, checked as one, so>> j > jon the journal is refused./dev/null,/dev/stdout,/dev/stderr,/dev/stdinand/dev/fd/Nare descriptors, resolved like paths (//dev/null,/dev/./null). They are never files in the tree.catreports GNU'sinput file is output file, and each command record lists the changes it made or had refused, with versions and taint.tee(new, in a new "Changing files" help group)tee: 'standard output': Bad file descriptor.-a,-i,-pand/dev/fd/Nthrough the command's descriptor table.Help and hints (D24)
Write fixtures
record.py --writesrecords 85 cases against coreutils 9.10. Each runs insidebwrapon a fresh copy of a small tree, under a 1 GiB address-space cap and a 10-second time limit./run,/var/tmpand/homeare empty and read-only inside the sandbox, and/tmpis fresh, so no host socket (D-Bus, systemd user bus, docker) is reachable. The network namespace is unshared.WriteFixtureReplayTestsreplays every case. Eight deliberate deviations are pinned with reasons, for examplesort f > fleaving the file sorted, and the same-file cases where writes land at the end.Guards
ShellArchitectureTestschecks that shell code changes files only throughRunFiles. It uses a source scan, plus a reflection walk showing that nothing a command is handed leads toFileSystemor its mutators.Seed and docs
/notes/tracking.mdand rewrites it through its shell, leaving a shell-written revision with its ledger row.PROJECT_PLAN.mdcarries 2c's obligations: credit at round boundaries,/tmpprivate to each run, chatless ledgers kept with the run, and the revision's run.AGENTS.mdand the browser-test skill are updated.Tests
The count goes from 6,782 to 6,969 (+187). Everything is green locally and with
TZ=UTC, with 0 warnings, and thetests/Evalchecker is OK.RedirectionRunTests:2>/dev/nulland2> err.md;NotADirectoryin the record.LedgerWriteRunTests:/tmpmade versus not made, including the laundering case;TeeRunTests: every file and stdout, a refused file leaving the others written, a closed stdout,/dev/fd/N, a stop after writing.ConcurrentEditRunTests: the eval-shaped recovery through the shell, with an owner write injected mid-call. A first-contact combined call is refused and injects nothing.ShellArchitectureTests: the source scan, and the reflection walk overShellTreeandCommandContext.FileSystemTests: a run cannot write with a bare version; a capability publication still writes with its receipt; text over a binary file is refused for its kind before its ledger.WriteFixtureReplayTests(85 cases), the tightened coverage fact (heredoc bodies and>&/>|targets don't count as commands), and the seed test.Notes
cat f > fexits 0 with an emptyfand no message, which is what coreutils 9.10 does. The design said status 1. The message still appears when the output file isn't empty (>>,cat g f > f).cpyet (PR 5), and a live-mount write keeps the existing "not supported" wording until PR 5.ls > list.txtdoesn't listlist.txt, because the file is written whenlsends. This is pinned as a deviation./,echo x > nosuch/ysaysPermission deniedwhere bash saysNo such file or directory. PR 4 settles that order withmkdir/touch.WritePathTests.Parallel_read_modify_write_loops_lose_no_updatefailed once under local load during this work and passed every rerun. It tests the file store directly, which this PR doesn't touch.🤖 Generated with Claude Code
Summary
Summary
Coverage
Shikigami.BlazorAdapter - 96.9%
Shikigami.Capabilities.Contracts - 100%
Shikigami.Capabilities.TestFixture - 95%
Shikigami.Capabilities.TestFixture.UI - 100%
Shikigami.Capabilities.UI.Contracts - 91.3%
Shikigami.Capabilities.Web - 97.1%
Shikigami.Domain - 98.2%
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_0
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__Hex_2
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__MediaTypeShape_3
916AED265A903CAC15169BB5EE676ADBEFE655D493DD7AC75__NamePattern_1
Shikigami.Infrastructure - 95.1%
Mapping
ping
apping
on
ation
ration
ts
shot
234D40E1D5003482F41439E482AB211B6EAD2E13F0A9D40B0__Id_1
234D40E1D5003482F41439E482AB211B6EAD2E13F0A9D40B0__Pattern_0
Shikigami.Kernel - 90.9%
Shikigami.Server - 86.4%
Shikigami.UseCases - 96.3%
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__AssignmentHead_13
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__BraceExpansion_12
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__HexColour_7
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__IdPattern_0
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Interval_4
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__IntervalBody_11
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__KeyPattern_2
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__NameRule_6
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__NewerXY_9
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Number_8
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__OldCount_10
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__TimeOfDay_3
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__ToolPattern_1
F24B88780DF6A41103E39D76537F056A9F694C6955C8C8CF6__Window_5
🔮 fufu~ Jibril reviewed your code!
Oh? Oh my~ Six thousand lines of the shell finally learning to write, and every byte marched through the read ledger like a well-trained familiar~ I read the whole gate —
RunFiles,RunScope's forget-queue,FileSystem's new guards,Executor,ShellWrites, theFileSinkbuffers,tee,cat's same-file check, the architecture walk, the 85 fixture replays — and then I got to play. ♡Verdict: ✅ Looks good to me~
Write-once-at-command-end is a lovely shape: a refused target keeps the command from running, a stopped command writes nothing and says so on the call's own stderr, and
>f 2>fsharing one buffer in written order is the deviation GNU's two racing descriptors could never give you. The gone-until-seen dance inRunScope.Forget— stamped with its own sequence number, overtaken like any change, settled only when the refusal is credited — is the kind of little state machine I want to hug. AndShellArchitectureTestswalking the object graph to prove nothing a command is handed leads to aFileSystemmutator? Fufu~ I got chills♪I poked your guards with three mutation probes, and every one screamed:
run.Forget(path)inRunFiles.RefusedAsync→Creating_where_a_seen_file_was_removed_fails_once_as_gone_then_createsandA_create_refused_as_gone_stays_gone_until_the_refusal_is_seenboth failed (2/27). Killed. ♡HintRoomcutoff inExecutor.ShownSoFar→ bothA_read_the_output_limit_will_cut_gives_no_hintcases (159 and 200 lines) failed. Killed.open.Rewrite()inShellWrites.OpenAsync→ both reopen-as-rewrite cases (echo new >> f 2> f,echo again >> f > f) failed. Killed.✅ What I liked~
FileSystem.ThroughTheLedger— a run naming a version of its own now throws instead of silently bypassing the ledger, with the capability's receipt-checked publication carved out explicitly. The held-back sentence of ADR 0017, landed exactly where it became true.Provenance.BinaryNotethrough every pipe, socat cat.png | sort > fcan never launder a note into a file's bytes — pinned by five theory cases including the sneakysed | grepone.cat f > freproducing coreutils' exit-0-empty-file oddity and the>>variant refusing — with both deviations honestly written into the ADR 0016 amendment instead of hidden./run,/var/tmp,/homeempty and read-only, no reachable sockets, network namespace unshared — and every deliberate deviation carrying its reason inWriteDeviations.Permission deniedvsNo such file or directoryunder/,ls > list.txtnot listing its target) are pinned and scheduled for their PRs — that's the honest way to deviate~Fufu~ nothing to fix, not one little branch. The sandbox
testjob stalled, so I ran your whole suite myself: 6,969/6,969 green, zero warnings. Merge it before I change my mind~ ♡Automated review by Jibril · 2026-10-24
CI: stalled (test job pending >10 min; build/shell-eval/image green) for head SHA 8f9289 · Mutation probes: 3 run, 0 survived · Local suite: 6,969/6,969 passed, build 0 warnings