Mission Specification: Custom mission types are second-class citizens

Mission Branch: fix/custom-mission-type-second-class-3830 Created: 2026-09-02 Status: Draft Input: User description: "Three related GitHub issues (#3830, #3831, #3832), all confirmed live on main at eff63532607d2f292efc16e3f8bfd0e61e0544aa, showing that non-built-in (custom) mission types are treated as second-class citizens across three independent subsystems of spec-kitty's own runtime: composition dispatch cannot resolve profile_hint for a custom mission's canonical actions, the legacy mission loader never consults the org tier so a custom mission type is silently replaced by software-dev, and the plan-substantive gate applies a software-dev-shaped Technical Context check to every mission type regardless of what that type's own plan template asks for."

Summary

Three defects, one theme, one PR: spec-kitty's own runtime machinery — the tooling every downstream mission (including this one) depends on — special-cases software-dev at three disjoint points, so a mission type that is not software-dev either cannot advance, is silently misidentified, or is held to a gate it cannot satisfy even when following its own pack's instructions. All three were reproduced first-hand driving a real qa-type mission (int-e2e-qa-run-01M1F2N8) and are recorded in the spec-kitty tooling defect ledger as SK-126 (#3830), SK-128 (#3831), and SK-129 (#3832).

Refined scope note (per operator verification, see Clarifications): the #3832 defect is not confined to custom mission types — it also breaks three of the four built-in mission types (documentation, research, plan), because only software-dev's own plan template scaffolds a Language/Version field. This mission states that corrected scope explicitly; it does not change the fix shape.

User Scenarios & Testing (mandatory)

User Story 1 - A custom mission type cannot advance past its first step (Priority: P1)

As the author/user of a custom (non-built-in) mission type, when I run spec-kitty next on a mission of my custom type and the action being dispatched is one of my own mission type's canonical action_sequence actions, I want profile-hint resolution to succeed (by consulting my mission's own PromptStep.agent_profile, per the FR-008-mandated path) so that the mission can advance past its very first step, instead of failing with profile_hint is required when no action default exists for <type>/<action>.

Why this priority: This is a hard blocker — without it, a custom mission type cannot execute a single control-loop transition through spec-kitty next, regardless of any other fix in this mission. Nothing downstream of step 1 is reachable.

Independent Test: Drive a mission of a non-built-in mission type (e.g. qa) through spec-kitty next for an action present in that type's action_sequence (true of every canonical step) and confirm the composition succeeds and dispatches using the mission's own PromptStep.agent_profile/pack-declared agent-profile: mapping — not a blocked result citing _ACTION_PROFILE_DEFAULTS.

Acceptance Scenarios:

