8000 Local box egress proxy — sealed GitHub credentials and host-store secrets without Gatehouse · Issue #1501 · gominimal/minimal · GitHub
[go: up one dir, main page]

Skip to content

Local box egress proxy — sealed GitHub credentials and host-store secrets without Gatehouse #1501

Open
12 of 21 issues completed

Description

@norrietaylor

Epic

Transcribed from https://github.com/gominimal/inbox/issues/625.

Summary

A local Box Egress Proxy (BEP): the un-enrolled, no-Gatehouse form of the architecture's BEP (architecture.md D6, Gatehouse §6.10, networking design §7.1). It runs on the host OS outside the VM, beside gvproxy, and:

  • terminates TLS only for a box's declared credentialed upstream hosts, under a host-generated, name-constrained interception CA that min box spec shows;
  • substitutes sealed GitHub values and referenced host-store secrets (set once with min secret) into requests on the way out, so a box never holds a plaintext credential;
  • re-checks the box's declared [network] egress (the single reachability authority) and audits every admit and refuse to a local append-only log.

git, gh, SDKs and MCP clients need no configuration because boxes talk to the real hostnames. Ships in gominimal/minimal as Rust crates; the hosted/enterprise form imports them into Gatehouse later behind a plugin surface. The open-source form is complete on its own: nothing is hidden or detoured to a proprietary piece.

This is the first slice of the secrets plane, not the plane: one upstream module (GitHub), one host store (macOS Keychain), one host. Anthropic and MCP upstream modules, 1Password and other stores, and the Gatehouse-hosted BEP are later epics that reuse these crates.

Problem

