Skip to content

feat: govern commands through the devguard owner and fit Cargo jobs (DG1-P4) - #5

Merged
novelKR merged 3 commits into
mainfrom
codex/dg1-p4
Sep 24, 2026
Merged

novelKR merged 3 commits into
mainfrom
codex/dg1-p4

Conversation

@novelKR

@novelKR novelKR commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

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 fenced devguard-launch helper, 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)

  • Owner. 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 and dev-cli credential from the operating account.
  • Meaning. The program is resolved as a shell would, without a shell (127 missing, 126 not executable). Argv, working directory, environment and exit status are kept. The execution digest covers executable and directory identity, argv, environment changes, TTY and intent. Budgets come from explicit flags, then project limits, then the adapter default; --project requires an operator-registered root that contains the working directory.
  • Waiting. Without --wait a denial ends the run. --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.
  • Launch. The helper leads its own group from the start. On a controlling terminal the workload's group gets the terminal, and job-control stops are mirrored to the shell. Signals sent to the CLI reach the workload's group only while its root is unreaped. The CLI observes the attempt before reaping the root, so survivors stay tracked and charged.
  • Receipts and doctor. --receipt writes devguard-exec-receipt/v1 without permits, caller credentials or inherited values. devguard doctor reports whether managed execution is available, with --require admission,registration,macos-cooperative checks.

C08 — preserve pipeline semantics and shared jobserver budgets (42b2179)

  • Estimate. One compiler job needs one CPU and 1.5 GiB 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 one in place, refuses values Cargo rejects, and otherwise inserts --jobs N. 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 exported 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.
  • Inherited jobservers are preserved when valid, judged the way Cargo reads them, as the approved design requires; stale references are removed so Cargo does not fall back to its own pool silently.