1. Given a custom mission type qa whose mission-runtime.yaml declares real agent-profile: entries (e.g. researcher-robbie) for its steps, When spec-kitty next dispatches an action that is a member of qa's own action_sequence (e.g. discovery), Then _composition_dispatch_inputs resolves profile_hint via _resolve_step_agent_profile/PromptStep.agent_profile and the mission advances, instead of returning (None, None) and later raising StepContractExecutionError from _resolve_profile_hint. 2. Given the same scenario, When the composition-dispatch code path executes, Then no error is silently swallowed by a bare except Exception: pass that also hides genuine resolve_mission_type_context failures — a real resolution failure must be distinguishable from "this action is in my own sequence, blank the hint" (the current code conflates the two). 3. Given an action for software-dev, research, or documentation (the built-in types with _ACTION_PROFILE_DEFAULTS entries), When the same dispatch path executes, Then behavior is unchanged — built-in defaults still resolve exactly as before (see NFR-001). 4. Given the fourth built-in type, plan, dispatching an action that IS a member of plan's own action_sequence (action_sequence is non-empty for plan by construction, locked by the golden-parity suite), When the same composition-dispatch path executes, Then this fix does not change plan's existing failure mode: StepContractExecutor.execute still raises StepContractExecutionError with "No step contract found for mission/action plan/<action>" (verified: no built_in_step_contracts/*.step-contract.yaml declares mission: plan, and _ACTION_PROFILE_DEFAULTS has no plan entries) — a different, pre-existing failure mode from #3830's profile_hint is required, on the same hot path, that this fix must leave unaltered (registering step contracts for plan is explicitly out of this mission's scope). Drift note: _should_dispatch_via_composition's own header comment (runtime_bridge_composition.py) and _dn_composition_dispatch's docstring (runtime_bridge.py) both currently claim dispatch is "hard-guarded on mission == 'software-dev'" — verified against the live code, this is stale: _should_dispatch_via_composition returns True whenever the normalized step_id is a member of the RESOLVED mission type's own action_sequence, for any mission type, not only software-dev. An implementer must not trust that comment; the actual as-shipped gate is action_sequence membership, which is why plan (and any custom type) already reaches this path today.


User Story 2 - An org-tier custom mission type must not be silently replaced by software-dev (Priority: P1)

As the author/user of a mission type shipped and activated through an org-tier doctrine pack (.kittify/config.yaml mission_type_activations, org: pack entry, no .kittify/missions/<name> project-tier directory), when spec-kitty resolves the legacy Mission/mission.yaml object for my mission (get_mission_for_feature / _mission_path_by_name), I want the org tier to be consulted (or, if a plan-phase investigation determines the legacy schema cannot represent org-tier mission types without a bridge, I want a loud, actionable signal instead of a silent substitution) so that my mission is never evaluated against software-dev's path conventions and artifact sets without my knowledge.

Why this priority: Silent misidentification corrupts every downstream Mission-object consumer (path-convention evaluation, optional-artifact enumeration) with no operator-visible signal — this is a correctness and trust defect, not merely a missing capability. See Clarifications / Decision 2 for the research-phase checkpoint governing exactly how far this mission's fix reaches.

Independent Test: Activate a custom mission type (e.g. qa) at the org tier only (no project-tier .kittify/missions/qa/) in a synthetic consumer project, call get_mission_for_feature/_mission_path_by_name for a mission whose meta.json declares that type, and confirm the resolver either (a) finds and loads the org-tier mission type, or (b) fails loudly (not via a warnings.warn that never reaches CLI output) rather than silently substituting software-dev's Mission object.

Acceptance Scenarios:

1. Given a project with qa activated only at the org tier and no .kittify/missions/qa/ project-tier directory, When _mission_path_by_name("qa", kittify_dir) is called, Then the resolution outcome (found via a new org-tier consultation, or an explicit non-silent failure) is determined by the plan-phase decision point in Clarifications — but it must NOT be the current behavior of returning None, being swallowed by except MissionNotFoundError, and falling back to software-dev behind an invisible warnings.warn. 2. Given the same setup, When the fallback (if any remains) does occur, Then the signal is visible in normal CLI operation (not solely a Python warnings call filtered out by default), so an operator can tell their mission was evaluated against the wrong conventions. 3. Given a built-in mission type (software-dev, research, documentation, plan) or a project-tier custom mission type, When the same resolver runs, Then behavior is unchanged — the existing two-tier lookup still finds them exactly as before (see NFR-002).


User Story 3 - A mission type's plan artifact must be checked against its own template's substantive-content shape, not software-dev's (Priority: P1)

As the author/user of any mission type whose own plan template does not scaffold a ## Technical Context section with a Language/Version field (this includes the custom qa type and the built-in documentation, research, and plan types — see Clarifications / Decision 1's verified table), when setup-plan checks whether my plan.md is "substantive" before allowing the mission to proceed, I want the required fields to be derived from my mission type's own plan template, not from a hardcoded software-dev-shaped check, so that a plan.md written exactly as my type's own template instructs is never rejected as non-substantive.

Why this priority: This is "a check that cannot pass" — for the qa type (and, per the corrected scope, documentation/research/plan too where applicable) the gate demands content the mission type's own template neither has nor should have. It blocks legitimate mission progress at the plan phase for every affected type, independent of whether #3830/#3831 are also fixed.

Independent Test: Author a plan.md for a mission type whose own template has no Language/Version field (e.g. qa, whose missions/qa/templates/test-plan-template.md has eight sections — Test Items, Environments, Test-Data Strategy, Suite Breakdown, Tooling, Schedule, Responsibilities, Traceability-Matrix Skeleton — none of them Technical Context), fill it in completely and substantively per that template, and confirm mission_setup_plan.py's is_substantive(plan_file, "plan") call sites accept it without requiring a fabricated software-dev section.

Acceptance Scenarios:

1. Given a qa-type mission with a plan.md fully and substantively populated per the QA pack's own test-plan-template.md (no Technical Context/Language/Version present, by design), When is_substantive(plan_file, "plan") runs, Then it derives its required fields from the qa mission type's own plan template and returns True, instead of requiring a Language/Version field that template never asks for. 2. Given a software-dev-type mission with a plan.md fully and substantively populated per its own plan-template.md (## Technical Context with Language/Version plus a peer field), When the same check runs, Then it returns True. 3. Given a software-dev-type mission with a plan.md missing ## Technical Context/Language/Version (or carrying only placeholder content), When the same check runs, Then it still returns False — the fix must not weaken the existing, correct software-dev behavior (see NFR-003). 4. Given the fix, When a reviewer inspects mission_check_prerequisites.py:364's existing mission_type != "software-dev" guard, Then that name-based, all-or-nothing guard is either replaced by the same template-derived mechanism or reconciled with it — the mission must not leave one call site template-derived and a sibling call site still name-based-blanket (an architectural-gate-non-vacuity concern; a gate that is skipped entirely for "not software-dev" cannot fail for those types, which is itself a defect class per the charter's architectural gate discipline). 5. Given a documentation-type mission with a plan.md fully and substantively populated per documentation-plan-template.md's own peer fields (Documentation Framework / Languages Detected / Output Format / Hosting Platform / Build Commands — no Language/Version, by design), When the check runs, Then it derives its required fields from documentation's own resolved plan template and returns True, instead of requiring Language/Version, which that template never scaffolds (see NFR-004). 6. Given a documentation-type mission with an unfilled/placeholder-only documentation-plan-template.md scaffold, When the check runs, Then it returns False (see NFR-004, NFR-005). 7. Given a research-type mission with a plan.md fully and substantively populated per research-plan-template.md's own scaffolded fields (Research Context / Methodology / Data Sources), When the check runs, Then it returns True; Given an unfilled/placeholder-only research-plan-template.md scaffold, When the check runs, Then it returns False (see NFR-004, NFR-005, Decision 3). 8. Given a plan-type mission with a plan.md fully and substantively populated per plan-plan-skeleton.md's own scaffolded fields (Problem Decomposition / Scope — MoSCoW / Sequencing & Prioritisation / Decisions), When the check runs, Then it returns True; Given an unfilled/placeholder-only plan-plan-skeleton.md scaffold, When the check runs, Then it returns False (see NFR-004, NFR-005, Decision 3).


Edge Cases

  • What happens when a custom mission type's action_sequence (Story 1) legitimately fails to resolve via resolve_mission_type_context for an unrelated reason (e.g. a malformed org pack)? The fix must not conflate "resolution failed" with "action is in my own sequence, blank the hint" — these must be distinguishable outcomes, not both swallowed by the same bare except Exception: pass.
  • What happens when an org-tier mission type (Story 2) and a project-tier mission type of the same name both exist? Precedence must be defined and tested (existing two-tier precedence — project over built-in — must not be silently altered by adding a third tier).
  • What happens when a mission type's own plan template (Story 3) is malformed, missing entirely, or unresolvable (e.g. a custom type declares no plan template at all)? The template-derived substantive check must fail closed (never silently pass) rather than crash or falsely approve.
  • What happens to a mission type that scaffolds ## Technical Context but with different peer fields than software-dev (e.g. documentation's Documentation Framework / Languages Detected / Output Format / Hosting Platform / Build Commands, none of which is Language/Version)? Per the corrected scope (Decision 1), this type is also currently broken by #3832 and must be fixed identically — its own template's fields, not software-dev's, define "substantive."
  • What happens for research and plan mission types, whose templates scaffold neither Technical Context nor any Technical-Context-shaped peer field? Resolved by Clarifications / Decision 3: a "documented neutral pass" (always-substantive) is explicitly REJECTED, since it would structurally exempt these two types from NFR-005's non-vacuity requirement. FR-006's template-derived check instead uses research's own scaffolded fields (Research Context / Methodology / Data Sources) and ALL FOUR of plan's own scaffolded fields (Problem Decomposition / Scope — MoSCoW / Sequencing & Prioritisation / Decisions) as the substantive-content criterion, combined under the primary-plus-peer AND/OR combination rule (see FR-006, Decision 3). Not "always fails" and not "always passes." How the check detects substantiveness for each field's shape is an open design question deferred to the plan phase — see "Deferred to Plan Phase" below. See User Story 3 Acceptance Scenarios.
  • What happens to the pre-existing, known-red test baseline (GitHub issue #3284: 23 failures + 2 errors, plus the shared test-venv lock GitHub issue #3283) while this mission runs? Every red encountered must be classified against that baseline per the charter's Pre-existing Failure Reporting Rule — a red not already covered by GitHub issue #3284 must be filed as its own issue before being treated as baseline, never bundled into "the suite is red" as an unexamined finding.
  • What happens to a mission already mid-flight when this fix lands — blocked by #3830's profile_hint is required failure, silently mis-evaluated under #3831's software-dev substitution, or rejected/passed by #3832's software-dev-shaped plan-substantive gate? Re-running spec-kitty next on a previously #3830-blocked mission is the expected recovery path — the fix changes profile_hint resolution and the plan-substantive check, not persisted run state, so no run/state.json/lane-state migration is required. This mission does not retroactively re-validate past runs; for a mission possibly mis-evaluated under #3831's substitution, FR-005's loud fallback signal (once live) is the operator-visible cue to identify which past missions were affected and re-check them manually, rather than an automatic re-validation step. See C-007/SC-005 for the separate, pre-existing test-baseline handling.

Requirements (mandatory)

Functional Requirements

IDTitleUser StoryPriorityStatus
FR-001Composition dispatch must not blank profile_hint for a custom mission type's own canonical actionsAs a custom-mission-type user, I want _composition_dispatch_inputs (src/runtime/next/runtime_bridge_composition.py) to consult _resolve_step_agent_profile/PromptStep.agent_profile when the dispatched action is a member of the mission type's own action_sequence, instead of returning (None, None), so the FR-008-mandated resolution path (src/specify_cli/mission_step_contracts/executor.py lines 68-71) is actually reachable. Refs #3830.HighOpen
FR-002Distinguish a genuine resolve_mission_type_context failure from the "action in own sequence" branchAs a maintainer, I want the bare except Exception: pass around resolve_mission_type_context in _composition_dispatch_inputs to stop silently swallowing real resolution errors together with the (now-removed) blank-hint branch, so a malformed mission type surfaces a diagnosable error rather than a mysterious downstream profile_hint is required failure. Refs #3830.HighOpen
FR-003_ACTION_PROFILE_DEFAULTS in executor.py remains the built-ins-only table its FR-008 comment mandatesAs a maintainer, I want the fix for FR-001 to resolve custom-mission profile hints via PromptStep.agent_profile / pack-declared agent-profile: entries — the single canonical authority for custom-mission profile resolution — and NOT by adding custom-mission entries to _ACTION_PROFILE_DEFAULTS, preserving the table's stated built-in-only contract. Refs #3830.HighOpen
FR-004_mission_path_by_name / get_mission_for_feature org-tier consultation, per the Decision 2 checkpointAs an org-pack mission-type author, I want the legacy Mission loader (src/specify_cli/mission.py::_mission_path_by_name, ::get_mission_for_feature) to consult the org tier for mission-type resolution, consistent with the modern expected-artifacts resolver (src/charter/activation/org_expected_artifacts.py::resolve_org_expected_artifacts, called from mission_type_profiles.py::_resolve_expected_artifacts_slot) — IF the plan-phase investigation (Clarifications / Decision 2) finds the legacy Mission schema and the org-tier MissionType schema compatible without a schema bridge. If incompatible, this FR is descoped to this mission per the split rule in Decision 2, and #3831 is referenced (Refs #3831) rather than closed. Refs #3831 (status conditional on the plan-phase checkpoint).HighOpen
FR-005The get_mission_for_feature software-dev fallback must be loud, not a filtered warnings.warnAs an operator, when a mission-type lookup does fall back to software-dev (whether the org tier is added per FR-004 or not), I want that substitution to be visible in normal CLI output — not solely a Python warnings.warn call that is typically filtered/suppressed — so a mission is never silently evaluated against the wrong mission type's conventions. This FR applies regardless of the FR-004 split outcome. Refs #3831.HighOpen
FR-006is_substantive(file_path, kind) for kind == "plan" derives required fields from the mission type's own plan templateAs a mission-type author, I want the plan-substantive check (src/specify_cli/missions/_substantive.py::is_substantive) to derive its required fields from the mission type's OWN resolved plan template — not the hardcoded ## Technical Context / Language/Version shape, which is specific to software-dev. This is the operator-mandated fix shape (Decision 1); a name-based mission_type != "software-dev" guard mirroring mission_check_prerequisites.py:364 is explicitly rejected as a fix shape for this FR. Combination rule: for each mission type, the template's FIRST scaffolded field is the primary field and must be substantive, PLUS at least one further (peer) scaffolded field must also be substantive — mirroring _has_substantive_technical_context's existing Language/Version-plus-a-peer semantics (for plan, the primary is Problem Decomposition; see Decision 3). Not ALL scaffolded fields, and not ANY ONE alone. Every call site in mission_setup_plan.py that checks kind="plan" substantiveness must route through this same check, with none left on the old hardcoded software-dev shape. How the check locates each type's primary/peer fields and detects substantiveness per field's shape, and which call site(s) need updating, are open design questions deferred to the plan phase — see "Deferred to Plan Phase" below. Refs #3832.HighOpen
FR-008Reconcile mission_check_prerequisites.py:364's name-based guard with the FR-006 template-derived checkAs a maintainer, I want mission_check_prerequisites.py's existing mission_type != "software-dev" or is_substantive(spec_file, "spec") guard (the ONE call site that is kind="spec", using _has_substantive_fr_row, not the kind="plan" check FR-006 targets) reviewed for consistency with the new template-derived plan check — a mission must not ship with one call site skipping the check entirely by name for non-software-dev types while a sibling call site derives requirements from the template; document the reconciled behavior for the kind="spec" check even if its own fix is out of this mission's strict scope (the spec-row check is not template-shaped the way Technical-Context is, per Clarifications). Refs #3832.MediumOpen

Non-Functional Requirements

IDTitleRequirementCategoryPriorityStatus
NFR-001No composition-dispatch regression for built-in mission typessoftware-dev, research, and documentation (the three types with entries in _ACTION_PROFILE_DEFAULTS) must resolve profile_hint via composition dispatch exactly as before the FR-001/FR-002/FR-003 fix — same resolved profile per (mission, action) pair, verified by existing/extended tests in the composition-dispatch test surface. plan (the fourth built-in type) must likewise be unaffected wherever it currently traverses this path — concretely, User Story 1 Acceptance Scenario 4: plan must still fail with StepContractExecutionError("No step contract found for mission/action plan/<action>"), not profile_hint is required or any newly-introduced behavior.ReliabilityHighOpen
NFR-002No mission-loader regression for built-in and project-tier mission typesThe FR-004/FR-005 change to _mission_path_by_name/get_mission_for_feature must not alter resolution for: (a) any built-in mission type (software-dev, research, documentation, plan) resolved via _packaged_missions_dir(), or (b) any project-tier custom mission type resolved via kittify_dir / "missions" / <name>. Existing precedence (project tier over built-in tier) must be preserved exactly if an org tier is added.ReliabilityHighOpen
NFR-003No substantive-gate regression for software-dev plan checksThe FR-006 template-derived plan-substantive check must still reject a software-dev-type plan.md missing ## Technical Context/Language/Version (or a placeholder-only value), exactly as _has_substantive_technical_context does today — verified by the existing test coverage for that function plus new template-derived-path tests.ReliabilityHighOpen
NFR-004No substantive-gate regression for documentation, research, plan plan checks after the corrected-scope fixOnce FR-006 makes the plan-substantive check template-derived, documentation-type plans must be checked against documentation's own template fields (Documentation Framework, Languages Detected, Output Format, Hosting Platform, Build Commands — none of which is Language/Version), and research/plan-type plans must be checked against ALL of their own actually-scaffolded fields — not a convenient subset — (Research Context / Methodology / Data Sources for research; Problem Decomposition / Scope — MoSCoW / Sequencing & Prioritisation / Decisions for plan, all four), combined per type under the primary-plus-peer AND/OR rule (for plan, primary = Problem Decomposition), per Clarifications / Decision 3 — never an accidental always-pass/always-fail, and never a "neutral pass" that structurally exempts these two types from NFR-005. This closes the corrected-scope gap identified in Clarifications / Decision 1 for the three built-in types beyond software-dev. How each field's shape is detected is deferred to the plan phase — see "Deferred to Plan Phase" below.CorrectnessHighOpen
NFR-005Architectural gate non-vacuity for the reconciled substantive checkPer the charter's architectural-gate-discipline standing order, the fixed is_substantive plan check (and its reconciliation with mission_check_prerequisites.py:364, FR-008) must be a gate that CAN fail for every mission type it applies to — no mission type may be structurally exempted from ever having its plan checked, mirroring the _ACTION_PROFILE_DEFAULTS/FR-008 single-canonical-authority principle applied to substantive-content checking. Concretely, for EVERY mission type covered by FR-006 (software-dev, documentation, research, plan, and any custom type), the gate must be demonstrably CAPABLE of both PASSING (given a plan.md faithfully populated per that type's own template) and FAILING (given an unfilled/placeholder-only scaffold of that type's own template) — no type may be structurally exempted, no "neutral pass" that always returns True, and no accidental implementation that always returns False regardless of content. How the detection mechanism achieves this for each type's field shapes is an open design question deferred to the plan phase — see "Deferred to Plan Phase" below.CorrectnessHighOpen

Constraints

IDTitleConstraintCategoryPriorityStatus
C-001Single canonical authority for profile resolutionDo not invent a second authority for custom-mission profile_hint resolution. PromptStep.agent_profile (consulted via _resolve_step_agent_profile) is the FR-008-mandated canonical path; _ACTION_PROFILE_DEFAULTS remains built-ins-only per its own comment (executor.py:68-71).TechnicalHighOpen
C-002Single canonical authority for mission-type-scoped resolutionThe FR-004 org-tier change (if it proceeds) must reconcile with, not duplicate, the existing org-tier pattern already used by resolve_org_expected_artifacts (src/charter/activation/org_expected_artifacts.py) and its callers (mission_type_profiles.py::_resolve_expected_artifacts_slot, dossier/manifest.py::ManifestRegistry.load_manifest, runtime/resolver.py) — reuse resolve_existing_org_roots/the same org-roots precedence convention rather than inventing a third org-tier-walking mechanism.TechnicalHighOpen
C-003Fix-site blast radius is disjoint per issue, all on hot paths shared with built-insThe three fix sites are disjoint file sets: (#3830) src/runtime/next/runtime_bridge_composition.py + src/specify_cli/mission_step_contracts/executor.py; (#3831) src/specify_cli/mission.py; (#3832) src/specify_cli/missions/_substantive.py + src/specify_cli/cli/commands/agent/mission_check_prerequisites.py + src/specify_cli/cli/commands/agent/mission_setup_plan.py. All three sit on control-flow paths that software-dev, research, documentation, and plan traverse too — every FR above must state (via its paired NFR) what must NOT regress for each built-in type.TechnicalHighOpen
C-004Decision 1 fix shape is binding: template-derived, not name-basedThe #3832 fix (FR-006) MUST derive required Technical-Context-equivalent fields from the mission type's own resolved plan template. A name-based guard (mission_type != "software-dev", mirroring mission_check_prerequisites.py:364) is explicitly REJECTED as a fix shape — see Clarifications / Decision 1.TechnicalHighOpen
C-005Decision 2 is a research-phase checkpoint, not a resolved scopeWhether #3831 (org-tier lookup in _mission_path_by_name) is a small patch or a schema-bridging project between the legacy Mission/mission.yaml pydantic model and the modern org-tier MissionType/governance-profile system is UNRESOLVED at spec time — no org-pack fixture exists in this checkout to determine it. The plan/research phase must settle this against a real org-pack fixture. If compatible without a schema bridge, #3831 stays in this mission/PR (Closes #3831 is then appropriate if the fix is complete). If incompatible, #3831 splits to a tracked follow-up issue and this PR ships #3830+#3832 only, referencing #3831 with Refs #3831 (never Closes #3831 for a partial fix) per the charter's Issue Closure Linkage Rule. See Clarifications / Decision 2.TechnicalHighOpen
C-006#3830 and #3832 proceed unconditionally regardless of the #3831 split outcomeNo FR/AC tied to #3830 (FR-001–FR-003) or #3832 (FR-006, FR-008) may be written or later invalidated in a way that depends on how the #3831 split (C-005) resolves. These two issues are fully in scope for this mission's PR either way.BusinessHighOpen
C-007Pre-existing known-red baseline handlingGitHub issue #3284 (23 pre-existing test failures + 2 errors on main) documents the baseline, plus GitHub issue #3283 (shared test-venv lock). "The suite is red" is never itself an acceptable finding during this mission — every red encountered must be classified: matched against #3284's documented failures (accepted baseline, no action), or, if not already covered by #3284, filed as its own GitHub issue BEFORE being treated as baseline context, per the charter's Pre-existing Failure Reporting Rule.TechnicalHighOpen
C-008Do not fold #3831 into #2660#2660 (OPEN, "Remove software-dev template-selection fallback in mission.py (#2652-F)") targets the same get_mission_for_feature block as #3831 (its own scope note cites mission.py:780, :798-803) but is narrowly scoped to REMOVING the fallback for meta.json-less template-file selection under the specify_cli/missions retirement epic, is blocked on #2652-T and #2652-E, and asserts the governance leak was "already closed by #883 WP-CANON." This mission's own SK-128 verification shows the governance-adjacent leak (silent mission-type substitution affecting path conventions and artifact sets, not just template-file selection) is STILL LIVE — #2660's premise about #883 is contradicted by live evidence in this checkout. Do not merge #3831's scope into #2660; they are different scope with different fix shapes and different blocking dependencies. Note the relationship, do not resolve the contradiction as part of this mission.TechnicalMediumOpen
C-009Public repository — no local/private detailThis spec, and the eventual PR, target the public Priivacy-ai/spec-kitty repository on main. No local filesystem paths, usernames, or private-repo detail may appear anywhere in mission artifacts.RegulatoryHighOpen

Key Entities

  • Mission type (legacy Mission/mission.yaml): the pydantic model (specify_cli.mission.Mission, PhaseConfig, ArtifactsConfig) resolved by _mission_path_by_name/get_mission_for_feature. Strict schema: name/description/semver/domain/workflow/artifacts/paths. Two tiers today: project (.kittify/missions/<name>) and packaged built-in (_packaged_missions_dir()).
  • Mission type (modern MissionType/governance profile): the doctrine-native system under src/charter (mission_type_profiles.py, MissionTypeProfile, resolve_mission_type_context) that already consults org, project, and built-in tiers for governance concerns including action_sequence and (via resolve_org_expected_artifacts) expected-artifacts.yaml. Architecturally separate from the legacy Mission model; whether they can be bridged without new schema work is the Decision 2 checkpoint.
  • profile_hint: the string identifying which agent profile (e.g. researcher-robbie, architect-alphonso) should execute a given mission step/action. Resolved either from _ACTION_PROFILE_DEFAULTS (built-ins-only table) or from PromptStep.agent_profile (the FR-008-mandated custom-mission path).
  • Plan template / Technical Context: the per-mission-type plan-template.md (or equivalent) that defines what a "substantive" plan.md looks like for that type. Only software-dev's template scaffolds Language/Version; documentation's scaffolds a ## Technical Context section with different peer fields; research and plan scaffold neither (see Clarifications for the full verified table).

Clarifications

Decision 1 — #3832 fix shape: template-derived, not name-based (BINDING)

The fix for #3832 MUST derive the required Technical-Context-equivalent fields from the mission type's OWN plan template, not from the mission-type name. A name-based guard mirroring mission_check_prerequisites.py:364 (mission_type != "software-dev") is explicitly REJECTED as a fix shape for the plan-substantive check — it would abandon the substantive-content check entirely for every non-software-dev type (built-in or custom) rather than fixing the check to be mission-type-aware. See C-004, FR-006.

Re-verified four-mission-type Technical Context template table (grepped directly against this checkout, 2026-09-02, source eff63532607d2f292efc16e3f8bfd0e61e0544aa):

Mission typeTemplate file## Technical Context presentLanguage/Version presentOther Technical-Context-shaped fields present
software-devpacks/built-in/missions/software-dev/templates/plan-template.mdYes (line 14)Yes (line 26) — the ONLY type that scaffolds this field
documentationpacks/built-in/missions/documentation/templates/documentation-plan-template.mdYes (line 12)NoDocumentation Framework (line 14), Languages Detected (line 15), Output Format (line 22), Hosting Platform (line 23), Build Commands (line 24)
researchpacks/built-in/missions/research/templates/research-plan-template.mdNoNoNo matches for any of the searched field labels
planpacks/built-in/missions/plan/templates/plan-plan-skeleton.mdNoNoNo matches for any of the searched field labels

Result: CONFIRMED TRUE, exactly as expected — software-dev is the only mission type whose own template scaffolds Language/Version, and documentation/research/plan all lack it while documentation has its own distinct Technical-Context-shaped fields.

Corrected scope statement: issue #3832 as filed frames the defect against custom-mission-type behaviour, but the live, verified evidence above shows the defect ALSO breaks three of the four BUILT-IN mission types — documentation, research, and plan — whenever their plan.md is checked by the current hardcoded _has_substantive_technical_context. This mission states that corrected scope explicitly (NFR-004) rather than silently narrowing #3832 back to "custom types only."

Decision 2 — #3831 scope: research-phase checkpoint, then go/split (BINDING)

This mission does NOT resolve, at spec time, whether adding an org-tier lookup to _mission_path_by_name is a small patch or a schema-bridging project. The legacy Mission/mission.yaml schema (strict pydantic model: name/description/semver/domain/workflow/artifacts/paths — see PhaseConfig/ArtifactsConfig near _mission_path_by_name in src/specify_cli/mission.py) is architecturally separate from the modern org-tier MissionType/governance-profile/expected-artifacts system (src/charter/activation/mission_type_profiles.py, src/charter/activation/org_expected_artifacts.py). No org-pack fixture exists in this checkout to determine compatibility.

Binding process: the plan/research phase of this mission MUST settle this against a real org-pack fixture:

  • If the legacy Mission schema and the org-tier MissionType schema are compatible (an org-tier mission type can be represented/consulted from _mission_path_by_name/get_mission_for_feature without a schema bridge), #3831 stays in this mission and this PR — implement FR-004/FR-005 in full, and the PR body uses Closes #3831 if the fix is complete.
  • If incompatible, #3831 splits to a tracked follow-up issue and this PR ships #3830 + #3832 only, referencing Refs #3831 (not Closes #3831) per the charter's Issue Closure Linkage Rule, with a tracker comment naming the remaining scope.
  • #3830 and #3832 proceed unconditionally either way (C-006) — no FR/AC tied to those two issues is invalidated by how this checkpoint resolves.

Decision 3 — #3832 research/plan resolution: template-derived actual fields, no neutral pass (BINDING)

The Edge Cases section previously left two candidate resolutions open for research/plan mission types, whose plan templates scaffold neither ## Technical Context nor any Technical-Context-shaped peer field: (a) derive the substantive-content criterion from that type's own actually-scaffolded template fields, or (b) a "documented neutral pass" (treat plan.md as always-substantive for these two types). This mission REJECTS option (b): NFR-005 requires the plan-substantive gate to be able to fail for every mission type it applies to, and an always-substantive outcome would structurally exempt research/plan from ever failing — the exact pattern NFR-005 forbids. A neutral pass would also depart from Decision 1's own binding fix shape (derive from the mission type's OWN template) by treating "no Technical-Context-shaped section" as "nothing to check," rather than deriving from what the template DOES scaffold.

Resolution (option a): FR-006's template-derived check uses research's own scaffolded fields — Research Context, Methodology, Data Sources, per packs/built-in/missions/research/templates/research-plan-template.md — and plan's own scaffolded fields — Problem Decomposition, Scope (MoSCoW), Sequencing & Prioritisation, Decisions, per packs/built-in/missions/plan/templates/plan-plan-skeleton.md — as the substantive-content criterion for those two types. This is the same template-derived PRINCIPLE FR-006 already applies to documentation's distinct peer fields — deriving what "substantive" means from what the mission type's own template actually scaffolds — extended to types whose template has no Technical-Context-shaped section at all rather than exempting them. See User Story 3 Acceptance Scenarios, NFR-004, NFR-005.

Detection mechanism: deferred to the plan phase — see "Deferred to Plan Phase" below. (Two prior fix rounds on this spec pushed regex/detector/call-site design into FR-006, NFR-004, and User Story 3's acceptance scenarios; each restatement was independently audited and independently found gapped by adversarial review. Per operator ruling #2 on this mission's spec-phase review, the spec states the required behavior and lets the plan phase design how it is achieved.)

AND/OR combination rule (behavioral — stays in the spec): mirroring _has_substantive_technical_context's EXISTING semantics — Language/Version is the primary field and must be substantive, PLUS at least one peer field in the same section must also be substantive — generalized: for each mission type, the template's FIRST scaffolded field is the primary field and must be substantive, PLUS at least one further (peer) scaffolded field must also be substantive. Not ALL scaffolded fields (stricter than software-dev is held to today, which fails legitimate authors who leave one peer field thin) and not ANY ONE alone (near-vacuous). For plan, whose four scaffolded fields appear in template order Problem Decomposition (line 17) → Scope — MoSCoW (line 32) → Sequencing & Prioritisation (line 41) → Decisions (line 53) — verified directly against plan-plan-skeleton.md's own heading order — the primary field is Problem Decomposition: a plan-type plan.md passes only if Problem Decomposition is substantive AND at least one of Scope — MoSCoW / Sequencing & Prioritisation / Decisions is also substantive. See User Story 3 Acceptance Scenarios, FR-006, NFR-004, NFR-005.

Deferred to Plan Phase (open design questions — not answered in this spec)

Ruling #2 (reviews/spec.ruling-2.md) directs that the substantive-check DETECTION MECHANISM — how the gate finds and evaluates each mission type's fields — is plan-phase design work, not spec content. The plan phase's opening agenda must settle:

1. Which section each mission type's template designates for this check, and how that is determined. 2. How the primary field label is obtained per type — both the section heading AND the primary field label (Language/Version) are hardcoded today in _has_substantive_technical_context (src/specify_cli/missions/_substantive.py:158-195), so both must be addressed, not just the heading. 3. Per-shape detection: plan's and research's scaffolded fields appear as markdown tables, bulleted bold-field lists, and nested third-level headings — these differing shapes need detection, without prescribing an algorithm here. 4. Placeholder-pattern coverage for research/plan's own bracket vocabulary (their bracket placeholders are not recognized by the existing _PLACEHOLDER_PATTERNS list). 5. Call-site routing in mission_setup_plan.py — which of its is_substantive call sites needs the mission-type-aware version.

Relationship to #2660 (do not fold, do not merge)

#2660 ("Remove software-dev template-selection fallback in mission.py (#2652-F)", OPEN) targets the same get_mission_for_feature code block as #3831 but is scoped narrowly to template-file selection under the specify_cli/missions retirement epic (#2652), is blocked on #2652-T and #2652-E, and its own scope note asserts the governance leak "already closed by #883 WP-CANON." This mission's live verification (ledger SK-128 — an org-tier qa mission type silently substituted with software-dev, corrupting path conventions and optional-artifact enumeration) shows that governance-adjacent leak is STILL LIVE, contradicting #2660's stated premise. #3831 is different scope (a correctness fix for silent misidentification, addressable now) from #2660 (a template-fallback removal blocked on a larger epic) — do not fold #3831's work into #2660, and do not treat #2660 as already covering this mission's #3831 fix.

Architectural gate non-vacuity note (charter governing principle)

The current mission_check_prerequisites.py:364 guard (mission_type != "software-dev" or is_substantive(spec_file, "spec")) is a gate that CANNOT FAIL for any mission type except software-dev — every other type, built-in or custom, is unconditionally exempted from the spec-substantive check. Per the charter's architectural-gate-discipline standing order ("a gate that cannot fail is itself a defect class"), this pattern is called out explicitly (FR-008, NFR-005) so the #3832 fix does not simply extend the same vacuous-for-non-software-dev shape to the plan check — the template-derived approach (Decision 1) is chosen specifically because it keeps the gate capable of failing for every mission type it applies to.

Success Criteria (mandatory)

Measurable Outcomes

  • SC-001: A qa-type (or any other non-built-in) mission whose action_sequence includes the dispatched action successfully advances past its first spec-kitty next step, resolving profile_hint via PromptStep.agent_profile/pack-declared agent-profile: entries, with zero regression in profile resolution for software-dev, research, and documentation actions covered by _ACTION_PROFILE_DEFAULTS. (#3830)
  • SC-001a: plan's pre-existing, distinct composition-dispatch failure mode is verified unchanged by the fix — dispatching an action that is a member of plan's own action_sequence still raises StepContractExecutionError("No step contract found for mission/action plan/<action>"), not profile_hint is required or any newly-introduced behavior, mirroring NFR-001's updated text and User Story 1 Acceptance Scenario 4. (#3830)
  • SC-002: The plan-phase research checkpoint (Decision 2) is resolved with a documented go/split decision backed by a real org-pack fixture, and the corresponding path (full org-tier fix landed in this PR, or #3831 referenced with a tracked follow-up issue) is executed exactly as decided — no silent narrowing or silent scope creep either direction. (#3831)
  • SC-003: A plan.md written exactly per a mission type's own plan template — verified for at least qa (custom) and documentation (built-in, corrected scope) — passes every is_substantive(plan_file, "plan") call site in mission_setup_plan.py, while a software-dev-type plan.md missing Language/Version still correctly fails the same check. (#3832)
  • SC-004: The get_mission_for_feature fallback to software-dev (if it remains reachable after FR-004/Decision 2) is observable in normal CLI operation — verified by a test that captures actual CLI stdout/stderr under default warning filters, not solely pytest.warns. (#3831)
  • SC-005: Every pre-existing test failure encountered during this mission's implementation is classified against GitHub issue #3284 (23 pre-existing test failures + 2 errors) or filed as its own tracked issue before being treated as baseline — zero unclassified "the suite is red" findings in the mission's tracker record. (charter Pre-existing Failure Reporting Rule)
  • SC-006: The PR closing this mission correctly applies the charter's Issue Closure Linkage Rule per the Decision 2 outcome — Closes #3830, Closes #3832, and either Closes #3831 (full fix landed) or Refs #3831 plus a linked tracked follow-up (partial/split) — never a closing keyword on a partial fix.