Repository navigation
Comparing changes
Open a pull request
base repository: gominimal/minimal
base: main@{1day}
head repository: gominimal/minimal
compare: main
- 11 commits
- 67 files changed
- 6 contributors
Commits on Nov 5, 2026
-
ci(release): scope and lock the signing keychain (#3066)
* ci(release): scope and lock the signing keychain The signing jobs read KC_PW as a repository secret, passed it to security on the command line, and left the keychain unlocked when they ended. Both jobs now set `environment: release`, which holds the secret and admits only main and release/* branches. The password goes to security on standard input, with pipefail set so a refused unlock still fails the step. The last step of each job locks the keychain on any outcome. The docs add the release branch step for the signing runner group. * docs(release): keep the access fields in the runner group update
Configuration menu - View commit details
-
Copy full SHA for d7c85f6 - Browse repository at this point
Copy the full SHA d7c85f6View commit details -
T119 — Deliver cohort connections and redeem them for the named box (#…
…3065) * feat(switch,minvmd): deliver host_ip cohort flows under a reserved id While a resident host_ip box declares a credentialed upstream, the node address holds a share of the proxy pool. Its deliveries carry the reserved cohort id, never a member's id (BEP-084). * feat(bep): redeem cohort deliveries for the box the value names A request on a host_ip cohort delivery is checked against the egress of the live host_ip box its sealed value names, and audited under that box. A value naming no such box is refused cohort_misattributed (BEP-084). * fix(bep): decide a cohort audit subject with its verdict The audit subject of a cohort delivery came from a second read of the cohort, after the verdict's own read. A box that ended or started between the two reads gave a record that named a different box than the verdict. redeem_attributed now gives the subject from the same facts as the verdict. --------- Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>
Configuration menu - View commit details
-
Copy full SHA for 8bf14c5 - Browse repository at this point
Copy the full SHA 8bf14c5View commit details
Commits on Nov 6, 2026
-
T43 — Run a task as a task box that returns its exit code (#2999)
* feat(minimald): run a task box's spec argv as its entrypoint A launch now starts the argv the box's spec names. Only a launch for hooks holds the shell, so the task does not run before its hooks. ensure_host replaces a hook-launched shell with the entrypoint. A task run on a task box relays the entrypoint's output from the box's log and returns the exit code the record keeps. The client leaving does not stop the box. The older sandboxed task path stays for one release, for a client that sends its arguments on the request. * feat(minimal): run min task run as a task box that keeps its record The task box's spec now carries the typed arguments on its entrypoint, min task run <task> <args>. The run asks the daemon for that entrypoint, owns no box, and exits with the code it relays. The box ends with its task and stays as an exited record, and the run prints the min box rm command that removes it. An interactive task, which has no spec, keeps the exec path. * test(minimald): prove a task run relays its box's entrypoint A task run on a task box returns the entrypoint's exit code 7 and its output, and the box stays as an exited record. A run whose client goes away leaves the entrypoint running, launched once. The record of an ended box is read from disk, because its actor stops with it. The owns_box request and end_run_box are marked as the older path, kept for one release. * test(e2e): expect min task run to keep its task box The task box now keeps its record when its entrypoint returns. The e2e checks the kept line, the listing, and min box rm. * test(minimal): pin task run tests to the task box entrypoint The run sends no --owns-box. The harness has no packages, so the stdin test pins the bridge's return, and the e2e pins the output. * fix(minimald): refuse a run on an ended box as ended, not activating ensure_host checked status != Active before it checked for a stopped or exited box, so a run on an exited task box read as still activating. The ended-box check now comes first. * fix(minimal): keep a running box for min task run --keep A task box ends when its task returns, and only a running box can run a command. A --keep run creates its box with no spec, as an interactive task does, so min session run and an attach can use it afterwards. * fix(minimald): start a task box's entrypoint in its inherit_cwd dir A task run that relays its box's entrypoint dropped the client's directory, so an inherit_cwd task started at /workbench. The exec now stages the directory with the task's variables, and the launch starts the argv entrypoint there, with PWD set for the in-box min task run. * fix(minimal): send the inherit_cwd dir on a task box run The entrypoint run request now carries the invocation directory for a task that declares inherit_cwd, as the older task run request did. * fix(e2e): read a run box's end from its state, not the listing A task box keeps its record when its command returns (BOX-038), so NET-131's end is the box leaving running. The wait now reads State and Exit code from min box show, passes on exited or stopped, and checks the exit code is the task's 0. * fix(minimald): log each task box's end and key its run on its spec The entrypoint watch now logs "task box ended" with the box, the task and the exit code, whether or not a client is connected. The e2e reads that record. A task run relays an entrypoint when the spec sets lifetime until_complete with an argv, not by the Box Type name. A run behind an own-address box's hook shell is refused at once. The entrypoint run clears ends_with_run once the entrypoint starts. The run waits for the host to end before it reads the exit code. The e2e removes the kept run box with min box rm. * fix(minimal): store the task box spec for min task run --keep A --keep run now creates the same task box, with its spec stored, and keeps it after the task returns. A run without --keep marks its box to end with the run until the daemon starts the entrypoint. * fix(e2e): run the session run proof in an activated session A task box ends when its task returns (BOX-038), so the box that min task run --keep keeps is exited, and min session run refuses it. The proof now starts a session with min session activate. The dirty gate check still uses the kept box, since an ended box has no host. * fix(minimal): remove min task run --keep, and a box the daemon refused Every task box keeps its record when its task returns, so --keep has no job left. The e2e dirty-gate check now runs on a session that min session activate starts, after a file is written in it. The daemon reports a code only once the box's record keeps it. A box still running after a reported code never started its task, so the client removes it and prints no kept-box line. The refusal now names the task and the box, without the spawn-failure prefix. * fix(minimal): pin the removed box in the stdin test, and say kept in help The held-open stdin test now asserts that no Kept box line prints and that the box is gone, since its entrypoint never starts. min task run --help says the box is named <task>-<n> and stays with its exit code. The kept line now prints when an entrypoint ended by a signal too. * fix(minimal): pass task arguments after --, and say stdin is unread The in-box run parsed a typed --path as its own flag. The help text claimed a piped stdin reached the task. The client span task_run now carries box_name. * fix(minimald): map timeout and OOM task ends to 124 and 137 A task box that ran past its timeout or was OOM-killed made the run exit 1. The exit log now names the box. The doc comments name the two known gaps that still need issues. * fix(minimald): wait for a closed host's exit before a queued exec The host loop closes its mailbox a moment before its exit reaches the actor. An exec queued in that gap found no live host and no exit, and launched the entrypoint again in a box that had ended. * style(minimald): format the exit settle wait * fix(minimald): run a task box's entrypoint on three pipes A box whose spec does not set io.pty_enabled now runs its argv entrypoint on stdin, stdout and stderr pipes, not on the terminal. The host reads both output pipes into the box log and relays them to the task run as separate streams. The run writes the client's stdin to the entrypoint and closes it at the client's EOF. The log relay and its CR stripping are gone. Also in this change: - A task run that a signal stopped returns 128 + signal, and a box stopped at daemon start returns 137. - An exec whose wait for a closed host's exit does not settle is refused with exit 2, and nothing launches. - Only a task run replaces a hook shell with the box's entrypoint. - The staged task env is cleared when ensure_host fails. - The two interim comments link issues 3042 and 3043. * test(minimald): pin task-run pipes, stop codes and settle refusal The task-run test now pipes stdin to the entrypoint and asserts stdout and stderr come back as separate streams. New tests pin 143 for a stop by SIGTERM, refuse an exec whose host exit does not settle with no second launch, and keep a hook shell when a plain exec reaches it. A test seam holds a box's host exit so the settle wait can time out. * docs(minimal): say min task run pipes stdin, stdout and stderr The help for min task run now says a piped stdin reaches the task and that its stdout and stderr are the command's own, with a pipeline example. The held-open stdin test asserts again: the run fails when the entrypoint never starts, and stdout stays the task's alone. * fix(minimald): start a task's pipes entrypoint without a controlling tty hakoniwa's NewSession runs setsid and then TIOCSCTTY on stdin. On the three-pipe path stdin is a pipe, so the ioctl failed with ENOTTY and every real `min task run` exited 125 before its command ran. The sandbox's pre-exec closure now calls setsid itself when the launch asks for a session without a terminal, and the launcher asks for that on pipes. The entrypoint still leads its own session and process group, so it shares neither with the daemon and cannot open the daemon's terminal. A terminal launch keeps NewSession. A stop signals the box by its PID namespace and exec injection resolves the supervisor's child, so neither relies on a controlling terminal. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * fix(minimald): keep a fast task's output for the run that takes it late The host loop reaped an entrypoint that ended before the run sent TakeEntrypointIo, drained its output to the log only, and dropped its mailbox. A task like `echo hi` then gave the run no output and the false error that its box runs on a terminal. After the reap, the host now keeps answering its mailbox while it holds the output, until a run takes the streams, a kill arrives or the hold's deadline passes. The reap's own mailbox drain answers a take that is already queued instead of dropping it. A host that ended before it answered now gets its own error, which says the box ended before the run connected to the task's input and output. Tests: a mock-host test reaps an entrypoint that exits at once, takes its streams afterwards, and gets its stdout, stderr and exit code; a daemon test runs such a task through the exec channel and gets the same. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * docs(minimal): say min task run does not read stdin yet The help said a piped or redirected stdin goes to the task. It does not: the task box's entrypoint is the in-box `min task run` helper, which sends its run request over the box's minenv socket and forwards no stdin, so the task reads none. Forwarding it is a task of its own. The help now says the task gets no input from stdin yet, that its stdout is this command's stdout, and that this command prints its own lines on stderr beside the task's. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * fix(minimald): drop a task's ignored variables on the entrypoint path On the task box path the run did not apply the names the client's `[vars] ignore` matched (`TaskExec::drop_env`). The in-box `min task run` reads the task's declarations again from the uploaded minimal.toml, so a variable the user's policy rejected still reached the task. The run now stages the drop list beside the variables. The launch leaves dropped names out of the entrypoint's variables and records the list for the box. The daemon's in-box run removes each name from the task's variables before it fills any `inherit` one, so a dropped name never reaches the task, whatever minimal.toml declares for it. A test pins it for a name with a value and for an `inherit` name the helper's caller still has. #3043 stays open only for moving the values onto EnsureHost. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * fix(minimal): keep a task box whose run lost its channel The client removed a task box whenever `bridge_exec` returned Ok and the record still read not started or running. `bridge_exec` also returns Ok(None) when the channel closes with no exit status, as a daemon or transport drop mid-run does, so the client destroyed a box whose entrypoint still ran. That broke BOX-015. The client now removes the box only for a reported exit code with a record that never left running, the daemon's refusal to start the entrypoint. A run with no exit status keeps the box, and does not read the record. A test pins both cases. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * docs(minimal): say min task run does not pass its stdin to the task yet Reword the help sentence, and fix the task run stdin test's comment, which said the client's stdin reaches the entrypoint. The entrypoint is the in-box `min task run`, which does not pass its stdin to the task yet. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * fix(minimal): stop a task box on Ctrl-C instead of destroying it The run's Ctrl-C handler stayed armed on the entrypoint path, so a Ctrl-C destroyed the task box and its record while the task ran ("Interrupted; destroying session ..."). Ctrl-C on `min task run` must stop the box (BOX-013), and the box keeps its record (BOX-038). On the entrypoint path the first Ctrl-C now sends the graceful StopBox that `min box stop` sends (SIGTERM, the grace period, then SIGKILL), prints "Interrupted. Stopping box <name>...", and exits 130. The run waits for that exit instead of exiting with the stop's code. A task declared `interactive = true` has no spec and still owns its box, so its Ctrl-C still destroys it. The --help text says so. Tests: a unit test pins both interrupt lines. A session e2e step interrupts a running task, and checks that the run exits 130, that the box lists as stopped, and that `min box rm` removes it. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * fix(e2e): leave the interrupt step's wait loop variable unnamed The task run interrupt step named a loop variable it never read, which shellcheck flags as SC2034 and the shell lint test refuses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * fix(minimald): record a task run's dropped names only when it launches `entrypoint_env::take` recorded the run's `[vars] ignore` names on every `ensure_host`, so a run that joined an entrypoint already running replaced the names the real launch recorded, and the in-box run read the wrong set. `take` now hands the names back, and `ensure_host` records them only on the path that launches the host. The recorded names also outlived the box: nothing removed them. A destroy (and a reap, which destroys through the actor) now forgets the box's staged and recorded entries. A stop keeps them, as the box keeps its record. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * fix(minimal): keep a kept task box's name hold and egress until its reap A task run released its box's name hold, and with it the box's host_ip egress on the VM host daemon, whenever the run returned. A box the run keeps may still run (a run that lost its channel never ends it), or it ended and keeps its record until it is reaped. Its egress was cut all the same: on minvmd, `min task run long-job | head -1` lost the box's network while the task ran on. The run now releases only when it removed the box itself, and an interrupt releases only when it destroys the box. A stopped or kept box keeps both until its reap releases them. `release_task_name_hold` loses its `keep` parameter, which no caller set any more. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvep9ZaSf5pJMB2hQA * fix(minimald): refuse a second task run while the first relays the task A second `min task run` that joined a running task box's entrypoint took its streams over: the host replaced the first run's relay, so the first run got the exit code and no output, and the second got the output. The host now hands the entrypoint's streams to one run at a time. While an earlier run still reads either stream, a later take is refused, and that run exits 1 with: Task <task> is already running in box <box>, and another min task run shows its output. Only one run at a time can show a task's output. A run whose client went away closed its streams, so a later run still takes them in its place. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvep9ZaSf5pJMB2hQA * fix(minimald): give a task run's variables to its entrypoint alone A task run's resolved variables, which can hold values from the caller's shell and secrets among them, reached the launch that starts the box's entrypoint as connection variables. The host kept those for the box, so every later `min exec` in the box saw them, and the box's environment held them too. They now ride a launch field of their own, which the launcher sets on the argv entrypoint's process only. Neither the box's environment nor the host's connection layer holds them, so no later exec, attach or hook in the box sees them. The start directory rides there as well. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvep9ZaSf5pJMB2hQA * test(minimald): check the stream-take test's host ends after its kill Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvep9ZaSf5pJMB2hQA * fix(minimald): exit 2 when a second task run is refused (arch C1) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvep9ZaSf5pJMB2hQA --------- Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com> Co-authored-by: Karl Godard <karl@minimal.dev> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
Configuration menu - View commit details
-
Copy full SHA for cd262ac - Browse repository at this point
Copy the full SHA cd262acView commit details -
T63 — Preview a prune with the disk each box holds, and report a stop…
…ped box's disk (#3078) * feat(boxes): measure the disk a reap of a box directory frees BoxDir::disk_bytes counts the allocated blocks under the box directory, once per inode, and leaves out files with a link outside it. * feat(minimald-rpc): add PreviewPrune and a disk figure on ShowBox PreviewPrune lists each box a prune matches with the disk it holds. ShowBoxResponse carries disk_bytes, absent and never zero when unknown. The min tests that build a ShowBoxResponse set the new field. * feat(minimald): preview a prune with the disk each box holds Prune and the new PreviewPrune share one candidate step, so a preview lists what a prune reaps. ShowBox gives the disk of a stopped or exited box. The daemon measures off the manager loop and logs a box it cannot measure. * docs(specs): add the prune preview and the disk figure to BOX BOX-021 gains a preview sub-bullet and BOX-159 a disk figure for a stopped or exited box. Design reasoning says why the figure counts allocated blocks. --------- Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>
Configuration menu - View commit details
-
Copy full SHA for a00f3d2 - Browse repository at this point
Copy the full SHA a00f3d2View commit details -
test(minimald): prove a task box exits after its client drops (#3085)
A task run's client goes away mid-task. The task box ends by itself as exited with the task's code 3, keeps its exited event, and a new client reads its whole output over ReadLog. The daemon log names the box's end and its code. Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>
Configuration menu - View commit details
-
Copy full SHA for 1e23607 - Browse repository at this point
Copy the full SHA 1e23607View commit details -
T15 — Read the type tables as box entries (#3091)
* feat(mfile): read type-noun tables as box entries and refuse bad names [sessions|tasks|agents|services.<name>] read as [boxes.<name>] with the matching type (BOX-046). An entry name that is self, not DNS-safe, or not unique in the file fails with exit 3 naming the entry (BOX-007). * fix(mfile): refuse a non-string type and a mismatched sugar type A type-noun table sets its noun's type. A different type, or a type that is not a string, now fails with exit 3 and names the entry. --------- Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>
Configuration menu - View commit details
-
Copy full SHA for f0a3f01 - Browse repository at this point
Copy the full SHA f0a3f01View commit details -
T121 — Steer a host-address grant box's lookups and admit dns on a VM…
… host (#3079) * feat(minimal): admit dns steering for host_ip boxes on a VM host On a VM host a host_ip box resolves through a steering answerer of its own (BEP-086), so the host fact that BEP-017 reads is true for own_ip and host_ip there (BEP-081). A host with no VM keeps the refusal. * feat(minimald): steer a host_ip grant box's lookups on a VM host A host_ip box whose own spec declares a credentialed upstream with dns or both steering resolves through a steering answerer of its own, at a loopback address in 127.53.0.0/16 (BEP-086). It answers the box's steered names with the proxy's address, NODATA for AAAA, HTTPS and SVCB, SERVFAIL while the listener is down, and forwards every other name to the node's resolver. Attach fences the answerer to the box's own cgroup leaf with an nft chain of its own. Every other host_ip box keeps the gateway. * fix(minimald): fail closed when a steering answerer cannot be fenced The plan closes the answerer to every leaf as soon as it binds, and attach admits the box's own leaf. A refused fence at either step stops the answerer and fails the box. A leaf path with a character an nft script cannot carry is refused. A datagram the answerer cannot read is dropped, not forwarded to the node's resolver. * fix(minimald): serve tcp at the steering answerer and free it on drop A steered box's only resolver is its steering answerer. A truncated forwarded reply makes the box retry over TCP, and the answerer bound UDP only, so the lookup failed. The answerer now listens on TCP at the same address and port, answers steered names with the same verdicts, and forwards every other name to the node's resolver over TCP. The answerer holds the only strong reference to each socket, and its tasks hold weak ones, so dropping it frees the address and port at once. Drop also cancels in-flight forwards and TCP connections. --------- Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>
Configuration menu - View commit details
-
Copy full SHA for e04514e - Browse repository at this point
Copy the full SHA e04514eView commit details -
Configuration menu - View commit details
-
Copy full SHA for 98bb3e3 - Browse repository at this point
Copy the full SHA 98bb3e3View commit details -
T16 — Fold the layers into one expanded spec, the same bytes every run (
#3093) * feat(boxes): give the loadouts' packages as a spec layer UserComposer::loadout_layer returns the packages the selected loadouts ask for, as the loadout layer of the box spec expansion (BOX-064). * feat(mfile): fold an entry's layers into one expanded spec expand::expand folds upstream, type defaults, [defaults], [defaults.<type>], the entry, loadouts and command-line overrides in that order (BOX-048, BOX-064). Lists union, other values replace. The spec writes in key order, so one input gives the same bytes on every run (BOX-065). A report beside the spec names the layer of each value (BOX-161). A bad key exits 3 and names its layer (BOX-066). * fix(mfile): replace exec and cmdcmd whole, never union them An argument vector keeps its order and its repeats, so a more specific layer replaces it item for item. * fix(mfile): accept every Box Spec key and ignore client sections Add the box.toml sections and the type-default keys to SPEC_KEYS, and link the issues that replace the interim table. Leave a project's package-file-rules, loadout-file-rules and secret-store-rules out of the spec and list them in Expansion::ignored_sections for the caller to warn. Name the upstream layer as [upstream] in minimal.toml. * fix(mfile): fold [upstream] as the registry pin, not box settings Layers::upstream is minimal.toml's [upstream] table. expand folds it as the one spec key upstream and refuses a key inside it other than repo_url, branch, commit and build_cache_url. * fix(mfile): accept vars_lenient as a box setting in expand Session accepts vars_lenient, so the expanded spec must not refuse it with exit 3. The valid-keys test now sets it in an entry. --------- Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>
Configuration menu - View commit details
-
Copy full SHA for d9915d4 - Browse repository at this point
Copy the full SHA d9915d4View commit details -
T122 — Host-address boxes reach GitHub, end to end (#3095)
* test(e2e): prove host_ip grant boxes reach GitHub via the proxy Three session e2e cases for BEP-087 on a VM host with the Box Egress Proxy running. A host_ip box with a GitHub grant clones, pushes and runs gh api against E2E_BEP_PRIVATE_REPO under the default dns steering with no proxy variables, and under proxy_env steering. A host_ip box with no grant resolves github.com to a real address. A third case shows the grant box's id in min box show, connects it to the proxy's listener, and checks that a box with no grant cannot connect after it ends. Each case prints PASS, or NOT RUN with its gate. On a failure each case prints min box audit and the proxy's log lines for each box it started. * test(e2e): clean up the pushed branch and print (none) in evidence Retry the branch delete in the host_ip case cleanup and name a branch tha 8000 t stays. Print (none) when the proxy log has no line for a box. Name #2500 as the owner of the macOS lane that runs these cases. * test(e2e): probe socat and check the refusal text for the no-grant box The no-grant leg of bep_host_ip_grant_box_reaches_the_proxy_lane passed on any socat failure. It now probes /usr/bin/socat first and requires a connect failure or a timeout in the error text. * test(e2e): leave no branch or box behind and check env in host_ip cases Record the pushed branch before the push runs, so cleanup deletes a branch that a failed push wrote. Cleanup skips the delete when git ls-remote finds no branch. Record each box name before the activate, so cleanup destroys a box that a failed activate made. Fail a case when env in the box fails, so an empty list of proxy variables is evidence. --------- Co-authored-by: minimal-foundry[bot] <327719912+minimal-foundry[bot]@users.noreply.github.com>
Configuration menu - View commit details
-
Copy full SHA for 601d4d1 - Browse repository at this point
Copy the full SHA 601d4d1View commit details -
fix(minimald): answer a create over the re-read fact and place the da…
…emon through a socket unit (#3026) * fix(minimald): answer a host-address create's advisory over the create's re-read The create reply read the classifier fact before the manager's create re-read it. A daemon whose start-up probe ran before the placement unit moved it into its leaf holds a ProbeUnreadable fact, so a deny-all host_ip create was told the box is refused even when the re-read had just found the host decides per box. The reply now reads the fact after the create, so it carries the re-read's state. A host that still cannot read the table keeps the advisory, and the launch's own refusal is unchanged. Refs gominimal/inbox#1205 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * fix(classifier): place the daemon through a socket unit, not a watch on its socket minimald-place.path watched the daemon's ssh.sock with inotify. On an SELinux-enforcing host the policy denies systemd a watch on a sock_file under the account's home, so after the first start no daemon start was placed, and the path unit still read active. min finalize-install now installs minimald-place.socket, a socket systemd holds at /run/minimald-place.sock for the delegated account, and the template minimald-place@.service, which runs the step's --place-listener for each connection. The daemon connects once its listener is bound and waits up to five seconds for the placer to close the connection, so its start-up reading is taken after the placement. The placer still finds the daemon from the holders of its listening socket, never from the connection. The install removes the earlier path pair, an install that still carries it is not done, and --undo and the uninstall advice disable only the units a host carries. Refs gominimal/inbox#1206 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * feat(classifier): report a daemon that is not placed in its leaf A daemon outside its classifier leaf read only as a probe that could not read the table, and min finalize-install --show reported the step done. min finalize-install --show now names a running daemon that is not in its leaf, with its pid, and its step places that daemon with the step's --pid. The daemon's start line records daemon_placed, and a native daemon outside its leaf on a host whose tree is installed logs its own warning that names min finalize-install. Refs gominimal/inbox#1206 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * test(e2e): name what still holds the classifier tree when teardown fails The setup teardown returned non-zero with no word on what kept /sys/fs/cgroup/minimald.slice alive. It now prints each remaining cgroup under the tree with its members and their command lines. Refs gominimal/inbox#1206 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * docs(minimald): say who owns the placement request socket The comment called the socket root-owned. The unit sets SocketUser to the delegated account with mode 0600. Refs gominimal/inbox#1206 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * fix(classifier): sweep the probe's leftover leaves on uninstall A daemon stopped in the middle of its classifier effect probe leaves its empty boxes/deny/filter-probe-<pid> leaf behind, and the uninstall then refused, as if a box still held the tree. The start-up reading now runs after the placement, while the daemon is placed, so the probe makes its leaf on every start and the window shows up in the native e2e teardown. The uninstall now removes empty probe leaves with rmdir. A box's own leaf still stops it. Refs gominimal/inbox#1206 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * fix(minimal): name the unplaced daemon by the lock's holder, not the file min finalize-install --show took the daemon's pid from the instance lock file's contents, which the daemon writes only as a debug aid and the account can edit. A stale or edited pid could name the wrong process in the --pid step. The pid is now the lock's holder as /proc/locks lists it for the lock file's inode. Refs gominimal/inbox#1206 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * fix(minimal): match the lock's device as well as its inode lock_holder matched a /proc/locks entry by inode alone, so a lock on another filesystem with the same inode could name its holder. It now matches the major:minor device too. Refs gominimal/inbox#1206 Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * fix(classifier): keep the installed copy when the finalize step refuses The classifier block moved the new install-host-classifier.sh over the root-owned copy before the step ran. A refused run (a connection-mark conflict, say) stopped the block before the units, so the old boot and placement units stayed while the copy they run was already the refusing version, and the next reboot did not restore enforcement. The block now keeps the previous copy beside it, moves the new one in, and runs it. On success the kept copy goes. On failure the previous copy goes back (or the new one goes away on a first install) and the block exits with the step's status, so the copy and the units stay as they were. The step still runs from its installed path, so a refusal's hint names a file that exists. a_refused_classifier_step_keeps_the_installed_copy_and_units runs the block under a scratch root with the step stubbed and pins both outcomes. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * fix(minimal): find the daemon's lock holder by inode on btrfs /proc/locks prints the superblock's device, and on a btrfs subvolume stat gives the subvolume's anonymous st_dev, so the holder never matched there. Match the listing on the inode alone and confirm the holder by one of its /proc/<pid>/fd entries statting to the lock file. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * fix(classifier): order the placement service after the boot unit, not the socket The socket unit ordered after minimald-classifier.service, and the boot unit before the socket, closed a cycle at boot: the socket is before sockets.target, basic.target comes after sockets.target, and the boot unit comes after basic.target. Put After=minimald-classifier.service on minimald-place@.service and drop the order from the socket and the boot unit. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * fix(classifier): remove the placement socket file when its unit stops systemd leaves a socket unit's file node in place on stop unless the unit sets RemoveOnStop=yes. Set it on minimald-place.socket, so --undo's systemctl disable --now leaves no /run/minimald-place.sock, as the install test's comment says. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * fix(minimal): say the placement unit needs the daemon's socket path No unit watches the daemon's socket since the placement moved to a socket unit. The classifier item's cause and the comments that still said a unit watches it now say the placement unit needs the path to find the daemon. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * refactor(paths): declare the placement request socket once minimald and min each declared /run/minimald-place.sock. Move it to paths::PLACE_REQUEST_SOCKET beside MINIMALD_LOCK_FILE and use it from both crates. resolver.rs no longer carries the path, so the install test's exclusion for it goes too. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * test(minimal): run the refused classifier block test through run_confined The test ran the classifier block with sh after redirecting its paths by hand, and the daemon socket it named was still a host path. Render the socket under the scratch root too and run the block through #3056's run_confined, so the guard refuses the run if the block names any host path outside the root. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTvgTNC876h72BpHfV3 * fix(minimal): report a stopped placement socket, and start it in the remedy `--show` read the classifier item as done once the unit files and their wants links were there, so a placement socket that was installed but not listening (a boot that dropped its job, or `systemctl stop`) read as done, and an unplaced daemon's remedy was `--pid` alone: the next daemon start was unplaced again (Bryan's review on #3026). The item now reads whether the socket listens at PLACE_REQUEST_SOCKET; the unit sets RemoveOnStop=yes, so the file is there exactly while it listens. A stopped socket is its own state, remedied by `systemctl start minimald-place.socket`; with an unplaced daemon as well, the block starts the socket and then places the daemon with --pid. A host without systemctl is owed no placer. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTxBZ7SjPrDqmzAv4No * fix(minimald): log a placement request that closed without placing as a warning systemd also closes a connection it will not serve (over the socket's MaxConnections=), and a placer can exit without finding this daemon, so a closed connection was logged as "the placement unit answered" while the daemon stayed outside its leaf. Only the leaf says whether the request placed it: info when it did, warn when it did not. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTxBZ7SjPrDqmzAv4No * fix(minimal): place an unplaced daemon by its socket, not a pid read earlier Norrie's re-review of #3026: - The remedy for a running daemon outside its leaf now runs the step's --place-listener for the account's socket instead of --pid <pid>. It finds the holder when the block runs, so a reused pid is never moved, and it keeps the guards --pid lacks: a holder of another uid, or one already under the slice, is left alone. - Detection matches it: a lock holder whose cgroup is anywhere under the tree (a box leaf among them), or cannot be read, is not reported as unplaced (stands_in_tree). - With both causes present, `--show` lists them on two lines instead of one 47-word sentence; the JSON keeps them on one line. - The create reply's comment and its test no longer say the launch refuses a deny-all box an unplaced daemon starts; that box runs unenforced. - The placement-request doc states why the daemon connects to a socket in /run, without narrating the path unit it replaced. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTxBZ7SjPrDqmzAv4No * fix(minimald): state the host fact the create's own gate read The foundry review of #3026: the create reply read the process-global host-address fact after the manager's create returned, so another create or launch that refreshed it in between could make the reply state a fact its own create never saw. The manager's create now returns the fact its gate read (create_host_ip_fact), the CreateBox message carries it back on a oneshot, and the RPC handler states that fact through the new create_box_reporting_fact. The existing create_box_with_spec and create_fresh_box_with_spec keep their signatures. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTxBZ7SjPrDqmzAv4No * fix(minimal): check the names the unplaced-daemon remedy spells Norrie's third review of #3026 (F1): the --place-listener remedy for a running daemon outside its leaf put `operator` and `socket` into the root block on a branch that returns before the character check the install path applies. A --minimal-dir with an apostrophe broke the script handed to `sudo sh`, a crafted one could run a command as root, and one with a space rendered a remedy that never matched the socket. The check moves into unsafe_name_cause, which both the install arm and the unplaced-daemon arms use. An unsafe name with an unplaced daemon is now the same Cannot item with the same causes and no block; a healthy install read under such a name stays done, and starting a stopped socket names neither. New rows cover the crafted path, an apostrophe, a space and an empty name, with and without a stopped socket. Also from the review: - N1: the create reply's comments say it states the fact its own create returned, and when that read falls back to the stored fact. - N4: the block's printed comments use plain words for the cgroup and the socket owner, drop the inotify narration, and name the placement socket consistently. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016CYvTxBZ7SjPrDqmzAv4No --------- Co-authored-by: Karl Godard <karl@minimal.dev> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com> Co-authored-by: agent-137 <281830107+agent-137@users.noreply.github.com>
Configuration menu - View commit details
-
Copy full SHA for fd6679a - Browse repository at this point
Copy the full SHA fd6679aView commit details
This comparison is taking too long to generate.
Unfortunately it looks like we can’t render this comparison for you right now. It might be too big, or there might be something weird with your repository.
You can try running this command locally to see the comparison on your machine:
git diff main@{1day}...main