Review fixes (d2b7794)

  • Launch outcomes. The CLI reads the helper transcript to its end on its own thread while it waits for the root from the start, so a stop before READY is mirrored at once and no deadline can turn a slow launch into "not started". Only a transcript that ended without READY means nothing started. 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, exits with the root's own status and is never retried.
  • Signals and terminals. Signals the caller inherited as ignored (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.
  • Waits. A --wait request 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.
  • Instance pool. The service reconciles a consumer's ended instances before refusing a registration for a full pool, so owners that have already exited never exhaust it. A failed pass is reported and leaves the pool's own answer.
  • Cargo. Jobs values are resolved as Cargo 1.95 reads them (-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 set CARGO_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.
  • Project settings. .devguard.toml is 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.

    • The full validator passed formatting, Clippy with warnings denied and the explicit dependency graph, with 237 Rust tests on local macOS 27.0 (arm64, 8 CPUs, 16 GiB). The graph now has eight crates. The CLI may use the daemon's test fixtures only in its tests, and the validator enforces that.
    • Each commit passes the validator alone: fe5b00f with 204 tests and 42b2179 with 222.
    • The original 44-test DG-0 baseline is unchanged.
  • Functional suites (declared raw receipts and hashed logs, all on the final tree d2b7794 with one source fingerprint):

    Suite Cases Notes
    dg1-authority 20
    dg1-auth 26
    dg1-probes 28
    dg1-scopes 25
    dg1-launch 27 Adds a transcript read to its end
    dg1-reconcile 23
    dg1-cli 38 New. The CLI as a real owner process against isolated authorities with the real helper: a command's lifecycle with its environment names and descriptors checked; signal death; SIGTERM and SIGHUP forwarding; ignored signals; a SIGSTOP left alone; observe before reap; a refused budget and a wait beyond the host's capacity; admitted, expired and cancelled waits; an unavailable authority and one without fenced launch; doctor; projects; the instance pool and sequential owners; unstartable programs. On a pseudo-terminal: interrupt keys, including with only output on the terminal, and a mirrored stop
    dg1-cargo 21 New. Real offline Cargo builds: direct and explicit jobs, refusals, cargo test, a Python pipeline sharing one FIFO jobserver, nested Cargo, inherited FIFO and descriptor-pair jobservers, stale descriptors, concurrent consumers and cancellation
  • Cargo's own behavior, probed with Cargo 1.95.0: -j=N, --jobs=-3 and -j -100 are accepted, and a negative count never goes below one job. 0 and repeated jobs options are rejected. With a FIFO jobserver, CARGO_BUILD_JOBS is silent while --jobs warns; a closed descriptor pair falls back silently to CARGO_BUILD_JOBS.

  • Clamped environments. The hosted macOS 14 runner leaves 500 mCPU of work capacity, which cannot fit one Cargo job. There, the dg1-cargo build cases record not_run and 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 at 6e8250d, 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:

    • Memory was normal, and admission opened after 2.0 seconds.
    • Both cycles advertised all five capabilities and refused a duplicate authority; each shut down in under 0.1 seconds.
    • The real devguard binary ran four things:
      • doctor, with every requirement;
      • /usr/bin/true, which exited 0 and was released as scope termination;
      • a budget the host cannot fit, which exited 125 with one denied attempt and started nothing;
      • an offline three-crate Cargo build, with --jobs 2 inserted, which was released.
    • Every owner instance was retired, and no credential appeared in the receipts or logs.
    • The journal gained exactly three attempts and four instances.
  • 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:

      • A lost or incomplete transcript was reported as not started. It is now read to its end, and an unreadable one is uncertain, never retried.
      • A pipeline with an inherited descriptor pair claimed a CARGO_BUILD_JOBS fallback 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.
      • Signals the caller ignored were overridden. They now stay ignored.

      Its should-fix items and nits are fixed as listed under Behavior. Nested devguard exec is documented as C10's parent-budget boundary.

    • Test and evidence review. It found two must-fix items, both fixed:

      • The harness's CARGO_BUILD_JOBS=1 made the jobserver-sharing tests pass for the wrong reason. Builds now run with CARGO_BUILD_JOBS=16, and the tests assert the one jobserver each compilation received and the free-token floor of inherited pools.
      • The transcript loss above.

      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-cli and dg1-cargo, on macOS only. At d2b7794, PR run 35984812601 and branch run 35984798455 both passed. Every report's source fingerprint matches the local runs.

    • macOS 14. Passed: the validator (237), dg1-authority (20), dg1-auth (26), dg1-probes (28), dg1-reconcile (23) and dg1-cli (38), including every new real-process case. dg1-scopes (25) and dg1-launch (27) are incomplete as before, only for their one unclamped case each. dg1-cargo (21) is incomplete: the runner's 500 mCPU of work capacity cannot fit one Cargo job, so its ten build cases record not_run. Its unit stages and the refusal case ran.
    • Ubuntu 24.04. Passed: the validator (157), dg1-authority (19) and dg1-auth (26).
    • Separate post-merge main CI is verified before cleanup.

Compatibility, limitations and rollback

  • Compatibility. No wire, journal or configuration schema change. The fixture gains pressure_normal, and the daemon configuration gains observed_work_capacity, which the service's own policy derivation already computed. devguard-exec-receipt/v1 is new in this PR.
  • Limitations.
    • macOS enforcement stays cooperative (QoS utility and nice) with accounting; memory is an estimate, not measured enforcement.
    • A workload that runs devguard exec again starts a separate managed execution that is neither charged to nor contained by the outer scope; bounding that is C10's parent-budget capability.
    • Cargo jobs bound compilation, not the threads of the test programs Cargo runs. A caller's inherited jobserver is preserved, and its size cannot be observed.
    • On the hosted macOS 14 runner the work capacity cannot fit one Cargo job, so the dg1-cargo build cases record not_run and CI runs the suite with --allow-incomplete; they pass on the local host.
  • Rollback. C07 and C08 change no service state. To roll back the CLI, stop starting commands through it; commands it already started stay charged until their scopes end, and it never falls back to unmanaged execution. To stop fitting Cargo jobs, select --adapter generic; targets and caches are kept. Never delete the journal or its tombstones.

🤖 Generated with Claude Code

novelKR and others added 3 commits September 24, 2026 09:40
…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>
@novelKR
novelKR merged commit d30fbce into main Sep 24, 2026
4 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant