Research: Upgrade reports one outcome
All findings were verified on main at 9adc68803f by the pre-spec grounding squad (a reproduction lens and a scope lens) unless marked as an inference.
R1 — Where the exit code and the messages come from
- Decision: extend
UpgradeOutcome; do not add a new type. - Rationale:
derive_exit_code()is called once (upgrade/finalize.py) and its result raised once (cli/commands/upgrade.py). The error list is built by_combined_errors(outcome, summary)in the CLI module, with the surface summary passed through_FinalizerRenderContext._display_no_migrations_resultsprintsProject is already up to date!unconditionally and printsresult.warnings,activation_errorsandresult.errors, never the drift message. - Alternatives considered: a print added to the no-migrations renderer (fixes the instance, keeps two error-list builders and the unconditional success line); a new
UpgradeReportwrapper around the outcome (a second authority).
R2 — What surface_drift_failed means today
- Decision: replace it with
drifted_pathsandsurface_repair_failed. - Rationale: it is true for consent-required drift, for a repair result that is neither
appliednorskipped, and for an incomplete dry-run preview. The renderer words all three as drift, which yieldsdrift in 0 file(s). - Alternatives considered: keep the boolean and guard the message on a non-zero count (hides the repair failure instead of naming it).
R3 — Is non-zero on drift intended?
- Decision: keep it; record the open operator decision in the spec and the ADR.
- Rationale: pinned by FR-006 of mission
agent-profile-projection-plugin-production-01KV3NGS, bytests/specify_cli/tool_surface/test_drift_policy.py, by the changelog entries for the earlier exit-honesty fix and the Windows drift mission, and restated in the code. No ADR rules on it. - Alternatives considered: exit 0 with a warning on a no-migrations run (what the reporter first expected; reverses the pinned contract).
R4 — Is the legacy surface-repair branch reachable?
- Decision: verify with a test, then remove.
- Rationale (inference from code reading): the fallback runs only when
ctx.prepared_repairs is Noneon a real successful run; preparation either sets it or returns errors that skip the step. - Alternatives considered: leave it and route it through the new report type (keeps dead code and the tests that patch it).
R5 — Collision with neighbouring work
- Decision: own
upgrade/outcome.py,upgrade/finalize.pyand the renderers incli/commands/upgrade.py; leaveupgrade/runner.pyandupgrade/assessment.pyalone. - Rationale: the sibling mission for per-worktree metadata writes works in
runner.py; an open pull request touchesassessment.pyand the managed-skills provider.
R6 — Supply chain
No dependency is added, upgraded or removed. DIRECTIVE_051 controls: not applicable.