# Migrating from spec_led_ex

This checklist is for the Argos, Engage, Builder, and Atlas migration PRs. Do
the steps in one PR so configuration, spec edits, and CI agree on the same
Ancora version.

## Checklist

1. Replace the dependency with the pinned Ancora release and refresh the lock
   file.
2. Run `mix spec.init`, then compare the generated agent guide, skill, README,
   and configuration with the repository's existing `.spec/` files. Keep
   repository-specific instructions.
3. Move authored subjects to `.spec/specs/` and decisions to
   `.spec/decisions/`. Remove fields and verification kinds that Ancora no
   longer reads.
4. Tag tests with each requirement ID. A tagged test must call the production
   function that anchors its subject.
5. Set `default_base`, `test_paths`, and `lib_paths` for the repository. Treat
   `default_base` as a local-development convenience. Copy severity choices
   through the code map below. Every subject override needs a reason.
6. Configure CI checkout with `fetch-depth: 0`, then replace the old CI command
   with `mix spec.check --base origin/main`, using the repository's actual trunk
   ref when it differs. CI must always pass `--base` explicitly. For an existing
   shallow clone, run `git fetch --unshallow` before the gate.
7. Run `mix spec.check --base HEAD` once to fix corpus and tag errors. Then run
   against the trunk base and review every drift, growth, shrink, and uncovered
   file finding.
8. Budget for migration-driven load shifts. Retagged suites and added compile
   work can expose latent consumer flakes even when the migrated behavior is
   unchanged.
9. Remove the compatibility shim only after the repository's CI passes with
   Ancora alone.

`Spec-Ack:` trailers are temporary development acknowledgments. Ancora warns
when an applied trailer exists only below the branch tip because a squash merge
will discard it. Before merging, copy that severity into `.spec/config.yml`
under `severities:` or a subject override, add the reason for an override, and
commit the config change. Subject overrides are scoped to one subject and
one finding code, optionally narrowed to one requirement with `requirement:`.
The warning clears once config supplies the same severity. It remains when
config is more severe because removing the trailer would still change the gate
result.

## Declaring a subject surface

A subject may declare `surface:` as a nonempty list of exact repo-relative
source paths. `surface: []`, globs, absolute paths and traversal are rejected.
Observed drift, additions and removals outside the list produce informational
`derived/drift_transitive`, `derived/growth_transitive` and
`derived/shrink_transitive` findings. Omitting the field keeps primary checks.
When both sides declare ownership, either side can keep a change primary;
first introduction and later ownership edits appear in review as policy changes.
Surface never claims coverage or unchanged behavior.


`spec-exceptions` blocks and the `"exceptions"` key returned by
`Ancora.Parser.parse_file/2` are deprecated. Ancora 1.x still parses and returns
them. Remove these blocks before upgrading to Ancora 2.0, which removes both.

Engage and Builder must review accepted ADRs during their next Ancora upgrade.
An ADR whose `affects:` lists only a subject no longer authorizes deleting or
downgrading every requirement in that subject. Add each exact requirement id
that the ADR is meant to authorize. Subject ids remain valid when they only
document the ADR's broader scope.

Use `retires:` when an accepted ADR removes a requirement or whole subject from
the corpus. Repeat each retired id in `affects:` and `retires:`. Cold validation
then accepts the missing id, and the append-only guard accepts deletion of the
named requirement or every requirement under the named subject. `retires:` does
not authorize a `must` to `should` downgrade.

When upgrading, Engage should remove its `adr/affects_unresolved: info`
demotion and return to the registry default. Builder should replace its six
identifier tombstone files with `retires:` entries, then delete the tombstones
and their `derived/unanchored_subject` overrides.

## Command gates

Replace forbidden-text command gates with a tagged ExUnit test that calls
`Ancora.SourceScan` in the consumer's test suite. Keep the vacuity guard. An
empty glob must fail instead of turning a missing directory into a green test.
Keep whole-token matching too. A plain string such as `System.cmd` should not
match inside a longer identifier. Use a regular expression when you want
broader matching.

```elixir source-scan-test
defmodule MyApp.SourcePolicyTest do
  use ExUnit.Case, async: true

  @tag spec: "my_app.no_shell_calls"
  test "production source does not invoke shell commands" do
    assert Ancora.SourceScan.scan(
             dirs_or_globs: ["lib", "config/*.exs"],
             tokens: ["System.cmd", "System.shell", ~r/:os\.cmd/],
             allowlist: ["lib/my_app/approved_runner.ex"]
           ) == []
  end
end
```

