Repository navigation
Description
Task
| slice | size | difficulty | model | who | estimated lines |
|---|---|---|---|---|---|
| Types only narrow | M | needs care | strongest | an agent | 380 (160 code, 220 tests) |
Each lifecycle hook in a box's expanded and stored spec names who declared it:
{ kind = "loadout", name = "<loadout>" }for a hook a selected loadout declares, and{ kind = "project" }for one that any layer ofminimal.tomldeclares. The selected loadouts reach the expansion as their own layer, so a loadout's hooks get their declarer and BOX-183's refusal of a conflicting loadout var runs before the box is created.
Covers:
- BOX-179
This task also closes #3302. box_entry builds Layers with an empty loadouts table today, so neither a loadout's hooks nor its vars reach mfile::expand::expand. BOX-179 needs the loadout's name on each of its hooks, so this task fills that layer, one table for each selected loadout with its name.
Layers:
- expansion:
- crates/mfile/src/expand.rs:
Layers::loadoutsbecomes one table for each selected loadout, in selection order, each with the loadout's name. When the fold appends a layer'slifecycle_hooks, it writesdeclared_byinto each hook table:{ kind = "project" }for[defaults],[defaults.<type>], a project Box Type's defaults and the entry, and{ kind = "loadout", name }for a loadout's layer. A hook that two declarers declare stays once for each. Thevarsmerge (T125) reads the loadout layers in the same order, so BOX-183 still names the loadout. - crates/mfile/src/spec.rs: a
lifecycle_hookstable accepts thedeclared_bykey, withkindprojectorloadout, andnameonly forloadout cargo nextest run -p mfile expanded_lifecycle_hooks_carry_declared_by
- crates/mfile/src/expand.rs:
- client:
- crates/minimal/src/cmd/boxes/create.rs:
box_entrypasses each gated, selected loadout (T126) toLayers::loadoutswith its name, formin box create,min session startandmin task run - crates/minimal/src/tests.rs: tests
cargo nextest run -p minimal stored_spec_keeps_each_hook_with_its_declarercargo nextest run -p minimal conflicting_loadout_var_refused_before_create
- crates/minimal/src/cmd/boxes/create.rs:
stored_spec_keeps_each_hook_with_its_declarer creates a box whose entry declares one hook and whose loadout dev declares another. It reads the box's stored spec back and checks both hooks: one with { kind = "project" }, one with { kind = "loadout", name = "dev" }. conflicting_loadout_var_refused_before_create runs min task run with a loadout that sets a var to a different value than the entry. It checks exit 3, T125's BOX-183 message, and that no box record exists.
User-facing text. This task adds no message. min box spec <entry> and min box spec <box> show declared_by in the hook tables they already render. BCLI T41 (#3283) renders the hooks rows from it. The BOX-183 refusal text is T125's and is pinned there.
Observability. The expansion report records the layer each hook came from, as it does for other keys.
Owner decisions this task follows:
- BOX: does the stored spec keep each lifecycle hook's declarer? (BCLI-022 / session hooks parity) #2877: the stored spec keeps each lifecycle hook's declarer. docs(specs): add restart, hook declarer and command-line merge rule to spec 29 #3177 merged that ruling into spec 29 as BOX-179.
- In-session on 2026-11-12: BOX-179 and minimal: fill the loadouts layer in box_entry so BOX-183 refuses a conflicting loadout var before the create #3302 are one task under S5, after T125 (T125 — Merge vars by name across the layers, and refuse a conflicting loadout var or a name set twice in one layer #3300) and T126 (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), which change the same files. BCLI's T41 (T41 — Box spec lists a box's hooks with their declarer #3283), T38 (T38 — Move the scripts and the CLI reference off the old session verbs #3217) and T31 (T31 — Remove the old session verbs and move their callers to the box verbs #2917) wait on it.
Plan block
### T127 — Each lifecycle hook in the stored spec names its declarer, and the selected loadouts reach the expansion
slice: S5
kind: task
covers: BOX-179
size: M
est: 160 code, 220 tests
executor: agent
difficulty: needs care
model: strongest
diagnostics: min box spec shows declared_by on each hook; a BOX-183 refusal names the loadout
observability: the expansion report records the layer of each hook
layers:
- expansion:
- crates/mfile/src/expand.rs: one loadouts layer per selected loadout with its name; write declared_by into each hook as the fold appends it
- crates/mfile/src/spec.rs: a lifecycle hook table accepts declared_by
test: cargo nextest run -p mfile expanded_lifecycle_hooks_carry_declared_by
- client:
- crates/minimal/src/cmd/boxes/create.rs: box_entry passes each gated selected loadout with its name
- crates/minimal/src/tests.rs: tests
test: cargo nextest run -p minimal stored_spec_keeps_each_hook_with_its_declarer
test: cargo nextest run -p minimal conflicting_loadout_var_refused_before_create
produces: none
consumes:
- crates/mfile/src/expand.rs
proof: A box with a project hook and a loadout hook stores both with their declarers, and a conflicting loadout var fails the create with exit 3 before any record exists.
depends on:
- blocked by #3248
- blocked by #3254
Activity