A developer working in a box today has three ways to give an agent a credential: paste it in, pass it through from the client environment, or run the Python reference broker. All three put a bearer in the box, where a prompt-injected agent can read it and use it from anywhere for its full lifetime. The architecture ruled on 2026-09-06 that brokered secrets enter a box only as sealed values redeemed at a Box Egress Proxy, and on 2026-09-17 that on a laptop the BEP runs node-local, on the host OS outside the VM. The Gatehouse-hosted path (#512, gominimal/gatehouse#1) lands with the identity plane's fifth phase. An un-enrolled laptop has no credential path at all until then. The local ordering decided on 2026-09-15 puts the un-enrolled laptop first.

Decisions (stated as decisions)

  • 2026-09-06 (architecture D6, Gatehouse v1.10): sealed values plus a TLS-intercepting BEP replace in-box token delivery and the credential-free-endpoint shape. Dismissed shapes are recorded in Gatehouse §12.11 and are not re-proposed here.
  • 2026-09-12 (Gatehouse v1.18): module host sets. The GitHub v1 set is github.com, api.github.com, uploads.github.com, codeload.github.com; objects.githubusercontent.com stays ordinary egress; port discipline is the BEP's, so github.com:22 is never proxied.
  • 2026-09-15: local-first ordering. The un-enrolled host ships before the enrolled one. NET (docs(specs): add the box networking spec (NET) #1380) was re-scoped the same way.
  • 2026-09-15, local substitutions for the D6/§6.10 roles:
    • Trust domain = the host. "Node binding" means a value is redeemable only by this host's proxy; the "tenant" is the single local operator. Per-box attribution is the box's own source address under minimald's switch anti-spoof (own_ip); host_ip boxes are cohort-granular with the T28 residual recorded; none boxes get no CA and no grants.
    • Three-layer CA. A root CA whose certificate is the trust anchor injected at session creation; a signing CA that rotates freely, its private key held in the host keychain (Secure Enclave where available, never a file on disk); throwaway leaf certificates per upstream hostname. Name-constrained to the declared credentialed upstream set.
    • Sign-in and minting are local. With no Gatehouse configured, min performs the GitHub device-flow sign-in, holds the refresh, mints tokens with ≤ 8 h expiry narrowed to the session's repositories, seals them for this host's proxy, and delivers only the sealed value into the box environment. With Gatehouse configured, min delegates sign-in and minting (out of scope; the seam is kept).
    • Two secret styles. (a) Sealed GitHub values from the sign-in above. (b) Referenced host-store secrets, macOS Keychain first, substituted by name into declared upstream requests under the client's [secret-store-rules], with pluggable resolver backends. v1 = (a) plus Keychain (b).
    • Steering. proxy_env works on day one; dns steering is the default once NET's box-zone resolver lands. A PAC recipe is provided for macOS browsers. UDP is dropped; quic443 = auto blocks QUIC iff the box holds credentialed upstreams.
    • Audit is a local append-only log.
  • 2026-09-17 (gominimal/arch#67, Gatehouse v1.19, networking design v0.7): the node-local BEP and store references ([secrets] source = "store", [secret-store-rules]) are the architecture's shape for LocalVM. This epic implements that shape un-enrolled. One contradiction is filed against arch (see References): v1.19 says an un-enrolled node-local BEP carries "no broker grants, at-redemption store members only", while the 2026-09-15 decision has local min mint sealed GitHub values. This epic follows the 2026-09-15 decision.

Desired outcome — user stories and acceptance criteria

L1. Sign in once

AS A developer using Minimal on a laptop with no Gatehouse,
I WANT to sign in to GitHub once with min,
SO THAT every box I start afterwards can reach my repositories without me handling a token.

Acceptance criteria:

  • One min command completes the GitHub device flow in the browser; a second box start needs no sign-in.
  • min reports that a sign-in exists and when it expires, never a token.
  • The refresh material lives in the host keychain, never in a file under the project or the box.

L2. Clone, push and gh in a box with no token present

AS A developer working in a box,
I WANT git clone, git push and gh to work against my repositories,
SO THAT I do real work without configuring any tool.

Acceptance criteria:

  • With [network.bep] steering = "proxy_env", git clone, git push and gh api against a private repository in the session's set succeed inside the box with no tool configuration beyond what the box already carries.
  • Every environment variable, file and volume in the box is free of the GitHub token; the only credential-shaped value present is a sealed value that decrypts nowhere but this host's proxy.
  • The box's egress declaration must admit the module's full host set; a spec with a GitHub grant whose egress omits a member is refused at expansion with exit 3 naming the missing hosts.

L3. Keychain secret by reference

AS A developer with an API key in my macOS Keychain,
I WANT to reference it by identifier in a box spec,
SO THAT requests from the box to that key's upstream carry it without the key entering the box.

Acceptance criteria:

  • A [secrets] entry of source = "store", store = "keychain", id = <item> under a matching [secret-store-rules] rule causes requests from the box to the rule's registered upstream to carry the value in the rule's injection form.
  • The value is read from the Keychain per request under the Keychain's own access control and is never written to disk by the proxy.
  • A rule naming a deep-module host (api.github.com) or Minimal's own infrastructure is refused when the rule is read.

L9. Set a third-party secret once

AS A developer with a third-party API key, such as a Claude OAuth token or an MCP server credential,
I WANT to store it once with min secret,
SO THAT box specs reference it by identifier and its value never appears in min output, a spec, or a box.

Acceptance criteria:

  • min secret set <id> reads the value from the terminal or standard input, never from a command-line argument, stores it in the host keychain under the identifier, and prints the identifier only.
  • The stored item is readable by the proxy without a per-request prompt and by no other application without one; the consent happens once, at set.
  • min secret list shows each identifier, its store, the upstream a [secret-store-rules] rule registers for it or that none does, and whether the item is readable; never a value.
  • min secret rm <id> removes the item; the next request that would inject it is refused.
  • Setting an identifier again replaces the value; the next request carries the new value with no restart of the proxy or the box.

L4. Out-of-set refusal

AS A developer,
I WANT the proxy to refuse any use of a sealed or referenced secret outside its declared authority,
SO THAT a compromised workload cannot redirect my credential.

Acceptance criteria:

  • A request carrying a sealed GitHub value to a host outside the GitHub host set, or to an admitted host whose Host header names another authority, is refused before it leaves the host and is audited.
  • A CONNECT to any authority outside the configured modules' host sets is refused (off_module).
  • A request to a credentialed host whose egress the box does not declare is refused even with a valid sealed value.

L5. Stolen-value refusal

AS A developer,
I WANT a sealed value copied out of a box to be worthless anywhere else,
SO THAT exfiltration of the box environment yields nothing usable.

Acceptance criteria:

  • A sealed value copied to the host shell and presented to api.github.com directly, or through the proxy from the host, is refused.
  • A sealed value copied into another box on the same host (own_ip) is refused by box attribution.
  • On host_ip boxes the residual is cohort-granular use within the value's own mint-time bound, audited, until expiry or revocation; the spec records this as the T28 residual.

L6. min box spec shows the CA and the upstream set

AS A developer reviewing what a box may reach,
I WANT the expanded box spec to show the interception CA and the derived credentialed upstream set,
SO THAT interception is declared, not discovered.

Acceptance criteria:

  • min box spec renders the root CA fingerprint, the derived upstream set per grant, the steering mode, and every store reference with its registered authorities.
  • A box with steering = "off" or mode = "none" shows no CA and, with a grant present, a validation warning or error respectively.

L7. Audit readable

AS A developer,
I WANT to read what the proxy admitted and refused,
SO THAT I can see what my agents did with my credentials.

Acceptance criteria:

  • A local append-only log records each admit and refuse with the box, the upstream authority, the member or store identifier, the mapped resource where the module extracts one (module_unmapped otherwise), and the decision; never the credential, header value or body.
  • min offers one command to read the log filtered by box.

L8. Revocation

AS A developer,
I WANT to revoke a session's credentials,
SO THAT a value already delivered stops working.

Acceptance criteria:

  • Stopping or destroying a session stops redemption of its sealed values within 60 seconds.
  • min signing out stops redemption of every outstanding sealed GitHub value on this host within 60 seconds.
  • Deleting or disabling a Keychain item stops its substitution on the next request.

Requirements not covered by a user story

  • Production grade. Rust, shipped in the Minimal release under the same signing and diagnostics discipline as the daemons; secret values never reach Debug, logs or support bundles; streamed upstream responses are never buffered.
  • Pure redemption core. The redemption decision (host binding, box attribution, authority in host set, egress re-check, member scope, expiry) is a pure function over owned values, separate from I/O, so the spec can put property tests on it.
  • Process separation. The proxy holds the signing-CA key handle and store access; gvproxy holds forwarding state; they share no writable state.
  • Delegation seam. With a Gatehouse configured, min hands sign-in, minting and sealing to it and the proxy consumes the same envelope. Nothing here forecloses that.

Scope

≤ 2 weeks of agent walk. First slice (the demo): proxy_env steering, the GitHub v1 host set, sealed value minted by local min sign-in, redemption with every check, local audit, min box spec showing CA and upstream set. Later slices: Keychain resolver with min secret set | list | rm, dns steering (after NET's resolver), signing-CA rotation, revocation feed, PAC recipe, host_ip cohort handling.

Out of scope, with destinations:

  • Caching public artifacts through the proxy: separate story.
  • Anthropic and MCP upstream modules, 1Password and other store backends: follow-on epics reusing the module and resolver seams.
  • The Gatehouse-hosted BEP, the enrolled gateway helper, the tenant store: docs(spike): gvproxy v0.8.9 switch-attachment protocol for DM2 #512, gominimal/gatehouse#1, Gatehouse §6.10/§8.6.
  • min secret against the Gatehouse tenant store (deposits): Gatehouse §8.6 and gominimal/arch open item 17; here the noun binds the host keychain only.
  • The execution-facade lane: a separate design.
  • Box-zone DNS and dns steering's resolver: NET (docs(specs): add the box networking spec (NET) #1380).
  • The [network] table and its egress.* fields: NET (docs(specs): add the box networking spec (NET) #1380, NET-060, NET-066) owns them; slice 1 here implements the mode and egress.allow_dns_hosts acceptance it needs and records it as covering NET-060. The full schema is inbox#570's.

Prior art

  • docs/spec-credential-lane branch (2026-08-14, crates/credlane): superseded in shape (a lane endpoint in the box) by D6; its policy gate, resolver seam and review surface are candidates for reuse. The spec states reuse / adapt / drop per unit.
  • The shipped :7654 hostname proxy and the gvproxy/minvmd egress filter and own-ip switch: what this proxy sits beside.
  • GHI-021 to GHI-029 (gominimal/gatehouse#1): the egress-proxy redemption requirements for the enrolled case. The local spec is their home for the un-enrolled case and cross-references their IDs.

Coding-agent guidance

Coding agents working this epic use the skills in gominimal/minimal-skills (minimal-setup, minimal-networking, minimal-config) for driving min, and this repository's AGENTS.md for build and test. Nouns: box, box spec, box host, box provider, BEP, EGW. Never "sandbox" in user-facing text. Never a plaintext credential in a box, a spec file, a patch or a bind mount.

References

Activity

  1. norrietaylor commented on Sep 18, 2026

    @norrietaylor
    MemberAuthor

    Survey

    • repo: gominimal/minimal (origin)
    • one test: just test, just test-one, just test-archive, just test-installer
    • verify commands: cargo nextest run -p minimal, ./scripts/session-e2e.sh, cargo nextest run -p boxes, cargo nextest run -p bep, cargo nextest run -p minimald, cargo nextest run -p minvmd, cargo nextest run -p switch, cargo nextest run -p bep --run-ignored ignored-only
    • missing crates: none
    • lanes: kani, proptest
    • gated: * (.github/CODEOWNERS)
    • frozen: ? (AGENTS.md:156), .github/workflows/ (AGENTS.md:248), .github/workflows/ (CONTRIBUTING.md:53), .github/ (CONTRIBUTING.md:55)
    • spikes dir: docs/spikes (5 findings)
    • specs dir: docs/specs (26 documents)
    • diagnostics: diagnostics (crates/diagnostics), diag (crates/minimal/src/diag), 10-spec-diagnostics (docs/specs)
    • observability: tracing (opentelemetry, opentelemetry-otlp, opentelemetry-semantic-conventions, opentelemetry_sdk, tracing, tracing-opentelemetry), logs (tracing-appender, tracing-subscriber)

    Constraints

    • BLOCKER (gated): .github/workflows/ — release.yml is CODEOWNER-gated, so a person builds, signs and ships bep in it — T101
    • BLOCKER (gated): .github/ — the release workflow lives under it — T101
    • CROSS-REPO: Box — one record, one spec, one grammar for every session and task on a stock install #2236 — the BOX plan's expander and min box verbs later absorb the [session.network.bep] and [session.secrets] parsing and the min box spec and min box audit verbs this plan adds on today's create path; confirmed by hand
    • CROSS-REPO: gominimal/arch#85 — the root's validity duration; OQ3 plans on the mint reading the root's own expiry, so the ruling changes one constant

    Open questions

    • OQ1 (MEDIUM) "When does local repository narrowing land?" — needs an issue: the identity-plane owner of local repository narrowing (Gatehouse §14.4 item 7)
    • OQ2 (MEDIUM) "Does every intended client accept the sealed handle as its bearer unmodified?" — spike: SP1
    • OQ3 (MEDIUM) "How long is the root valid" — assumed: the mint reads the root certificate's own expiry, and the root is generated with a fixed default validity in one constant that gominimal/arch#85's ruling replaces
    • OQ4 (MEDIUM) "On a Linux LocalVM host, which key store holds the signing CA key as non-exportable" — assumed: v1 binds the macOS Keychain behind a key-store seam; the Linux host stays unverified for the signing-key rule until a later backend

    Slices

    • S0 — Clone, push and gh from a box with no token in it: proxy_env steering, the GitHub v1 host set (Gatehouse §6.10 Module host sets: github.com, api.github.com, uploads.github.com, codeload.github.com; pre-signed content hosts such as release-assets.githubusercontent.com stay ordinary egress) as the one module, a sealed reference to the device-flow min auth login sign-in, redemption with every check, the local audit log with its one-shot min box audit <box> read, and min box spec showing the CA and the upstream set. The demo box declares github:user-token, the honest spelling of a full member, so the slice also exercises min box spec's full marker (BEP-038). Keychain references with min secret, dns steering, the browser sign-in flow, min box audit --follow and --parent, audit segment rotation, signing-CA rotation, the PAC recipe and host_ip cohort handling are later slices.
    • S1 — Revoke a box or sign out: Destroy a box or log out, and every value naming it is refused within a minute; a re-created box works, and a later sign-in revives nothing.
    • S2 — Overnight boxes keep GitHub access: A box running past the token's 8 hours still clones and pushes, renewed once at the proxy, with no new sign-in.
    • S3 — Keychain secrets by reference: Store an API key with min secret, reference it in a box spec, and the box's requests to its upstream carry it while the key never enters the box.
    • S4 — DNS steering fails closed: A steered name answers SERVFAIL while the proxy is down, AAAA, HTTPS and SVCB answer NODATA, and UDP 443 from a blocked box is rejected at once.
    • S5 — Audit segments rotate and the chain is checked: The audit log rotates with its chain carried across, min box audit reads every segment, and min doctor reports an edited chain.
    • S6 — Signing certificate rotation: Rotate the signing certificate under the same root, and every running box keeps cloning and pushing unchanged.
    • S7 — Host-address cohort: A host-address box redeems a value naming another live host-address box, and its audit record is marked cohort-attributed.
    • S8 — Proxy as the operator, VM as its own user: The proxy runs as the login user, and the EGW process, gvproxy and the VM run as separate processes sharing no writable state, as dedicated unprivileged users where the vm-users drop-in is installed. Moved 2026-10-31: the spawn through sudo -n (T66 — Spawn gvproxy and the VMM child through sudo -n as their dedicated users #2566) and the EGW process (T38 — Run the egress gate and bep_host in an EGW process apart from the proxy #2285) go to gominimal/inbox#1184 with BEP-047; this slice keeps the design and the users it laid out.
    • S9 — Conditions closed: the residuals the arch left open at merge are done: git in a grant box sends a header the proxy substitutes, an operator rotates a running proxy's signing certificate and the box keeps pushing, a blocked host_ip cohort sends no UDP 443, every store reference is checked at creation, and the CA deny set is the decided one. Its tasks re-cover requirements whose origin tasks merged with an arch condition held on a filed residual; the owner accepted that double coverage on 2026-10-24 so the residuals dispatch as one slice.
    • S10 — Round-two conditions closed: The second round of residuals is done: bep serve injects store values under the client's store rules, a replaced client key keeps verifying until its boxes end, the audit record carries the handle's jti, the key bound and kid diagnostics are in place, BEP-063 states revocation and replacement, a blocked 10BC8 cohort ends its earlier UDP 443 flows and its injected processes, a revoked id never returns, and an unreadable audit line has a chain-preserving way out. Its tasks re-cover requirements whose origin tasks merged with an arch condition held on a filed residual; the owner accepted that double coverage on 2026-10-26 so the residuals dispatch as slices.
    • S11 — macOS lane proofs: Every Box Egress Proxy leg that records NOT RUN on Linux records OK on the macOS lane against a live proxy with a sign-in. Its tasks re-cover requirements whose origin tasks merged with an arch condition held on a filed residual; the owner accepted that double coverage on 2026-10-26 so the residuals dispatch as slices. The debug-build key task re-covers the key-custody requirements; the owner accepted that double coverage on 2026-10-31.
    • S12 — Round-three conditions closed: The third round of residuals is done: the spec names the audit jti and the quarantine, a real kernel rejects a blocked cohort's pre-flip UDP 443 socket, min finalize-install names the QUIC block helper and uninstall removes it, a minted id is checked with no proxy running, a stranded box frees its client key, a widened store rule and a min --config-dir rule reach bep serve without a restart. Its tasks re-cover requirements whose origin tasks merged with an arch condition held on a filed residual; the owner accepted that double coverage on 2026-10-26 so the residuals dispatch as slices.
    • S13 — Round-four residuals closed: The fourth round of residuals is done: the minvmd node-port test no longer races a parallel test for its released port, a person has run the revoke and sign-out case live on a Mac, a box spec writes a store reference as source = "store" with store and id, and box creation seals a store handle for each accepted store rule. Its tasks re-cover requirements whose origin tasks merged with an arch condition held on a filed residual; the owner accepted that double coverage on 2026-10-26 so the residuals dispatch as slices.
    • S14 — Installed hosts run the proxy: On a Mac installed from a release, sign in with min auth login, and a GitHub grant box runs gh api user through the proxy with no extra step.
    • S15 — Audit, HTTP and message conditions: min box audit shows the records the proxy wrote for a box and merges a parent's children with --parent, a relayed response carries no Alt-Svc header on the live path, a refused ambiguous request head shows in min box audit, the log bep serve writes is the log min box audit reads, the missing-proxy refusal, the sign-in refusal read as -o json and the proxy's credential refusals each name their cause and one next step, and bep --replace-key keeps the old key when it cannot make a new one. The owner added these conditions from the close-out walk on 2026-10-31. Its tasks re-cover requirements whose origin tasks merged with the gap open; the owner accepted that double coverage on 2026-10-31 so the conditions dispatch as one slice.
    • S16 — DNS steering through the box-zone resolver: On a host whose switch resolves box names, a box with dns steering reaches GitHub and its stored-key upstreams through the proxy with no proxy variables, and those names fail closed while the proxy is down. Its tasks re-cover requirements whose first tasks merged with the steering wired and the refusal still in place; the owner planned them into this epic on 2026-10-31 so they dispatch as one slice.
    • S17 — Host-address boxes reach GitHub: A box started on the default host network clones, pushes and runs gh against a private repository through the proxy under its default dns steering with no proxy variables, and a host-address box with no grant still resolves GitHub's real address.

    Spikes

    SP1 — Do Claude Code and an MCP client send a sealed handle unmodified?

    question: Does each intended client send the sealed handle as its bearer, unmodified, in the header the store rule names?
    hypothesis: Claude Code with an OAuth token and one MCP client send Authorization: Bearer <value> with no token-shape check
    assumption: if false, the injection task adds a prefix-shaped handle or waits on the harness adapter, and its proof changes
    method: run Claude Code and one MCP client against a local TLS-terminating stand-in that logs the request head, with an opaque handle in place of the token
    time box: 3d
    resolves: OQ2
    executor: human
    deliverable: docs/spikes/2026-10-20-sealed-handle-bearer-clients.md

    Tasks

    T1 — Create the bep crate with the GitHub module and the sealed envelope

    slice: S0
    kind: task
    covers: BEP-006, BEP-027
    size: M
    est: 300 code, 160 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: an envelope that fails to open names the bound field that differed (box id, host, module, host-set version, mode or expiry) in the error the proxy logs, never the value
    observability: a failed open names the bound field that differed, which the proxy logs per failed redemption, never the value
    layers:
    - crate:
    - Cargo.toml: adds crates/bep to the members
    - Cargo.lock: the new crate and its dependencies
    - crates/bep/Cargo.toml: the crate, its seal dependencies and proptest as a dev-dependency
    - crates/bep/src/lib.rs: module lines
    - crates/bep/src/module.rs: the GitHub module's v1 host set at port 443, identifier, version and the fixed deny set
    - crates/bep/src/seal.rs: the envelope of BEP-006, in a form GitHub never accepts as a token
    test: cargo nextest run -p bep sealed_context_binds_box_host_module_version_expiry
    test: cargo nextest run -p bep --run-ignored ignored-only sealed_value_rejected_by_github
    produces:
    - crates/bep/src/module.rs
    - crates/bep/src/seal.rs
    consumes: none
    proof: A sealed member fails to open when any bound field differs, and api.github.com rejects it as a credential.

    T2 — Hold the proxy's keys non-exportable in the host keychain and publish their public halves

    slice: S0
    kind: task
    covers: BEP-014, BEP-059, BEP-060, BEP-072
    size: L
    est: 400 code, 420 tests
    executor: agent
    difficulty: unknown ground
    model: strongest
    diagnostics: when the keychain refuses a key, bep refuses to start and prints "The keychain refused the proxy its key. Run bep --replace-key <role> to make a new one; boxes created before then must be re-created." on stderr, pinned by refused_key_names_the_replacement_command
    observability: one info log line at start naming each key role and its fingerprint, and one warn line per replacement naming the role
    layers:
    - keys:
    - crates/bep/src/keys.rs: a key-store seam; the macOS Keychain backend holds the sealing, root and signing keys non-exportable
    - crates/bep/src/publish.rs: publishes and parses by role the sealing public key, fingerprints and root (BEP-072)
    - crates/bep/src/main.rs: the bep binary with --replace-key <role>, which runs without the files a serving proxy reads
    - crates/bep/src/lib.rs: module lines
    - crates/bep/Cargo.toml: security-framework on macOS
    - Cargo.lock: the keychain dependency
    test: cargo nextest run -p bep signing_key_is_non_exportable_and_has_no_file
    test: cargo nextest run -p bep sealing_and_root_keys_persist_non_exportable
    test: cargo nextest run -p bep a_value_sealed_to_the_published_identity_unseals_under_the_keys
    test: cargo nextest run -p bep a_published_identity_carrying_no_key_is_refused_by_role
    test: cargo nextest run -p bep keys_never_regenerated_on_restart
    test: cargo nextest run -p bep key_replacement_kills_outstanding_members
    test: cargo nextest run -p bep refused_key_names_the_replacement_command
    produces:
    - crates/bep/src/keys.rs
    - crates/bep/src/publish.rs
    - crates/bep/src/main.rs (entry point)
    consumes:
    - crates/bep/src/seal.rs
    proof: The proxy's keys survive a restart and appear in no file, and replacing the sealing key refuses every member sealed to the old one.
    depends on:
    - blocked by T1
    - assumes OQ4

    T3 — Issue leaf certificates under a name-constrained root and a keychain-held signing CA

    slice: S0
    kind: task
    covers: BEP-013
    size: M
    est: 350 code, 140 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: a leaf request for a name outside the union is refused with a log line naming the name and the union it fell outside
    observability: one debug log line per leaf issued with the hostname, and one info line per root re-issue naming the changed constraints
    layers:
    - ca:
    - crates/bep/src/ca.rs: the three-layer CA of BEP-013: constrained self-signed root, keychain signing CA, leaves for the union alone, re-issue under the same key
    - crates/bep/src/lib.rs: module line
    - crates/bep/Cargo.toml: the certificate dependency
    - Cargo.lock: the certificate dependency
    test: cargo nextest run -p bep prop_root_constraints_equal_registered_union
    test: cargo nextest run -p bep root_recertified_under_same_key_and_subject
    produces:
    - crates/bep/src/ca.rs
    consumes:
    - crates/bep/src/keys.rs
    proof: A leaf for each GitHub host validates against the root alone, and a leaf for any other name does not.
    depends on:
    - blocked by T2

    T4 — Decide redemption as one pure function over host key, attribution and member bounds

    slice: S0
    kind: task
    covers: BEP-019, BEP-020, BEP-058
    size: L
    est: 300 code, 360 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: every refusal names its first failing check in the audit record's marker, which min box audit <box> shows
    observability: the connection span and the audit marker name a refusal's first failing check
    layers:
    - decide:
    - crates/bep/src/redeem.rs: the pure decision over owned values, apart from the TLS shell; the BEP-019, BEP-020 and BEP-058 checks
    - crates/bep/src/lib.rs: module line
    - scripts/kani.sh: expects the bep crate's harnesses
    test: cargo nextest run -p bep prop_off_host_sealed_value_is_refused
    test: cargo nextest run -p bep prop_cross_box_sealed_value_is_refused
    test: cargo nextest run -p bep prop_member_without_bounds_is_refused
    test (deferred: kani): cargo nextest run -p bep kani_redeem_refuses_undecryptable
    test (deferred: kani): cargo nextest run -p bep kani_redeem_refuses_misattributed
    test (deferred: kani): cargo nextest run -p bep kani_redeem_refuses_unbounded_member
    produces:
    - crates/bep/src/redeem.rs
    consumes:
    - crates/bep/src/seal.rs
    proof: A value sealed to another host, sent from another own-address box, or missing its bounds is refused.
    depends on:
    - blocked by T1

    T5 — Refuse authorities outside both host sets, off-port connections, and expired or revoked values

    slice: S0
    kind: task
    covers: BEP-021, BEP-024, BEP-031
    size: L
    est: 150 code, 360 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: every refusal names its first failing check in the audit record's marker, which min box audit <box> shows
    observability: the connection span and the audit marker name a refusal's first failing check
    layers:
    - decide:
    - crates/bep/src/redeem.rs: the BEP-021, BEP-024 and BEP-031 checks
    - scripts/kani.sh: the added harnesses' count
    test: cargo nextest run -p bep prop_authority_outside_bound_or_current_set_is_refused
    test: cargo nextest run -p bep prop_expired_or_revoked_value_is_refused
    test: cargo nextest run -p bep prop_off_port_connection_is_refused
    test (deferred: kani): cargo nextest run -p bep kani_redeem_requires_authority_in_both_sets
    test (deferred: kani): cargo nextest run -p bep kani_redeem_refuses_expired_or_revoked
    test (deferred: kani): cargo nextest run -p bep kani_redeem_enforces_port_discipline
    produces: none
    consumes:
    - crates/bep/src/redeem.rs
    proof: A request outside either host set, on another port, or with an expired or revoked value is refused.
    depends on:
    - blocked by T4

    T6 — Pin the request authority, re-check the box's egress, and admit only when every check passes

    slice: S0
    kind: task
    covers: BEP-022, BEP-023, BEP-025
    size: L
    est: 150 code, 360 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: every refusal names its first failing check in the audit record's marker, which min box audit <box> shows
    observability: the connection span and the audit marker name a refusal's first failing check
    layers:
    - decide:
    - crates/bep/src/redeem.rs: the BEP-022 and BEP-023 checks, and Admit iff every check passes, else Refuse naming the first failing one
    - scripts/kani.sh: the added harnesses' count
    test: cargo nextest run -p bep prop_undeclared_egress_authority_is_refused
    test: cargo nextest run -p bep prop_host_header_mismatch_is_refused
    test: cargo nextest run -p bep prop_admit_iff_every_check_passes
    test (deferred: kani): cargo nextest run -p bep kani_redeem_requires_egress_admission
    test (deferred: kani): cargo nextest run -p bep kani_redeem_pins_request_authority
    test (deferred: kani): cargo nextest run -p bep kani_redeem_admits_iff_all_checks_pass
    produces: none
    consumes: none
    proof: A mismatched Host or an egress-denied authority is refused, and the decision admits only when every check passes.
    depends on:
    - blocked by T5

    T7 — Verify signed handles against the client's registered key and the current rule

    slice: S0
    kind: task
    covers: BEP-064
    size: L
    est: 300 code, 240 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: a refused handle is audited store_handle_invalid, and min box audit <box> shows the marker with the store and identifier
    observability: one debug log line per handle checked with the store, the identifier and the outcome, never the signature
    layers:
    - handle:
    - crates/bep/src/handle.rs: the store-handle wire format, the client-key registry, and the BEP-064 check against the rule read per request
    - crates/bep/src/redeem.rs: the handle check stands where the module checks read a host set, mode and breadth
    - crates/bep/src/lib.rs: module line
    - scripts/kani.sh: the added harness's count
    test: cargo nextest run -p bep prop_store_handle_verified_against_current_rule
    test: cargo nextest run -p bep edited_store_rule_applies_to_next_request
    test: cargo nextest run -p bep sign_in_reference_is_judged_against_the_module
    test (deferred: kani): cargo nextest run -p bep kani_redeem_refuses_invalid_store_handle
    produces:
    - crates/bep/src/handle.rs
    consumes: none
    proof: A handle with a bad signature, past expiry, or wider than its rule is refused, and an edited rule decides the next request.
    depends on:
    - blocked by T6

    T8 — Parse requests strictly and forward a canonical head over HTTP/1.1

    slice: S0
    kind: task
    covers: BEP-074, BEP-076, BEP-077
    size: L
    est: 450 code, 360 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: a refused head is audited framing_ambiguous, and min box audit <box> shows the marker
    observability: one debug log line per closed connection naming the framing rule that closed it
    layers:
    - http:
    - crates/bep/src/http.rs: the strict HTTP/1.1 parser and canonical re-serializer of BEP-077, with BEP-074's close and h2c strip and BEP-076's Alt-Svc strip
    - crates/bep/src/upstream.rs: upstream connections keyed per box, never shared across boxes
    - crates/bep/src/lib.rs: module lines
    test: cargo nextest run -p bep h2_prior_knowledge_preface_is_closed
    test: cargo nextest run -p bep h2c_upgrade_is_stripped
    test: cargo nextest run -p bep alt_svc_is_stripped_from_responses
    test: cargo nextest run -p bep prop_forwarded_head_is_canonical
    test: cargo nextest run -p bep prop_ambiguous_framing_is_refused
    test: cargo nextest run -p bep upstream_connections_never_shared_across_boxes
    produces:
    - crates/bep/src/http.rs
    - crates/bep/src/upstream.rs
    consumes: none
    proof: Ambiguous framing or the h2 preface closes the connection, and every forwarded head is canonical.
    depends on:
    - blocked by T1

    T9 — Append hash-chained audit records the proxy alone writes

    slice: S0
    kind: task
    covers: BEP-039, BEP-040, BEP-041, BEP-048
    size: L
    est: 300 code, 240 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: the log is plain JSONL under the proxy's state directory, one record per decision, readable with min box audit <box> once that task lands
    observability: the audit log is the record; one warn log line if an append fails, naming the path
    layers:
    - audit:
    - crates/bep/src/audit.rs: min/v1 JSONL records of BEP-039 built from the decision alone, chained per BEP-048, opened append-only
    - crates/bep/src/lib.rs: module line
    test: cargo nextest run -p bep every_decision_appends_one_audit_record
    test: cargo nextest run -p bep prop_audit_records_never_contain_secrets
    test: cargo nextest run -p bep audit_log_is_opened_append_only
    test: cargo nextest run -p bep audit_record_carries_previous_hash
    produces:
    - crates/bep/src/audit.rs
    consumes: none
    proof: Every decision appends one chained record naming the box and the authority, never a credential or a body.
    depends on:
    - blocked by T1

    T10 — Accept only box connections and terminate TLS for the module's hosts over HTTP/1.1

    slice: S0
    kind: task
    covers: BEP-026, BEP-029, BEP-073, BEP-075
    size: L
    est: 450 code, 240 tests
    executor: agent
    difficulty: unknown ground
    model: strongest
    diagnostics: a connection refused at the listener is logged with its reason and peer, and a request outside the modules is audited off_module, which min box audit <box> shows
    observability: one span per connection with the box id, the authority and the outcome; one warn log line per listener refusal with its reason
    layers:
    - listen:
    - crates/bep/src/listener.rs: the acceptor for the switch peer's deliveries: per-boot token, header version and box id; BEP-026, BEP-029 and BEP-075
    - crates/bep/src/proxy.rs: terminates the box's TLS with a leaf for the admitted name, offering http/1.1 alone in ALPN
    - crates/bep/src/control.rs: the control socket, separate from the listener, taking client key registrations into the handle registry
    - crates/bep/src/main.rs: bep serve
    - crates/bep/src/lib.rs: module lines
    - crates/bep/Cargo.toml: the TLS and async dependencies
    - Cargo.lock: the TLS dependencies
    test: cargo nextest run -p bep host_shell_connection_is_refused
    test: cargo nextest run -p bep request_outside_modules_is_refused_off_module
    test: cargo nextest run -p bep proxy_offers_http11_only_on_both_legs
    test: cargo nextest run -p bep connect_and_absolute_form_accepted_over_http11_only
    produces:
    - crates/bep/src/proxy.rs
    - crates/bep/src/control.rs
    consumes:
    - crates/bep/src/ca.rs
    - crates/bep/src/http.rs
    - crates/bep/src/audit.rs
    - crates/bep/src/handle.rs
    proof: A host-shell connection is refused at the listener, and a box's CONNECT to a GitHub host completes TLS offering HTTP/1.1 alone.
    depends on:
    - blocked by T3
    - blocked by T7
    - blocked by T8
    - blocked by T9

    T11 — Validate the upstream before substituting, pass unsealed requests through, and stream responses

    slice: S0
    kind: task
    covers: BEP-030, BEP-055, BEP-N01
    size: L
    est: 400 code, 300 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: a refused upstream is audited upstream_tls_invalid or upstream_not_tls, and a passthrough carrying its own credential foreign_credential, each shown by min box audit <box>
    observability: the connection span gains the upstream's validation outcome and the substitution decision, never the credential
    layers:
    - forward:
    - crates/bep/src/forward.rs: upstream TLS validated against the host store before forwarding (BEP-055), substitution on admit, passthrough (BEP-030), chunked relay (BEP-N01)
    - crates/bep/src/proxy.rs: hands each parsed request to the decision and the forwarder
    - crates/bep/src/lib.rs: module line
    test: cargo nextest run -p bep unsealed_request_passes_through_and_is_audited
    test: cargo nextest run -p bep prop_upstream_tls_validated_before_substitution
    test: cargo nextest run -p bep upstream_tls_failure_is_refused_and_audited
    test: cargo nextest run -p bep plaintext_request_to_credentialed_host_is_refused
    test: cargo nextest run -p bep streamed_response_is_not_buffered
    produces:
    - crates/bep/src/forward.rs
    consumes:
    - crates/bep/src/proxy.rs
    - crates/bep/src/upstream.rs
    proof: An upstream that fails validation receives nothing, an unsealed request passes unchanged, and responses stream.
    depends on:
    - blocked by T8
    - blocked by T10

    T12 — Sign in to GitHub with min auth and keep the sign-in in one keychain item

    slice: S0
    kind: task
    covers: BEP-001, BEP-002, BEP-004
    size: L
    est: 410 code, 480 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: min auth status prints "Signed in to GitHub as . The token expires at ." or, with expired token and live refresh material, "Signed in to GitHub as . The token renews at the proxy on the next request that uses it.", never a token, pinned by auth_status_reports_expiry_without_token
    observability: one info log line per sign-in naming the account, the flow and whether the instance is new
    layers:
    - cli:
    - crates/minimal/src/cli.rs: min auth login [--device], min auth status
    - crates/minimal/src/cmd/mod.rs: dispatch and module line
    - crates/minimal/src/cmd/auth.rs: device and PKCE browser flows under the App (client id Iv23liv6KetAFJp4Eb5K, callback http://127.0.0.1/callback, any port), browser by default; the secret is option_env!("MINIMAL_GITHUB_APP_CLIENT_SECRET"), and a build without it takes the device flow; status from the item's metadata
    - crates/minimal/Cargo.toml: the bep crate
    - Cargo.lock: the new dependency edge
    test: cargo nextest run -p minimal auth_login_completes_device_flow_and_reports_account
    test: cargo nextest run -p minimal auth_login_completes_browser_pkce_flow
    test: cargo nextest run -p minimal auth_login_without_built_in_secret_takes_device_flow
    test: cargo nextest run -p minimal auth_status_reports_expiry_without_token
    test: cargo nextest run -p minimal auth_status_reports_renewal_of_expired_sign_in
    - item:
    - crates/bep/src/signin.rs: the one sign-in keychain item and its instance identifier (BEP-002)
    - crates/bep/src/lib.rs: module line
    test: cargo nextest run -p minimal auth_login_stores_material_in_keychain_only
    test: cargo nextest run -p minimal a_sign_in_with_no_held_item_is_a_new_instance
    test: cargo nextest run -p minimal re_login_to_the_same_account_keeps_the_instance
    produces:
    - crates/bep/src/signin.rs
    consumes: none
    proof: min auth login completes the device flow and names the account, and min auth status shows the expiry and no token.
    depends on:
    - blocked by T1

    T13 — Refuse GitHub grants at activation that the box or the un-enrolled host cannot honour

    slice: S0
    kind: task
    covers: BEP-008, BEP-009, BEP-010, BEP-016, BEP-017, BEP-056
    size: L
    est: 400 code, 480 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: a refused activation exits 3 and prints, for a missing host, "This box declares a GitHub grant, but its egress does not allow: . Add them to egress.allow_dns_hosts." on stderr, pinned by prop_grant_host_set_outside_egress_is_exit_3
    observability: one debug log line per activation refusal naming the rule and the fields
    layers:
    - spec:
    - crates/boxes/src/core/bep.rs: steering (default dns), proxy_env, no_proxy, quic443 and the GitHub grants; the BEP-008, 009, 010, 017 and 056 refusals
    - crates/boxes/src/core/mod.rs: module line
    - crates/boxes/src/lib.rs: CredentialedUpstream carries the settings, defaulting so an empty declaration reads as before
    - crates/boxes/Cargo.toml: proptest as a dev-dependency
    - Cargo.lock: the dev-dependency edge
    test: cargo nextest run -p boxes prop_grant_host_set_outside_egress_is_exit_3
    test: cargo nextest run -p boxes prop_none_mode_with_grant_is_exit_3
    test: cargo nextest run -p boxes steering_off_with_grant_warns_and_injects_no_ca
    test: cargo nextest run -p boxes steering_off_with_proxy_env_is_refused
    test: cargo nextest run -p boxes steering_defaults_to_dns
    test: cargo nextest run -p boxes prop_dns_steering_without_resolver_is_exit_3
    test: cargo nextest run -p boxes prop_installation_mode_unenrolled_is_exit_3
    - read:
    - crates/mfile/src/lib.rs: reads [session.network.bep] and [session.secrets] from the project's [session] table
    - crates/minimal/src/cmd/session.rs: validates before the create, passing the module's host set
    - crates/minimal/src/main.rs: maps the refusal to exit 3
    test: cargo nextest run -p minimal activation_refuses_grant_before_create_with_exit_3
    produces:
    - crates/boxes/src/core/bep.rs
    consumes:
    - crates/bep/src/module.rs
    proof: Activating a grant box whose egress omits a GitHub host exits 3 naming each missing host.
    depends on:
    - blocked by T1

    T14 — Mint a sealed sign-in reference for each GitHub grant and register the box with the proxy

    slice: S0
    kind: task
    covers: BEP-003, BEP-005
    size: L
    est: 360 code, 360 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: creating a grant box with no sign-in held exits with code github_sign_in_required and prints "This box declares a GitHub grant, and no GitHub sign-in is held on this machine. Run min auth login, then create the box again." on stderr, pinned by creation_without_sign_in_fails_with_defined_error
    observability: one info log line per mint naming the box id, the module and the member's expiry, never the handle or the sealed value; one debug line per register_box naming the box id and the count of its declared authorities
    layers:
    - publish:
    - crates/bep/src/publish.rs: the published identity carries the root's validity end (root_not_after), the root key's creation time plus the fixed default validity in one constant (OQ3), until gominimal/arch#85 rules otherwise
    test: cargo nextest run -p bep published_identity_carries_the_root_validity_end
    - mint:
    - crates/minimal/src/mint.rs: the BEP-005 sign-in reference under the client's key, registered over the control socket, sealed from the published identity, with the member's expiry read from the identity's root_not_after
    - crates/minimal/src/lib.rs: module line
    - crates/minimal/src/tests.rs: the mint and registration tests
    - crates/minimal/src/cmd/boxes/create.rs: refuses creation with no sign-in held and never prompts; mints and seals per grant; sends register_box over the proxy's control socket with the box id, its declared egress and its own_ip or host_ip stance (T41)
    - crates/bep/src/main.rs: drops the box-egress interim sentence from the bep serve startup warning and the wire comment, the create half of that flip
    test: cargo nextest run -p minimal local_member_is_a_sign_in_reference_expiring_with_the_root
    test: cargo nextest run -p minimal grant_box_registers_its_declared_egress_with_the_proxy
    - refuse:
    - crates/boxes/src/core/bep.rs: the defined error github_sign_in_required
    test: cargo nextest run -p boxes creation_without_sign_in_fails_with_defined_error
    - e2e:
    - scripts/session-e2e.sh: adds the case
    test: ./scripts/session-e2e.sh bep_second_box_needs_no_sign_in
    produces:
    - crates/minimal/src/mint.rs
    consumes:
    - crates/bep/src/signin.rs
    - crates/bep/src/handle.rs
    - crates/bep/src/publish.rs
    - crates/bep/src/control.rs
    - crates/boxes/src/core/bep.rs
    proof: A grant box is created with no prompt, each grant's sealed value is minted under the client's key with the root's expiry, and the proxy holds the box's declared egress.
    depends on:
    - blocked by T2
    - blocked by T7
    - blocked by T10
    - blocked by T12
    - blocked by T13
    - blocked by T41
    - assumes OQ3

    T42 — Deliver each sealed value into its grant's environment variable in the box

    slice: S0
    kind: task
    covers: BEP-007
    size: L
    est: 260 code, 260 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: min box spec <box> shows each grant's variable name and never its value; a box whose create request carries no sealed value for a declared grant is refused at creation with exit 3 naming the grant
    observability: one debug log line per delivered variable naming the box id and the variable, never the value
    layers:
    - carry:
    - crates/minimald-rpc/src/lib.rs: BoxConfig carries the sealed deliveries and the root certificate, and the shared create-request constructor takes them
    - crates/minimal/src/cmd/boxes/create.rs: the MintGrants step hands each Minted.sealed to the create request as its grant's delivery in place of drop(minted) (arch C2 on #2490, residual filed here)
    - crates/minimald-rpc/src/tests.rs: the new fields in its full literals
    - crates/minimald/src/box_manager.rs: record_from_config copies the deliveries into the box record
    - crates/boxes/src/lib.rs: Record holds the deliveries
    - crates/minimald/src/box_manager/tests.rs: the new fields in its full literals
    test: cargo nextest run -p minimald sealed_deliveries_ride_the_record_to_the_host_params
    - place:
    - crates/minimald/src/box_actor/host.rs: builds HostParams with the deliveries
    - crates/minimald/src/box_runtime.rs: places each sealed value in its grant's environment variable and no plaintext
    - crates/minimald/src/box_runtime/tests.rs: the placement test
    test: cargo nextest run -p minimald sealed_value_is_placed_in_its_grant_variable_only
    - e2e:
    - scripts/session-e2e.sh: adds the case
    test: ./scripts/session-e2e.sh bep_box_env_holds_sealed_value_only
    produces: none
    consumes:
    - crates/minimal/src/mint.rs
    - crates/minimald-rpc/src/lib.rs
    proof: A second grant box starts with no prompt and holds only a sealed value in its grant's variable.
    depends on:
    - blocked by T14

    T43 — Issue and publish the un-enrolled host root certificate and hand it to each grant box

    slice: S0
    kind: task
    covers: BEP-072, BEP-013
    size: L
    est: 240 code, 360 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: min box spec <box> shows the root certificate's SHA-256 fingerprint under the label root CA certificate in place of the key thumbprint, pinned by box_spec_shows_the_root_certificate_fingerprint; a bep start whose held root key cannot sign refuses with a message naming bep --replace-key root
    observability: one info log line at bep start naming the root certificate's fingerprint and the count of its permitted names, never a key
    layers:
    - issue:
    - crates/bep/src/keys.rs: CaKey for the keychain-held key through SecKey::create_signature (ECDSA P-256, SHA-256), so the root and signing CA keys issue in production (#2440)
    - crates/bep/src/ca.rs: the root certificate is issued from the held root key at bep start with the permitted names of the configured modules less the deny set, and re-issued under the same key and subject when the union changes; the store rules join the union when T25 wires them
    test: cargo nextest run -p bep root_certificate_is_issued_from_the_held_root_key
    test: cargo nextest run -p bep keychain_root_key_signs_the_root_certificate
    - publish:
    - crates/bep/src/publish.rs: Identity carries root_certificate, the PEM of the current root certificate, beside the root key (BEP-072); a start or a root replacement publishes the certificate it issued
    - crates/bep/src/main.rs: bep start and bep --replace-key root issue and publish the root certificate; bep serve keeps the issued root as the CA's anchor
    test: cargo nextest run -p bep published_identity_carries_the_root_certificate
    test: cargo nextest run -p bep root_replacement_publishes_a_new_root_certificate
    - hand:
    - crates/minimal/src/cmd/boxes/create.rs: the MintGrants step puts the identity's root_certificate on the create request for a grant box, and refuses a grant box whose identity publishes none with a message naming bep
    - crates/minimal/src/cmd/box_spec.rs: shows the root certificate's fingerprint (#2446)
    - crates/minimal/src/tests.rs: the create-request and spec tests
    test: cargo nextest run -p minimal grant_box_create_request_carries_the_published_root_certificate
    test: cargo nextest run -p minimal box_spec_shows_the_root_certificate_fingerprint
    produces: none
    consumes:
    - crates/bep/src/ca.rs
    - crates/bep/src/keys.rs
    - crates/bep/src/publish.rs
    - crates/minimald-rpc/src/lib.rs
    proof: A started proxy publishes a self-signed, name-constrained root certificate issued from its held root key, a grant box's create request carries it, and min box spec shows its fingerprint.
    depends on:
    - blocked by T2
    - blocked by T3
    - blocked by T42

    T15 — Show the root fingerprint, the upstream set, steering and the full marker in min box spec

    slice: S0
    kind: task
    covers: BEP-038, BEP-057
    size: L
    est: 400 code, 360 tests
    executor: agent
    difficulty: routine
    model: standard
    diagnostics: min box spec prints root CA <sha256 fingerprint>, one grant github:user-token full row per grant with its upstream hosts, steering proxy_env, and, where the stance is allowed on a grant box, quic443 allowed (HTTP/3 bypasses the proxy), pinned by box_spec_renders_ca_upstreams_steering_and_references
    observability: one debug log line per render naming the box and whether the published root was found
    layers:
    - cli:
    - crates/minimal/src/cli.rs: the min box noun with spec
    - crates/minimal/src/cmd/mod.rs: dispatch and module line
    - crates/minimal/src/cmd/box_spec.rs: the BEP-038 rows: root fingerprint, upstreams with the full marker, steering, store references, the QUIC flag
    - crates/minimal/src/config.rs: reads [secrets] acknowledge_full_breadth_unenrolled from the user and organization client configuration
    test: cargo nextest run -p minimal box_spec_renders_ca_upstreams_steering_and_references
    test: cargo nextest run -p minimal box_spec_marks_full_breadth_member
    test: cargo nextest run -p minimal box_spec_flags_allowed_quic_stance_on_grant_box
    test: cargo nextest run -p minimal acknowledged_narrow_grant_renders_full
    - spec:
    - crates/boxes/src/core/bep.rs: the BEP-057 refusal and the quic443 stance
    - crates/mfile/src/lib.rs: ignores a project's acknowledgement with a warning
    test: cargo nextest run -p boxes prop_narrow_scopes_unenrolled_need_acknowledgement
    test: cargo nextest run -p boxes project_acknowledgement_is_ignored_with_warning
    produces:
    - crates/minimal/src/cmd/box_spec.rs (entry point)
    consumes:
    - crates/boxes/src/core/bep.rs
    - crates/bep/src/publish.rs
    proof: min box spec on the demo box shows the root fingerprint, the four GitHub hosts marked full, and proxy_env steering.
    depends on:
    - blocked by T2
    - blocked by T13

    T16 — Put the interception root in the box's trust store

    slice: S0
    kind: task
    covers: BEP-011
    size: L
    est: 330 code, 420 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: a box spec that sets one of the three trust variables itself keeps its value and activation prints "This box sets itself, so tools that read it will not trust the proxy's certificate." on stderr, pinned by spec_trust_env_shadows_platform_with_warning
    observability: one debug log line per box naming whether the root was injected
    layers:
    - carry:
    - crates/boxes/src/lib.rs: Record.root_certificate, the proxy's root certificate PEM beside sealed_deliveries, so every host launch of the box reads it, after a daemon restart included (stale layers found by the coder on 2026-10-22; the record fixtures are struct-literal additions)
    - crates/minimald/src/box_manager.rs: the record build copies config.root_certificate from the create request
    - crates/minimald/src/box_manager/tests.rs: the record test
    - crates/minimald/src/box_runtime/launcher.rs: the launch hands the record's root certificate to EnvArgs, so Env::build writes the box's bundle copy and hashed directory
    test: cargo nextest run -p minimald record_carries_the_root_certificate_to_the_launch
    - trust:
    - crates/minimald/src/env.rs: the root in the box's own bundle copy and hashed directory, per BEP-011, never the package store's copy
    - crates/minimald/src/box_runtime.rs: the trust variables of BEP-011
    - crates/minimald/src/box_runtime/tests.rs: the trust-variable tests
    - scripts/session-e2e.sh: adds three cases
    test: ./scripts/session-e2e.sh bep_root_ca_present_in_box_trust_store
    test: ./scripts/session-e2e.sh bep_root_ca_absent_from_box_without_credentialed_upstream
    test: ./scripts/session-e2e.sh bep_trust_env_points_at_bundle_and_root
    - spec:
    - crates/boxes/src/core/bep.rs: the warning for a spec that sets a trust variable
    test: cargo nextest run -p boxes spec_trust_env_shadows_platform_with_warning
    produces: none
    consumes:
    - crates/minimald-rpc/src/lib.rs
    - crates/boxes/src/core/bep.rs
    proof: A grant box has the root in both trust-store forms, and a box with no grant has none.
    depends on:
    - blocked by T13
    - blocked by T14
    - blocked by T42

    T40 — Point the box at the proxy

    slice: S0
    kind: task
    covers: BEP-012
    size: M
    est: 60 code, 200 tests
    executor: agent
    difficulty: routine
    model: standard
    diagnostics: min box spec <box> shows the box's steering, which decides whether the proxy variables are set
    observability: one debug log line per box naming the proxy variables set
    layers:
    - env:
    - crates/minimald/src/box_runtime.rs: HTTPS_PROXY, HTTP_PROXY, NO_PROXY and NODE_USE_ENV_PROXY under proxy_env or both
    - scripts/session-e2e.sh: adds two cases
    test: ./scripts/session-e2e.sh bep_proxy_env_and_no_proxy_are_set
    test: ./scripts/session-e2e.sh bep_proxy_env_sets_node_use_env_proxy
    produces: none
    consumes:
    - crates/boxes/src/core/bep.rs
    proof: A proxy_env box has the proxy address, the no-proxy list and NODE_USE_ENV_PROXY=1 in its environment.
    depends on:
    - blocked by T13
    - blocked by T14
    - blocked by T42

    T17 — Read one box's audit trail with min box audit

    slice: S0
    kind: task
    covers: BEP-042
    size: L
    est: 300 code, 300 tests
    executor: agent
    difficulty: routine
    model: standard
    diagnostics: inside a box, min box audit self prints "Reading your own audit trail from inside a box needs a Gatehouse. On the host, run min box audit <box>." on stderr and exits non-zero, pinned by box_audit_self_unenrolled_refused_with_error; a name prints "Showing (box )." before the records
    observability: one debug log line per read naming the box id and the segments read
    layers:
    - cli:
    - crates/minimal/src/cli.rs: min box audit <box|self> | --parent <box|self> [-o jsonl] [--follow]
    - crates/minimal/src/cmd/mod.rs: dispatch and module line
    - crates/minimal/src/cmd/box_audit.rs: the BEP-042 grammar: one box id, a name, --follow, --parent, self refused
    - crates/bep/src/audit.rs: a reader over the log for the client
    test: cargo nextest run -p minimal box_audit_filters_to_one_box
    test: cargo nextest run -p minimal box_audit_name_reads_live_else_latest_creation
    test: cargo nextest run -p minimal box_audit_follow_replays_then_tails
    test: cargo nextest run -p minimal box_audit_parent_merges_children
    test: cargo nextest run -p minimal box_audit_self_unenrolled_refused_with_error
    produces:
    - crates/minimal/src/cmd/box_audit.rs (entry point)
    consumes:
    - crates/bep/src/audit.rs
    proof: min box audit web lists only that box's decisions and names the id it read.
    depends on:
    - blocked by T9
    - blocked by T15

    T18 — Run the proxy beside gvproxy so git and gh reach GitHub from a box

    slice: S0
    kind: task
    covers: BEP-015
    size: L
    est: 280 code, 420 tests
    executor: agent
    difficulty: unknown ground
    model: strongest
    diagnostics: the support bundle carries the proxy's log tail and the audit log segments, collected beside the VM host daemon's log
    observability: one info log line when the VM host daemon starts the proxy, naming its socket and pid, and one warn line when it exits
    layers:
    - host:
    - crates/minvmd/src/cmd/run.rs: starts bep serve beside gvproxy with the per-boot token in production, the stub staying test-only
    test: cargo nextest run -p minvmd proxy_is_started_with_the_per_boot_token
    - ship:
    - justfile: builds and signs the bep binary beside minvmd
    - crates/minimal/src/diag/collect.rs: collects the proxy log and the audit segments
    test: cargo nextest run -p minimal bundle_collects_the_proxy_log_and_audit_segments
    - e2e:
    - scripts/session-e2e.sh: adds the case; the T14 leg bep_second_box_needs_no_sign_in records NOT RUN on every lane until this task starts bep serve (do not change that leg to pass without a proxy); the T16 grant legs bep_root_ca_present_in_box_trust_store, the grant half of bep_root_ca_absent_from_box_without_credentialed_upstream and bep_trust_env_points_at_bundle_and_root record NOT RUN off macOS the same way; the macOS passes against the live proxy that #2490 (arch C1) and #2495 (arch C2) filed here moved to #2500 (arch C3 on #2497)
    test: ./scripts/session-e2e.sh bep_git_and_gh_against_private_repo
    test: ./scripts/session-e2e.sh bep_second_box_needs_no_sign_in
    test: ./scripts/session-e2e.sh bep_box_env_holds_sealed_value_only
    produces: none
    consumes:
    - crates/bep/src/main.rs
    - crates/bep/src/forward.rs
    proof: From a box holding only a sealed member, git clone, git push and gh api succeed against a private repository.
    depends on:
    - blocked by T2
    - blocked by T11
    - blocked by T14
    - blocked by T16
    - blocked by T40
    - blocked by T41
    - blocked by T42

    T19 — Clone, push and gh from a box with no token in it, end to end

    slice: S0
    kind: integration
    covers: none
    size: M
    est: 120 code, 200 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: min box audit <box> shows every admit of the run and the refusals of the copied value
    observability: one connection span per request of the run, with the box id, the authority and the outcome
    layers:
    - e2e:
    - scripts/session-e2e.sh: one case: sign in, create the demo box, read min box spec, clone and push, see the copied value refused on the host and in a second box, read min box audit
    test: ./scripts/session-e2e.sh bep_first_slice_end_to_end
    test: ./scripts/session-e2e.sh bep_git_and_gh_against_private_repo
    produces: none
    consumes: none
    proof: proxy_env steering, the GitHub v1 host set (Gatehouse §6.10 Module host sets: github.com, api.github.com, uploads.github.com, codeload.github.com; pre-signed content hosts such as release-assets.githubusercontent.com stay ordinary egress) as the one module, a sealed reference to the device-flow min auth login sign-in, redemption with every check, the local audit log with its one-shot min box audit <box> read, and min box spec showing the CA and the upstream set. The demo box declares github:user-token, the honest spelling of a full member, so the slice also exercises min box spec's full marker (BEP-038). Keychain references with min secret, dns steering, the browser sign-in flow, min box audit --follow and --parent, audit segment rotation, signing-CA rotation, the PAC recipe and host_ip cohort handling are later slices.
    depends on:
    - blocked by T1
    - blocked by T2
    - blocked by T3
    - blocked by T4
    - blocked by T5
    - blocked by T6
    - blocked by T7
    - blocked by T8
    - blocked by T9
    - blocked by T10
    - blocked by T11
    - blocked by T12
    - blocked by T13
    - blocked by T14
    - blocked by T15
    - blocked by T16
    - blocked by T17
    - blocked by T18
    - blocked by T40
    - blocked by T41
    - blocked by T42
    - blocked by T43

    T41 — Wire the redemption decision and each box's declared egress into bep serve

    slice: S0
    kind: task
    covers: BEP-019, BEP-020, BEP-022, BEP-025
    size: L
    est: 400 code, 400 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: min box audit <box> shows each redemption's admit with the member identifier and each refusal with its check marker (undecryptable, cross_box, outside_declared_egress, host_mismatch), never the credential; a box whose declared egress the proxy has not registered is refused outside_declared_egress
    observability: one info log line when bep serve installs the redeemer naming the registry path, and one debug line per registered box naming its id and the count of its declared authorities, never a credential
    layers:
    - registry:
    - crates/bep/src/boxes.rs: per box id, the declared egress authorities and the own_ip or host_ip stance, registered over the control socket at creation and removed at destroy
    - crates/bep/src/control.rs: a RegisterBox request carrying the box id, its declared egress and its stance, and a ForgetBox request
    - crates/bep/src/lib.rs: module line
    test: cargo nextest run -p bep registered_box_egress_is_read_per_request
    test: cargo nextest run -p bep forgotten_box_is_refused_no_live_box
    - decide:
    - crates/bep/src/redeemer.rs: the forward::Redeemer over redeem::decide: opens the sealed value under the proxy key, verifies the handle against the registered client keys, resolves a sign-in reference to the member's credential from the Keychain item, takes attribution and the declared egress from the registry, and passes the revocations in force
    - crates/bep/src/forward.rs: Unwired stays the test default; the serving proxy installs the real redeemer
    test: cargo nextest run -p bep sealed_member_is_redeemed_within_declared_egress
    test: cargo nextest run -p bep cross_box_value_is_refused_cross_box
    test: cargo nextest run -p bep prop_serve_redeems_iff_decide_admits
    - serve:
    - crates/bep/src/main.rs: bep serve installs the redeemer through Proxy::with_redeemer and gives the proxy the control registry; the NoSigningKey doc and the startup warn! say that #2440 alone gates leaf signing now that the decision is wired
    test: cargo nextest run -p bep serve_installs_the_redeemer_and_registry
    produces: none
    consumes:
    - crates/bep/src/redeem.rs
    - crates/bep/src/forward.rs
    - crates/bep/src/handle.rs
    - crates/bep/src/signin.rs
    - crates/bep/src/seal.rs
    - crates/bep/src/control.rs
    proof: A request carrying a sealed member from a registered box inside its declared egress reaches the upstream with the credential in place, and the same request from another box, or to a host the box did not declare, is refused before anything goes upstream.
    depends on:
    - blocked by T1
    - blocked by T4
    - blocked by T7
    - blocked by T10
    - blocked by T11
    - blocked by T12

    T20 — Record a revocation when a live box is destroyed and refuse its values within a minute

    slice: S1
    kind: task
    covers: BEP-043, BEP-067, BEP-071
    size: L
    est: 380 code, 440 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: min box audit <box> shows the box's revocation record and each later refusal of its values
    observability: one info log line per revocation recorded, naming the box id; one warn line when a destroy completes with the proxy unreachable
    layers:
    - proxy:
    - crates/bep/src/revoke.rs: revocations by box id, consulted before any resolution (BEP-071); its module line goes in the crate root beside the other modules
    - crates/bep/src/control.rs: takes mint and revocation submissions and appends them to the one log
    - crates/bep/src/proxy.rs: AuditWriter::try_write reports an append failure to revoke_box, so a lost revocation record is logged as an error and never silent (widened 2026-10-25 per review finding 4369863226 on #2548)
    - crates/bep/src/redeemer.rs: the interim doc comments say that revoke_box on the control socket puts a revocation in force and that the one interim left is the empty set at start until T33 (#2290); revoked is built with crate::revoke::in_force_for for this box only (widened 2026-10-25 per arch C2 and C3 on #2548)
    test: cargo nextest run -p bep prop_revocation_covers_exactly_its_box
    test: cargo nextest run -p bep revoked_value_never_triggers_renewal
    - client:
    - crates/minimal/src/mint.rs: submits a mint record per member
    - crates/minimal/src/tests.rs: the mint tests and their stub control servers live here, so the stubs answer the new record_mint and revoke_box requests and the revocation test sits beside them (widened 2026-10-25)
    - crates/minvmd/src/box_registry.rs: on a live box's destroy submits its revocation and sends forget_box over the control socket (T41), completing the destroy when the proxy is down
    - crates/minvmd/src/cmd/run.rs: the supervisor start path sets the proxy control socket on the registry through with_proxy_control, so only the daemon, never a test, sends revocations (widened 2026-10-25 per review finding 4369790164 on #2548)
    - crates/bep/src/main.rs: drops the box-egress interim from the bep serve startup warning and the wire comment if T14 has not already, the destroy half of that flip
    test: cargo nextest run -p minimal mint_and_destroy_append_audit_records
    - e2e:
    - scripts/session-e2e.sh: adds two cases
    test: ./scripts/session-e2e.sh bep_destroyed_box_values_refused_within_60s
    test: ./scripts/session-e2e.sh bep_destroy_with_proxy_down_refuses_by_attachment
    produces:
    - crates/bep/src/revoke.rs
    consumes:
    - crates/bep/src/control.rs
    - crates/minimal/src/mint.rs
    proof: After min session destroy web, a value copied from web is refused within a minute, even if the proxy was down.
    depends on:
    - blocked by T10
    - blocked by T14
    - blocked by T19

    T21 — Log out: delete the sign-in and refuse every reference to it

    slice: S1
    kind: task
    covers: BEP-044
    size: M
    est: 220 code, 220 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: min auth logout prints "Signed out of GitHub. Boxes created before now lose GitHub access at their next request." and, when GitHub cannot be reached, "GitHub did not confirm the sign-out; the sign-in is removed from this machine." on stderr, pinned by logout_revokes_at_github_best_effort
    observability: one info log line per logout naming the account and whether GitHub confirmed the revocation
    layers:
    - cli:
    - crates/minimal/src/cli.rs: min auth logout
    - crates/minimal/src/cmd/auth.rs: deletes the item, asks GitHub to revoke best effort, reports either outcome without failing
    test: cargo nextest run -p minimal logout_revokes_at_github_best_effort
    test: cargo nextest run -p minimal sign_in_after_logout_revives_no_earlier_reference
    - e2e:
    - scripts/session-e2e.sh: adds the case
    test: ./scripts/session-e2e.sh bep_logout_refuses_every_reference_at_next_request
    produces: none
    consumes: none
    proof: After min auth logout, every running box's next GitHub request is refused, and signing in again revives none of them.
    depends on:
    - blocked by T19

    T22 — Prove a box re-created under the same name gets an id of its own that redeems

    slice: S1
    kind: task
    covers: BEP-070
    size: M
    est: 0 code, 220 tests
    executor: agent
    difficulty: routine
    model: standard
    diagnostics: min box audit <name> names the id it read, so a re-created box's records read apart from its predecessor's
    observability: the registration log line already names each minted id; this task adds tests only
    layers:
    - tests:
    - crates/minimal/src/tests.rs: the spec's named tests over the host-side mint and the collision refusal
    - scripts/session-e2e.sh: adds the case
    test: cargo nextest run -p minimal box_id_is_a_csprng_uuidv7_per_creation
    test: cargo nextest run -p minimal colliding_box_id_refuses_creation
    test: ./scripts/session-e2e.sh bep_box_recreated_under_its_name_redeems
    produces: none
    consumes: none
    proof: A box re-created under its old name redeems its new values while the old ones stay refused.
    depends on:
    - blocked by T20

    T23 — Revoke a box or sign out, end to end

    slice: S1
    kind: integration
    covers: none
    size: M
    est: 80 code, 120 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: min box audit <box> shows the revocation and the refusals that follow it
    observability: one info log line per revocation and per logout, as the tasks emit them
    layers:
    - e2e:
    - scripts/session-e2e.sh: one case: destroy a box, re-create it under its name, log out, sign in again, and see each refusal and admit
    test: ./scripts/session-e2e.sh bep_revoke_and_sign_out_end_to_end
    produces: none
    consumes: none
    proof: Destroy a box or log out, and every value naming it is refused within a minute; a re-created box works, and a later sign-in revives nothing.
    depends on:
    - blocked by T20
    - blocked by T21
    - blocked by T22

    T24 — Renew an expiring sign-in once at the proxy

    slice: S2
    kind: task
    covers: BEP-069
    size: L
    est: 350 code, 420 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: min box audit <box> shows each renewal as its own record, and a sign-in that cannot renew as a refusal marked github_sign_in_required
    observability: one info log line per renewal naming the account and the new expiry, and one warn line when GitHub refuses it
    layers:
    - renew:
    - crates/bep/src/signin.rs: renews from the refresh material when the token has expired or expires within 5 minutes, writing the item update-only before first use
    - crates/bep/src/forward.rs: one renewal in flight per item, the others waiting for its outcome; refuses when the write fails or the item is gone
    - crates/bep/src/redeemer.rs: BoxRedeemer::credential no longer refuses an item whose standing is Live but expired; it hands the item to the renewal path (widened 2026-10-24)
    - crates/bep/src/main.rs: wires a GitHub token-endpoint client into the proxy, with the app's client id and the client secret embedded at build time through option_env!("MINIMAL_GITHUB_APP_CLIENT_SECRET"), as min's BUILT_IN_SECRET is; no launcher passes it at run time (widened 2026-10-24; wording per arch C3 on #2518)
    - crates/bep/src/audit.rs: Kind::Renewal (serde renewal), the renewal's own audit record kind (added 2026-10-24 per arch C2 on #2518)
    - crates/minimal/src/cmd/box_audit.rs: 10BC8 maps Kind::Renewal to renewal in the kind match (added 2026-10-24 per arch C2 on #2518)
    test: cargo nextest run -p bep an_expiring_sign_in_is_renewed_at_the_proxy
    test: cargo nextest run -p bep concurrent_redemptions_share_one_renewal
    test: cargo nextest run -p bep renewal_not_persisted_is_refused
    test: cargo nextest run -p bep renewal_finding_item_deleted_refuses_and_creates_nothing
    test: cargo nextest run -p bep a_sign_in_with_time_to_live_is_not_renewed
    test: cargo nextest run -p bep unrenewable_sign_in_refuses_with_sign_in_required
    - client:
    - crates/minimal/src/cmd/auth.rs: reads only the item's metadata after sign-in
    test: cargo nextest run -p minimal client_never_presents_or_refreshes_the_token
    produces: none
    consumes: none
    proof: A box running past the token's 8 hours still clones and pushes, renewed once at the proxy, with no new sign-in.
    depends on:
    - blocked by T19

    T25 — Read client-owned store rules and refuse rules in the deny set

    slice: S3
    kind: task
    covers: BEP-034, BEP-035, BEP-036, BEP-037
    size: L
    est: 350 code, 300 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: a refused rule prints "The secret-store rule in names , which a secret can never be sent to. Remove or change the rule." on stderr when the configuration is read, pinned by store_rule_in_deny_set_is_refused
    observability: one debug log line per configuration read naming each rule refused and why
    layers:
    - config:
    - crates/minimal/src/config.rs: reads [secret-store-rules] from the client configuration
    - crates/boxes/src/core/bep.rs: the BEP-034, BEP-035 and BEP-036 refusals
    - crates/mfile/src/lib.rs: ignores a project's [secret-store-rules] with a warning
    - crates/boxes/src/client/config.rs: Config gains the secret-store-rules field, so a client file that holds rules still parses under deny_unknown_fields on every read, the attach path included (arch C1 on #2514)
    test: cargo nextest run -p boxes store_rule_in_deny_set_is_refused
    test: cargo nextest run -p boxes store_rule_naming_sign_in_item_is_refused
    test: cargo nextest run -p boxes ask_rule_without_tty_denies
    test: cargo nextest run -p boxes prop_store_upstream_outside_egress_is_exit_3
    test: cargo nextest run -p boxes project_store_rules_are_ignored_with_warning
    produces: none
    consumes: none
    proof: A store rule aimed at a GitHub host or the Host header is refused by name when the configuration is read.
    depends on:
    - blocked by T19

    T26 — Store, list and remove secrets with min secret under proxy-only access

    slice: S3
    kind: task
    covers: BEP-050, BEP-051, BEP-052, BEP-053
    size: L
    est: 380 code, 480 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: min secret set <id> <value> is refused with "min secret set reads the value from the terminal or standard input, never from an argument or an environment flag." on stderr, pinned by secret_set_refuses_value_argument_and_env_flag; min secret list shows store, authorities, injection form, consent rule and whether the proxy's access entry is present, never a value
    observability: one info log line per set and remove naming the store and the identifier, never the value
    layers:
    - cli:
    - crates/minimal/src/cli.rs: min secret set | list | rm [--store <s>]
    - crates/minimal/src/cmd/mod.rs: dispatch and module line
    - crates/minimal/src/cmd/secret.rs: set, list, rm per BEP-050, BEP-052 and BEP-053
    test: cargo nextest run -p minimal secret_set_stores_in_keychain_and_prints_id_only
    test: cargo nextest run -p minimal secret_set_refuses_value_argument_and_env_flag
    test: cargo nextest run -p minimal secret_set_without_tty_or_stdin_fails_immediately
    test: cargo nextest run -p minimal secret_set_refuses_gatehouse_store
    test: cargo nextest run -p minimal secret_list_shows_metadata_without_values
    test: cargo nextest run -p minimal secret_rm_removes_item
    - store:
    - crates/bep/src/store.rs: Keychain store items with the BEP-051 access control
    - crates/bep/src/lib.rs: module line
    test: cargo nextest run -p minimal secret_item_acl_admits_proxy_only
    test: cargo nextest run -p minimal secret_set_refuses_when_proxy_binary_absent
    produces:
    - crates/bep/src/store.rs
    consumes: none
    proof: min secret set claude prints only the identifier, and min secret list shows its metadata and no value.
    depends on:
    - blocked by T25

    T39 — Write the store rule from min secret set, with fixed-token presets

    slice: S3
    kind: task
    covers: BEP-079
    size: L
    est: 280 code, 600 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: an unknown preset prints "There is no preset named . Known presets: anthropic, anthropic-api-key, cloudflare, linear, sentry." on stderr, pinned by secret_set_unknown_preset_lists_known_ones
    observability: one info log line per rule written naming the identifier and upstream, never the value
    layers:
    - cli:
    - crates/minimal/src/cli.rs: --upstream, --header, --prefix, --basic-auth, --preset and --yes on min secret set
    - crates/minimal/src/cmd/secret.rs: presets, the rule check before the value is read, the change prompt, the partial-failure restore
    - crates/minimal/src/config.rs: writes one [secret-store-rules] rule to the user-level config.toml
    test: cargo nextest run -p minimal secret_set_writes_user_level_store_rule
    test: cargo nextest run -p minimal secret_set_writes_no_project_or_org_config
    test: cargo nextest run -p minimal secret_set_presets_resolve_to_their_rules
    test: cargo nextest run -p minimal secret_set_unknown_preset_lists_known_ones
    test: cargo nextest run -p minimal secret_set_refuses_deny_set_rule_before_reading_value
    test: cargo nextest run -p minimal secret_set_shows_rule_change_and_asks
    test: cargo nextest run -p minimal secret_set_rule_change_without_tty_needs_yes
    test: cargo nextest run -p minimal secret_set_yes_replaces_rule_without_asking
    test: cargo nextest run -p minimal secret_set_partial_failure_restores_both
    test: cargo nextest run -p minimal secret_set_without_rule_flags_leaves_rule_unchanged
    produces: none
    consumes: none
    proof: min secret set claude --preset anthropic stores the key and writes its rule to the user-level configuration.
    depends on:
    - blocked by T25
    - blocked by T26

    T27 — Mint store handles and keep the client's key registered across proxy restarts

    slice: S3
    kind: task
    covers: BEP-063
    size: M
    est: 250 code, 240 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: min box spec lists each store reference with its authorities; a refused registration prints "The proxy did not accept this client's signing key, so the box was not created." on stderr, pinned by store_handle_signed_and_key_registered
    observability: one info log line per key registration with its fingerprint, and one at start with the count restored
    layers:
    - mint:
    - crates/minimal/src/mint.rs: the BEP-063 store handle; a refused key replaced and registered before minting
    - crates/minimal/src/mint.rs: refuses a mint whose root_not_after is not later than now with an error naming bep --replace-key root, so an expired root never mints already-expired members (review MEDIUM 4347249409 on #2490, residual filed here)
    test: cargo nextest run -p minimal store_handle_signed_and_key_registered
    - proxy:
    - crates/bep/src/control.rs: registrations persisted across restarts; the socket owned by the operator's user at mode 0600
    test: cargo nextest run -p bep client_key_registrations_survive_restart
    test: cargo nextest run -p bep a_client_key_this_program_is_refused_is_replaced
    test: cargo nextest run -p bep control_socket_is_owner_only_and_separate_from_listener
    produces: none
    consumes:
    - crates/minimal/src/mint.rs
    - crates/bep/src/control.rs
    proof: A box created before a proxy restart keeps redeeming its store references after it.
    depends on:
    - blocked by T10
    - blocked by T14
    - blocked by T25
    - assumes OQ3

    T28 — Inject a stored key in the registered form, read from the Keychain per request

    slice: S3
    kind: task
    covers: BEP-032, BEP-033, BEP-045, BEP-054, BEP-065, BEP-066
    size: L
    est: 380 code, 420 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: min box audit <box> shows each injection's store and identifier, and a refused one marked injection_invalid or as a deleted item, never the value
    observability: one debug log line per injection naming the store, the identifier and the header, never the value
    layers:
    - inject:
    - crates/bep/src/inject.rs: emits the registered prefix and the value as the header's value, or one basic_auth field; refuses CR or LF in a value or prefix
    - crates/bep/src/store.rs: reads the value per request, writes it nowhere, refuses a deleted or disabled item
    - crates/bep/src/forward.rs: injects for an admitted store reference; refuses a sign-in reference naming another instance
    - crates/bep/src/lib.rs: module line
    test: cargo nextest run -p bep store_reference_injects_in_registered_form
    test: cargo nextest run -p bep store_value_read_per_request_never_written
    test: cargo nextest run -p bep deleted_keychain_item_refuses_next_injection
    test: cargo nextest run -p bep reference_to_another_sign_in_instance_is_refused
    test: cargo nextest run -p bep replaced_secret_is_injected_on_next_request
    test: cargo nextest run -p bep injected_value_is_exactly_prefix_plus_value
    test: cargo nextest run -p bep crlf_in_value_or_prefix_refuses_injection
    produces: none
    consumes:
    - crates/bep/src/store.rs
    proof: A request to the rule's upstream carries the stored key in the registered header, and a replaced key is sent next time.
    depends on:
    - blocked by T26
    - blocked by T27
    - blocked by SP1

    T29 — Keychain secrets by reference, end to end

    slice: S3
    kind: integration
    covers: none
    size: M
    est: 80 code, 120 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: min box audit <box> shows each injection of the run by store and identifier
    observability: one connection span per injected request, with the store and identifier, never the value
    layers:
    - e2e:
    - scripts/session-e2e.sh: one case: set a secret, add a rule, reference it in a box spec, see the upstream receive it, and find it nowhere in the box
    test: ./scripts/session-e2e.sh bep_store_reference_end_to_end
    produces: none
    consumes: none
    proof: Store an API key with min secret, reference it in a box spec, and the box's requests to its upstream carry it while the key never enters the box.
    depends on:
    - blocked by T25
    - blocked by T26
    - blocked by T27
    - blocked by T28
    - blocked by T39

    T30 — Steer credentialed names to the proxy and fail closed while it is down

    slice: S4
    kind: task
    covers: BEP-062, BEP-078
    size: M
    est: 280 code, 120 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: the bundle's daemon log tail carries each SERVFAIL answered for a steered name while the proxy was down, with the name and the box
    observability: one rate-limited warn log line per steered name answered SERVFAIL, and one info line when the proxy's listener comes back
    layers:
    - dns:
    - crates/minimald/src/net/dns_gate.rs: steered names answer the proxy's address; SERVFAIL while it is down; NODATA for AAAA, HTTPS, SVCB
    - crates/minimald/src/net/dns.rs: carries the proxy's listener state to the gate
    test: cargo nextest run -p minimald steered_name_servfail_while_proxy_down
    test: cargo nextest run -p minimald steered_name_aaaa_https_svcb_are_nodata
    produces: none
    consumes: none
    proof: With the proxy stopped, a dns-steered box resolving github.com gets SERVFAIL, never a real address.
    depends on:
    - blocked by T19

    T31 — Reject UDP to port 443 where a box's QUIC stance is blocked

    slice: S4
    kind: task
    covers: BEP-018
    size: L
    est: 280 code, 300 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: the bundle's daemon log tail carries each rejected UDP 443 datagram's box and destination, rate-limited
    observability: one rate-limited debug log line per rejected datagram with the box and the destination
    layers:
    - net:
    - crates/minimald/src/net/switch.rs: rejects UDP to 443 with ICMP port unreachable for a blocked box, and to the steered proxy address always
    - crates/minimald/src/net/classifier.rs: blocks the host_ip cohort when any resident member is blocked
    test: cargo nextest run -p minimald quic443_blocked_rejects_udp_443
    test: cargo nextest run -p minimald udp_443_to_steered_proxy_rejected_in_every_stance
    test: cargo nextest run -p minimald host_ip_cohort_quic_stance_most_restrictive_wins
    - spec:
    - crates/boxes/src/core/bep.rs: the property test over the stance resolution
    test: cargo nextest run -p boxes prop_quic443_auto_resolves_on_grants_and_steering
    - policy:
    - crates/boxes/src/lib.rs: CredentialedUpstream carries the declared quic443 stance to the daemon (arch C1 on #2513)
    - crates/minimal/src/cmd/boxes/create.rs: credentialed_upstream_for fills it from [session.network] quic443
    - crates/minimald/src/net/gvproxy_network.rs: builds the gate with the declared stance, so block and allow override auto (spec §5.3 line 150)
    test: cargo nextest run -p minimald declared_quic443_block_rejects_without_test_hook
    produces: none
    consumes: none
    proof: A dns-steered grant box's QUIC to port 443 gets an immediate port-unreachable.
    depends on:
    - blocked by T19

    T32 — DNS steering fails closed, end to end

    slice: S4
    kind: integration
    covers: none
    size: M
    est: 80 code, 120 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: the bundle's daemon log tail carries the SERVFAIL answers and the rejected datagrams of the run
    observability: the warn and debug lines both tasks emit, in one run
    layers:
    - e2e:
    - scripts/session-e2e.sh: one case: stop the proxy, resolve a steered name, query its HTTPS record, send QUIC to 443
    test: ./scripts/session-e2e.sh bep_dns_steering_fails_closed_end_to_end
    produces: none
    consumes: none
    proof: A steered name answers SERVFAIL while the proxy is down, AAAA, HTTPS and SVCB answer NODATA, and UDP 443 from a blocked box is rejected at once.
    depends on:
    - blocked by T30
    - blocked by T31

    T33 — Rotate audit segments with the chain carried across and revocations restored

    slice: S5
    kind: task
    covers: BEP-068
    size: M
    est: 250 code, 240 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: min box audit <box> reads across every retained segment, so a box's trail is whole after rotation
    observability: one info log line per rotation with the closed segment and its final hash, and one at start with the revocations restored
    layers:
    - audit:
    - crates/bep/src/audit.rs: segments rotated with the chain carried, revocation segments retained (BEP-068)
    - crates/bep/src/main.rs: bep serve passes the restored revocations to BoxRedeemer::with_revocations and drops the revocation interim from the startup warning (T41)
    - crates/bep/src/revoke.rs: restores every revocation from every retained segment at start
    test: cargo nextest run -p bep segment_rotation_continues_chain
    test: cargo nextest run -p bep revocations_restored_from_every_segment_on_start
    test: cargo nextest run -p bep segment_with_live_revocation_is_retained
    - cli:
    - crates/minimal/src/cmd/box_audit.rs: reads every retained segment
    test: cargo nextest run -p minimal box_audit_reads_across_segments
    produces: none
    consumes:
    - crates/bep/src/revoke.rs
    proof: After rotation, min box audit still shows a box's whole trail, and a restart still refuses a destroyed box.
    depends on:
    - blocked by T19
    - blocked by T20

    T34 — Report an edited audit chain in min doctor

    slice: S5
    kind: task
    covers: BEP-049
    size: M
    est: 220 code, 180 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: the chain check prints "Audit log: the hash chain breaks at record of ; a record was changed or removed." or "Audit log: chain verified." on stdout, pinned by prop_audit_chain_detects_unrecomputed_edit
    observability: one info log line per check naming the segments verified and the outcome
    layers:
    - verify:
    - crates/bep/src/audit.rs: chaining and verification as pure functions over bytes, apart from the writer
    - scripts/kani.sh: the added harness's count
    test: cargo nextest run -p bep prop_audit_chain_detects_unrecomputed_edit
    test (deferred: kani): cargo nextest run -p bep kani_audit_chain_detects_edit
    - cli:
    - crates/minimal/src/cli.rs: min doctor
    - crates/minimal/src/cmd/mod.rs: dispatch and module line
    - crates/minimal/src/cmd/doctor.rs: runs the audit chain check across every segment
    test: cargo nextest run -p minimal doctor_reports_a_broken_audit_chain
    produces:
    - crates/minimal/src/cmd/doctor.rs (entry point)
    consumes: none
    proof: Editing one audit record makes min doctor name where the chain breaks.
    depends on:
    - blocked by T33

    T35 — Audit segments rotate and the chain is checked, end to end

    slice: S5
    kind: integration
    covers: none
    size: M
    est: 120 code, 180 tests
    executor: agent
    difficulty: routine
    model: standard
    diagnostics: the case's last step prints the chain verified across the rotated segments
    observability: the rotation log lines of the run, and one info line at bep serve start naming the segment bound when the test override sets it
    layers:
    - bound:
    - crates/bep/src/main.rs: open_audit_log takes the segment bound from BEP_AUDIT_SEGMENT_BYTES when it is set to a positive integer, else audit::SEGMENT_BYTES; the variable is a test surface, read only by bep serve, which minvmd spawns with its own environment (widened 2026-10-31, owner, #2295 noop: no runtime knob)
    test: cargo nextest run -p bep audit_segment_bound_follows_the_test_override
    - e2e:
    - scripts/session-e2e.sh: one case: set BEP_AUDIT_SEGMENT_BYTES for the restarted host daemon, drive requests past the bound, read the trail, edit a record, run min doctor
    test: ./scripts/session-e2e.sh bep_audit_rotation_and_doctor_end_to_end
    produces: none
    consumes: none
    proof: The audit log rotates with its chain carried across, min box audit reads every segment, and min doctor reports an edited chain.
    depends on:
    - blocked by T33
    - blocked by T34

    T36 — Rotate the signing certificate under an unchanged root

    slice: S6
    kind: task
    covers: BEP-061
    size: S
    est: 120 code, 60 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: bep --rotate-signing prints "Signing certificate rotated. Running boxes keep working; nothing inside them changes." on stdout, pinned by signing_ca_rotation_invisible_to_boxes
    observability: one info log line per rotation naming the old and new signing fingerprints
    layers:
    - ca:
    - crates/bep/src/ca.rs: issues a new signing CA under the same root and key identifier
    - crates/bep/src/main.rs: bep --rotate-signing
    - crates/bep/src/main.rs: bep serve renews its signing CA through Ca::renew_signing before signing_expiry(), so a long-running proxy keeps signing leaves past the 365-day signing validity (arch C2 on #2493, residual filed here)
    test: cargo nextest run -p bep signing_ca_rotation_invisible_to_boxes
    produces: none
    consumes: none
    proof: Rotate the signing certificate under the same root, and every running box keeps cloning and pushing unchanged.
    depends on:
    - blocked by T19

    T37 — Attribute a host-address redemption to its cohort and mark the audit

    slice: S7
    kind: task
    covers: BEP-028
    size: S
    est: 100 code, 120 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: min box audit <box> shows a cohort redemption marked cohort_attributed
    observability: the connection span gains the cohort flag
    layers:
    - decide:
    - crates/bep/src/redeem.rs: admits a host-address sender presenting a value naming a live host-address box, marked cohort_attributed
    test: cargo nextest run -p bep host_ip_redemption_is_cohort_attributed
    - audit:
    - crates/bep/src/redeemer.rs: carries Decision::marker() through Verdict::Admit, so a cohort admit reaches the record with its marker (arch C1 on #2511)
    - crates/bep/src/forward.rs: writes the admitted audit record with that marker and records cohort_attributed on the box_connection span (arch C1 on #2511)
    - crates/bep/src/proxy.rs: declares the cohort_attributed field on the box_connection span that forward.rs records
    test: cargo nextest run -p bep host_ip_redemption_is_cohort_attributed_in_the_audit_record
    produces: none
    consumes: none
    proof: A host-address box redeems a value naming another live host-address box, and its audit record is marked cohort-attributed.
    depends on:
    - blocked by T19

    T57 — Design the provider directory and socket ownership for a split-user VMM

    slice: S8
    kind: enablement
    covers: none
    size: S
    est: 20 code, 60 tests
    executor: agent
    difficulty: unknown ground
    model: strongest
    diagnostics: the design document names, for each socket and file in the provider directory, the user that creates it, the mode it gets and the user that opens it
    observability: none; this task ships a design document and no code
    layers:
    - design:
    - crates/minvmd/src/sock.rs: a test reads the design document and checks that every socket and file prepare_socket_dir and the VMM child create is named with an owner, a mode and an opening user
    - docs/design/2026-10-25-split-user-vmm-provider-dir.md: with gvproxy and the VMM running as a dedicated unprivileged user, how the marker, report, telemetry, gate and ssh bridge sockets, the data volume and the boot log in the provider directory stay reachable by min and the proxy as the login user, given that sock::prepare_socket_dir makes that directory 0700 owned by the caller and each socket 0600; the hypothesis is a group shared by the two users, a 0770 directory and 0660 sockets, with the uid change handed over by the sudoers drop-in min finalize-install installs; the document reads crates/minvmd/src/sock.rs, vm.rs, image.rs, volume.rs, cmd/vmm_child.rs and crates/bep/src/listener.rs, lists every caller that opens each path, and ends with the ownership and permission model that arch ruled on, or with the fallback that only gvproxy runs as the dedicated user
    test: cargo nextest run -p minvmd split_user_provider_dir_design_names_every_socket_owner
    produces: none
    consumes: none
    proof: The design document names an owner, a mode and the opening user for every socket and file in the provider directory, and arch has ruled on it, so T38 can be cut against it.
    depends on:
    - blocked by T19

    T65 — Lay out the split users, directories and socket modes for a split-user VMM

    slice: S8
    kind: integration
    covers: none
    size: L
    est: 380 code, 320 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: min finalize-install names the vm-users item and why it cannot set up the split users (a user-writable binary path, or a home directory the dedicated users cannot search)
    observability: one debug log line when minvmd gives a socket 0600 because it does not hold the socket's group
    layers:
    - install:
    - crates/minimal/src/cmd/finalize_install.rs: the vm-users script creates the minimal-vmm and minimal-gvproxy users and groups, the provider dir at 0710 group minimal-vmm, vmm/, <gvproxy dir>, <gvproxy config dir> and <gate dir> as docs/design/2026-10-25-split-user-vmm-provider-dir.md lays them out, and renders the sudoers drop-in; the item is not offered until T66 spawns through sudo -n (narrowed 2026-10-25 by the epic owner to the foundation #2561 delivered; arch C3 on #2561)
    test: cargo nextest run -p minimal finalize_install_creates_the_split_users_dirs_and_drop_in
    - sockets:
    - crates/minvmd/src/sock.rs: prepare_socket_dir, restrict_owned_dir and verify_provider_dir_ownership accept 0710 with group minimal-vmm; enforce_socket_permissions and verify_socket_permissions take the mode and the group from the design's table
    - crates/minvmd/src/cmd/vmm_child.rs: creates the boot log with mode 0640
    - crates/minvmd/src/volume.rs: create_sparse_raw creates the data volume with mode 0600
    - crates/minvmd/src/control.rs: the guest door admits the minimal-vmm uid and the supervisor's own uid; the host door keeps its check
    - crates/minvmd/src/guest_telemetry.rs: accept_loop admits the minimal-vmm uid and the supervisor's own uid
    test: cargo nextest run -p minvmd split_user_provider_dir_modes_are_enforced
    produces: none
    consumes: none
    proof: The proxy runs as the login user, and the EGW process, gvproxy and the VM run as separate processes sharing no writable state, as dedicated unprivileged users where the vm-users drop-in is installed. Moved 2026-10-31: the spawn through sudo -n (#2566) and the EGW process (#2285) go to gominimal/inbox#1184 with BEP-047; this slice keeps the design and the users it laid out.
    depends on:
    - blocked by T57

    T50 — Ship a git credential helper in grant boxes so git sends a header the proxy substitutes

    slice: S9
    kind: task
    covers: BEP-015
    size: M
    est: 160 code, 180 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: git credential fill inside a grant box answers for the grant's hosts with the placeholder, and min box spec lists the helper under the grant
    observability: one debug log line per helper answer naming the host, never the value
    layers:
    - spec:
    - docs/specs/24-spec-box-egress-proxy/24-spec-box-egress-proxy.md: adds the requirement for the git credential helper a grant box carries (Gatehouse §6.4 line 718), under BEP-015
    - box:
    - crates/minimald/src/box_actor.rs: declares the new module
    - crates/minimald/src/box_runtime/launcher.rs: the connection env layer carries GIT_CONFIG_COUNT, GIT_CONFIG_KEY_<n> and GIT_CONFIG_VALUE_<n> for a box that holds sealed deliveries, so the shipped helper is git's sole helper for each grant host with store and cache disabled, at a scope git reads after every config file (widened 2026-10-25 per arch C1 on #2547)
    - crates/minimald/Cargo.toml: depends on bep, so the helper reads the host set from bep::module::GITHUB instead of a copy (widened 2026-10-25 per arch C2 on #2547)
    - Cargo.lock: regenerated by the new bep dependency of minimald (widened 2026-10-25)
    - crates/minimald/src/box_actor/tests.rs: the helper's tests
    - crates/minimald/src/box_actor/bep_git_helper.rs: writes the helper and its git config credential.helper line into a grant box at creation, for the grant's hosts only, with the sealed value as the placeholder the proxy substitutes
    test: cargo nextest run -p minimald grant_box_git_credential_helper_answers_for_grant_hosts
    - e2e:
    - scripts/session-e2e.sh: a case that clones and pushes from a grant box with the shipped helper and no helper of its own; records NOT RUN off macOS, without a sign-in or without E2E_BEP_PRIVATE_REPO (#2500)
    test: ./scripts/session-e2e.sh bep_git_credential_helper_sends_header
    produces: none
    consumes:
    - crates/bep/src/forward.rs
    proof: From a grant box with no git configuration of its own, git clone and git push send an Authorization header the proxy substitutes.
    depends on:
    - blocked by T11
    - blocked by T18

    T51 — Rotate a running proxy's signing certificate through the control socket

    slice: S9
    kind: task
    covers: BEP-061
    size: M
    est: 220 code, 140 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: the command prints one line naming the old and new signing fingerprints, and the proxy's info log line records the same rotation
    observability: the info log line T36 added, for operator-driven and scheduled rotations alike
    layers:
    - control:
    - crates/bep/src/control.rs: accepts a rotate-signing request from the host and runs Ca::renew_signing on the serving proxy's CA under the same root and key identifier
    - crates/bep/src/main.rs: bep rotate-signing sends the request over the control socket and prints the result
    test: cargo nextest run -p bep rotate_signing_over_control_socket_keeps_leaves_valid
    produces: none
    consumes: none
    proof: An operator rotates the serving proxy's signing certificate now, and a leaf issued before and one issued after both validate against the root a box holds.
    depends on:
    - blocked by T36

    T52 — A signing rotation under a running grant box keeps clone and push working, end to end

    slice: S9
    kind: task
    covers: BEP-061
    size: M
    est: 40 code, 200 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: the e2e case prints the root fingerprint from min box spec before and after the rotation, and they match
    observability: none beyond the case's own output
    layers:
    - e2e:
    - scripts/session-e2e.sh: starts a grant box, clones and pushes, rotates through bep rotate-signing, clones and pushes again from the same box; records NOT RUN off macOS, without a sign-in or without E2E_BEP_PRIVATE_REPO, as bep_first_slice_end_to_end does
    test: ./scripts/session-e2e.sh bep_signing_rotation_keeps_running_box_working
    produces: none
    consumes: none
    proof: A running grant box clones and pushes before and after a signing rotation without being re-created, and its root fingerprint is unchanged.
    depends on:
    - blocked by T51

    T53 — Enforce the host_ip cohort's blocked QUIC stance in the host-address ruleset

    slice: S9
    kind: task
    covers: BEP-018
    size: M
    est: 200 code, 160 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: the bundle's daemon log tail carries one line per cohort stance change naming the box that flipped it
    observability: one info log line when the cohort flag turns on or off, with the blocking members
    layers:
    - ruleset:
    - crates/minimald/src/net/classifier.rs: CohortQuic443 learns the path of the cohort's blocked leaf and the daemon's move of a host_ip box's processes into and out of it (a cgroup.procs write inside the delegated slice, which needs no CAP_NET_ADMIN and never rewrites the table); its doc stops saying the type is unenforced
    - scripts/install-host-classifier.sh: render_ruleset gains one rule keyed on a new delegated leaf under the boxes directory, the cohort's blocked leaf: socket cgroupv2 level <n> "<blocked leaf>" udp dport 443 reject with icmpx port-unreachable; the install step creates that leaf with the rest of the tree, so the privileged step ships the rule and the daemon flips it by membership (widened 2026-10-25: classifier.rs only embeds this script with include_str!, so the table is rendered here)
    - scripts/install_test.sh: the install harness models the blocked leaf boxes/quic443-blocked beside deny and allow (the expected delegation list and both drop_cgroup_files loops before --uninstall), so the un-enrolled and installed trees uninstall again (widened 2026-10-25: common::shell_harnesses all_script_test_harnesses_pass runs this harness on the native lane)
    test: cargo nextest run -p minimald host_ip_cohort_blocked_stance_rejects_udp_443
    - lifecycle:
    - crates/minimald/src/box_actor/host.rs: the host_ip launch path calls CohortQuic443::join with the box's resolved stance and moves every resident host_ip box into the blocked leaf when blocked() turns true; the stop path calls leave and moves them back to their own leaves when the last blocking member goes
    - crates/minimald/src/box_runtime.rs: command_in_session hands the shim the cohort's blocked leaf instead of the box's own leaf while the cohort is blocked, and records the shim pid under the box id so the injected process goes home when the block lifts (widened 2026-10-25 per arch C1 on #2553: min box exec and injected hooks must not escape the stance)
    - crates/minimald/src/box_runtime/tests.rs: the exec-placement tests live here, where box_runtime.rs keeps its tests (widened 2026-10-25)
    test: cargo nextest run -p minimald host_ip_cohort_join_block_leave_drives_the_rule
    produces: none
    consumes: none
    proof: A host_ip cohort with one blocked member sends no UDP 443 and gets ICMP port unreachable; when that member stops, the cohort sends UDP 443 again.
    depends on:
    - blocked by T31

    T54 — Call the store-rule checks from box creation and expansion

    slice: S9
    kind: task
    covers: BEP-034, BEP-035, BEP-036
    size: M
    est: 240 code, 220 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: a refused store reference exits 3 with the rule and the reason on stderr; an ask rule without a terminal prints the denial
    observability: one info log line per creation naming the store references checked and their outcome
    layers:
    - create:
    - crates/minimal/src/cmd/boxes/create.rs: reads the rules through read_client_config_and_store_rules, calls check_store_references for every host-store reference the spec declares and store_consent for ask rules, and passes the accepted rules to the proxy registration
    - crates/minimal/src/config.rs: read_client_config no longer drops the rules on the creation path
    test: cargo nextest run -p minimal store_reference_without_rule_is_refused_at_creation
    test: cargo nextest run -p minimal store_rule_outside_egress_exits_3
    test: cargo nextest run -p minimal ask_rule_without_a_terminal_denies
    produces: none
    consumes:
    - crates/boxes/src/core/bep.rs
    proof: A box spec that references a host-store secret is refused at creation when no rule matches or the rule's upstream is outside the box's egress, and an ask rule prompts on a terminal and denies without one.
    depends on:
    - blocked by T13
    - blocked by T25

    T55 — Replace the provisional CA deny set with the decided infrastructure deny set

    slice: S9
    kind: task
    covers: BEP-013
    size: S
    est: 60 code, 120 tests
    executor: agent
    difficulty: routine
    model: standard
    diagnostics: min box spec shows the root's excluded subtrees, and a refused rule names the deny-set entry it hit
    observability: none beyond the refusal text
    layers:
    - ca:
    - crates/bep/src/ca.rs: DENY_SET becomes the decided set from one shared definition: min.internal, minimal.dev, the tenant issuer, the BEP, EGW and relay endpoints, the registry and build cache, and the node-plane baseline set; localhost, .local, home.arpa and .internal stay allowed (owner's ruling of 2026-10-24 on #2441)
    - crates/boxes/src/core/bep.rs: the BEP-034 check reads the same definition
    - docs/specs/24-spec-box-egress-proxy/24-spec-box-egress-proxy.md: names the set
    test: cargo nextest run -p bep root_excludes_every_deny_set_name
    test: cargo nextest run -p boxes every_deny_set_name_is_refused_as_a_store_rule
    produces: none
    consumes: none
    proof: The root certificate excludes every name of the decided deny set, and a store rule naming any of them is refused.
    depends on:
    - blocked by T3
    - blocked by T50

    T58 — Steer a dns or both box's credentialed names through the relay's gate in production

    slice: S9
    kind: task
    covers: BEP-062, BEP-078
    size: L
    est: 260 code, 300 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: with the proxy stopped, a dns-steered box resolving github.com gets SERVFAIL, and the daemon log names the box and the name once per minute
    observability: one info line when the proxy's listener is reported live and the steered names resolve to it again
    layers:
    - host:
    - crates/minimald-rpc/src/lib.rs: a BoxControlRequest::ProxyListening ask and its BoxControlReply::ProxyListening { listening } answer, admitted on the guest reports door beside RowStanding (re-planned 2026-10-25 by the epic owner: the listener state lives in minvmd on the host and the guest relay cannot link bep_host, so it asks over the report door)
    - crates/minvmd/src/control.rs: the guest reports door answers ProxyListening from the proxy's listening flag, and answers false when no proxy runs
    - crates/minvmd/src/cmd/run.rs: hands BepProxy::listening to the control server so the guest door can read it
    test: cargo nextest run -p minvmd guest_door_answers_proxy_listening
    - relay:
    - crates/minimald/src/net/listeners.rs: proxy_listening asks over the report door the way host_row_standing does; None (a native host, which runs no proxy, or a door that does not answer within its deadline) reads as not live, so steered names fail closed with SERVFAIL
    - crates/minimald/src/net/dns.rs: ProxyListenerState refreshes from proxy_listening with a short cached answer; the allow(dead_code) interim goes
    - crates/minimald/src/net/dns_gate.rs: the allow(dead_code) interims on with_steering go, and the comment stops naming #2523
    - crates/minimald/src/net/switch.rs: BoxGate::for_session calls DnsGate::with_steering(policy, <credential module hosts>, <proxy address on the switch>, <shared ProxyListenerState>) for every box whose steering resolves to dns or both
    - crates/minimald/src/net/gvproxy_network.rs: passes the shared ProxyListenerState and the control channel it asks over into BoxGate::for_session
    - scripts/session-e2e.sh: in bep_dns_steering_fails_closed_end_to_end, leg 1's known_gap for the steered A lookup becomes an ::error:: line and ds_fail, and ds_a_gap with its known-gap branch goes, so the leg is a hard assertion of Networking §5.3 line 147 once steering is wired (arch C1 on #2550, widened 2026-10-25)
    test: cargo nextest run -p minimald a_dns_steered_box_gets_servfail_while_the_proxy_is_down
    test: cargo nextest run -p minimald a_steered_name_resolves_to_the_proxy_once_its_listener_is_live
    test: cargo nextest run -p minimald a_native_host_steered_name_gets_servfail
    produces: none
    consumes: none
    proof: In a running daemon with the proxy stopped, a dns-steered box resolving github.com gets SERVFAIL and never a real address; once the listener is live the same lookup gets the proxy's address; on a native host, which runs no proxy, a steered name always gets SERVFAIL.
    depends on:
    - blocked by T30

    T59 — Sign store handles under the client's one local key from a host-keychain ClientKeyStore

    slice: S9
    kind: task
    covers: BEP-063
    size: L
    est: 260 code, 240 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: min box spec names the client key's identifier under the grant, and the proxy log records one registration per client, not one per box
    observability: one info line at first use naming the registered key identifier
    layers:
    - key-store:
    - crates/minimal/src/mint.rs: a host-keychain ClientKeyStore backend behind the existing key-store seam holds the client's one local signing key (macOS Keychain; the Linux host behind the same seam), and the Interim: notes and expect(dead_code) reasons that name #2524 go
    - crates/minimal/src/cmd/boxes/create.rs: mint_grants reads local_client_key over that store and signs the store handle and the grant registrations with it, never ClientKey::generate() per box
    - crates/bep/src/control.rs: the module doc stops naming #2524; the registry keeps one key per client
    test: cargo nextest run -p minimal two_creations_register_one_client_key
    test: cargo nextest run -p minimal a_refused_client_key_is_replaced_and_registered_before_the_mint
    produces: none
    consumes: none
    proof: Two box creations register one client key with the proxy, and a key the proxy refuses is replaced and registered before the mint.
    depends on:
    - blocked by T27
    - assumes OQ3

    T60 — Carry a store handle sealed as a store member kind

    slice: S9
    kind: task
    covers: BEP-063
    size: M
    est: 140 code, 160 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: min box audit <box> shows a redemption of a store member with its store reference, and a store member whose handle fails verification as a refusal
    observability: one debug line per store member redeemed naming the store reference, never the value
    layers:
    - seal:
    - crates/bep/src/seal.rs: MemberKind gains Store (serde store), so a store handle travels sealed as {kind: "store", jws}
    - crates/bep/src/redeem.rs: a store member's handle is verified against the registered client key and resolved to the store reference it names
    test: cargo nextest run -p bep a_store_member_round_trips_sealed
    test: cargo nextest run -p bep a_store_handle_with_an_unregistered_key_is_refused
    produces: none
    consumes: none
    proof: A store handle sealed as a store member round-trips through the envelope, redeems against the registered client key, and is refused under an unregistered one.
    depends on:
    - blocked by T27
    - assumes OQ3

    T61 — List each store reference with its authorities in min box spec

    slice: S9
    kind: task
    covers: BEP-063
    size: S
    est: 60 code, 80 tests
    executor: agent
    difficulty: routine
    model: standard
    diagnostics: min box spec <box> lists each store reference the box declares with the authorities its rule registers
    observability: none beyond the printed listing
    layers:
    - spec:
    - crates/minimal/src/cmd/box_spec.rs: prints each store reference under the grant with the authorities the matching store rule registers
    test: cargo nextest run -p minimal box_spec_lists_store_references_with_their_authorities
    produces: none
    consumes: none
    proof: min box spec on a box with one store reference prints the reference and the authorities its rule registers.
    depends on:
    - blocked by T27
    - assumes OQ3

    T62 — Give the proxy-binary resolver one owner shared by min secret and minvmd

    slice: S9
    kind: task
    covers: BEP-051
    size: S
    est: 80 code, 100 tests
    executor: agent
    difficulty: routine
    model: standard
    diagnostics: min secret set and minvmd run print the same proxy path in their logs for one PATH and one MINVMD_BEP_BIN
    observability: one debug line naming the resolved proxy path at each resolution
    layers:
    - resolver:
    - crates/bep/src/store.rs: proxy_binary_path is the one rule, and its doc stops naming #2527
    - crates/minvmd/src/cmd/run.rs: resolve_bep_binary calls bep::store::proxy_binary_path instead of its own copy
    - crates/minimal/src/cmd/secret.rs: resolve_proxy uses the shared rule and its doc stops naming #2527
    test: cargo nextest run -p minvmd minvmd_and_min_secret_resolve_the_same_proxy_binary
    produces: none
    consumes: none
    proof: Under one PATH and one MINVMD_BEP_BIN, min secret set and minvmd run resolve the same proxy binary, and the rule has one definition.
    depends on:
    - blocked by T26

    T63 — Revoke or replace a registered client key at the proxy and bound the registry

    slice: S9
    kind: task
    covers: BEP-063
    size: M
    est: 180 code, 200 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: the proxy log records each key removal with its identifier, and a store handle under a revoked key is refused with a reason naming the key
    observability: one info line per removal; one warn line when the bound drops a key
    layers:
    - control:
    - crates/bep/src/control.rs: a revoke request from the socket's owner removes a key from the registry and the key file; the registry drops keys past a configured count or idle period
    - crates/minimal/src/mint.rs: local_client_key revokes the key it replaces after a refusal
    test: cargo nextest run -p bep a_revoked_client_key_no_longer_verifies_a_store_handle
    test: cargo nextest run -p bep a_key_removal_survives_a_restart
    test: cargo nextest run -p minimal a_replaced_client_key_is_revoked_at_the_proxy
    produces: none
    consumes: none
    proof: A store handle signed under a key the client revoked or replaced is refused by the proxy, after a restart included, and the registry never holds more keys than its bound.
    depends on:
    - blocked by T27
    - blocked by T59
    - assumes OQ3

    T64 — Leave the host_ip QUIC cohort when a box's shell exits on its own

    slice: S9
    kind: task
    covers: BEP-018
    size: S
    est: 40 code, 80 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: the daemon log tail carries one line when a box whose shell exited on its own leaves the cohort, naming the box and the members still blocking
    observability: one info log line per departure through the exit path, with the blocking members left
    layers:
    - lifecycle:
    - crates/minimald/src/box_actor/host.rs: the path that observes the host process's exit calls leave_quic443_cohort the way stop_running does, so a box whose shell exits on its own stops blocking the cohort and the remaining members move home when the last blocker goes
    test: cargo nextest run -p minimald host_ip_cohort_shell_exit_leaves_and_lifts_the_block
    produces: none
    consumes: none
    proof: A host_ip cohort whose only blocked member's shell exits on its own sends UDP 443 again without a Stop or destroy.
    depends on:
    - blocked by T53

    T56 — Conditions closed, end to end

    slice: S9
    kind: integration
    covers: BEP-015, BEP-061, BEP-018, BEP-034
    size: M
    est: 0 code, 220 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: each e2e case prints PASS or NOT RUN with its gate, and the daemon log tail names the cohort flag changes
    observability: none beyond the cases' own output
    layers:
    - e2e:
    - scripts/session-e2e.sh: one walk that runs the helper, rotation and cohort cases on a lane with a sign-in and E2E_BEP_PRIVATE_REPO, and records NOT RUN elsewhere (#2500)
    test: ./scripts/session-e2e.sh bep_conditions_closed_end_to_end
    produces: none
    consumes: none
    proof: the residuals the arch left open at merge are done: git in a grant box sends a header the proxy substitutes, an operator rotates a running proxy's signing certificate and the box keeps pushing, a blocked host_ip cohort sends no UDP 443, every store reference is checked at creation, and the CA deny set is the decided one. Its tasks re-cover requirements whose origin tasks merged with an arch condition held on a filed residual; the owner accepted that double coverage on 2026-10-24 so the residuals dispatch as one slice.
    depends on:
    - blocked by T50
    - blocked by T51
    - blocked by T52
    - blocked by T53
    - blocked by T54
    - blocked by T55
    - blocked by T58
    - blocked by T59
    - blocked by T60
    - blocked by T61
    - blocked by T62
    - blocked by T63
    - blocked by T64

    T67 — Inject store values in bep serve

    slice: S10
    kind: task
    covers: BEP-032
    size: M
    est: 120 code, 160 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: an admitted store reference that the serve path cannot read is refused as store_item_unreadable in min box audit, never forwarded without the key
    observability: one debug line per store injection naming the store and identifier, never the value
    layers:
    - redeem:
    - crates/bep/src/redeemer.rs: BoxRedeemer overrides Redeemer::redeem_reference and returns Redeemed::Store for an admitted store member (Gatehouse §6.10 line 849, redemption step 4), with the header check still before redeem runs
    - crates/bep/src/proxy.rs: the bep serve path calls Flow::with_store(Arc::new(HostStore)) where it builds each Flow
    - crates/bep/src/forward.rs: the two interim notes on redeem_reference and with_store (#2586) are removed
    test: cargo nextest run -p bep serve_injects_a_stored_key_for_an_admitted_store_member
    produces: none
    consumes: none
    proof: A box's request to a store rule's registered upstream leaves bep serve with the stored key in the registered form, and the box never holds the key.
    depends on:
    - blocked by SP1
    - blocked by T28

    T68 — Carry the handle jti into the audit record

    slice: S10
    kind: task
    covers: BEP-067
    size: M
    est: 110 code, 100 tests
    executor: agent
    difficulty: routine
    model: standard
    diagnostics: min box audit <box> shows the handle's jti on each store-handle record, empty when the handle does not parse
    observability: the audit record carries jti
    layers:
    - audit:
    - crates/bep/src/audit.rs: Decision and Record gain jti: String, empty when the handle does not parse
    - crates/bep/src/redeemer.rs: facts() carries handle::Read::jti into the verdict (Gatehouse §6.10 line 842)
    - crates/bep/src/forward.rs: Verdict::Admit and Verdict::Refuse carry the jti, and record and audit_refusal write it into the Decision (stale layers amended 2026-10-26 by the epic owner's rule)
    - crates/bep/src/control.rs: its Decision literal sets an empty jti
    - crates/bep/src/proxy.rs: its Decision literal sets an empty jti
    - crates/minimal/src/cmd/box_audit.rs: min box audit shows the jti, and the test helper's Decision sets it
    test: cargo nextest run -p bep a_store_handle_audit_record_carries_its_jti
    produces: none
    consumes: none
    proof: A refused store member (store_handle_invalid) and a redeemed one both write an audit record that carries the handle's jti.
    depends on:
    - blocked by T60

    T69 — Wire the client-key 10BC8 bound into bep serve

    slice: S10
    kind: task
    covers: BEP-063
    size: S
    est: 50 code, 60 tests
    executor: agent
    difficulty: routine
    model: standard
    diagnostics: bep serve --help names the bound flag, and a bad value is refused before the proxy starts
    observability: one info line at start with the key bound in force
    layers:
    - serve:
    - crates/bep/src/main.rs: bep serve builds its control socket through Control::bind_with_key_bound with a KeyBound from a flag; the idle period stays None until gominimal/arch#85 decides the root validity
    test: cargo nextest run -p bep serve_passes_its_key_bound_to_the_control_socket
    produces: none
    consumes: none
    proof: bep serve started with a smaller key bound keeps at most that many client keys.
    depends on:
    - assumes OQ3
    - blocked by T63

    T70 — Name the kid when a store handle is refused as unknown_key

    slice: S10
    kind: task
    covers: BEP-063
    size: S
    est: 20 code, 60 tests
    executor: agent
    difficulty: routine
    model: standard
    diagnostics: the store handle checked log line names the handle's header kid when the handle is refused as unknown_key
    observability: the existing debug line gains the kid
    layers:
    - handle:
    - crates/bep/src/handle.rs: read() puts the header kid in the store handle checked line for an unknown_key refusal; the store_handle_invalid audit marker and jti stay as they are
    test: cargo nextest run -p bep an_unknown_key_refusal_names_the_kid
    produces: none
    consumes: none
    proof: A store handle signed under a key the proxy does not hold is refused, and the log line names that key's kid.
    depends on:
    - assumes OQ3
    - blocked by T63

    T71 — Amend BEP-063 for client-key revocation, replacement and the registry bound

    slice: S10
    kind: task
    covers: BEP-063
    size: S
    est: 0 code, 60 tests
    executor: agent
    difficulty: routine
    model: standard
    diagnostics: none, a spec change
    observability: none: a spec change writes no log line
    layers:
    - spec:
    - docs/specs/24-spec-box-egress-proxy/24-spec-box-egress-proxy.md: the BEP-063 sub-requirements and the [NEEDS CLARIFICATION] entry that #2578 lists (revocation by kid, replacement in three steps with the pending-revoke retry, the registry bound, the verify-line move, the rationale at line 475, gominimal/arch#144)
    test: cargo nextest run -p minimal a_replaced_client_key_is_revoked_at_the_proxy
    produces: none
    consumes: none
    proof: Spec 24 states client-key revocation, replacement and the registry bound, each with a verify line that names an existing test.
    depends on:
    - assumes OQ3
    - blocked by T63

    T72 — Reject UDP 443 from a blocked cohort's own leaves, sockets opened before the flip included

    slice: S10
    kind: task
    covers: BEP-018
    size: M
    est: 220 code, 200 tests
    executor: agent
    difficulty: unknown ground
    model: strongest
    diagnostics: the daemon log names each leaf added to or removed from the blocked set, and min finalize-install names the helper item and why it cannot install it
    observability: one info line per set change with the leaf and the cohort's blocking members
    layers:
    - ruleset:
    - scripts/install-host-classifier.sh: render_ruleset declares a set of cgroup v2 leaves (typeof socket cgroupv2 level N) and the UDP 443 reject rule also matches a socket whose leaf is in it, so a socket created in a member's own leaf before the flip is rejected from its next datagram; the install step adds a root-owned helper that only adds or removes one leaf under the delegated boxes slice in that set, refusing any other path, and a sudoers drop-in that lets the login run only that helper with sudo -n (owner's decision 2026-10-26: a narrow helper, not CAP_NET_ADMIN for minimald)
    - scripts/install_test.sh: the rendered set and rule, and the helper refusing a path outside the slice
    test: cargo nextest run -p minimald the_ruleset_rejects_udp_443_from_leaves_in_the_blocked_set
    - cohort:
    - crates/minimald/src/net/classifier.rs: CohortQuic443 keeps every resident allow member's own leaf in the blocked set while the cohort blocks: all of them when it turns blocked, a joining member's at admit, none once the last blocker leaves; the process moves stay
    - crates/minimald/src/box_actor/host.rs: runs the helper through sudo -n for each set change, and logs a refusal without failing the box
    test: cargo nextest run -p minimald cohort_flip_puts_member_leaves_in_the_blocked_set
    produces: none
    consumes: none
    proof: A host_ip cohort member's UDP 443 socket opened before the cohort turns blocked stops sending when the block flips, and sends again when it lifts.
    depends on:
    - blocked by T53

    T73 — Close the race between the cohort lock and the shim's leaf join

    slice: S10
    kind: task
    covers: BEP-018
    size: S
    est: 60 code, 100 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: the daemon log names an injected process that the post-join check moved into the blocked leaf
    observability: one debug line per post-join move
    layers:
    - inject:
    - crates/minimald/src/box_runtime.rs: after the shim joins its leaf, Host::command checks the cohort again under the lock and moves the shim into the blocked leaf and records it, or refuses the injection
    - crates/minimald/src/box_runtime/tests.rs: the interleaving test
    test: cargo nextest run -p minimald an_admit_between_injection_leaf_and_the_join_ends_in_the_blocked_leaf
    produces: none
    consumes: none
    proof: A process injected while a blocking member's admit runs ends in the blocked leaf and is recorded.
    depends on:
    - blocked by T53

    T74 — Refuse a minted box id that a retained revocation record holds

    slice: S10
    kind: task
    covers: BEP-070
    size: M
    est: 100 code, 100 tests
    executor: agent
    difficulty: unknown ground
    model: strongest
    diagnostics: the CollidingBoxId refusal names every source the check reads
    observability: one warn line per colliding mint
    layers:
    - collide:
    - crates/bep/src/control.rs: an owner-only control op returns the box ids the proxy's restored revocations hold
    - crates/minvmd/src/box_registry.rs: holds_box_id also refuses an id that op returns, and the AllocationError::CollidingBoxId text names live rows, kept creations, attachments and revocation records
    test: cargo nextest run -p minvmd a_minted_id_a_revocation_holds_is_refused
    produces: none
    consumes: none
    proof: A minted box id that only a retained revocation record holds is refused as a collision.
    depends on:
    - blocked by T22

    T75 — Give bep serve a chain-preserving way out of an unreadable audit line

    slice: S10
    kind: task
    covers: BEP-068
    size: M
    est: 120 code, 140 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: the refusal names bep serve --quarantine-unreadable in place of a hand edit
    observability: one warn line per quarantined segment, and a quarantine audit record
    layers:
    - recover:
    - crates/bep/src/audit.rs: a quarantine moves each segment holding an unreadable line into <audit dir>/quarantine/ and appends a quarantine record that carries the moved segment's final hash, so the chain stays verifiable; a record of a kind this bep does not know is told apart from a corrupt line
    - crates/bep/src/main.rs: bep serve --quarantine-unreadable runs the quarantine and starts; refuse_unreadable names that flag
    test: cargo nextest run -p bep quarantine_keeps_the_chain_and_starts
    produces: none
    consumes: none
    proof: bep serve --quarantine-unreadable starts past an unreadable audit line, and the chain still verifies across the quarantine.
    depends on:
    - blocked by T33

    T83 — Give bep serve the client's store rules so a store member is admitted

    slice: S10
    kind: task
    covers: BEP-032
    size: M
    est: 140 code, 140 tests
    executor: agent
    difficulty: unknown ground
    model: strongest
    diagnostics: a CONNECT to an authority that no module and no store rule admits stays refused off_module in min box audit, and a store handle with no matching rule stays refused store_handle_invalid
    observability: one info log line at start naming the count of store rules bep serve holds, never a value
    layers:
    - rules:
    - crates/bep/src/main.rs: bep serve gets the client's [secret-store-rules] with the BEP-034 refusals applied and gives them to BoxRedeemer::with_rules; the rules' authorities join registered_union and the admitted set, less the deny set; the three interim notes that still name T25 (#2280) are removed (#2599); how the rules reach bep serve (a read of the user-level config.toml, or a control-socket op that min sends) is this task's choice, left to the implementer and the arch review by the epic owner on 2026-10-26
    - crates/bep/src/redeemer.rs: the facts doc names the rules bep serve gives in place of the interim note
    - crates/bep/Cargo.toml: bep depends on boxes and paths to read and check the client's rules (layer amended 2026-10-26: the arch review on #2614 accepted this delivery)
    - Cargo.lock: regenerated for the new bep dependencies
    - crates/boxes/src/client/config.rs: one function merges the user and organization [secret-store-rules] with the action floor and the deny check, for min and bep (arch C2 on #2614)
    - crates/minimal/src/config.rs: min calls that merge in place of its own (arch C2 on #2614)
    test: cargo nextest run -p bep serve_admits_a_store_member_under_the_clients_rule
    produces: none
    consumes: none
    proof: A box's request to a [secret-store-rules] rule's registered upstream, sent through a running bep serve, reaches that upstream with the stored key in the rule's injection form.
    depends on:
    - blocked by SP1
    - blocked by T67
    - blocked by T25

    T84 — Keep a replaced client key verifying until the boxes minted under it end

    slice: S10
    kind: task
    covers: BEP-063
    size: M
    est: 160 code, 160 tests
    executor: agent
    difficulty: unknown ground
    model: strongest
    diagnostics: when every registered client key is in use and the bound refuses a registration, the box creation fails with this text on stderr: "min did not create the box: the Box Egress Proxy already holds 16 client keys, and running boxes use each one. A key is freed when the last box created under it ends."
    observability: one info log line when a kept key is retired, naming its kid
    layers:
    - keep:
    - crates/bep/src/control.rs: replace_client_key keeps a key registered while a running box's store handles or sign-in references are signed under it, and revokes it when the last such box ends; the bound drops the least recently registered key that no running box uses, and refuses the registration when every key is in use (owner ruling on gominimal/arch#144, 2026-10-26; #2605)
    - crates/minimal/src/mint.rs: the replacement keeps its pending-revoke file and lock, and a bound refusal fails the creation with the diagnostics text
    - crates/bep/src/main.rs: the test literals of the registration record set its new box_id field (layer amended 2026-10-26 after round 1 of #2615)
    - crates/minimal/src/tests.rs: the test inventory names the new verify tests in place of a_replaced_client_key_is_revoked_at_the_proxy (layer amended 2026-10-26 after round 1 of #2615)
    - docs/specs/24-spec-box-egress-proxy/24-spec-box-egress-proxy.md: the BEP-063 replace and bound sub-requirements state the keep and the in-use bound, name the two verify tests below, drop their known-gap notes, and the pending-revoke comment says the client removes the file (arch C1 on #2594)
    test: cargo nextest run -p minimal a_replaced_client_key_keeps_verifying_until_its_boxes_end
    test: cargo nextest run -p bep the_registry_bound_never_drops_a_key_a_running_box_uses
    produces: none
    consumes: none
    proof: After a client key replacement, a box created under the old key keeps redeeming its store handles and sign-in references until it ends, and the registry bound never drops a key that a running box uses.
    depends on:
    - assumes OQ3
    - blocked by T63
    - blocked by T71

    T76 — Round-two conditions closed, end to end

    slice: S10
    kind: integration
    covers: BEP-032, BEP-067, BEP-063, BEP-018, BEP-070, BEP-068
    size: M
    est: 0 code, 120 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: each case prints PASS or NOT RUN with its gate
    observability: none beyond the cases' own output
    layers:
    - e2e:
    - scripts/session-e2e.sh: one walk over the round-two conditions: a store value injected by bep serve under a client rule, a replaced client key that keeps verifying, the audit jti, the key bound, the cohort flows and race, the revoked-id collision and the quarantine start; NOT RUN where its gate is missing (#2500)
    test: ./scripts/session-e2e.sh bep_round_two_conditions_closed
    produces: none
    consumes: none
    proof: The second round of residuals is done: bep serve injects store values under the client's store rules, a replaced client key keeps verifying until its boxes end, the audit record carries the handle's jti, the key bound and kid diagnostics are in place, BEP-063 states revocation and replacement, a blocked cohort ends its earlier UDP 443 flows and its injected processes, a revoked id never returns, and an unreadable audit line has a chain-preserving way out. Its tasks re-cover requirements whose origin tasks merged with an arch condition held on a filed residual; the owner accepted that double coverage on 2026-10-26 so the residuals dispatch as slices.
    depends on:
    - blocked by SP1
    - assumes OQ3
    - blocked by T67
    - blocked by T68
    - blocked by T69
    - blocked by T70
    - blocked by T71
    - blocked by T72
    - blocked by T73
    - blocked by T74
    - blocked by T75
    - blocked by T83
    - blocked by T84

    T77 — Run the real-Keychain proof of min secret on a Mac

    slice: S11
    kind: task
    covers: BEP-050, BEP-051
    size: S
    est: 0 code, 60 tests
    executor: human
    difficulty: routine
    model: standard
    diagnostics: the case prints PASS or NOT RUN with its gate
    observability: the case output, posted on #2562
    layers:
    - mac:
    - crates/bep/src/store.rs: run on a Mac against main, unchanged; min secret set, list and rm run against the login Keychain and the transcript is posted on #2562
    test: cargo nextest run -p bep --run-ignored ignored-only host_store_set_list_rm_round_trip
    produces: none
    consumes: none
    proof: On a Mac, min secret set stores a key in the real Keychain with access control that lets the proxy read it without a prompt, and the case records OK.
    depends on:
    - blocked by T26
    - blocked by T111

    T78 — Run bep_git_credential_helper_sends_header on the macOS lane

    slice: S11
    kind: task
    covers: BEP-015
    size: M
    est: 0 code, 100 tests
    executor: human
    difficulty: routine
    model: standard
    diagnostics: the case prints PASS or NOT RUN with its gate
    observability: the case output, posted on #2563
    layers:
    - mac:
    - scripts/session-e2e.sh: run on the manual macOS lane of #2500 against main, with a GitHub sign-in and E2E_BEP_PRIVATE_REPO, unchanged
    test: ./scripts/session-e2e.sh bep_git_credential_helper_sends_header
    produces: none
    consumes: none
    proof: On the macOS lane, git in a grant box clones and pushes through the shipped credential helper, and the case records OK.
    depends on:
    - blocked by T50
    - blocked by T111

    T79 — Run the two revocation e2e legs on the macOS lane

    slice: S11
    kind: task
    covers: BEP-043
    size: M
    est: 0 code, 100 tests
    executor: human
    difficulty: routine
    model: standard
    diagnostics: each case prints PASS or NOT RUN with its gate
    observability: the case output, posted on #2564
    layers:
    - mac:
    - scripts/session-e2e.sh: run on the manual macOS lane of #2500 against main, with a GitHub sign-in, unchanged
    test: ./scripts/session-e2e.sh bep_destroyed_box_values_refused_within_60s
    produces: none
    consumes: none
    proof: On the macOS lane, a destroyed box's values are refused within 60 seconds, a destroy completes with the proxy down (bep_destroy_with_proxy_down_refuses_by_attachment), and both cases record OK.
    depends on:
    - blocked by T20
    - blocked by T111

    T80 — Run bep_signing_rotation_keeps_running_box_working on the macOS lane

    slice: S11
    kind: task
    covers: BEP-061
    size: M
    est: 0 code, 100 tests
    executor: human
    difficulty: routine
    model: standard
    diagnostics: the case prints PASS or NOT RUN with its gate
    observability: the case output, posted on #2565
    layers:
    - mac:
    - scripts/session-e2e.sh: run on the manual macOS lane of #2500 against main, with a GitHub sign-in, unchanged
    test: ./scripts/session-e2e.sh bep_signing_rotation_keeps_running_box_working
    produces: none
    consumes: none
    proof: On the macOS lane, a running box keeps cloning and pushing across a signing-certificate rotation, and the case records OK.
    depends on:
    - blocked by T52
    - blocked by T111

    T81 — Run bep_box_recreated_under_its_name_redeems on the macOS lane

    slice: S11
    kind: task
    covers: BEP-070
    size: M
    est: 0 code, 100 tests
    executor: human
    difficulty: routine
    model: standard
    diagnostics: the case prints PASS or NOT RUN with its gate
    observability: the case output, posted on #2587
    layers:
    - mac:
    - scripts/session-e2e.sh: run on the manual macOS lane of #2500 against main, with a GitHub sign-in, unchanged
    test: ./scripts/session-e2e.sh bep_box_recreated_under_its_name_redeems
    produces: none
    consumes: none
    proof: On the macOS lane, a box re-created under the same name gets an id of its own and redeems, and the case records OK.
    depends on:
    - blocked by T22
    - blocked by T111

    T82 — Run the Box Egress Proxy legs on the macOS lane

    slice: S11
    kind: integration
    covers: BEP-050, BEP-051, BEP-015, BEP-043, BEP-061, BEP-070
    size: M
    est: 0 code, 100 tests
    executor: human
    difficulty: routine
    model: standard
    diagnostics: each case prints PASS or NOT RUN with its gate
    observability: the walk's output, posted on #2500
    layers:
    - mac:
    - scripts/session-e2e.sh: the full BEP walk on the manual macOS lane against main, with a GitHub sign-in and E2E_BEP_PRIVATE_REPO, unchanged
    - scripts/session-e2e.sh: bep_round_two_conditions_closed on the same lane with E2E_BEP_QUARANTINE=1, for its quarantine leg, on a host whose audit log may be moved aside (widened 2026-10-27: T76's quarantine leg needs a live proxy, which only this lane has)
    test: ./scripts/session-e2e.sh bep_live_gate
    produces: none
    consumes: none
    proof: Every Box Egress Proxy leg that records NOT RUN on Linux records OK on the macOS lane against a live proxy with a sign-in. Its tasks re-cover requirements whose origin tasks merged with an arch condition held on a filed residual; the owner accepted that double coverage on 2026-10-26 so the residuals dispatch as slices. The debug-build key task re-covers the key-custody requirements; the owner accepted that double coverage on 2026-10-31.
    depends on:
    - blocked by T77
    - blocked by T78
    - blocked by T79
    - blocked by T80
    - blocked by T81
    - blocked by T111

    T111 — Accept a key the keychain made exportable in a debug build of bep, and scope the custody rule to release builds

    slice: S11
    kind: task
    covers: BEP-014, BEP-059
    size: M
    est: 90 code, 300 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: a debug build of bep that keeps an exportable key writes one warn line per role to bep.log, kept the proxy's <role> key although the keychain made it exportable: this is a debug build, and a release build refuses it; a release build still prints "the keychain made the key exportable" and "the proxy root key is exportable, so the proxy does not use it for a CA", pinned by a_release_build_refuses_an_exportable_key_at_both_sites
    observability: the warn line above, once per role at each start of a debug build, in the proxy's log; a release build writes no new line
    layers:
    - keys:
    - crates/bep/src/keys.rs: the refusal at if key.exportable() in the keychain's generate() (about line 653) goes through one custody decision that both refusal sites share; in a debug build (cfg!(debug_assertions)) the decision keeps the key and writes the warn line, and in a release build it refuses as today; the decision takes the build as an argument, so a test on every host pins both builds
    - crates/bep/src/ca.rs: the CA key check at if key.exportable() (about line 721), which returns CaError::Exportable, takes the same decision, so a debug build that kept an exportable root or signing key also starts its CA; ca_refuses_a_wrong_role_or_exportable_key pins the release refusal and the debug keep
    test: cargo nextest run -p bep ca_refuses_a_wrong_role_or_exportable_key
    test: cargo nextest run -p bep signing_key_is_non_exportable_and_has_no_file
    test: cargo nextest run -p bep sealing_and_root_keys_persist_non_exportable
    - start:
    - crates/bep/src/main.rs: two start tests over keys that report exportable(): a debug build creates its keys, builds its CA and starts serving, and a release build refuses at the generation site and at the CA site, each with its own text
    test: cargo nextest run -p bep a_debug_build_starts_with_exportable_keys
    test: cargo nextest run -p bep a_release_build_refuses_an_exportable_key_at_both_sites
    - spec:
    - docs/specs/24-spec-box-egress-proxy/24-spec-box-egress-proxy.md: BEP-014 and BEP-059 say a release build of the proxy holds its keys non-exportable; a note on each says why a debug build keeps an exportable key with a warning: the macOS login keychain lets the process that made a key export it although kSecAttrIsExtractable is false (the probe on #2436), and the keychain that enforces it needs an entitlement only a Developer ID signature carries, so a debug build would never start on a developer's Mac (decided 2026-10-31, owner: this spec note is enough, and the architecture's key-custody rule stays as it is)
    produces: none
    consumes: none
    proof: A debug build of bep on a developer's Mac starts and serves with keys the login keychain made exportable, and writes a warning for each, while a release build still refuses such a key.
    depends on:
    - blocked by T2
    - blocked by T3
    - assumes OQ4

    T85 — Name the handle jti in spec 24 BEP-039

    slice: S12
    kind: task
    covers: BEP-039
    size: S
    est: 0 code, 60 tests
    executor: agent
    difficulty: routine
    model: standard
    diagnostics: none: the task changes only the spec text, so no tool prints anything new.
    observability: none: the task changes only the spec text, so no trace, metric or log line changes.
    layers:
    - spec:
    - docs/specs/24-spec-box-egress-proxy/24-spec-box-egress-proxy.md: BEP-039's field list and the property under it name jti, the handle's correlation id, which on a refusal is the unverified claim (Gatehouse §6.10 line 842; #2600)
    test: cargo nextest run -p bep a_store_handle_audit_record_carries_its_jti
    produces: none
    consumes: none
    proof: BEP-039 lists every field of a decision record, the handle's jti included.
    depends on:
    - blocked by T68

    T86 — State the audit quarantine in spec 24 BEP-041 and BEP-068

    slice: S12
    kind: task
    covers: BEP-041
    size: S
    est: 0 code, 60 tests
    executor: agent
    difficulty: routine
    model: standard
    diagnostics: none: the task changes only the spec text, so no tool prints anything new.
    observability: none: the task changes only the spec text, so no trace, metric or log line changes.
    layers:
    - spec:
    - docs/specs/24-spec-box-egress-proxy/24-spec-box-egress-proxy.md: BEP-041 lets bep serve --quarantine-unreadable move a segment with an unreadable line out of the restored set after it re-records the segment's revocations, and BEP-068 names the quarantine record, the quarantine_restated marker and the warn line (#2613)
    test: cargo nextest run -p bep forged_quarantine_record_bridges_no_gap
    produces: none
    consumes: none
    proof: BEP-041 and BEP-068 state the quarantine move, its record and its marker.
    depends on:
    - blocked by T75

    T87 — Prove on a real kernel that a blocked cohort rejects a pre-flip UDP 443 socket

    slice: S12
    kind: task
    covers: BEP-018
    size: M
    est: 40 code, 160 tests
    executor: agent
    difficulty: unknown ground
    model: strongest
    diagnostics: the leg records NOT RUN with its gate where the host has no root netns or no nft
    observability: one line per step of the leg
    layers:
    - kernel:
    - crates/minimald/tests/netns_root_integration.rs: a leg that opens a UDP 443 socket in a host_ip member's leaf before the cohort flips to blocked, expects ECONNREFUSED on the next datagram after the flip, and a send again after the block lifts, against the loaded classifier table (#2606)
    test: cargo nextest run -p minimald --run-ignored only a_blocked_cohort_rejects_a_pre_flip_udp_443_socket
    produces: none
    consumes: none
    proof: On a real kernel, a host_ip member's UDP 443 socket opened before the cohort turns blocked is rejected after the flip and sends again after the block lifts.
    depends on:
    - blocked by T72

    T88 — Name the QUIC block helper in min finalize-install

    slice: S12
    kind: task
    covers: BEP-018
    size: S
    est: 60 code, 60 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: when the helper or its sudoers drop-in is missing or not root-owned, the item prints: "QUIC block helper: not installed at /usr/local/lib/minimal/quic443-leaf. Run min finalize-install to install it."
    observability: none: finalize-install is a one-shot command whose item line is its whole output.
    layers:
    - finalize:
    - crates/minimal/src/cmd/finalize_install.rs: an item quic443-helper that is done when the helper and its sudoers drop-in exist root-owned, and otherwise names the path and the step (#2607)
    test: cargo nextest run -p minimal finalize_install_names_the_quic_block_helper
    produces: none
    consumes: none
    proof: min finalize-install shows the QUIC block helper item as done or names its path and the step that installs it.
    depends on:
    - blocked by T72

    T89 — Check a minted box id against retained revocations when no proxy runs

    slice: S12
    kind: task
    covers: BEP-070
    size: M
    est: 100 code, 100 tests
    executor: agent
    difficulty: unknown ground
    model: strongest
    diagnostics: a refused id names the revocation record that holds it
    observability: one warn line per colliding mint
    layers:
    - collide:
    - crates/minvmd/src/box_registry.rs: when no proxy answers, holds_box_id reads the box ids that the retained audit segments' revocation records hold, or refuses creation; the choice is the implementer's and the arch review's (#2616)
    - docs/specs/24-spec-box-egress-proxy/24-spec-box-egress-proxy.md: the BEP-070 limit comment states the no-proxy case as the code does it
    test: cargo nextest run -p minvmd a_minted_id_a_revocation_holds_is_refused_with_no_proxy
    produces: none
    consumes: none
    proof: A minted box id that a retained revocation record holds is refused with no proxy running.
    depends on:
    - blocked by T74

    T90 — Free the client key of a box that ended without forget_box

    slice: S12
    kind: task
    covers: BEP-063
    size: M
    est: 120 code, 120 tests
    executor: agent
    difficulty: unknown ground
    model: strongest
    diagnostics: none beyond the bound's refusal text
    observability: one info log line per key freed at start, naming its kid
    layers:
    - keys:
    - crates/bep/src/control.rs: at start, each key's boxes= set is reconciled against the boxes that are still registered, so a box that ended with no forget_box (a Ctrl-C before minvmd records it, a stopped minvmd, a failed release write) no longer holds its key in use (#2617)
    - docs/specs/24-spec-box-egress-proxy/24-spec-box-egress-proxy.md: the BEP-063 known-gap text that links #2617 is removed
    test: cargo nextest run -p bep a_stranded_box_frees_its_key_at_start
    produces: none
    consumes: none
    proof: After a restart, a box that ended without forget_box no longer holds its client key in use, and the bound counts only keys that running boxes use.
    depends on:
    - blocked by T84
    - assumes OQ3

    T91 — Admit a widened store rule without restarting bep serve

    slice: S12
    kind: task
    covers: BEP-032
    size: M
    est: 120 code, 100 tests
    executor: agent
    difficulty: unknown ground
    model: strongest
    diagnostics: none: a widened rule admits its upstream as any other admitted upstream, and the log line names the re-sign.
    observability: one info log line when the root re-signs or the admitted set changes
    layers:
    - admit:
    - crates/bep/src/main.rs: a changed [secret-store-rules] re-signs the root with the new names under the same key and subject, and rebuilds the Admission set (#2618)
    - crates/bep/src/proxy.rs: Admission takes a source that it asks at each CONNECT
    test: cargo nextest run -p bep a_widened_store_rule_is_admitted_without_a_restart
    produces: none
    consumes: none
    proof: A store rule that adds an upstream is admitted without restarting bep serve.
    depends on:
    - blocked by T83
    - blocked by SP1

    T92 — Keep store rules under min --config-dir in step with bep serve

    slice: S12
    kind: task
    covers: BEP-032
    size: S
    est: 60 code, 60 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: when min would mint under a rule bep serve does not read, it refuses: "min did not mint a store handle for : bep serve reads store rules from , not from --config-dir

    ."
    observability: none: the refusal prints to the person running min, and min logs nothing for a mint.
    layers:
    - config:
    - crates/minimal/src/mint.rs: a store handle under a rule that exists only below a --config-dir override is refused with the diagnostics text, or the override reaches bep serve; the choice is the implementer's and the arch review's (#2620)
    - crates/bep/src/main.rs: the client_store_rules doc names the behaviour in place of the #2620 link
    test: cargo nextest run -p minimal a_store_rule_under_config_dir_is_minted_only_when_bep_reads_it
    produces: none
    consumes: none
    proof: A store rule written under min --config-dir is either read by bep serve or refused at mint with the reason.
    depends on:
    - blocked by T83
    - blocked by SP1

    T93 — Remove the QUIC block helper and its sudoers rule on uninstall

    slice: S12
    kind: task
    covers: BEP-018
    size: M
    est: 20 code, 180 tests
    executor: agent
    difficulty: routine
    model: standard
    diagnostics: uninstall names the helper and its sudoers rule among the finalize-install files it lists.
    observability: none: the installer is a shell script that writes no log.
    layers:
    - uninstall:
    - scripts/install.sh: the uninstall list and its _cls_found check name /usr/local/lib/minimal/quic443-leaf and /etc/sudoers.d/minimal-quic443-leaf (#2631)
    - scripts/install_test.sh: after the uninstall rehearsal neither file exists
    test: just test-installer finalize_install_uninstall
    produces: none
    consumes: none
    proof: Uninstall removes the QUIC block helper and its sudoers rule.
    depends on:
    - blocked by T72

    T94 — Round-three conditions closed, end to end

    slice: S12
    kind: integration
    covers: BEP-039, BEP-041, BEP-018, BEP-070, BEP-063, BEP-032
    size: M
    est: 0 code, 200 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: each case prints PASS or NOT RUN with its gate
    observability: none beyond the cases' own output
    layers:
    - e2e:
    - scripts/session-e2e.sh: one walk over the round-three conditions; NOT RUN where its gate is missing
    test: ./scripts/session-e2e.sh bep_round_three_conditions_closed
    produces: none
    consumes: none
    proof: The third round of residuals is done: the spec names the audit jti and the quarantine, a real kernel rejects a blocked cohort's pre-flip UDP 443 socket, min finalize-install names the QUIC block helper and uninstall removes it, a minted id is checked with no proxy running, a stranded box frees its client key, a widened store rule and a min --config-dir rule reach bep serve without a restart. Its tasks re-cover requirements whose origin tasks merged with an arch condition held on a filed residual; the owner accepted that double coverage on 2026-10-26 so the residuals dispatch as slices.
    depends on:
    - blocked by T85
    - blocked by T86
    - blocked by T87
    - blocked by T88
    - blocked by T89
    - blocked by T90
    - blocked by T91
    - blocked by T92
    - blocked by T93
    - blocked by SP1
    - assumes OQ3

    T95 — Stop node_port_assigned_on_host_and_handed_to_daemon racing a parallel test for its port

    slice: S13
    kind: enablement
    covers: none
    size: S
    est: 20 code, 60 tests
    executor: agent
    difficulty: routine
    model: standard
    diagnostics: none: the task changes a unit test, so no tool prints anything new.
    observability: none: the task changes a unit test, so no trace, metric or log line changes.
    layers:
    - flake:
    - crates/minvmd/src/cmd/run.rs: the step that releases a port and expects assign_node_port to hand it back as asked no longer fails when another test in the process takes the released port first (#2653)
    test: cargo nextest run -p minvmd node_port_assigned_on_host_and_handed_to_daemon
    produces: none
    consumes: none
    proof: node_port_assigned_on_host_and_handed_to_daemon passes under a loaded parallel run.

    T96 — Run bep_revoke_and_sign_out_end_to_end live on a Mac

    slice: S13
    kind: integration
    covers: BEP-043, BEP-044
    size: M
    est: 0 code, 200 tests
    executor: human
    difficulty: routine
    model: standard
    diagnostics: the case prints each step line and Revoke and sign out OK, or NOT RUN with its gate
    observability: the case output, posted on #2655
    layers:
    - mac:
    - scripts/session-e2e.sh: run by a person on a Mac with a live Box Egress Proxy and a GitHub sign-in, with E2E_BEP_LOGOUT=1, against main, unchanged; the person enters the device code (#2655)
    test: ./scripts/session-e2e.sh bep_revoke_and_sign_out_end_to_end
    produces: none
    consumes: none
    proof: The fourth round of residuals is done: the minvmd node-port test no longer races a parallel test for its released port, a person has run the revoke and sign-out case live on a Mac, a box spec writes a store reference as source = "store" with store and id, and box creation seals a store handle for each accepted store rule. Its tasks re-cover requirements whose origin tasks merged with an arch condition held on a filed residual; the owner accepted that double coverage on 2026-10-26 so the residuals dispatch as slices.
    depends on:
    - blocked by T23
    - blocked by T95
    - blocked by T97
    - blocked by T98
    - blocked by T111

    T97 — Accept source = "store" with store and id in a box spec's grant

    slice: S13
    kind: task
    covers: BEP-032
    size: M
    est: 160 code, 240 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: a store reference spelled with source = "store" and no store or id is refused with text that names the missing field
    observability: none: the parse runs inside min box creation, which logs no line for a grant
    layers:
    - grant:
    - crates/boxes/src/core/bep.rs: GrantSource accepts source = "store" with store and id, and store_references() reads store references from that form, not from broker scopes (#2656)
    - scripts/session-e2e.sh: the seed of proof_bep_store_reference_end_to_end writes source = "store", store = "keychain" and id, and the # Interim spelling: comment goes
    test: cargo nextest run -p boxes a_store_grant_parses_store_and_id
    - review:
    - crates/minimal/src/cmd/box_spec.rs: render reads each store row from boxes::core::bep::store_references() of the source = "store" form, not from grant.scopes; its test builds the reference in that form (widened 2026-10-31, owner, #2656 noop)
    test: cargo nextest run -p minimal box_spec_lists_store_references_with_their_authorities
    - create:
    - crates/minimal/src/cmd/boxes/create.rs: the tests of accept_store_references build store references as source = "store" with store and id (widened 2026-10-31, owner, #2656 noop)
    test: cargo nextest run -p minimal store_reference_without_rule_is_refused_at_creation
    test: cargo nextest run -p minimal store_rule_outside_egress_exits_3
    produces: none
    consumes: none
    proof: A box spec that declares a store reference as source = "store" with store and id mints and redeems it, and min box spec lists it with its authorities.
    depends on:
    - blocked by T29
    - blocked by SP1

    T98 — Seal a store handle per accepted store rule at box creation

    slice: S13
    kind: task
    covers: BEP-063, BEP-032
    size: M
    est: 160 code, 200 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: min box spec and min box audit <box> show each store member the box carries, as T60 and T85 print them today, now with a caller
    observability: none new: each redemption of the sealed member writes the BEP-039 audit record T85 writes, with the handle's jti
    layers:
    - mint:
    - crates/minimal/src/mint.rs: mint_store_handle and store_claims gain their caller, and the dead_code expectations that name #2525 go, and so do the module doc's sentences that say mint_store_handle has no caller and that #2526 owns the min box spec store references
    - crates/minimal/src/cmd/boxes/create.rs: box creation mints a store handle for each accepted store rule and seals it as a store member (#2525), in place of _accepted_store_rules
    test: cargo nextest run -p minimal box_creation_seals_a_store_member_per_accepted_rule
    - audit:
    - crates/minimal/src/cmd/box_audit.rs: a test renders a refused store-handle record and pins store_handle_invalid, the item as <store>:<id> and jti=<jti> on its line, with the subject in the spelling the reader matches (moved here 2026-10-31, owner, from #2468)
    test: cargo nextest run -p minimal box_audit_shows_a_refused_store_handle_with_its_item_and_jti
    produces: none
    consumes: none
    proof: A box created with an accepted store rule carries its store handle sealed as a store member, and the proxy redeems it into the store value that the rule admits. Its tasks re-cover requirements whose origin tasks merged with an arch condition held on a filed residual; the owner accepted that double coverage on 2026-10-26 so the residuals dispatch as slices.
    depends on:
    - blocked by T27
    - blocked by T60
    - blocked by T97
    - blocked by SP1
    - assumes OQ3

    T100 — Stage bep beside minvmd in the macOS release and install it

    slice: S14
    kind: enablement
    covers: none
    size: S
    est: 30 code, 160 tests
    executor: agent
    difficulty: routine
    model: standard
    diagnostics: the installer prints bep in its component list beside minvmd on a macOS install
    observability: none: the stager and the installer are shell scripts that write no log
    layers:
    - stage:
    - scripts/stage-release.sh: a darwin row "bep|darwin|arm64|file|bin/bep|bep-macos-arm64" beside the minvmd row, so the installer puts bep where minvmd resolves it (#2498)
    - scripts/stage-release_test.sh: the darwin manifest carries the bep row
    - scripts/install_test.sh: a mock bep-macos-arm64 in the darwin release, and a case that finds bin/bep beside bin/minvmd after install and gone after uninstall
    test: just test-installer bep_installs_beside_minvmd
    produces: none
    consumes: none
    proof: A macOS install from a release puts bep beside minvmd, and an uninstall removes it.
    depends on:
    - blocked by T101

    T101 — Build, sign and notarize bep in the macOS release

    slice: S14
    kind: enablement
    covers: none
    size: M
    est: 40 code, 60 tests
    executor: human
    difficulty: needs care
    model: standard
    diagnostics: the release run's artefact list names bep-macos-arm64, and codesign -dv on it shows the Developer ID team
    observability: none: the release workflow's own job log is the record
    layers:
    - release:
    - .github/workflows/release.yml: the macOS job builds cargo build --release --locked -p bep --bin bep with MINIMAL_GITHUB_APP_CLIENT_SECRET set (a browser-flow sign-in renews at the proxy only with it), renames it bep-macos-arm64, signs it with the Developer ID beside minvmd, notarizes it and uploads it (#2498); the first signed bep this job ships is the key-custody probe for #2436 (owner decision 2026-10-31): it must start on a stock Mac with non-exportable keys, and if it refuses an exportable key the custody fix lands before T102
    test: just test-installer bep_installs_beside_minvmd
    produces: none
    consumes: none
    proof: A release carries a Developer-ID-signed, notarized bep-macos-arm64 built with the App's client secret.

    T102 — Installed hosts run the proxy, end to end

    slice: S14
    kind: integration
    covers: none
    size: M
    est: 0 code, 100 tests
    executor: human
    difficulty: routine
    model: standard
    diagnostics: bep.log beside the host daemon's state shows the proxy listening, and min box audit shows the admit
    observability: the proxy's connection spans in bep.log name the box and the authority
    layers:
    - mac:
    - scripts/session-e2e.sh: bep_first_slice_end_to_end on a Mac installed from the release, with a GitHub sign-in and E2E_BEP_PRIVATE_REPO, unchanged; the person also runs the close-out walk's stories 01 to 03 and posts both results on the epic
    test: ./scripts/session-e2e.sh bep_first_slice_end_to_end
    produces: none
    consumes: none
    proof: On a Mac installed from a release, sign in with min auth login, and a GitHub grant box runs gh api user through the proxy with no extra step.
    depends on:
    - blocked by T100
    - blocked by T101

    T103 — Match min box audit on the box id spelling the proxy writes

    slice: S15
    kind: task
    covers: BEP-042
    size: M
    est: 60 code, 180 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: min box audit <id> and min box audit <name> list the records the proxy wrote for that box; the line "Showing (box )." keeps the hyphenated id a person sees in min ls, pinned by box_audit_reads_a_record_the_proxy_wrote_for_a_minted_id
    observability: the existing debug line of each read names the box ids it matched in the proxy's spelling, 32 lowercase hex digits
    layers:
    - audit:
    - crates/minimal/src/cmd/box_audit.rs: resolve(), render() and children() match a record's subject against the box id in the proxy's one text form, bep::listener::box_id_text (32 lowercase hex digits), not the hyphenated SessionId display form, so min box audit shows the records the proxy writes (#2462); the name rule keeps choosing the live box, else the newest creation by UUIDv7 order; the test fixtures write subjects in the proxy's form, as bep serve does
    test: cargo nextest run -p minimal box_audit_filters_to_one_box
    test: cargo nextest run -p minimal box_audit_name_reads_live_else_latest_creation
    test: cargo nextest run -p minimal box_audit_reads_a_record_the_proxy_wrote_for_a_minted_id
    produces: none
    consumes: n 10BC8 one
    proof: min box audit <box> lists the decisions the running proxy recorded for a box created on this host, by id and by name.
    depends on:
    - blocked by T17
    - blocked by T22

    T104 — Read each box's parent so min box audit --parent merges its children

    slice: S15
    kind: task
    covers: BEP-042
    size: M
    est: 80 code, 120 tests
    executor: agent
    difficulty: routine
    model: standard
    diagnostics: min box audit --parent <box> lists each child box's records with the child's id at the start of each line; a box with no child prints "Box has no child boxes." on stderr; the refusal "min box audit --parent cannot run here: this host does not record which boxes are children of box . Run min box audit <box> for each child box." goes, pinned by box_audit_parent_reads_parents_from_the_box_records
    observability: the existing debug line of each read names the child box ids it merged
    layers:
    - audit:
    - crates/minimal/src/cmd/box_audit.rs: known_boxes reads each listed box's parent from its record (boxes::Record::parent, which the daemon stamps at creation since #2386), LISTING_RECORDS_PARENTS, refuse_parent_before_listing and the refusal text go, and the tests drive run with the parents known_boxes gives, the path min box audit takes, not with a hard-coded parents_recorded = true (BEP-042 --parent)
    test: cargo nextest run -p minimal box_audit_parent_merges_children
    test: cargo nextest run -p minimal box_audit_parent_reads_parents_from_the_box_records
    produces: none
    consumes: none
    proof: min box audit --parent <box> merges the records of every child of that box, each line naming its child, on a host whose boxes carry a parent.
    depends on:
    - blocked by T103

    T105 — Strip Alt-Svc on the live response relay and point the HTTP checks at the live path

    slice: S15
    kind: task
    covers: BEP-076
    size: L
    est: 260 code, 360 tests
    executor: agent
    difficulty: needs care
    model: strongest
    diagnostics: a box that requests through the proxy sees no Alt-Svc header in any relayed response, so a client never learns an HTTP/3 endpoint to try past the proxy; the box sees the first chunk of a response before the upstream sends the rest, as before
    observability: one debug line per relayed response that carried an Alt-Svc header, naming the box and the authority, beside the flow's existing span
    layers:
    - relay:
    - crates/bep/src/forward.rs: relay_responses reads each response head on the flow, removes every Alt-Svc header (BEP-076), writes the head, then streams the body chunks as they arrive by the response's framing (BEP-N01), for every response on a kept-alive flow; the tests alt_svc_is_stripped_from_responses, h2_prior_knowledge_preface_is_closed, prop_forwarded_head_is_canonical and prop_ambiguous_framing_is_refused drive forward::forward_requests and relay_responses, the path bep serve runs
    test: cargo nextest run -p bep alt_svc_is_stripped_from_responses
    test: cargo nextest run -p bep h2_prior_knowledge_preface_is_closed
    test: cargo nextest run -p bep prop_forwarded_head_is_canonical
    test: cargo nextest run -p bep prop_ambiguous_framing_is_refused
    test: cargo nextest run -p bep streamed_response_is_not_buffered
    - http:
    - crates/bep/src/http.rs: the four tests of the same names, which drive relay_response_head and forward_request_head that no live path calls, go; the response-head parse and strip_alt_svc serve the live relay
    test: cargo nextest run -p bep ambiguous_response_framing_is_refused
    produces: none
    consumes: none
    proof: A response relayed to a box through the running proxy carries no Alt-Svc header, and the box still gets each chunk as the upstream sends it.
    depends on:
    - blocked by T8
    - blocked by T11

    T106 — Audit a refused ambiguous request head on the live path and show it in min box audit

    slice: S15
    kind: task
    covers: BEP-039
    size: S
    est: 30 code, 120 tests
    executor: agent
    difficulty: routine
    model: light
    diagnostics: min box audit <box> lists a request head the proxy refused for ambiguous framing as a refuse line with the marker framing_ambiguous, pinned by box_audit_lists_a_refused_ambiguous_head
    observability: the proxy's log line for the refused head names the box and the framing rule; the audit record carries the marker, since BEP-039's record has no field for the rule
    layers:
    - proxy:
    - crates/bep/src/forward.rs: a test sends an ambiguous head through forward::forward_requests and reads one framing_ambiguous record for the box from the audit log; the refusal's log line names the rule (#2435)
    - crates/bep/src/http.rs: the AuditSink doc says the live path writes through Flow::record, and that the trait serves this module's own tests
    test: cargo nextest run -p bep live_path_audits_a_framing_ambiguous_head
    - audit:
    - crates/minimal/src/cmd/box_audit.rs: a test renders a framing_ambiguous record that bep serve wrote, in the proxy's id spelling
    test: cargo nextest run -p minimal box_audit_lists_a_refused_ambiguous_head
    produces: none
    consumes: none
    proof: After the proxy refuses a box's request head for ambiguous framing, min box audit <box> lists the refusal with the marker framing_ambiguous.
    depends on:
    - blocked by T103
    - blocked by T105

    T107 — Prove that the log bep serve writes is the log min box audit reads

    slice: S15
    kind: task
    covers: BEP-068
    size: S
    est: 0 code, 60 tests
    executor: agent
    difficulty: routine
    model: light
    diagnostics: min box audit <box> lists the records that bep serve wrote to audit-<n>.jsonl under ~/.minimal/bep/audit
    observability: the proxy's start line names the active segment, as today
    layers:
    - serve:
    - crates/bep/src/main.rs: a test starts the audit log as bep serve does (start_audit_log over a directory), appends a record, and reads it back with LogReader::read_new over the same directory, under the audit-<n>.jsonl name (#2460)
    test: cargo nextest run -p bep serve_writes_audit_segments_the_reader_reads
    produces: none
    consumes: none
    proof: A record bep serve appends is read back by the reader min box audit uses, from audit-.jsonl in the same directory.
    depends on:
    - blocked by T33

    T108 — Say in control.rs that bep serve sets the client-key bound

    slice: S15
    kind: task
    covers: BEP-063
    size: S
    est: 5 code, 60 tests
    executor: agent
    difficulty: routine
    model: light
    diagnostics: none: the task changes a doc comment, so nothing prints anything new.
    observability: none: the task changes a doc comment, so no trace, metric or log line changes.
    layers:
    - docs:
    - crates/bep/src/control.rs: the doc of Control::bind_with_key_bound says bep serve calls it with the value of --max-client-keys, in place of "The proxy binary does not call it, so its bound is the fixed default." (#2598)
    test: cargo nextest run -p bep the_registry_never_holds_more_keys_than_its_bound
    produces: none
    consumes: none
    proof: The control socket's docs say that bep serve sets the client-key bound with --max-client-keys.
    depends on:
    - blocked by T69
    - assumes OQ3

    T109 — Name the cause and the next step in the grant and proxy refusals

    slice: S15
    kind: task
    covers: BEP-003
    size: M
    est: 180 code, 300 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: each refusal below states its cause and one next step in the words the person types, pinned by the tests in its layer
    observability: the existing info line of each refused creation keeps its rule, fields and code; each proxy refusal keeps its audit marker
    layers:
    - create:
    - crates/minimal/src/cmd/boxes/create.rs: when no proxy serves boxes, the check tells apart a bep that is not installed (the proxy binary that bep::store::proxy_binary_path resolves does not exist) from an installed proxy that does not answer, and passes the cause to the refusal
    - crates/boxes/src/core/bep.rs: the exit-5 refusal no longer says only "Remove the grant". With no bep installed it reads "Cannot create the box: it declares the GitHub grant , and bep, the Box Egress Proxy, is not installed at , so no proxy serves boxes on this machine. To create the box without the grant, remove from the [secrets] table in minimal.toml." With bep installed and not answering, it says the proxy at is installed and does not answer, names the log file the host daemon writes for it, and keeps the same sentence about removing the grant
    - crates/minimal/src/tests.rs: a_grant_box_with_no_proxy_is_refused_as_an_unenrolled_node pins both texts and exit 5
    test: cargo nextest run -p minimal a_grant_box_with_no_proxy_is_refused_as_an_unenrolled_node
    test: cargo nextest run -p boxes creation_without_sign_in_fails_with_defined_error
    - json:
    - crates/minimal/src/main.rs: machine_mode_answer gives a refused activation's defined error as the -o json code (github_sign_in_required, gatehouse_unenrolled_node), not unspecified, with the refusal's next step as the hint: "run min auth login, then create the box again" for the sign-in
    test: cargo nextest run -p minimal machine_mode_names_the_grant_refusal_code
    - proxy:
    - crates/bep/src/forward.rs: refusal_body splits the one text "it did not accept the sealed credential in the request" by cause. github_sign_in_required reads "The Box Egress Proxy refused the request to , because the GitHub sign-in on the host has ended. On the host, run min auth login, then send the request again." expired, revoked, undecryptable and unbounded_member say the box's sealed credential no longer works on this host, and that re-creating the box gives it a new one. no_live_box and cross_box say the sealed credential belongs to another box. off_port, outside_bound_set and outside_current_set say is not a host this credential is sent to. store_handle_invalid says no rule in [secret-store-rules] on the host admits this stored secret for
    test: cargo nextest run -p bep each_refusal_names_its_cause_and_next_step
    produces: none
    consumes: none
    proof: A grant box refused for a missing proxy, a refusal read as -o json, and a request the proxy refuses each tell the person the cause and one next step they can take.
    depends on:
    - blocked by T13
    - blocked by T62

    T110 — Make bep --replace-key keep the old key until the new key exists

    slice: S15
    kind: task
    covers: BEP-060
    size: M
    est: 140 code, 180 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: when no new key can be made, bep --replace-key <role> prints "The key was not replaced, because no new key was made: . The old key is still in use." and exits non-zero; the text "Run bep --replace-key <role> again." goes, pinned by replace_key_failure_keeps_the_old_key_in_use
    observability: the warn line "replaced a proxy key" names the new key's fingerprint, as today; a failed generation writes one warn line naming the role and the cause, and that the old key is kept
    layers:
    - keys:
    - crates/bep/src/keys.rs: replace generates the new key beside the held one under a second label, then removes the old key and moves the new key to the role's label; when the generation fails, the old key stays and replace returns an error that says so; the memory store and the keychain store both do this, and a failed move of the label removes the new key and keeps the old one
    test: cargo nextest run -p bep a_failed_generation_keeps_the_old_key
    test: cargo nextest run -p bep refused_key_names_the_replacement_command
    - command:
    - crates/bep/src/main.rs: replace_key prints the text above for a failed generation, and the identity file is left as it was
    test: cargo nextest run -p bep replace_key_failure_keeps_the_old_key_in_use
    produces: none
    consumes: none
    proof: When bep --replace-key cannot make a new key, the old key stays in use and the message names the cause, not the same command again.
    depends on:
    - blocked by T2
    - blocked by T111

    T112 — Audit, HTTP and message conditions, end to end

    slice: S15
    kind: integration
    covers: none
    size: M
    est: 0 code, 200 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: each case prints PASS or NOT RUN with its gate
    observability: none beyond the cases' own output
    layers:
    - e2e:
    - scripts/session-e2e.sh: one walk over the slice: min box audit <box> lists a running grant box's decisions by id and by name, --parent merges a child box's records, a relayed response through the proxy carries no Alt-Svc, and a grant box with MINVMD_BEP_BIN naming a missing file is refused with exit 5 and the install text; NOT RUN where its gate is missing
    test: ./scripts/session-e2e.sh bep_audit_http_and_messages_closed
    produces: none
    consumes: none
    proof: min box audit shows the records the proxy wrote for a box and merges a parent's children with --parent, a relayed response carries no Alt-Svc header on the live path, a refused ambiguous request head shows in min box audit, the log bep serve writes is the log min box audit reads, the missing-proxy refusal, the sign-in refusal read as -o json and the proxy's credential refusals each name their cause and one next step, and bep --replace-key keeps the old key when it cannot make a new one. The owner added these conditions from the close-out walk on 2026-10-31. Its tasks re-cover requirements whose origin tasks merged with the gap open; the owner accepted that double coverage on 2026-10-31 so the conditions dispatch as one slice.
    depends on:
    - blocked by T103
    - blocked by T104
    - blocked by T105
    - blocked by T106
    - blocked by T107
    - blocked by T108
    - blocked by T109
    - blocked by T110

    T113 — Admit dns and both steering where the host's switch resolves box names

    slice: S16
    kind: task
    covers: BEP-062, BEP-078
    size: M
    est: 120 code, 180 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: on a host whose switch steers box lookups, a grant box with steering = "dns" or "both" is created, and min box spec shows the steering it resolves to; on a native host it is still refused with exit 3 and "This box declares a secret with steering = "dns", but this host has no box DNS resolver. Set steering = "proxy_env" in [network.bep].", pinned by dns_steering_is_admitted_where_the_switch_resolves_boxes
    observability: the existing info line of each creation's activation check carries the host fact it used, box_zone_resolver, beside its rule and fields
    layers:
    - activation:
    - crates/minimal/src/cmd/boxes/create.rs: refuse_unhonourable_grants sets HostFacts::box_zone_resolver from the host, not from HostFacts::LOCAL: true where the box's lookups go through the VM host's switch, whose DNS gate steers the credential module's names and asks the proxy's listener (ProxyListenerState::for_switch, T58), and false on a native host, where a steered name always fails closed; the doc line "Every host today is un-enrolled and has no box-zone resolver." goes (#2523)
    - crates/minimal/src/cmd/box_spec.rs: the preview reads the same host fact, so min box spec and creation agree
    - crates/boxes/src/core/bep.rs: the doc of HostFacts::box_zone_resolver and of HostFacts::LOCAL say where the fact comes from; the BEP-017 refusal is unchanged where the fact is false
    test: cargo nextest run -p minimal dns_steering_is_admitted_where_the_switch_resolves_boxes
    test: cargo nextest run -p boxes prop_dns_steering_without_resolver_is_exit_3
    - gate:
    - crates/minimald/src/net/switch.rs: a test builds a dns-steered box's gate as the VM host attaches it, and pins that github.com resolves to the proxy's address while its listener is live and gets SERVFAIL while it is down, never a real address
    test: cargo nextest run -p minimald a_dns_steered_grant_box_resolves_the_module_through_the_switch
    produces: none
    consumes: none
    proof: On a host whose switch resolves box names, a GitHub grant box with dns steering is created, git reaches GitHub through the proxy with no proxy variables, and its credentialed names fail closed while the proxy is down.
    depends on:
    - blocked by T13
    - blocked by T30
    - blocked by T58

    T114 — Steer a dns box's store-rule authorities to the proxy

    slice: S16
    kind: task
    covers: BEP-062
    size: M
    est: 140 code, 180 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: a dns-steered box with a store reference resolves its rule's upstream, such as api.openai.com, to the proxy's address while the proxy listens, and gets SERVFAIL while it does not, pinned by store_authority_of_a_dns_box_resolves_to_the_proxy
    observability: the gate's existing debug line for a steered answer names the box and the steered name
    layers:
    - policy:
    - crates/boxes/src/lib.rs: CredentialedUpstream carries the upstream authorities of the store rules the creation accepted, written only when the box has one, so a daemon that predates the field still reads a policy without store references
    - crates/minimal/src/cmd/boxes/create.rs: creation puts each accepted store rule's upstream into the box's policy beside its grants (#2575)
    test: cargo nextest run -p minimal accepted_store_rule_authorities_reach_the_box_policy
    - gate:
    - crates/minimald/src/net/switch.rs: the steered set of a dns or both box is the GitHub module's hosts and the store-rule authorities its policy carries, in place of credential_module_hosts() alone; the full literals of CredentialedUpstream in this file's tests gain the field
    - crates/minimald/src/net/dns_gate.rs: the full literals of CredentialedUpstream in this file's tests gain the field
    - crates/minimald/src/box_manager/tests.rs: the full literals of CredentialedUpstream gain the field
    test: cargo nextest run -p minimald store_authority_of_a_dns_box_resolves_to_the_proxy
    produces: none
    consumes: none
    proof: A dns-steered box that references a stored key resolves its rule's upstream to the proxy, and the request carries the stored key while the key never enters the box.
    depends on:
    - blocked by T113
    - blocked by T98

    T115 — DNS steering through the box-zone resolver, end to end

    slice: S16
    kind: integration
    covers: none
    size: M
    est: 0 code, 200 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: each case prints PASS or NOT RUN with its gate
    observability: none beyond the cases' own output
    layers:
    - e2e:
    - scripts/session-e2e.sh: one walk over the slice on a VM host: a grant box with steering = "dns" and no proxy variables clones a private repository through the proxy, a store reference's upstream resolves to the proxy and its request carries the stored key, and with the proxy stopped both names get SERVFAIL; NOT RUN where its gate is missing
    test: ./scripts/session-e2e.sh bep_dns_steering_through_the_box_zone_resolver
    produces: none
    consumes: none
    proof: On a host whose switch resolves box names, a box with dns steering reaches GitHub and its stored-key upstreams through the proxy with no proxy variables, and those names fail closed while the proxy is down. Its tasks re-cover requirements whose first tasks merged with the steering wired and the refusal still in place; the owner planned them into this epic on 2026-10-31 so they dispatch as one slice.
    depends on:
    - blocked by T113
    - blocked by T114
    - blocked by T116

    T116 — Carry dns-steered TLS to the proxy on port 443

    slice: S16
    kind: task
    covers: BEP-080
    size: L
    est: 320 code, 380 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: min box audit <box> shows a request admitted by its SNI authority on port 443, and a refused one marked off_module; the proxy's debug log names the entry form; a dns-steered GitHub grant box on a VM host is created, where before it was refused with exit 3 (#2805)
    observability: the proxy's box_connection span and its debug line carry the entry form, connect or direct_tls, beside the authority; the per-request audit record keeps the BEP-039 fields
    layers:
    - proxy:
    - crates/bep/src/proxy.rs: a connection whose first bytes are a TLS ClientHello, not a CONNECT or an absolute-form request, takes its authority from the SNI and enters the same termination, substitution and egress re-check a CONNECT to that authority enters; no SNI, or an SNI outside the module host sets and the registered store authorities, is refused off_module
    test: cargo nextest run -p bep direct_tls_on_443_routes_by_sni
    test: cargo nextest run -p bep direct_tls_without_admitted_sni_is_refused_off_module
    - listener:
    - crates/switch/src/bep_host.rs: the proxy's address takes TCP connections on port 443 beside PROXY_PORT, delivered to the proxy with the same box attribution, and the pool counts both ports as one lane
    test: cargo nextest run -p switch proxy_address_delivers_port_443_with_box_attribution
    - relay:
    - crates/boxes/src/core/egress.rs: proxy_lane_admits admits TCP 443 at the proxy's address for a box that declares a credentialed upstream, beside the listener's port, and nothing else; UDP 443 there stays rejected
    test: cargo nextest run -p boxes proxy_lane_admits_tls_443_and_listener_port_only
    - activation:
    - crates/boxes/src/core/bep.rs: the interim refusal DnsWithoutDirectTls and its test go, so dns is admitted wherever the host fact box_zone_resolver is true
    test: cargo nextest run -p boxes dns_steering_is_admitted_with_the_resolver_and_no_interim_refusal
    produces: none
    consumes: none
    proof: On a VM host, a box with steering = "dns" and no proxy variables opens TLS to github.com, the proxy routes it by its SNI and substitutes the sealed value, and a ClientHello naming another host is refused and audited off_module.

    T117 — Give a host-address grant box an id and open the proxy lane to its cohort

    slice: S17
    kind: task
    covers: BEP-082, BEP-083
    size: L
    est: 480 code, 580 tests
    executor: agent
    difficulty: unknown ground
    model: strongest
    diagnostics: min box show <box> shows the host-minted box id of a host_ip grant box, and min box audit <box> reads its records by that id; a host_ip grant box on a VM host is no longer refused with "no Box Egress Proxy serves boxes on this machine"; min session policy <box> shows the credentialed-upstream row for a host_ip grant box, and the gate's drop log names ProxyLane for a cohort connection while no resident member declares a lane
    observability: the existing info line of each registration names the box id, the network mode and whether a switch address was allocated, and the gate's existing debug line for a host-address verdict names the lane state of the cohort and the count of members that declare one
    layers:
    - registry:
    - crates/minvmd/src/control.rs: RegisterBox accepts a host_ip box and returns a creation-unique box id with no switch address; mint_box_id_reply answers that id when host_ip_name names the registration, claimed by one create_token as claim_row_box_id claims a row's id, another token refused, and a name with no registration keeps the fresh mint
    - crates/minvmd/src/box_registry.rs: a host_ip registration records the minted id with its registration name and no row in the address table, answers a lookup by that name, and its revocation check is the one an own_ip id takes
    test: cargo nextest run -p minvmd host_ip_box_registration_mints_a_creation_unique_id
    - client:
    - crates/minimal-client/src/box_registration.rs: register_box_for_activation registers a host_ip box on a VM host when its spec declares a credentialed upstream, and still returns None for one that declares none
    - crates/minimal/src/cmd/boxes/create.rs: proxy_box_facts gets the minted id for a host_ip grant box, so the NotServed refusal prints only where no proxy serves the host
    test: cargo nextest run -p minimal host_ip_grant_box_on_vm_host_is_not_refused_as_unserved
    - rpc:
    - crates/minimald-rpc/src/lib.rs: HostIpBoxRequest carries whether the box declares a credentialed upstream, and MintBoxIdRequest carries host_ip_name, defaulted and skipped when absent
    - crates/minimald-rpc/src/tests.rs: a round-trip test pins the new field and its default for an older client
    test: cargo nextest run -p minimald-rpc host_ip_box_request_carries_the_lane_declaration
    - client:
    - crates/minimal-client/src/attach.rs: AdmitHostIpBox sends the lane declaration, and the admission completes before the box's first process starts, not after the create
    test: cargo nextest run -p minimal-client host_ip_admission_carries_the_lane_and_waits_for_the_answer
    - gate:
    - crates/minvmd/src/box_registry.rs: HostIpBoxRules carries the lane bit, and the cohort's lane is the union of its resident members' bits
    - crates/minvmd/src/net/egress_gate.rs: host_ip_verdict admits the cohort address to the proxy's listener port and to TCP 443 at the proxy's address while the cohort's lane is open, and drops it as ProxyLane while it is closed
    test: cargo nextest run -p minvmd host_ip_cohort_reaches_proxy_while_a_member_declares_a_lane
    test: cargo nextest run -p minvmd host_ip_cohort_proxy_lane_dropped_without_a_declaring_member
    - identity:
    - crates/minimald/src/box_manager.rs: the box id mint for a HostNet box with no box addresses sends its creation name as host_ip_name, so the record takes the registration's id
    test: cargo nextest run -p minvmd host_ip_box_record_takes_the_registration_id
    produces:
    - the cohort's proxy lane at the host gate
    consumes: none
    proof: On a Mac, a GitHub grant box started with --network host_ip is created with a box id and reaches the proxy's listener, and after it ends, a host-address box with no grant cannot reach the proxy's address.

    T123 — Revoke a host-address grant box's minted id at the proxy when the box ends

    slice: S17
    kind: task
    covers: BEP-043
    size: S
    est: 60 code, 120 tests
    executor: agent
    difficulty: routine
    model: standard
    diagnostics: min box audit <box> for an ended host_ip grant box shows the revocation of its id, as it does for an ended own-address box
    observability: the existing bep-revocation thread's warning names the box id and the step that failed, for a host_ip box as for an own-address box
    layers:
    - revoke:
    - crates/minvmd/src/box_registry.rs: retire_host_ip_box sends spawn_revocation for the box's minted id when the retired box declared a credentialed upstream and the id is one HostIpIds minted, as the own-address destroy does (BEP-043); the id stays in the id set so it never returns
    test: cargo nextest run -p minvmd retiring_a_host_ip_grant_box_revokes_its_minted_id
    test: cargo nextest run -p minvmd retiring_a_host_ip_box_with_no_grant_sends_no_revocation
    produces:
    - the revocation of an ended host-address grant box
    consumes: none
    proof: After a host-address grant box ends, the proxy refuses a sealed value under its id, and min box audit shows the revocation.
    depends on:
    - blocked by T117

    T124 — Refuse a host-address box with no grant the proxy's address inside the VM

    slice: S17
    kind: task
    covers: BEP-083
    size: L
    est: 300 code, 420 tests
    executor: agent
    difficulty: unknown ground
    model: strongest
    diagnostics: inside a host_ip box with no grant on a Mac, a TCP connect to the proxy's address on its listener port or on 443 fails at once with "No route to host", while a host_ip grant box in the same VM connects; the VM's kernel log carries the classifier's rate-limited "minimal-classifier: refused" line for the refused connect
    observability: one info line per change of the classifier's proxy-lane set names the box id, its leaf and the lane members, as the UDP 443 set's change line does; the table's existing refusal counter counts each refused connect; the host gate's existing debug line for a cohort frame to the proxy's address still names the declaring members
    layers:
    - spec:
    - docs/specs/24-spec-box-egress-proxy/24-spec-box-egress-proxy.md: BEP-083 gains a sub-requirement that refuses, inside the VM, a resident member that declares no credentialed upstream the proxy's listener and TCP 443, verified by the kernel test below
    - table:
    - scripts/install-host-classifier.sh: a VM-guest-only --proxy-lane ADDR:PORT renders a set bep_lane_leaves of box leaves and an output rule that refuses a new TCP connection from the boxes subtree to ADDR at PORT or 443 unless the socket's leaf is in the set, with the deny chain's counter, log prefix and reject; the helper takes lane-add and lane-remove for that set as it takes add and remove for the UDP 443 set
    - crates/minimald/src/net/classifier.rs: GuestRender carries the proxy's listener, and render_guest_ruleset_over passes --proxy-lane; the cohort records each member's lane bit, a member that declares a credentialed upstream has its own leaf in bep_lane_leaves while resident and stays in its own leaf when the cohort blocks UDP 443, a member that declares none is never listed, and injection_leaf answers none for a lane member
    - crates/minimald/src/main.rs: the guest's render names DEFAULT_SUBNET.box_egress_proxy_address() and PROXY_LISTENER_PORT as the proxy's listener
    - crates/minvmd/tests/guest_classifier_integration.rs: native_ruleset_digest passes the same --proxy-lane, so the guest's loaded table still digests equal to the native render
    test: cargo nextest run -p minimald guest_render_refuses_the_proxy_address_outside_the_lane_set
    - cohort:
    - crates/minimald/src/box_actor/host.rs: HostMoves changes the lane set through the helper's new verbs, and join_quic443_cohort passes the record's lane bit to the cohort
    test: cargo nextest run -p minimald lane_less_cohort_member_stays_out_of_the_proxy_lane_set
    - kernel:
    - crates/minimald/tests/netns_root_integration.rs: a leg that loads the table rendered with --proxy-lane at a namespace address, places two members in their own leaves with only the first in the lane set, and shows the first's connects to the listener port and to 443 accepted and the second's refused with EHOSTUNREACH, both while the cohort blocks UDP 443 and while it does not
    - crates/minvmd/src/net/egress_gate.rs: the interim paragraph of host_ip_verdict's doc comment goes; the gate logic stays as the lane's host half
    test: cargo nextest run -p minimald --run-ignored ignored-only only_the_declaring_cohort_member_reaches_the_proxy_address
    produces:
    - the in-VM refusal of a host-address box with no grant at the proxy's address
    consumes:
    - the cohort's proxy lane at the host gate
    proof: On a Mac, while a GitHub grant box runs with --network host_ip, a second host-address box with no grant cannot connect to the proxy's address, and the grant box still reaches the proxy's listener.
    depends on:
    - blocked by T117

    T125 — Register each fresh host-address grant box under a name of its own creation

    slice: S17
    kind: task
    covers: BEP-082
    size: M
    est: 90 code, 260 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: none: no verb on this tree creates a fresh-instance box that declares a credentialed upstream, so nothing new prints until one does; then min box show <entry>-1 and min box show <entry>-2 print different box ids, and a second create under a fixed name a running box holds still exits 2 naming the holder
    observability: the VM host daemon's existing "registered box with the VM host daemon; a host_ip box gets an id and no address" line names the creation key, and the in-VM daemon's existing "took the box id the VM host daemon minted" line names it as host_ip_name beside the <base>-<n> name
    layers:
    - rpc:
    - crates/minimald-rpc/src/lib.rs: BoxConfig carries host_ip_registration, the name a host_ip grant box registered under when it is not the box's creation name, defaulted and skipped when absent
    - crates/minimald-rpc/src/tests.rs: a round trip pins the field, and its absence from an older client's config
    test: cargo nextest run -p minimald-rpc box_config_carries_the_host_ip_registration_name
    - client:
    - crates/minimal/src/cmd/boxes/create.rs: activate_box registers a fresh-instance host_ip grant box under <base>~<random hex>, a key no box name can spell, carries it on the config, and withdraws by it on a failed create; a create that is not a fresh instance keeps created_box_name
    test: cargo nextest run -p minimal fresh_instance_grant_box_registers_under_a_creation_key
    - identity:
    - crates/minimald/src/box_manager.rs: host_ip_mint_name sends host_ip_registration when the config carries one, else the creation name, so the mint after the <base>-<n> rename finds the registration
    - crates/minimald/src/box_manager/tests.rs: the mint test drives a fresh-instance config through the <base>-<n> rename
    test: cargo nextest run -p minimald fresh_instance_mint_finds_its_registration_after_the_rename
    - registry:
    - crates/minvmd/src/control.rs: a test drives two concurrent RegisterBox asks for one base under two creation keys, both mints by those keys and both admissions, and a second register of the fixed name, which is refused
    test: cargo nextest run -p minvmd concurrent_fresh_instance_grant_boxes_get_distinct_ids
    produces: none
    consumes: none
    proof: Two fresh instances of one host-address grant entry started at once each run under their own box id, and a second create under a held fixed name is still refused.
    depends on:
    - blocked by T117

    T126 — Keep a host-address grant box's minted id across a VM host daemon restart, so its destroy revokes it

    slice: S17
    kind: task
    covers: BEP-043
    size: M
    est: 80 code, 180 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: min box audit <box> for a host_ip grant box destroyed after a VM host daemon restart shows the revocation of its id, as it does with no restart between; the ids sit in box-host-ip-ids.json beside box-registry.json in the VM host daemon's state dir
    observability: the existing bep-revocation thread's warning names the box id and the step that failed; a failed write of the id file is a warn line naming the path, as a failed write of the held names is; the reload logs one info line with the count of minted and credentialed ids it read
    layers:
    - persist:
    - crates/minvmd/src/box_registry.rs: HostIpIds gains credentialed, the minted ids admitted with a credentialed lane, in place of stopped_credentialed, and a destroy revokes an id in it; ids and credentialed persist to a new HOST_IP_IDS_FILE beside REGISTRY_FILE after each change through write_registry_file, and persisting_to reloads them, so a reloaded id is held for the collision check and revoked at its box's destroy; an unusable file is set aside and reloads no id
    test: cargo nextest run -p minvmd restarted_registry_revokes_a_destroyed_grant_box_id
    test: cargo nextest run -p minvmd restarted_registry_never_mints_an_id_it_minted_before
    produces: none
    consumes:
    - the revocation of an ended host-address grant box
    proof: A host-address grant box destroyed after the VM host daemon restarted has its id revoked at the proxy, and min box audit shows the revocation.
    depends on:
    - blocked by T123

    T119 — Deliver cohort connections and redeem them for the named box

    slice: S17
    kind: task
    covers: BEP-084
    size: L
    est: 260 code, 360 tests
    executor: agent
    difficulty: unknown ground
    model: strongest
    diagnostics: min box audit <box> shows a host-address box's requests marked cohort_attributed under the box the sealed value names, with that box's egress verdict
    observability: the proxy's existing audit record carries the attribution, box or cohort, and the subject box id it was evaluated for
    layers:
    - delivery:
    - crates/switch/src/bep_host.rs: while the cohort's lane is open, the node address holds a share of the connection pool, box_id_for_source answers the cohort for it, and the delivery header carries the reserved cohort id, never a member's id
    - crates/minvmd/src/box_registry.rs: the box_id_for_source impl answers the cohort for the node address while a lane member is resident
    - crates/minvmd/src/cmd/run.rs: the second box_id_for_source impl answers the same
    - crates/bep/src/listener.rs: the header doc and its parse name the reserved cohort id, which maps to Attribution::HostIpCohort
    test: cargo nextest run -p switch cohort_delivery_names_no_box
    - redeem:
    - crates/bep/src/redeemer.rs: under Attribution::HostIpCohort the egress re-check and the audit subject read the box the sealed value names, which must be a live host_ip box of this host; a value naming an own_ip box, an ended box or a box of another host is refused and audited cohort_misattributed
    test: cargo nextest run -p bep cohort_redemption_uses_named_box_egress_and_subject
    test: cargo nextest run -p bep cohort_value_naming_no_live_host_ip_box_is_refused
    produces:
    - cohort-attributed delivery
    consumes:
    - the cohort's proxy lane at the host gate
    - the revocation of an ended host-address grant box
    proof: A host-address box's request through the proxy is admitted for the box its sealed value names, checked against that box's egress, and recorded in that box's audit log as cohort-attributed.
    depends on:
    - blocked by T117
    - blocked by T123

    T120 — Treat the proxy lane as in effect for a host-address box on a VM host

    slice: S17
    kind: task
    covers: BEP-085
    size: M
    est: 140 code, 200 tests
    executor: agent
    difficulty: routine
    model: standard
    diagnostics: min session policy <box> and the min dash detail pane show the credentialed-upstream row of a host_ip box on a VM host without the "(not in effect: host_ip box)" mark, and a host_ip box on a native Linux host keeps it
    observability: the launch's existing debug line for the proxy variables names the box's network mode, the host kind and whether the lane is in effect, so a host_ip box that got no proxy variables shows why
    layers:
    - rule:
    - crates/boxes/src/lib.rs: CredentialedUpstream::in_effect takes the host kind with the network mode, and is true for own_ip and for host_ip on a VM host
    test: cargo nextest run -p boxes credentialed_upstream_in_effect_by_mode_and_host
    - runtime:
    - crates/minimald/src/box_runtime.rs: launch_proxy_env reads the new rule, so a host_ip box on a VM host with proxy_env or both steering gets HTTPS_PROXY, HTTP_PROXY, NO_PROXY and NODE_USE_ENV_PROXY
    - crates/minimald/src/box_runtime/tests.rs: the test that a host_ip box on a VM host gets the four variables, and one on a native host gets none
    test: cargo nextest run -p minimald host_ip_box_on_vm_host_gets_the_proxy_variables
    - surfaces:
    - crates/minimal/src/cmd/boxes/policy.rs: the policy row reads the new rule
    - crates/minimal/tests/dash_snapshots.rs: a snapshot pins the detail pane of a host_ip lane in effect on a VM host
    - crates/minimal/src/cli.rs: the --credentialed-upstream help ends "Takes effect with --network own_ip, and with --network host_ip on a host that runs boxes in a VM." in place of "Takes effect only with --network own_ip."
    test: cargo nextest run -p minimal detail_pane_shows_a_host_ip_lane_in_effect_on_a_vm_host
    produces:
    - the in-effect rule for a host-address lane
    consumes: none
    proof: On a Mac, a host-address grant box with steering = "proxy_env" gets the proxy variables, and min session policy shows its credentialed-upstream row with no not-in-effect mark.
    depends on:
    - blocked by T117

    T121 — Steer a host-address grant box's lookups and admit dns on a VM host

    slice: S17
    kind: task
    covers: BEP-081, BEP-086
    size: L
    est: 340 code, 380 tests
    executor: agent
    difficulty: unknown ground
    model: strongest
    diagnostics: min box spec <entry> --network host_ip on a Mac shows steering = "dns" resolved and no refusal; inside a host-address grant box getent hosts github.com prints the proxy's address, and inside a host-address box with no grant it prints GitHub's real address
    observability: the steering answerer's debug line for a steered answer names the box and the steered name, as the switch's DNS gate does for an own_ip box
    layers:
    - resolver:
    - crates/minimald/src/net/provider.rs: a host_ip box with a credentialed upstream and dns or both steering gets a resolver plan that names the steering answerer; every other host_ip box keeps the resolver it has today
    - crates/minimald/src/net/dns.rs: a steering answerer in the guest's namespace answers the box's module host set with the proxy's address, NODATA for AAAA, HTTPS and SVCB, SERVFAIL while the listener is down, and forwards every other name to the node's resolver
    - crates/minimald/src/net/classifier.rs: the box's cgroup leaf may reach the steering answerer's port, and no other leaf may
    test: cargo nextest run -p minimald host_ip_grant_box_steered_names_resolve_to_proxy
    test: cargo nextest run -p minimald host_ip_lookup_outside_a_steered_box_resolves_real
    - activation:
    - crates/minimal/src/cmd/boxes/create.rs: host_resolves_box_names is true on a VM host for own_ip and host_ip boxes, so BEP-017 refuses dns and both only on a host with no VM
    - crates/minimal/src/cmd/box_spec.rs: the preview reads the same host fact for host_ip
    test: cargo nextest run -p minimal vm_host_admits_dns_and_both_for_own_ip_and_host_ip
    produces:
    - per-box dns steering for a host-address box
    consumes:
    - the in-effect rule for a host-address lane
    - cohort-attributed delivery
    proof: On a Mac, a host-address grant box with no steering line resolves github.com to the proxy and clones through it, while a host-address box with no grant resolves github.com to GitHub's real address.
    depends on:
    - blocked by T116
    - blocked by T119
    - blocked by T120

    T122 — Host-address boxes reach GitHub, end to end

    slice: S17
    kind: integration
    covers: BEP-087
    size: M
    est: 0 code, 260 tests
    executor: agent
    difficulty: needs care
    model: standard
    diagnostics: each case prints PASS, or NOT RUN with its gate where a GitHub sign-in or E2E_BEP_PRIVATE_REPO is missing
    observability: on a failure each case prints the box's min box audit records and the proxy's debug lines for the box id, so a refused or unsteered request shows where it stopped
    layers:
    - e2e:
    - scripts/session-e2e.sh: three cases on a VM host with a host_ip grant box: under the default steering, and under steering = "proxy_env", git clones and pushes and gh api reads a private repository; a second host_ip box with no grant resolves github.com to a real address in the first case; a third case proves T117 on a Mac: a GitHub grant box started with --network host_ip gets a box id from min box show and reaches the proxy's listener, and after it ends a host_ip box with no grant cannot reach the proxy's address
    test: ./scripts/session-e2e.sh bep_host_ip_git_and_gh_under_dns_steering
    test: ./scripts/session-e2e.sh bep_host_ip_git_and_gh_under_proxy_env
    test: ./scripts/session-e2e.sh bep_host_ip_grant_box_reaches_the_proxy_lane
    produces: none
    consumes:
    - per-box dns steering for a host-address box
    - the in-VM refusal of a host-address box with no grant at the proxy's address
    proof: A box started on the default host network clones, pushes and runs gh against a private repository through the proxy under its default dns steering with no proxy variables, and a host-address box with no grant still resolves GitHub's real address.
    depends on:
    - blocked by T117
    - blocked by T119
    - blocked by T120
    - blocked by T121
    - blocked by T123
    - blocked by T124
    - blocked by T125
    - blocked by T126

    Already exists

    None.

    Residue

    None.

  2. norrietaylor commented on Sep 18, 2026

    @norrietaylor
    MemberAuthor

    /dispatch dry-run

  3. minimal-foundry commented on Sep 18, 2026

    @minimal-foundry
    Contributor

    Walking, started by @norrietaylor at 2026-10-24T23:54:29.836Z.

    max parallel 16 · spend $423.39 of $2500.00 · revise cap 10 per PR

    task issue slice kind size state PR revise note
    SP1 #2257 spike done
    T1 #2258 S0 task M done #2346 8/10
    T2 #2259 S0 task L done #2363 6/10
    T3 #2265 S0 task M done #2438 9/10
    T4 #2260 S0 task L done #2359 4/10
    T5 #2266 S0 task L done #2448
    T6 #2268 S0 task L done #2450 2/10
    T7 #2270 S0 task L done #2467 6/10
    T8 #2261 S0 task L done #2362 3/10
    T9 #2262 S0 task L done #2361 6/10
    T10 #2271 S0 task L done #2475 1/10
    T11 #2272 S0 task L done #2481 6/10
    T12 #2263 S0 task L done #2364 6/10
    T13 #2264 S0 task L done #2365 3/10
    T14 #2273 S0 task L done #2490 10/10
    T15 #2267 S0 task L done #2443 2/10
    T16 #2274 S0 task L done #2495 5/10
    T17 #2269 S0 task L done #2457 7/10
    T18 #2275 S0 task L done #2497 10/10
    T19 #2276 S0 integration M done #2507
    T40 #2334 S0 task M done #2492 2/10
    T41 #2483 S0 task L done #2486 5/10
    T42 #2488 S0 task L done #2491 2/10
    T43 #2437 S0 task L done #2493 3/10
    T20 #2277 S1 task L done #2548 6/10
    T21 #2278 S1 task M done #2512 3/10
    T22 #2286 S1 task M done #2584 7/10
    T23 #2291 S1 integration M done #2610 1/10
    T24 #2279 S2 task L done #2518 3/10
    T25 #2280 S3 task L done #2597 10/10
    T26 #2287 S3 task L done #2522 9/10
    T27 #2288 S3 task M done #2520 4/10
    T28 #2292 S3 task L done #2574 10/10
    T29 #2294 S3 integration M done #2612 7/10
    T39 #2325 S3 task L done #2577 4/10
    T30 #2281 S4 task M done #2519 2/10
    T31 #2282 S4 task L done #2513 4/10
    T32 #2289 S4 integration M done #2550 1/10
    T33 #2290 S5 task M done #2583 4/10
    T34 #2293 S5 task M done #2603 3/10
    T35 #2295 S5 integration M done #2773 10/10
    T36 #2283 S6 task S done #2508 1/10
    T37 #2284 S7 task S done #2511 5/10
    T57 #2537 S8 enablement S done #2546 10/10
    T65 #2560 S8 task L done #2561 3/10
    T50 #2531 S9 task M done #2547 5/10
    T51 #2532 S9 task M done #2545
    T52 #2533 S9 task M done #2551 1/10
    T53 #2534 S9 task M done #2553 2/10
    T54 #2535 S9 task M done #2552 2/10
    T55 #2536 S9 task S done #2582 7/10
    T56 #2544 S9 integration M done #2604 6/10
    T58 #2538 S9 task L done #2567 2/10
    T59 #2539 S9 task L done #2557 2/10
    T60 #2540 S9 task M done #2568 1/10
    T61 #2541 S9 task S done #2569 1/10
    T62 #2542 S9 task S done #2581 3/10
    T63 #2543 S9 task M done #2571 1/10
    T64 #2556 S9 task S done #2570 1/10
    T67 #2586 S10 task M done #2597
    T68 #2576 S10 task M done #2596 3/10
    T69 #2579 S10 task S done #2593 1/10
    T70 #2580 S10 task S done #2592
    T71 #2578 S10 task S done #2594 9/10
    T72 #2558 S10 task M done #2601 7/10
    T73 #2559 S10 task S done #2595 1/10
    T74 #2585 S10 task M done #2611 2/10
    T75 #2588 S10 task M done #2608 1/10
    T76 #2591 S10 integration M done #2665 1/10
    T83 #2599 S10 task M done #2614 1/10
    T84 #2605 S10 task M done #2615 5/10
    T77 #2562 S11 task S done
    T78 #2563 S11 task M done
    T79 #2564 S11 task M done
    T80 #2565 S11 task M done
    T81 #2587 S11 task M done
    T82 #2500 S11 integration M human task
    T111 #2785 S11 task M done #2790 5/10
    T85 #2600 S12 task S done #2641
    T86 #2613 S12 task S done #2647
    T87 #2606 S12 task M done #2648 1/10
    T88 #2607 S12 task S done #2644 2/10
    T89 #2616 S12 task M done #2643 1/10
    T90 #2617 S12 task M done #2642 1/10
    T91 #2618 S12 task M done #2663 2/10
    T92 #2620 S12 task S done #2666 2/10
    T93 #2631 S12 task M done #2651 2/10
    T94 #2640 S12 integration M done #2673 2/10
    T95 #2653 S13 enablement S done #2660
    T96 #2655 S13 integration M human task
    T97 #2656 S13 task M done #2774 5/10
    T98 #2525 S13 task M done #2803 2/10
    T99 #2746 S13 task M done #2767 1/10
    T100 #2771 S14 enablement S done #2810
    T101 #2770 S14 enablement M done #2792
    T102 #2772 S14 integration M human task
    T103 #2462 S15 task M done #2793
    T104 #2781 S15 task M done #2800
    T105 #2782 S15 task L done #2791 1/10
    T106 #2435 S15 task S done #2809
    T107 #2460 S15 task S done #2789
    T108 #2598 S15 task S done #2788
    T109 #2783 S15 task M done #2794 3/10
    T110 #2784 S15 task M done #2801 10/10
    T112 #2786 S15 integration M done #2826 5/10
    T113 #2523 S16 task M done #2797 7/10
    T114 #2575 S16 task M done #2822
    T115 #2787 S16 integration M done #2860 1/10
    T116 #2805 S16 task L done #2860
    T117 #2854 S17 task L done #2861 10/10
    T119 #2855 S17 task L done #3065 1/10
    T120 #2856 S17 task M done #3025 1/10
    T121 #2857 S17 task L done #3079 2/10
    T122 #2858 S17 integration M done #3095 3/10
    T123 #3032 S17 task S done #3034 1/10
    T124 #3047 S17 task L done #3052 2/10
    T125 #3048 S17 task M done #3053 2/10
    T126 #3049 S17 task M done #3064 1/10
    — #2812 ? refused body is not a rendered task or spike (no '' marker and '## Task'/'## Spike' on line 3)

    Human tasks (the walk waits for these to close)

    Cross-repo blockers

    Not walked

    Updated 2026-11-06T21:36:12.518Z. Commands: /dispatch, /dispatch stop, /dispatch dry-run.

  4. norrietaylor commented on Oct 3, 2026

    @norrietaylor
    MemberAuthor

    Re-plan input: one task for BEP-018's active UDP/443 reject

    The BEP re-plan needs a task that owns BEP-018 and its two sub-requirements in spec 24. Nothing implements them today. None of the three verify tests exists on main, and the retired tree's T23 (#1527) was closed with that plan.

    • BEP-018: while a box's resolved quic443 stance is blocked, UDP to port 443 is rejected with ICMP port unreachable. Verify quic443_blocked_rejects_udp_443.
    • Sub-requirement: UDP to port 443 toward the steered proxy address is rejected with ICMP port unreachable whatever the stance. Verify udp_443_to_steered_proxy_rejected_in_every_stance.
    • Sub-requirement: the host_ip cohort blocks UDP/443 when any resident member's stance is blocked. Verify host_ip_cohort_quic_stance_most_restrictive_wins.

    Design basis: networking design §5.3 says quic443 is enforced by active reject in every stance where it is blocked, and steered h3 is rejected node-side. §5.7's determinism rule says every refusal is an active reject, never a silent timeout, so a blocked h3 attempt falls back to TCP at once.

    What the task must carry, per the architecture review:

    1. Two enforcement legs, each with its own ICMP type 3 code 3 assertion. One leg tested while the other only blocks is not acceptable.
      • Node-side, in the VM: the steered-proxy path, rejected in every stance.
      • Host-side gate: the blocked-stance resolver-bypass path. Enforcement belongs at the EGW per the policy feed; un-enrolled on a local VM, that is the host-side helpers implementing the EGW contract's semantics (design §7.1).
    2. The cohort sub-requirement names the reject form. Its text says "block". Since it sits under BEP-018, its test asserts ICMP port unreachable, not just that nothing gets through.
    3. The ICMP builder. The task reintroduces the shared ICMP port-unreachable builder in crates/switch/src/refusal.rs and emits through that file's shared refusal emitter and line format. NET's T70 (#1800) removed the builder because v1 ingress has no UDP caller; this task is its first consumer.
    4. The client-side proof. A QUIC client sees port unreachable within a short bound, not a timeout.

    Depends on T70 #1800 for refusal.rs.

  5. norrietaylor commented on Oct 4, 2026

    @norrietaylor
    MemberAuthor

    Rollout note for whoever extends the proxy document: the session policy's credentialed-upstream declaration (CredentialedUpstream, added by T45 in #1885) is deny_unknown_fields, as SessionPolicy is. Adding a field to it therefore needs a lockstep rollout: a client that sends the new field to an older guest daemon is refused at activation. That fails closed, which the design accepts, but plan the client and daemon release together.

  6. norrietaylor commented on Oct 4, 2026

    @norrietaylor
    MemberAuthor

    When the real Box Egress Proxy acceptor lands, run the session e2e case proxy_sees_boxes_by_address (#1930, NET walk T46) against it unchanged. Today that case proves box attribution, per-box attachments and lane-only reach against the MINVMD_BEP_STUB stand-in acceptor. It does not cover the real acceptor's own checks. The task that ships the acceptor should list that case among its test lines.

  7. 56 remaining items

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions

      0