8000 feat(minimal): T126 — Refuse a named loadout on a sealed box, a loadout that sets loadout_mode, and a contradicted constraint; a sealed box reads no default loadouts by minimal-foundry[bot] · Pull Request #3301 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

feat(minimal): T126 — Refuse a named loadout on a sealed box, a loadout that sets loadout_mode, and a contradicted constraint; a sealed box reads no default loadouts - #3301

Queued
minimal-foundry[bot] wants to merge 16 commits into
mainfrom
agent/3254-refuse-a-named-loadout-on-a-sealed-box-a-loadout
Queued

minimal-foundry[bot] wants to merge 16 commits into
mainfrom
agent/3254-refuse-a-named-loadout-on-a-sealed-box-a-loadout

Conversation

@minimal-foundry
Copy link
Copy Markdown
Contributor

T126 — Refuse a named loadout on a sealed box, a loadout that sets loadout_mode, and a contradicted constraint; a sealed box reads no default loadouts

slice: S5
kind: task
covers: BOX-171, BOX-172, BOX-173, BOX-175, BOX-181
size: M
est: 140 code, 160 tests
executor: agent
difficulty: needs care
model: strongest
diagnostics: each refusal names the loadout and the layer that set loadout_mode; the daemon log names the box's mode
observability: the daemon log names the box's mode at creation
layers:
- expansion:
- crates/mfile/src/expand.rs: refuse a constrained mode, a named loadout on a sealed box and a loadout-set mode before the loadouts layer
- crates/mfile/src/error.rs: the two loadout refusals
test: cargo nextest run -p mfile loadout_setting_loadout_mode_refused_exit3
test: cargo nextest run -p mfile named_loadout_on_sealed_box_refused_exit3
test: cargo nextest run -p mfile constrained_loadout_mode_refused_before_loadouts
- loadout:
- crates/boxes/src/core/loadout.rs: the loader keeps the raw loadout_mode key
- client:
- crates/minimal/src/loadouts.rs: a sealed box reads no default loadouts
- crates/minimal/src/cmd/boxes/create.rs: reads the mode before resolving loadouts; prints nothing for a skipped default
- crates/minimal/src/task.rs: the same for min task run
- crates/minimal/src/tests.rs: tests
test: cargo nextest run -p minimal sealed_task_run_prints_nothing_about_loadouts
test: cargo nextest run -p minimal sealed_box_ignores_broken_default_loadout
produces: none
consumes:
- crates/mfile/src/expand.rs
proof: Each refusal fails before a loadout is applied, and a sealed task never reads a broken default loadout or prints about it.
depends on:
- blocked by #3247

Proof

Each refusal fails with exit 3 bef 7438 ore a loadout is applied. A type constraint that the entry's loadout_mode breaks fails first. A --loadout for a sealed box fails next, and names the loadout, the layer that set sealed, and the remedy. On an open box, a loadout that sets loadout_mode fails and names the loadout. min task run of a sealed task (the task default) no longer reads the default loadouts. A missing or broken default loadout does not fail it, and no 'Applying loadouts' line prints. A session entry set to sealed behaves the same way.

Notes from the agent: Design: mfile::expand::Layers has three new fields: type_constraints, named_loadouts and loadouts_setting_mode. The new public loadout_gate runs the BOX-174, BOX-181 and BOX-173 checks in that order and reads no loadout. expand runs that gate and then the BOX-172 check. The client does not apply loadouts through mfile::expand today. So the client calls loadout_gate on the start's entry (loadouts::entry_loadout_gate, through session_loadout_mode and task_loadout_mode in create.rs) before it resolves any loadout. A sealed box gets LoadoutSelection::None. The BOX-172 check in the client runs in prepare_activation_inputs_with_config, on the loaded loadouts (Loadout::sets_loadout_mode). That check also covers the min dash create path, which shares that function but does not run the gate.

Three things are outside this task's scope. (1) No BOX-061 constraint check existed before this task. I added Error::ConstraintViolated and use it only for loadout_mode, so the general BOX-061 final check is still owed. The constraint value can be a string or a list of strings. (2) The client logs the box's loadout mode at info (tracing, target minimal::loadouts), and mfile logs it at debug. The daemon is not in this task's layers, so the daemon log does not name the mode yet. (3) An entry whose type does not resolve falls back to the built-in defaults of its type name, so the gate does not refuse it before the start…[truncated]

Tests

line exit tail
cargo nextest run -p mfile loadout_setting_loadout_mode_refused_exit3 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt
cargo nextest run -p mfile named_loadout_on_sealed_box_refused_exit3 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt
cargo nextest run -p mfile constrained_loadout_mode_refused_before_loadouts 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt
cargo nextest run -p minimal sealed_task_run_prints_nothing_about_loadouts 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt
cargo nextest run -p minimal sealed_box_ignores_broken_default_loadout 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt

Verify