## Finding code map

Ancora has 35 finding codes. The middle column is the closed registry. Several
old checks converge on one current code, while some current codes have no
direct predecessor.

| specled_ex code | Ancora code | Default severity |
|---|---|---|
| `branch_guard_realization_drift` | `derived/drift` | `error` |
| no direct predecessor | `derived/drift_transitive` | `info` |
| no direct predecessor | `derived/growth` | `warning` |
| no direct predecessor | `derived/shrink` | `warning` |
| no direct predecessor | `derived/growth_transitive` | `info` |
| no direct predecessor | `derived/shrink_transitive` | `info` |
| `detector_unavailable` | `derived/unresolved_calls` | `info` |
| `detector_unavailable` | `derived/unparseable_source` | `error` |
| `branch_guard_dangling_binding` | `derived/unanchored_subject` | `warning` |
| `branch_guard_unmapped_change` | `change/uncovered_file` | `warning` |
| `branch_guard_missing_decision_update` | `change/missing_decision` | `warning` |
| `branch_guard_requirement_without_test_tag` | `tags/new_requirement_untagged` | `warning` |
| no direct predecessor | `tags/tag_borrowed` | `info` |
| `tag_scan_parse_error` | `tags/parse_error` | `error` |
| `tag_dynamic_value_skipped` | `tags/dynamic_value` | `info` |
| `requirement_without_test_tag` | `tags/requirement_untagged` | `info` |
| `verification_cover_untagged` | `tags/unknown_requirement` | `warning` |
| `append_only/requirement_deleted` | `append/requirement_deleted` | `error` |
| `append_only/must_downgraded` | `append/must_downgraded` | `error` |
| no direct predecessor | `append/statement_changed` | `info` |
| `verification_kind_invalid`, `verification_unknown_kind` | `format/retired_construct` | `warning` |
| `verification_command_*`, `tagged_tests_cover_not_executed` | `spec/parse_error` | `error` |
| `duplicate_requirement_id`, `duplicate_scenario_id`, `duplicate_subject_id`, `duplicate_exception_id`, `duplicate_decision_id` | `spec/duplicate_id` | `error` |
| `invalid_id_format` | `spec/invalid_id` | `error` |
| `meta_field_missing`, `requirement_id_missing`, `scenario_id_missing` | `spec/missing_field` | `error` |
| `verification_target_missing*`, `surface_target_*`, `scenario_cover_unknown`, `subject_unknown_decision_reference` | `spec/unknown_reference` | `error` |
| `requirement_missing_verification` | `spec/requirement_unverified` | `info` |
| `decision_*` parse checks | `adr/parse_error` | `error` |
| `decision_*` section checks | `adr/missing_section` | `error` |
| `cross_field/affects_empty` | `adr/affects_empty` | `warning` |
| `cross_field/affects_unresolved` | `adr/affects_unresolved` | `error` |
| `overlap/duplicate_covers` | `overlap/duplicate_covers` | `error` |
| `overlap/must_stem_collision` | `overlap/must_stem_collision` | `error` |
| legacy configuration key checks | `config/unknown_key` | `warning` |
| legacy configuration value checks | `config/invalid_value` | `warning` |

The old trailer and environment settings map as follows:

| Old setting | Ancora setting |
|---|---|
| `Spec-Drift:` | `Spec-Ack: <code>=<info\|warning>` |
| `SPECLED_SHOW_INFO` | `ANCORA_SHOW_INFO` |
| `SPECLED_COMMAND_OUTPUT_DIR`, `SPECLED_DISABLE_TRACER` | removed |

Ancora attributes calls to each tagged test, its applicable setup and reachable
test helpers. Neighboring tests and unused helpers do not contribute bindings.
Inspect the reported callsite chain before editing a contract. An unresolved
call is an analysis limitation, not evidence that a contract was removed.
Do not add cosmetic requirements or split tests just to clear a finding.

For infrastructure without a statically observable test call, use an exact,
reasoned exception. The file remains excepted rather than covered:

```yaml
overrides:
  - file: lib/my_app_web/router.ex
    code: change/uncovered_file
    severity: info
    reason: Exercised through the Phoenix request pipeline.
```

File exceptions accept only `info`, require a reason, and cannot combine with
`subject:` or `requirement:`. Subject overrides prefer a matching `requirement:`
over the subject default; duplicate selectors are rejected regardless of order.
