Research: Live enforcement surface for the occurrence-map gate (resolves spec C-005)
Question: At the plan-completion / pre-implement boundary, which enforcement surface does the live runtime actually evaluate, so the gate reusing ensure_occurrence_classification_ready fires before implement?
Investigated read-only against the live checkout (branch feat/bind-occurrence-map-guard-finalize).
Findings
F1 — mission.yaml transition conditions are a DEAD surface (Option A rejected)
plan → implementdeclaresconditions: ['artifact_exists("plan.md")', 'artifact_exists("tasks.md")']—src/doctrine/missions/software-dev/mission.yaml:42-46(mirrorsrc/specify_cli/missions/software-dev/mission.yaml:45-46).- The only interpreter of those strings is
evaluate_guards()—src/runtime/next/decision.py:204-266— documented dead: "Legacy functionsderive_mission_stateandevaluate_guards... are no longer called bydecide_next" (decision.py:14-16). - Live
decide_next(decision.py:407-429) →runtime_bridge.decide_next_via_runtime(runtime_bridge.py:2514), which enforces boundaries with hand-rolled per-step guards, not mission.yaml conditions.
F2 — mission_v1 guard machinery has no live consumer
compile_guards/GUARD_REGISTRY(src/specify_cli/mission_v1/guards.py:270-378) consumed only insidemission_v1/__init__.py:104. Non-test imports ofmission_v1insrc/pull only.eventsand.schema— never.guards/.runner.- The registered
occurrence_map_completeguard (guards.py:245-277, wrappingensure_occurrence_classification_readyat:258-260) is registered-but-dead. Confirms the post-spec adversarial finding.
F3 — finalize-tasks is the correct command surface (Option B)
spec-kitty agent mission finalize-tasks→finalize_tasks()atsrc/specify_cli/cli/commands/agent/mission_finalize.py:1520+(registeredmission.py:277,326).- Linear validation pipeline over
planning_dir(SaaS preflight, requirement-mapping:1621-1628, dependency graph:1617, conflicts:1630, ownership:1664), each raisingtyper.Exit(1). No occurrence-map gate today. - Runs after
/spec-kitty.tasks, before anyimplement— correct timing.--validate-onlymode (:1523-1525,1673) is designed to surface finalization blockers; a call placed before theif validate_only:split fires in both modes.
F4 — Existing live call sites of ensure_occurrence_classification_ready (backstop)
- Definition
src/specify_cli/bulk_edit/gate.py:49-96→GateResult(passed, change_mode, errors, warnings); internallyload_meta→ early-pass if notbulk_edit(:54-60) → presence (:63-72) → schemavalidate_occurrence_map(:74-81) →check_admissibility(:83-90). Rendererrender_gate_failure(:99-107). - Call sites (both fire later than finalize — the status quo this mission improves on): the implement-time preflight
implement.py:1239-1244(insideimplement()), and the review-workflow gateagent/workflow.py:2365-2371(insidereview(), labelled FR-006 — fires at review, not implement). Each passes a singlefeature_dirand exits 1 viarender_gate_failure. Both remain unchanged (FR-004 backstop).
F5 — change_mode is read from meta.json
ensure_occurrence_classification_readycallsload_meta(feature_dir)thenmeta.get("change_mode")(gate.py:17,54-59). Self-conditioning — caller only supplies the correctfeature_dir(=planning_dirin finalize).
F6 — Admissibility is a distinct rejection axis
check_admissibility(occurrence_map.py:377-399) rejects placeholder terms and< MIN_ADMISSIBLE_CATEGORIES = 3(occurrence_map.py:107). "Valid" ⇒ schema-valid and admissible.
F7 — The live next-loop bypasses the finalize command
- The
next-loop's live pre-implement guards are_check_composed_action_guard(action == "tasks",runtime_bridge.py:1622-1637) and_check_cli_guards(runtime_bridge.py:1091-1108). Anext-driven mission that never invokesfinalize-taskswould only hit the bad-map block at implement-time. → motivates IC-02 (non-vacuousness).
Decision
Reuse ensure_occurrence_classification_ready at both live pre-implement surfaces: the finalize-tasks command (IC-01) and the next-loop tasks-finalize guard (IC-02, via one shared helper called from both enumerators). No new validation logic; no touching the dead mission.yaml / mission_v1 surfaces. The existing implement-time and review-time gates remain as backstops (FR-004).
Test seams
- Gate logic already covered:
tests/specify_cli/bulk_edit/test_gate.py(missing / schema-invalid /<3categories / pass). - New finalize integration:
tests/tasks/test_finalize_tasks_occurrence_gate.py(bulk_edit × {missing, schema-invalid, inadmissible, valid} + non-bulk pass +--validate-onlyblocks). next-loop guard tests undertests/next/.- Read-only invariant regression:
tests/specify_cli/cli/commands/test_finalize_tasks_validate_only_readonly.pymust still pass.