min task run agent-verify exited 100 under rustc 1.99.0, which the repository does not pin (1.99.1 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

 {
                "parent_span_id": "453fcb4d8bb98674",
                "rpc": "minimald-v1-GetSessionRecord",
                "span_id": "0424a047b6a2b4fe",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "0424a047b6a2b4fe",
                "queued_behind": "0",
                "span_id": "936191e9e45a2997",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "rpc",
            fields: {
                "parent_span_id": "453fcb4d8bb98674",
                "rpc": "minimald-v1-DestroySession",
                "span_id": "75e09a49c76680ee",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "75e09a49c76680ee",
                "queued_behind": "0",
                "span_id": "d4d3e2fe7643ce99",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "75e09a49c76680ee",
                "queued_behind": "0",
                "span_id": "cf32ac46ca516aa9",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "cf32ac46ca516aa9",
                "span_id": "b4f2d6d0cd4fd7fd",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "HandOverName",
                "parent_span_id": "cf32ac46ca516aa9",
                "queued_behind": "0",
                "span_id": "053441cab965f900",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
    ]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 
────────────
     Summary [  72.944s] 1876 tests run: 1875 passed, 1 failed, 1 skipped
        FAIL [   3.103s] (1876/1876) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
error: test run failed

Closes #3254

Expansion now refuses three cases with exit 3 before a loadout applies:
a type constraint that holds loadout_mode at another value, a loadout
named by --loadout for a sealed box, and a loadout on an open box that
sets loadout_mode. loadout_gate runs the first two checks without a
loadout, so a client can call it before it reads a loadout file.

Covers BOX-172, BOX-173 and BOX-181.
LoadoutFile dropped a key it did not know, so a loadout that set
loadout_mode looked like one that did not. The loader now keeps the key,
and Loadout::sets_loadout_mode lets the expansion refuse it (BOX-172).
min task run and a session start now read the entry's loadout_mode
first. A sealed box selects no loadout, so a missing or broken default
loadout cannot fail it, and no line prints about the skipped defaults.
A --loadout for a sealed box, or a type constraint the entry breaks,
fails with exit 3 before a loadout file is read. On an open box, a
loadout that sets loadout_mode fails with exit 3.

Covers BOX-171, BOX-172, BOX-173, BOX-175 and BOX-181.
@coderabbitai
coderabbitai Bot commented Nov 12, 2026 •
Copy link
Copy Markdown
Contributor

Important

Review skipped

Bot user detected.

To trigger a single review, invoke the @coderabbitai review command.

Run configuration

  • Source mode: Auto
  • Repository: Unavailable
  • Branch: Unavailable
  • File: No configuration file from this repository applied
  • Commit: Unavailable
  • Layers: Organization UI
  • Review profile: CHILL
  • Plan: Advanced
  • Run ID: e18cdb02-1a0d-43b4-a92d-1b785cee6612

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • Trigger review
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

@minimal-foundry
minimal-foundry Bot commented Nov 12, 2026 •
8000
Copy link
Copy Markdown
Contributor Author

Reviewed: no findings; approval withheld, 10 threads unresolved at 0809073b8c35.

APPROVE withheld: 10 unresolved threads — minimal-arch[bot] crates/minimal/src/cmd/boxes/create.rs, minimal-arch[bot] crates/minimal/src/loadouts.rs, minimal-arch[bot] crates/minimal/src/loadouts.rs, minimal-arch[bot] crates/minimal/src/loadouts.rs:736, minimal-arch[bot] crates/mfile/src/expand.rs:430, …. Resolving the last thread releases the approval on this head.

Resolve the open threads. Resolving the last one releases the approval on this head; no new review is required.

Clobber: none seen.

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — advisory review

Verdict COMMENT — 4 findings, 4 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/minimal/src/loadouts.rs Resolved
Comment thread crates/minimal/src/cmd/boxes/create.rs Resolved
@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: CONDITIONS · 4 conditions · the full ruling

The gate is in the right owner, and the refusals run in the order that the spec gives. Two client paths fail open. session_loadout_mode drops an undecided session pick and opens the gate on a bare session entry. It then reads loadout files for a start that is refused later. Make the change in C1. entry_loadout_gate drops the type's constraints when a project type does not resolve, and no start path refuses that type at this head. Line 1264 makes such a chain exit 3. Make the change in C2. File issues for the min dash path (C3) and for the duplicate constraint check (C4), and name them in the PR body.

Ruled at a02789c against gominimal/arch@15b603e6afb8 by claude-opus-5-5.

Comment thread crates/minimal/src/cmd/boxes/create.rs Outdated
let typed = entry.and_then(|name| entries.iter().find(|e| e.name == name));
let picked = match typed {
Some(entry) => Some(entry.clone()),
None => pick_session_entry(&entries).ok(),
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C1 · This .ok() drops an undecided pick. The gate then uses a bare session entry, which is open, and reads the default and --loadout files. session_box_entry refuses the same start later with exit 2. Use pick_session_entry(&entries)? here, as session_box_entry does. Keep the bare entry only when there is no project file.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in e4db6f54db44: session_loadout_mode now uses pick_session_entry(&entries)? when a project file exists, so an undecided pick exits 2 before any loadout is read. The bare entry applies only with no project file.

Comment thread crates/minimal/src/loadouts.rs Outdated
///
/// An entry whose type does not resolve takes the defaults of its type
/// name when that name is built in, and no type layer otherwise. The start
/// refuses such an entry elsewhere, so the gate does not refuse it first.
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C2 · This sentence is not true at this head. No start path resolves Box Types, in crates/minimal or in crates/minimald. Delete it.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in e4db6f54db44: Deleted the sentence that says the start refuses such an entry elsewhere.

Comment thread crates/minimal/src/loadouts.rs Outdated
match mfile::expand::expandable_type(entry, &types) {
Ok(resolved) => (resolved.defaults, resolved.constraints, project_types),
Err(error) => {
tracing::debug!(%error, "the loadout gate reads the entry without its type");
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C2 · For an entry whose type is not built in, this fallback drops the chain's constraints and decides the mode without them. Line 1264 makes a chain with no built-in root exit 3. Return the type error with exit 3 through a new mfile::Error variant. Keep the fallback for a built-in type name only.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in e4db6f54db44: A type name that is not built in and does not resolve now returns mfile::Error::BoxType (exit 3) through ProjectFileInvalid. The fallback stays only for a built-in type name.

let initial_policy = user_policy.clone();
let compose_options = compose_options_from_config(cfg);
let active = resolve_active_loadouts(selection, cfg, global)?;
// BOX-172: the gate read the mode before any loadout, so a loadout's
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C3 · The min dash create path also calls this function, but it never calls entry_loadout_gate. A dashboard create of a sealed entry still reads and applies loadouts. File an issue for that path, and name it in the PR body.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in e4db6f54db44: Filed as #3303 for the min dash create path. It is named as a residual in the PR body. The code is unchanged in this PR.

/// The most specific type of the chain whose constraints hold
/// `loadout_mode`, as a message names it, with the values the constraint
/// admits. A constraint is one value or a list of values.
fn holding_constraint(layers: &Layers) -> Option<(String, Vec<String>)> {
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C4 · This duplicates check_constraints and BoxTypeError::ConstraintBroken from #3292, with different words. File an issue, or extend #3295, to make it one check when #3292 merges. Name the issue in the PR body and in the ConstraintViolated doc comment.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in e4db6f54db44: Filed as #3304. The Error::ConstraintViolated doc comment now names #3304 and says that the check merges with check_constraints when #3292 merges.

@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 1 of 10 on the runtime's review findings: the coder changed nothing. Revise round 1: no code change this round. Both threads were judged against the branch. The dashboard create path (crates/minimal/src/dash/app.rs) still resolves default loadouts without the loadout-mode gate. That file is outside this task's layers, so a follow-up task must apply gate_selection there.

Review threads the coder judged:

  • 👎 invalid — crates/minimal/src/loadouts.rs:126: The gap is real, but the fix belongs in crates/minimal/src/dash/app.rs (lines 1095 and 1281), which is outside this task's layers. The dashboard path needs a follow-up task that reads the entry's loadout mode and applies gate_selection before it resolves loadouts.
  • 👎 invalid — crates/minimal/src/cmd/boxes/create.rs:1276: min task run runs only [tasks.<name>] entries (declared_task reads mfile::File::tasks), and entry_type refuses a [tasks.<name>] whose type is not task. A [boxes.<name>] entry of a project task type is not a task min task run can run, and task_box_entry uses the same match, so task_loadou

@agent-137
Copy link
Copy Markdown
Member

/revise

Arch conditions (arch_rulings 1173), in full:

  • C1: crates/minimal/src/cmd/boxes/create.rs session_loadout_mode(): when project_file_bytes returns a file, pick the entry exactly as session_box_entry() does: the named entry, else pick_session_entry(&entries)?. Remove the .ok() on line 1246, so an undecided pick ends with SessionEntryUndecided (exit 2) before any loadout file is read. Keep the bare [sessions.default] entry only for the case of no project file.
  • C2: crates/minimal/src/loadouts.rs entry_loadout_gate(): when the entry's type name is not built in (mfile::box_types::builtin returns None) and BoxTypes::from_file or expandable_type fails, return that type error with exit 3. Do not use builtin_type_layer and an empty constraint list for it. Carry the error in a new mfile::Error variant (for example Error::BoxType(BoxTypeError), exit_code 3) so ProjectFileInvalid maps it. Keep the fallback for a built-in type name. Delete the doc sentence "The start refuses such an entry elsewhere": no start path resolves Box Types at this head.
  • C3: File an issue for the min dash create path, and name it in the PR body as a residual. crates/minimal/src/dash/app.rs calls prepare_activation_inputs_with_config with opts.loadouts and never calls entry_loadout_gate. A dashboard create of a sealed entry therefore still reads and applies loadouts. The issue states that this path must call entry_loadout_gate and gate_selection before it resolves loadouts.
  • C4: File an issue, or extend minimal: run the BOX-061 constraint check after each expansion in min's create paths #3295, and name it in the PR body and in the Error::ConstraintViolated doc comment in crates/mfile/src/error.rs. The issue states the merge of the two constraint checks. When T23 — Check type constraints last, and let a derived type only narrow #3292 merges, gate() in crates/mfile/src/expand.rs reads the constraint by the rules of check_constraints and returns BoxTypeError::ConstraintBroken. Then Error::ConstraintViolated and holding_constraint() are removed, so one constraint check gives one message.

C3 is filed as #3303, and C4 as #3304. Name both in the PR body as residuals, and name #3304 in the Error::ConstraintViolated doc comment.

…gate

The loadout gate now picks the session entry as session_box_entry does,
so an undecided pick ends with exit 2 before any loadout is read. An
entry whose type is not built in and does not resolve fails with the new
mfile::Error::BoxType (exit 3). ConstraintViolated names #3304.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 2 of 10 on a /revise note, pushed as e4db6f54db44.

line exit tail
cargo nextest run -p mfile loadout_setting_loadout_mode_refused_exit3 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt
cargo nextest run -p mfile named_loadout_on_sealed_box_refused_exit3 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt
cargo nextest run -p mfile constrained_loadout_mode_refused_before_loadouts 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt
cargo nextest run -p minimal sealed_task_run_prints_nothing_about_loadouts 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt
cargo nextest run -p minimal sealed_box_ignores_broken_default_loadout 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt

Verify min task run agent-verify exited 100 under rustc 1.99.0, which the repository does not pin (1.99.1 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

 {
                "parent_span_id": "9d2da7544dc320ac",
                "rpc": "minimald-v1-GetSessionRecord",
                "span_id": "3c6b8d042d73340f",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "3c6b8d042d73340f",
                "queued_behind": "0",
                "span_id": "24d7b3b07c810ffc",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "rpc",
            fields: {
                "parent_span_id": "9d2da7544dc320ac",
                "rpc": "minimald-v1-DestroySession",
                "span_id": "aaa90ade40cb0ad8",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "aaa90ade40cb0ad8",
                "queued_behind": "0",
                "span_id": "790a9aba7b628ab0",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "aaa90ade40cb0ad8",
                "queued_behind": "0",
                "span_id": "65d8340500d09f02",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "65d8340500d09f02",
                "span_id": "e561639c7aab6043",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "HandOverName",
                "parent_span_id": "65d8340500d09f02",
                "queued_behind": "0",
                "span_id": "62b96ab848b9719f",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
    ]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 
────────────
     Summary [  73.270s] 1886 tests run: 1885 passed, 1 failed, 1 skipped
        FAIL [   3.071s] (1886/1886) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
error: test run failed

Review threads the coder judged:

Each refusal fails before a loadout is applied. This includes an undecided session pick (exit 2) and an entry whose project type does not resolve (exit 3). A sealed task never reads a broken default loadout and prints nothing about it.

Notes from the agent: Revise round 2. C1: session_loadout_mode now picks the entry as session_box_entry does. An undecided pick ends with SessionEntryUndecided (exit 2), and the bare entry applies only when there is no project file. C2: entry_loadout_gate returns the new mfile::Error::BoxType (exit 3) for a type name that is not built in and does not resolve. The built-in fallback stays, and the false doc sentence is deleted. C3 and C4 are filed as #3303 and #3304. Name both as residuals in the PR body. The ConstraintViolated doc comment now names #3304. Two new tests in crates/minimal/src/tests.rs pin C1 and C2: undecided_session_pick_refuses_before_loadouts and unresolved_project_type_refuses_loadout_gate_exit3. Both pass. I did not run just clippy-strict.

@minimal-foundry minimal-foundry Bot added the needs-human An agent handed off; a human must act, then clear this label. Set by defer and handoffs. label Nov 12, 2026
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

The runtime will not revise this pull request again on its own: the diff is +954/-49 (1003 lines), past 3x the plan's estimate of 300 lines (est: 140 code, 160 tests, as #3254 reads now), at round 2 (the last trigger was a /revise note). A person decides now. Remove needs-human from this pull request to let /revise <note> or the automatic rounds resume, or finish #3254 by hand.

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — advisory review

Verdict COMMENT — 1 finding, 1 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: CONDITIONS · 2 conditions · the full ruling

The gate now fails closed on an undecided pick and on an unresolved type. That closes the gaps from the last round. Two paths still read loadouts with no gate. min box spec <entry> expands with no constraints and no named loadouts. Thus it does not refuse what min box start refuses, and it reads a sealed box's defaults (§ minimal.toml layout line 1234; fix in C1). The min dash create still applies default loadouts to a sealed entry (§ Policy layers line 1285; fix in C2).

Outside the diff

  • crates/minimal/src/cmd/box_spec.rs:693: C1 · This call resolves loadouts with no gate. Line 1234 says the render is exactly what the host receives. Call entry_loadout_gate(Some(&bytes), entry, &overrides.loadout) first, and pass gate_selection(.., mode). Then refuse a loadout whose sets_loadout_mode() is true.
  • crates/minimal/src/dash/app.rs:1281: C2 · The dashboard create passes Defaults with no gate. A sealed entry then reads and applies the default loadouts. Line 1285 says loadouts are subject to loadout_mode. Call crate::session_loadout_mode(&root, None, &[]) and pass gate_selection(loadouts, mode).

Ruled at e4db6f5 against gominimal/arch@15b603e6afb8 by claude-opus-5-5.

Comment thread crates/mfile/src/expand.rs Outdated
held_by: held.map(|(box_type, _)| box_type),
});
}
// The daemon log names the box's mode at creation; this is the client's
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This comment says the daemon log names the mode. minimald does not log loadout_mode, and the PR body says so. Correct the comment. File the daemon log line as an issue.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 887f7cec4680: Rewrote the comment: it now says this is the client's record and that minimald does not log the box's loadout mode. I have no gh access, so the issue for the daemon log line is listed in the notes for a person to file.

@agent-137
Copy link
Copy Markdown
Member

/revise

Arch conditions (arch_rulings 1176), in full:

  • C1: crates/minimal/src/cmd/box_spec.rs expand_entry(), non-task branch: before resolve_active_loadouts (line 693), call let mode = crate::loadouts::entry_loadout_gate(Some(&bytes), entry, &overrides.loadout).map_err(invalid)?;. Pass crate::loadouts::gate_selection(LoadoutSelection::from_flags(&overrides.loadout, false), mode) to resolve_active_loadouts in place of the ungated selection. Then return ProjectFileInvalid { error: mfile::Error::LoadoutSetsLoadoutMode { loadout: Some(name) } } for the first resolved loadout whose sets_loadout_mode() is true, as prepare_activation_inputs_with_config does. Add tests in crates/minimal/src/tests.rs: min box spec of a sealed session entry with --loadout dev exits 3; a sealed entry with a broken default.toml renders with no loadouts and exits 0; an open entry whose loadout sets loadout_mode exits 3; an entry whose [box_types.<t>] constrains loadout_mode = "sealed" while the entry sets open exits 3 with ConstraintViolated.
  • C2: crates/minimal/src/dash/app.rs Effect::Create handler: inside the spawn_blocking closure, before prepare_activation_inputs_with_config (line 1281), call let mode = crate::session_loadout_mode(&root, None, &[])?; and pass crate::loadouts::gate_selection(loadouts, mode) in place of loadouts. Add a test that a dashboard create for a project whose session entry sets loadout_mode = "sealed" resolves LoadoutSelection::None and does not read a broken default loadout.

C2 closes the gap that residual #3303 tracks. Replace #3303 in the PR body with "Closes #3303".

min box spec <entry> now reads the box's loadout mode before any
loadout, as min box start does. A sealed entry refuses --loadout and
reads no default loadout. A loadout that sets loadout_mode refuses.

A min dash create reads the mode of the project's session entry, so a
sealed box reads no default loadout.
…amed-loadout-on-a-sealed-box-a-loadout

# Conflicts:
#	crates/minimal/src/tests.rs
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 3 of 10 on a /revise note, pushed as 887f7cec4680.

Resolved conflicts in: crates/minimal/src/tests.rs

line exit tail
cargo nextest run -p mfile loadout_setting_loadout_mode_refused_exit3 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt
cargo nextest run -p mfile named_loadout_on_sealed_box_refused_exit3 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt
cargo nextest run -p mfile constrained_loadout_mode_refused_before_loadouts 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt
cargo nextest run -p minimal sealed_task_run_prints_nothing_about_loadouts 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt
cargo nextest run -p minimal sealed_box_ignores_broken_default_loadout 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt

Verify min task run agent-verify exited 100 under rustc 1.99.0, which the repository does not pin (1.99.1 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

 {
                "parent_span_id": "95005a05caf29225",
                "rpc": "minimald-v1-GetSessionRecord",
                "span_id": "82fbab27720b6d66",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "82fbab27720b6d66",
                "queued_behind": "0",
                "span_id": "ba8f1e8d0912e1a2",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "rpc",
            fields: {
                "parent_span_id": "95005a05caf29225",
                "rpc": "minimald-v1-DestroySession",
                "span_id": "f4a278a7a13bed87",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "f4a278a7a13bed87",
                "queued_behind": "0",
                "span_id": "606c1c151ae51646",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "f4a278a7a13bed87",
                "queued_behind": "0",
                "span_id": "3fa3e47f98ac284e",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "3fa3e47f98ac284e",
                "span_id": "18030ec593b59da6",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "HandOverName",
                "parent_span_id": "3fa3e47f98ac284e",
                "queued_behind": "0",
                "span_id": "608dfe1edd306a24",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
    ]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 
────────────
     Summary [  63.927s] 1889 tests run: 1888 passed, 1 failed, 1 skipped
        FAIL [   2.772s] (1889/1889) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
error: test run failed

Outside the task's layers::

  • crates/minimal/src/cmd/box_spec.rs
  • crates/minimal/src/dash/app.rs

Converted to a draft: something above needs a person before review.

Review threads the coder judged:

  • 👍 valid — crates/mfile/src/expand.rs:389: Rewrote the comment: it now says this is the client's record and that minimald does not log the box's loadout mode. I have no gh access, so the issue for the daemon log line is listed in the notes for a person to file.

Each refusal fails with exit 3 before a loadout is applied, on min box start, min task run and now min box spec . A sealed task, a sealed session, a sealed min box spec and a sealed min dash create never read a broken default loadout or print about it.

Notes from the agent: Revise round 3. C1: expand_entry in crates/minimal/src/cmd/box_spec.rs calls entry_loadout_gate before resolve_active_loadouts, gates the selection, and refuses a loadout that sets loadout_mode. Test box_spec_entry_gates_loadouts_as_start covers the four cases in the note. A sealed entry renders '# Loadouts: none.' and exits 0. C2: crates/minimal/src/dash/app.rs gets create_loadout_selection, called in the Effect::Create spawn_blocking closure before prepare_activation_inputs_with_config. Test sealed_dash_create_reads_no_default_loadout is in the app.rs tests module. box_spec.rs and dash/app.rs are outside the task's layers. The /revise note named both files, so I changed them. One gap remains: min dash still resolves the default loadouts once at startup (app.rs, the resolve_active_loadouts call in run), before it knows a project. A broken default loadout still fails min dash startup. C2 did not name that call, so I left it. I cannot use gh. The runner or a person must file the issue for a minimald log line that names the box's loadout mode, and must write 'Closes #3303' in place of the #3303 residual in the PR body. just clippy-strict was not run.

@minimal-foundry
minimal-foundry Bot marked this pull request as draft November 12, 2026 04:14
@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — advisory review

Verdict COMMENT — 2 findings, 2 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/mfile/src/expand.rs Resolved
@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: CONDITIONS · 1 condition · the full ruling

The gate is in the right place on every creation path, and it reads no loadout before it decides the mode. One path is still open. min box spec on an open task entry reads no loadout, but min task run on the same entry applies its default loadouts. So a default loadout that sets loadout_mode fails the run and passes the spec. Line 1234 says the spec render is exactly what the host receives, and BOX-172 says both refuse. Run the gate and the loadout check for a task entry as C1 says.

Ruled at 887f7ce against gominimal/arch@15b603e6afb8 by claude-opus-5-5.

Comment thread crates/minimal/src/cmd/box_spec.rs Outdated
// the start. Folding their values into the spec is #3236.
// The box's loadout mode, read before any loadout as `min box start`
// reads it. A sealed box selects no loadout (BOX-171, BOX-173).
let loadout_mode =
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C1 · This gate runs only for an entry that is not a task. An open task entry takes its default loadouts at min task run, and that run refuses one that sets loadout_mode. Here the task branch reads no loadout. Move the gate, the selection and the BOX-172 check above the if box_type == boxes::TASK_BOX_TYPE split (line 1234 of the design).

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 583032c10bf6: Fixed in commit 498e164: entry_loadout_gate, gate_selection and the BOX-172 check now run above the task/session split in box_spec.rs.

Comment thread crates/minimal/src/cmd/box_spec.rs Outdated
&cfg,
global,
)?;
// BOX-172: a loadout that sets `loadout_mode` fails the start, so it
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

BOX-172 · This check does not run for an open task entry, so min box spec passes what min task run refuses. Run it for every entry, as C1 says.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 583032c10bf6: Fixed in commit 498e164 with C1: the BOX-172 check now runs for every entry, so min box spec refuses an open task entry the same way min task run does.

@agent-137
agent-137 marked this pull request as ready for review November 12, 2026 04:50
@agent-137
Copy link
Copy Markdown
Member

/revise

Arch condition (arch_rulings 1183, ruled at 887f7ce), in full:

  • C1: crates/minimal/src/cmd/box_spec.rs expand_entry(): run the loadout path for a task entry too. Keep the exit 2 refusal of --loadout and --ingress for a task entry first. Then, for every entry, call crate::loadouts::entry_loadout_gate(Some(&bytes), entry, &overrides.loadout), resolve crate::loadouts::gate_selection(LoadoutSelection::from_flags(&overrides.loadout, false), loadout_mode) through resolve_active_loadouts, refuse the first loadout where sets_loadout_mode() is true with mfile::Error::LoadoutSetsLoadoutMode { loadout: Some(name) } (exit 3), and set loadouts = Some(names). The simplest edit hoists the block at lines 694-720 above the if box_type == boxes::TASK_BOX_TYPE split. Today an open task entry ([tasks.x] with loadout_mode = 'open') takes its default loadouts at min task run (task_activation_inputs, pinned by sealed_task_run_prints_nothing_about_loadouts), and min task run x refuses a default loadout that sets loadout_mode. But min box spec x reads no loadout, so it renders # Loadouts: none. and does not refuse. Add a case to box_spec_entry_gates_loadouts_as_start (crates/minimal/src/tests.rs): an open task entry with a default loadout that sets loadout_mode fails min box spec with exit 3 and names the loadout, and a sealed task entry with a broken default loadout renders.

min box spec now reads the loadout mode and selects loadouts for every entry, as min task run does. An open task entry with a default loadout that sets loadout_mode now fails with exit 3.
A constraint that admits open does not hold the box sealed. The refusal now keeps the open remedy for it. Also formats box_spec.rs.
…amed-loadout-on-a-sealed-box-a-loadout

# Conflicts:
#	crates/mfile/src/error.rs
#	crates/mfile/src/expand.rs
@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — advisory review

Verdict COMMENT — 2 findings, 2 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/minimal/src/cmd/box_spec.rs Resolved
Comment thread crates/minimal/src/dash/app.rs Resolved
@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: PASS · the full ruling

The change conforms to the design. The loadout gate is in mfile::expand, and every creation path calls it before it reads a loadout file. min box spec calls it too, so the start and the spec agree, as architecture.md line 1234 requires. The two fix commits close the task-entry spec path and the BOX-173 remedy wording. The daemon log does not name the box's loadout mode yet. The design does not require it, but the task plan does. File that residual as an issue.

Ruled at ded1d14 against gominimal/arch@15b603e6afb8 by claude-opus-5-5.

@agent-137
Copy link
Copy Markdown
Member

/revise

Merge main into this branch and resolve the conflicts. #3292 (T23) merged at 2026-11-12T06:37Z and changed crates/mfile/src/expand.rs and box_types.rs, which this PR also changes. Keep this PR's behaviour as arch passed it at ded1d14 (arch PASS). Change nothing else.

…amed-loadout-on-a-sealed-box-a-loadout

# Conflicts:
#	crates/mfile/src/expand.rs
Main made BoxTypeError larger. Clippy result_large_err then failed
with -D warnings on mfile::Error and on the gate in loadouts.rs.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 5 of 10 on a /revise note, pushed as 583032c10bf6.

Resolved conflicts in: crates/mfile/src/expand.rs

line exit tail
cargo nextest run -p mfile loadout_setting_loadout_mode_refused_exit3 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt
cargo nextest run -p mfile named_loadout_on_sealed_box_refused_exit3 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt
cargo nextest run -p mfile constrained_loadout_mode_refused_before_loadouts 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields 8 in task 10BC8 runt
cargo nextest run -p minimal sealed_task_run_prints_nothing_about_loadouts 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt
cargo nextest run -p minimal sealed_box_ignores_broken_default_loadout 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt

Verify min task run agent-verify exited 100 under rustc 1.99.0, which the repository does not pin (1.99.1 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

 {
                "parent_span_id": "7d377d51f3a5e2b7",
                "rpc": "minimald-v1-GetSessionRecord",
                "span_id": "d56c443ccc59238f",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "d56c443ccc59238f",
                "queued_behind": "0",
                "span_id": "7cacadb25f259dff",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "rpc",
            fields: {
                "parent_span_id": "7d377d51f3a5e2b7",
                "rpc": "minimald-v1-DestroySession",
                "span_id": "15db376ccb1fb66f",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "15db376ccb1fb66f",
                "queued_behind": "0",
                "span_id": "4eaf7b223ec8e6e2",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "15db376ccb1fb66f",
                "queued_behind": "0",
                "span_id": "a6c438fe1613cd73",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "a6c438fe1613cd73",
                "span_id": "1dcd89c21ab122f4",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "HandOverName",
                "parent_span_id": "a6c438fe1613cd73",
                "queued_behind": "0",
                "span_id": "e5924accebcbe3de",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
    ]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 
────────────
     Summary [  63.975s] 1901 tests run: 1900 passed, 1 failed, 1 skipped
        FAIL [   2.727s] (1901/1901) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
error: test run failed

Outside the task's layers::

  • crates/minimal/src/cmd/box_spec.rs
  • crates/minimal/src/dash/app.rs

Converted to a draft: something above needs a person before review.

Review threads the coder judged:

  • 👍 valid — crates/minimal/src/cmd/box_spec.rs: Fixed in commit 498e164: entry_loadout_gate, gate_selection and the BOX-172 check now run above the task/session split in box_spec.rs.
  • 👍 valid — crates/minimal/src/cmd/box_spec.rs: Fixed in commit 498e164 with C1: the BOX-172 check now runs for every entry, so min box spec refuses an open task entry the same way min task run does.
  • 👍 valid — crates/minimal/src/cmd/box_spec.rs:703: Updated the EntrySpec::loadouts doc: a task entry carries its loadouts too, and the list is empty for a sealed box. The # Loadouts line for a task is intended, so the code stays as it is.
  • 👎 invalid — crates/minimal/src/dash/app.rs:1072: Outside this task's layers: crates/minimal/src/dash/app.rs. The startup read at line 1107 is deliberate and has no project, so no entry mode exists to gate it. BOX-175 covers expanding an entry, and each dashboard create gates its selection in create_loadout_selection. Changing the startup check nee

With main merged in, each refusal (a loadout that sets loadout_mode, a named loadout on a sealed box, a constrained loadout_mode) still fails with exit 3 before a loadout is applied. A sealed task still never reads a broken default loadout and prints nothing about it.

Notes from the agent: Merge: the one conflict in crates/mfile/src/expand.rs held two sets of new functions. I kept both: this branch's loadout gate and the check_constraints function from #3292. box_types.rs and spec.rs merged cleanly. The merge made BoxTypeError larger, so clippy result_large_err failed with -D warnings. I boxed the Error::BoxType variant (error.rs). I also replaced an and_then closure with a match in loadouts.rs. clippy -p mfile and -p minimal --all-targets -- -D warnings now pass. The behaviour arch passed at ded1d14 did not change. Out of layers: crates/minimal/src/cmd/box_spec.rs got a doc comment fix for thread PRRT_kwDOPdfcxM62fNF0. Earlier rounds already touched it. Commitlint: the failed check is about two earlier commits (498e164, 3dab862), where body lines are longer than 100 characters. Fixing them needs a history rewrite, which this round must not do. The new commits keep body lines short. I did not run just clippy-strict.

@minimal-foundry
minimal-foundry Bot marked this pull request as draft November 12, 2026 07:08
@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — advisory review

Verdict COMMENT — 1 finding, 1 inline.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

Comment thread crates/minimal/src/cmd/boxes/create.rs Resolved
@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: CONDITIONS · 1 condition · the full ruling

The merge from main adds built-in constraints. The build and container-build types now hold loadout_mode = "sealed". The fallback in entry_loadout_gate gives the gate built-in defaults with no constraints. A build entry that sets loadout_mode = "open" then passes the gate, and the start reads its loadouts. Lines 1212 and 1219 of the architecture make that entry a validation error, and BOX-181 refuses it before any loadout is read. Make the change in C1. The rest of the PR keeps the PASS from ded1d148.

Ruled at 583032c against gominimal/arch@15b603e6afb8 by claude-opus-5-5.

Comment thread crates/minimal/src/loadouts.rs Outdated
Err(error) => match builtin_type_layer(entry) {
Some(layer) => {
tracing::debug!(%error, "the loadout gate reads the entry with its built-in type");
(layer, Vec::new())
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C1 · This arm gives the gate no constraints. After the merge, the built-in build type constrains loadout_mode to sealed (architecture line 1219). Pass builtin_constraints(root) here, so the gate refuses an entry that sets open.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 16cd43cada4e: The fallback arm now passes builtin_constraints(root) to Layers::type_constraints, so a build entry that sets open fails with exit 3.

Comment thread crates/minimal/src/loadouts.rs Outdated
.box_type
.as_deref()
.and_then(mfile::box_types::builtin)
.map(|root| vec![(root.to_owned(), mfile::box_types::builtin_defaults(root))])
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

C1 · Return the built-in constraints together with the built-in defaults. mfile::box_types::builtin_constraints(root) gives them.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Addressed in 16cd43cada4e: builtin_type_layer() now returns the built-in defaults and mfile::box_types::builtin_constraints(root) together.

@agent-137
Copy link
Copy Markdown
Member

/revise

Arch condition (arch_rulings 1188, ruled at 583032c), in full:

  • C1: crates/minimal/src/loadouts.rs builtin_type_layer() returns the built-in constraints with the defaults, and the fallback arm of entry_loadout_gate() passes them to Layers::type_constraints in place of Vec::new(). For example, return (vec![(root, builtin_defaults(root))], vec![(root, mfile::box_types::builtin_constraints(root))]). Update the fallback comment, which says that only a name that is not built in has constraints. Add a test in crates/minimal/src/tests.rs: a [boxes.b] entry with type = "build" and loadout_mode = "open" fails session_loadout_mode with exit 3 (ConstraintViolated, naming the built-in build type) and reads no loadout.

The owner accepted this PR's size on 2026-11-12. The extra lines are arch-directed scope.

An entry of a built-in type that does not resolve as a start, such as
build, now keeps its built-in constraints at the gate. A build entry
that sets loadout_mode = open fails with exit 3 before a loadout is read.
@minimal-foundry
Copy link
Copy Markdown
Contributor Author

Round 6 of 10 on a /revise note, pushed as 16cd43cada4e.

line exit tail
cargo nextest run -p mfile loadout_setting_loadout_mode_refused_exit3 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt
cargo nextest run -p mfile named_loadout_on_sealed_box_refused_exit3 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt
cargo nextest run -p mfile constrained_loadout_mode_refused_before_loadouts 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt
cargo nextest run -p minimal sealed_task_run_prints_nothing_about_loadouts 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt
cargo nextest run -p minimal sealed_box_ignores_broken_default_loadout 0 warning: unknown fields in task runtime-implement-test-0: network warning: unknown fields in task runtime-implement-test-1: network warning: unknown fields in task runtime-implement-test-2: network warning: unknown fields in task runtime-implement-test-3: network warning: unknown fields in task runt

Verify min task run agent-verify exited 100 under rustc 1.99.0, which the repository does not pin (1.99.1 in rust-toolchain.toml); the PR is not a draft on that alone — CI, which honours the pin, is the gate.

 {
                "parent_span_id": "c6bf98272efc7c3c",
                "rpc": "minimald-v1-GetSessionRecord",
                "span_id": "35ed4f1d5428e748",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "35ed4f1d5428e748",
                "queued_behind": "0",
                "span_id": "03935cf3d1af36d4",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "rpc",
            fields: {
                "parent_span_id": "c6bf98272efc7c3c",
                "rpc": "minimald-v1-DestroySession",
                "span_id": "8b41087da7404f68",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "GetRecord",
                "parent_span_id": "8b41087da7404f68",
                "queued_behind": "0",
                "span_id": "47ae9ee21c2b6980",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "DeleteSession",
                "parent_span_id": "8b41087da7404f68",
                "queued_behind": "0",
                "span_id": "25bbec3f2a3bbc09",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "session.message",
            fields: {
                "kind": "Destroy",
                "parent_span_id": "25bbec3f2a3bbc09",
                "span_id": "cb83f259a929ff68",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
        LoggedSpan {
            name: "sessions.manager.message",
            fields: {
                "kind": "HandOverName",
                "parent_span_id": "25bbec3f2a3bbc09",
                "queued_behind": "0",
                "span_id": "77f18e4a10102ae7",
                "trace_id": "0af7651916cd43dd8448eb211c803111",
            },
            parent: None,
            closed: true,
        },
    ]
    note: run with `RUST_BACKTRACE=1` environment variable to display a backtrace

  Cancelling due to test failure: 
────────────
     Summary [  87.883s] 1902 tests run: 1901 passed, 1 failed, 1 skipped
        FAIL [   3.477s] (1902/1902) minimal::otel_cli exec_run_attach_and_task_run_share_the_commands_trace
error: test run failed

Outside the task's layers::

  • crates/minimal/src/cmd/box_spec.rs
  • crates/minimal/src/dash/app.rs

Converted to a draft: something above needs a person before review.

Review threads the coder judged:

  • 👎 invalid — crates/minimal/src/cmd/boxes/create.rs:1280: A [tasks.] entry always has box_type "task". mfile::entry::entry_type refuses a type that differs from the table noun (EntryNameRule::TypeMismatch). Also, create.rs refuses a task type outside [tasks]. So no task entry can carry a project Box Type, and the match on box_type == task finds e
  • 👍 valid — crates/minimal/src/loadouts.rs: The fallback arm now passes builtin_constraints(root) to Layers::type_constraints, so a build entry that sets open fails with exit 3.
  • 👍 valid — crates/minimal/src/loadouts.rs: builtin_type_layer() now returns the built-in defaults and mfile::box_types::builtin_constraints(root) together.

Each refusal fails with exit 3 before a loadout is applied. A [boxes.b] entry with type = "build" and loadout_mode = "open" now fails with exit 3 and names the built-in build type, and it reads no loadout. A sealed task never reads a broken default loadout and prints nothing about it.

Notes from the agent: C1: builtin_type_layer() now returns the built-in defaults and the built-in constraints. The fallback arm of entry_loadout_gate() passes the constraints to Layers::type_constraints. The fallback comment and the doc comment say this now. The new test builtin_build_entry_setting_open_refused_exit3 in crates/minimal/src/tests.rs pins it. The commitlint failure is on earlier commits: two body lines are longer than 100 characters and one footer has no leading blank line. Those commits are already on the branch, and the rules forbid a rewrite or a force-push, so they still fail. A squash merge with a clean message avoids the failure. The new commit passes the rules. cargo clippy -p minimal --all-targets and cargo fmt --check printed nothing.

@agent-137
agent-137 marked this pull request as ready for review November 12, 2026 16:50
@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: PASS · the full ruling

This head conforms. The gate fallback now reads the built-in constraints of the type name. A build entry that sets open now fails with exit 3 before a loadout file is read. Line 1219 of the architecture says that build holds loadout_mode at sealed. The new test proves this rule with a broken default loadout present. The rest of the change keeps the PASS on ded1d148.

Ruled at 16cd43c against gominimal/arch@15b603e6afb8 by claude-opus-5-5.

@minimal-foundry minimal-foundry Bot left a comment
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

minimal-review — approval withheld

Verdict COMMENT — APPROVE withheld: 11 unresolved threads — minimal-arch[bot] crates/minimal/src/cmd/boxes/create.rs, minimal-arch[bot] crates/minimal/src/loadouts.rs, minimal-arch[bot] crates/minimal/src/loadouts.rs, minimal-arch[bot] crates/minimal/src/loadouts.rs:736, minimal-arch[bot] crates/mfile/src/expand.rs:430, …. No findings.

Panel: 3 of 3 personas ran (correctness, security, supply-chain).

@norrietaylor norrietaylor changed the title T126 — Refuse a named loadout on a sealed box, a loadout that sets loadout_mode, and a contradicted constraint; a sealed box reads no default loadouts feat(minimal): T126 — Refuse a named loadout on a sealed box, a loadout that sets loadout_mode, and a contradicted constraint; a sealed box reads no default loadouts Nov 12, 2026
@agent-137 agent-137 removed the needs-human An agent handed off; a human must act, then clear this label. Set by defer and handoffs. label Nov 12, 2026
@minimal-arch minimal-arch Bot left a comment
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

arch-review: PASS · the full ruling

This head is a merge of main at 3edbb8e9. The merge changes only CI workflows and docs/commit-conventions.md. The PR's own diff is identical to the diff that the PASS on 16cd43ca covered. The loadout gate still checks a Box Type constraint before layer 6, and a sealed box still reads no loadout, as the design requires (architecture.md lines 1285 and 1288). The PASS carries forward with no conditions.

Ruled at 0809073 against gominimal/arch@15b603e6afb8 by claude-opus-5-5.

@mitodrummer mitodrummer left a comment
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approved.

@mitodrummer
mitodrummer added this pull request to the merge queue Nov 12, 2026
Any commits made after this event will not be merged.

This branch has not been deployed

No deployments
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.

T126 — Refuse a named loadout on a sealed box, a loadout that sets loadout_mode, and a contradicted constraint; a sealed box reads no default loadouts

3 participants

0