Gate execution targets a surface through a kernel selector and the topology placement seam
Filename: 2026-08-13-3-gate-execution-targets-through-kernel-surface-selector.md
Status: Proposed
Date: 2026-08-13
Deciders: Operator (ATDD)
Technical Story: Depends on ADR 2026-08-13-2. A declarative gate that executes an asset needs a defined working directory / execution context. Rather than a free-form enum, this reuses the existing placement seam.
Post-dialectics revision (2026-08-13)
Hardened after the dialectics pass (work/gate-design-dialectics/02-..., 99-COHERENCE.md). Settled deltas:
- CHANGE — no kernel elevation. Doctrine only needs to validate a token string, so
doctrine owns the surface-selector
frozenset[str](it owns the gate schema anyway), specify_cli owns the resolver map, and a parity test binds them. Layer directionality is satisfied with zero kernel change. Option C below (shipMissionTopologyinto doctrine) and the "elevate a vocabulary into kernel" framing are superseded by this. - CHANGE —
execute_dir≠read_dir. It must reuse the existing stampedGateExecutionContext(build_gate_execution_context, honouringsurface_cannot_hold), never return a barePath— a bare workdir strips the surface stamp and reopens the #2885/#1834 pass-by-default-against-empty-tree failure.laneadditionally needs awp_idand the materialization-awareresolve_workspace_for_wp(a different resolver from reads). - CLARIFY —
repo_rootis a loud, non-default escape hatch ("judge the ambient checkout"), never the default selector.
Context and Problem Statement
A gate that runs a check needs to know where it runs — the repo root, the mission's primary planning tree, the coord status tree, or a WP lane worktree. Which physical tree each of those is depends on the mission's topology.
That mapping already exists. The placement seam
(docs/architecture/artifact-placement-seam.md) resolves (MissionArtifactKind, MissionTopology, materialization) → a TopologySurface (src/mission_runtime/artifacts.py:22)
→ a physical tree, via PlacementSeam (src/mission_runtime/resolution.py:1373). It answers
read_dir and write_target today. It does not answer "execute in".
MissionTopology (src/mission_runtime/context.py:55) is the 2×2 coord×lanes grid
(SINGLE_BRANCH / LANES / COORD / LANES_WITH_COORD). It lives in mission_runtime,
which is above doctrine in the layer graph (kernel ← doctrine ← charter ← runtime).
A doctrine-layer gate schema that referenced it would be an upward import — a layering
violation.
Decision Drivers
- One mechanism for "where to look / write / run" — do not fork a second workdir resolver.
- Respect layer directionality (doctrine must not import upward into
mission_runtime). - Keep the doctrine schema's dependency footprint minimal.
Considered Options
- A — Free-form workdir enum on the gate (
repo_root|mission_dossier|wp_worktree), resolved by a new gate-local helper. - B — Surface selector routed through the placement seam. The gate declares an
executionTargetfrom a small kernel-owned surface-selector vocabulary; the topology-aware seam gains anexecute_dirverb that resolves it. - C — Ship the whole
MissionTopologyenum down intodoctrineso the gate schema types the field against it directly.
Decision Outcome
Chosen option: "B".
- A gate carries
executionTarget: <selector>where the selector comes from a small surface-selector vocabulary elevated intokernel(e.g.primary|coord|lane|repo_root). Kernel is the root layer, sodoctrinemay import it without violating directionality.src/kernel/already hosts this class of shared primitive (paths.py,clock.py). - The placement seam gains a third verb,
execute_dir, over the same(surface, topology, materialization)inputs it already uses forread_dir/write_target. The topology math stays inmission_runtime; doctrine only names a selector. - Option C is rejected — it drags a runtime-shaped enum into the doctrine layer. Option A is rejected — it forks a second "where" resolver the placement seam already owns.
This subsumes the gate's working-directory question into the one partition decision every
other mission artifact already routes through: in SINGLE_BRANCH all selectors collapse to
primary; in LANES_WITH_COORD coord and lane diverge, exactly as reads/writes do.
Consequences
Positive
- Gate execution location is topology-correct for free, and consistent with read/write placement.
- Elevating the selector strengthens the seam (the placement doc notes callers that still bypass it).
Negative
- Elevating a vocabulary into
kernel+ adding a seam verb is cross-cutting (touches kernel, the placement seam, and the gate schema) — larger than a gate-local field. - The selector vocabulary and
MissionTopology/TopologySurfacemust be kept in sync; a contract test is required to prevent drift.
Neutral
- Whether the elevated vocabulary is a brand-new selector enum or a subset of the existing
TopologySurfaceis a design detail (open Q).
Open questions (for the dialectics squad)
- New kernel selector enum, or elevate/reuse
TopologySurfaceitself? - Is
execute_dirtruly the same resolution asread_dir, or does execution need a distinct materialization rule (e.g. must the lane worktree already exist)? - Do gates ever need to run outside any mission surface (e.g. repo-root global checks), and
is
repo_roota first-class selector or an escape hatch?