feat: govern commands through the devguard owner and fit Cargo jobs (DG1-P4) - #5
Merged
Merged
Conversation
…G1-C07) Add devguard, the command-line owner of managed execution. It runs a command only through the central authority and the fenced devguard-launch helper; when a command cannot be admitted or started, nothing runs and it exits 125. There is no unmanaged fallback and no path or authority override: the binary derives the socket, operator configuration and dev-cli credential from the operating account. - exec resolves the program without a shell, keeps argv, working directory, environment and exit status, and digests the meaning (executable and directory identity, argv, environment changes, TTY, intent). Budgets come from explicit flags, then project limits, then the adapter default; --project requires an operator-registered root that contains the working directory. - --wait retries capacity and pressure denials, and a full instance pool, as new attempts until its deadline; a signal cancels it. A lost admission reply is replayed with the same key; a lost launch commit is released as never received and never recreated. - The helper starts leading its own group. On a controlling terminal the workload's group gets the terminal; stops are mirrored to the shell. Signals sent to the CLI reach the workload group while its root is unreaped. The CLI observes the attempt before reaping the root, so survivors stay tracked and charged. - --receipt writes devguard-exec-receipt/v1 without permits, caller credentials or the inherited environment. doctor reports whether managed execution is available, with --require checks. - The adapter interface is separate from admission; generic changes nothing and auto selects it until C08. Tests re-execute the test binary as the CLI owner against isolated fixture authorities with the real helper, including pseudo-terminal interrupt and job-control stop cases, and a scripted authority for lost replies. qualify.py gains dg1-cli (macOS) with a helper prebuild; CI runs it on macOS. The fixture can register projects and report its work capacity. Docs: contracts, operations, DG-1 ledger and verification (English authority, reviewed Korean), README and milestones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… (DG1-C08) Add devguard-cargo and the CLI's Cargo adapters, which fit Cargo's compiler parallelism to the reservation a command runs under. - Jobs follow the approved estimate: one CPU and 1.5 GiB per job plus a fixed 512 MiB. A reservation below one job is refused before admission; a job is never forced. The Cargo default request fits two jobs, an unqualified initial value. - Direct mode (--adapter cargo, or auto for a cargo subcommand that compiles) keeps an explicit -j within the reservation, rewrites a larger or host-relative one in place, refuses repeated, zero or unparsable values, and otherwise inserts --jobs after the subcommand. CARGO_TARGET_DIR, report paths and command selection are untouched. - Pipeline mode (--adapter cargo-pipeline) creates a private FIFO jobserver held by the CLI for the run and exports it through CARGO_MAKEFLAGS, so every nested Cargo shares one pool even through programs that close inherited descriptors. The receipt records the tokens back in the pool after the run. - A valid inherited jobserver is preserved, judged the way Cargo reads it; stale references are removed so Cargo does not fall back to its own pool silently. Cargo jobs bound compilation, not test threads, and memory remains an accounting estimate. The adapter interface gains held resources that outlive the run and report into the receipt (adapter_after). Real Cargo builds of small offline workspaces measure parallelism through a compiler wrapper and record, through a cargo shim, that each launch inherits only its standard descriptors. Hosts that cannot fit a Cargo job record those cases as not run. qualify.py gains dg1-cargo; CI runs it on macOS with --allow-incomplete. Docs: contracts, operations, DG-1 ledger and verification (English authority, reviewed Korean), README and milestones. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…-C07, DG1-C08) Launch outcomes are no longer inferred from a bounded transcript read. The CLI reads the helper transcript to its end on its own thread while it waits for the root, so a stop before READY is mirrored at once. A transcript that cannot be read to a well-formed end, READY without a final report, or a root that can no longer be waited for is recorded as `uncertain` and never retried; only a transcript that ended without READY means nothing started. Owner behavior: - keep signals the caller ignored (nohup) ignored and unforwarded - mirror only job-control stops, by the same signal; leave SIGSTOP alone - take the terminal back before reaping; find it on any standard fd - cancel instead of retrying when a signal arrived during the launch - refuse a --wait request above the host's work capacity at once, and back off to 10 s so a long wait leaves fewer denied tombstones - zero the core-file limit before ending by the workload's signal - open .devguard.toml without following links or blocking The service reconciles a consumer's ended instances before refusing a registration for a full pool, so exited owners never exhaust it. Cargo adapters: resolve -j values as Cargo reads them (-j=N, default, negatives counted back from the CPUs), validate them whether or not a jobserver governs, clamp explicit values under an inherited jobserver, always set CARGO_BUILD_JOBS as the silent fallback the receipt already described, report descriptor-pair jobservers truthfully, and cap the private pool at 1024 jobs. Tests replace fixed sleeps with release files, check descriptors and environment names, assert Normal pressure at decisions, and add a fake authority without fenced launch, ignored signals, SIGSTOP, output-only terminals, sequential owners, cargo test, a Python pipeline and an inherited descriptor-pair jobserver. qualify.py requires their raw receipts in dg1-cli and dg1-cargo. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Behavior
This PR delivers DG1-P4, made up of DG1-C07 and DG1-C08. It adds
devguard, the command-line owner that runs commands only through the central authority and the fenceddevguard-launchhelper, and the Cargo adapters that fit Cargo's compiler jobs to the reservation a command runs under. Wire version 1 and the journal schema are unchanged, and the service still runs in the foreground.C07 — govern commands with doctor receipts and explicit waits (
fe5b00f)devguard exec [--project ID] [--adapter …] [--wait DURATION] [--cpu …] [--memory …] [--tasks …] [--receipt PATH] -- PROGRAM [ARGS...]admits the command, commits the launch and starts the helper as its direct child. When a command cannot be admitted or started, nothing runs and the CLI exits 125. There is no unmanaged fallback and no path or authority override: the binary derives the socket, operator configuration anddev-clicredential from the operating account.--projectrequires an operator-registered root that contains the working directory.--waita denial ends the run.--waitretries capacity and pressure denials, and a full instance pool, as new attempts until its deadline; a signal cancels it. A lost admission reply is replayed with the same key. A lost launch commit is released as never received and never recreated.--receiptwritesdevguard-exec-receipt/v1without permits, caller credentials or inherited values.devguard doctorreports whether managed execution is available, with--require admission,registration,macos-cooperativechecks.C08 — preserve pipeline semantics and shared jobserver budgets (
42b2179)--adapter cargo, orautofor a Cargo subcommand that compiles) keeps an explicit-jwithin the reservation, rewrites a larger one in place, refuses values Cargo rejects, and otherwise inserts--jobs N.CARGO_TARGET_DIR, report paths and command selection are untouched.--adapter cargo-pipeline) creates a private FIFO jobserver, held by the CLI for the run and exported throughCARGO_MAKEFLAGS, so every nested Cargo shares one pool even through programs that close inherited descriptors. The receipt records the tokens back in the pool after the run.Review fixes (
d2b7794)uncertain, exits with the root's own status and is never retried.nohup) stay ignored and unforwarded, for the workload too. Only SIGTSTP, SIGTTIN and SIGTTOU are mirrored, by the same signal; a SIGSTOP or tracer stop is left alone. The terminal is found on any standard descriptor and is taken back before the root is reaped. A signal that arrives during a launch cancels the run instead of letting it retry. The CLI's core limit is zeroed before it ends itself by the workload's signal.--waitrequest above the host's work capacity is refused at once, with no attempt, and the backoff now caps at 10 s, because every denied attempt stays in the journal as a tombstone until its generation is retired.-j=N,default, negatives counted back from the CPUs and never below one) and validated whether or not a jobserver governs. An explicit value is clamped even under an inherited jobserver. Both modes now setCARGO_BUILD_JOBS=N, the silent fallback the receipt already described, and the report says when a descriptor-pair jobserver may not survive a program that closes descriptors. The private pool is capped at 1,024 jobs..devguard.tomlis opened without following a symbolic link or blocking, and must be a regular file.Validation
Rust 1.95.0, one Cargo job and one test thread.
fe5b00fwith 204 tests and42b2179with 222.Functional suites (declared raw receipts and hashed logs, all on the final tree
d2b7794with one source fingerprint):dg1-authoritydg1-authdg1-probesdg1-scopesdg1-launchdg1-reconciledg1-clidg1-cargocargo test, a Python pipeline sharing one FIFO jobserver, nested Cargo, inherited FIFO and descriptor-pair jobservers, stale descriptors, concurrent consumers and cancellationCargo's own behavior, probed with Cargo 1.95.0:
-j=N,--jobs=-3and-j -100are accepted, and a negative count never goes below one job.0and repeated jobs options are rejected. With a FIFO jobserver,CARGO_BUILD_JOBSis silent while--jobswarns; a closed descriptor pair falls back silently toCARGO_BUILD_JOBS.Clamped environments. The hosted macOS 14 runner leaves 500 mCPU of work capacity, which cannot fit one Cargo job. There, the
dg1-cargobuild cases recordnot_runand CI runs that suite with--allow-incomplete. They pass on the local host.Stability. Every suite passed in two full runs: at
d2b7794, and before at6e8250d, which had the same code and did not yet require the new receipts. The real-process CLI tests then passed twice more (exec 18/18, terminal 3/3), and the Cargo tests once more (11/11), with no failure. No test left processes or temporary directories behind.Foreground check on the normal authority at
d2b7794:devguardbinary ran four things:/usr/bin/true, which exited 0 and was released as scope termination;--jobs 2inserted, which was released.Documentation. 16 reviewed English/Korean pairs, eight checker tests, 46 work units and 23 logical groups. The approved design checksum and Apache-2.0 license are unchanged.
Independent reviews.
Boundary review. It found three must-fix issues, all fixed in
d2b7794:uncertain, never retried.CARGO_BUILD_JOBSfallback that was never set. The fallback is now always set, with truthful reports. The inherited jobserver stays preserved, as the approved design requires, rather than being replaced by a second pool.Its should-fix items and nits are fixed as listed under Behavior. Nested
devguard execis documented as C10's parent-budget boundary.Test and evidence review. It found two must-fix items, both fixed:
CARGO_BUILD_JOBS=1made the jobserver-sharing tests pass for the wrong reason. Builds now run withCARGO_BUILD_JOBS=16, and the tests assert the one jobserver each compilation received and the free-token floor of inherited pools.All its should-fix items are adopted, including the fake authority without fenced launch,
cargo test, the Python pipeline, release files instead of fixed sleeps, and descriptor and environment-name checks. One nit was not adopted: a digest test through the preparation step, which depends on the process's working directory; the end-to-end receipts cover that path.Hosted checks. The macOS 14 and Ubuntu 24.04 workflow runs the full validator and the portable suites on both runners, and the native suites, now including
dg1-clianddg1-cargo, on macOS only. Atd2b7794, PR run 35984812601 and branch run 35984798455 both passed. Every report's source fingerprint matches the local runs.dg1-authority(20),dg1-auth(26),dg1-probes(28),dg1-reconcile(23) anddg1-cli(38), including every new real-process case.dg1-scopes(25) anddg1-launch(27) areincompleteas before, only for their one unclamped case each.dg1-cargo(21) isincomplete: the runner's 500 mCPU of work capacity cannot fit one Cargo job, so its ten build cases recordnot_run. Its unit stages and the refusal case ran.dg1-authority(19) anddg1-auth(26).Compatibility, limitations and rollback
pressure_normal, and the daemon configuration gainsobserved_work_capacity, which the service's own policy derivation already computed.devguard-exec-receipt/v1is new in this PR.devguard execagain starts a separate managed execution that is neither charged to nor contained by the outer scope; bounding that is C10's parent-budget capability.dg1-cargobuild cases recordnot_runand CI runs the suite with--allow-incomplete; they pass on the local host.--adapter generic; targets and caches are kept. Never delete the journal or its tombstones.🤖 Generated with Claude Code