---
schema_version: 1
id: RAC-KTQ7KNPS2HEF
type: roadmap
---
# RAC v0.7.13 — Metadata Migration

## Status

Planned

## Context

v0.7.11 introduced hybrid artifact metadata and system-assigned identity, and
deliberately deferred migration tooling: the one-time retrofit of RAC's own
corpus was performed with a throwaway script.

Users have no repeatable way to bring existing artifacts onto canonical
frontmatter identity. The expected workflow today is:

1. A repository adopts RAC and runs `rac init`.
2. New artifacts receive identity through `rac new`.
3. Existing artifacts remain legacy-format until edited by hand.

This blocks the staged migration policy in ADR-025, which requires migration
tooling before frontmatter can ever become recommended-by-default for
existing repositories.

It also blocks RAC's own corpus: documents that previously failed to classify
are being repaired by hand, and each repair should be able to pick up
canonical identity through a supported command rather than a script.

## Outcomes

- Users can migrate an existing repository onto canonical frontmatter
  identity with one deterministic command.
- Migration is safe to re-run at any time: already-migrated artifacts are
  untouched, newly recognizable artifacts are picked up.
- Documents that do not classify are reported, never guessed at.
- The Markdown body of every migrated artifact is preserved byte-for-byte;
  only the metadata envelope is added.
- RAC Core owns migration; Explorer, CI, and scripts consume the same
  service.

## Initiatives

### Initiative 1 — `rac migrate metadata`

Introduce:

```bash
rac migrate metadata <directory> [--dry-run] [--json]
```

The subcommand form reserves `rac migrate` for future migrations (for
example, relationship sections into frontmatter `relationships`, staged
separately by v0.7.11).

The command shall:

- Require an established repository key (`rac init`), failing with a usage
  error otherwise.
- Walk the directory with the canonical discovery used by repository
  commands (`--top-level` / `--recursive` conventions apply).
- For every recognized artifact without frontmatter: prepend canonical
  frontmatter (`schema_version`, one newly generated opaque ID, classified
  `type`) in stable key order.
- Leave every other byte of the file unchanged.
- Skip and report artifacts that already carry frontmatter.
- Skip and report documents that do not classify (unknown type).
- Dedupe generated IDs within the run and against the repository index.

### Initiative 2 — Migration Service

Implement migration in the service layer; the CLI remains a thin adapter.

Representative interface:

```python
migrate_metadata(directory, dry_run=False) -> MigrationReport
```

The report shall model per-file outcomes — migrated, already-canonical,
skipped-unknown — and expose a stable `to_dict` JSON contract with
`schema_version`.

### Initiative 3 — Dry Run

`--dry-run` shall produce the full report without writing any file, so users
can preview a bulk rewrite before committing to it. Dry-run output shall be
identical to the real run apart from the IDs (generated IDs are unique per
run) and an explicit dry-run marker.

### Initiative 4 — Safety and Conflicts

Migration shall never:

- Overwrite or modify an existing frontmatter block.
- Rewrite Markdown content, headings, or formatting.
- Assign an ID that already exists in the repository.
- Choose between conflicting identities (existing conflict validation
  continues to own that).

Legacy identifiers remain resolvable after migration through the existing
alias model; migration does not rewrite relationship references.

## Implementation Contract

The following decisions are pinned for v0.7.13.

### Exit Codes

| Outcome | Exit code |
| --- | --- |
| Migration or dry run completed (including nothing to migrate) | `0` |
| Operational failure (malformed repository config, ID generation exhausted) | `1` |
| Usage error (not a directory, repository not initialized) | `2` |

### JSON Contract

```json
{
  "schema_version": "1",
  "directory": "rac/",
  "recursive": true,
  "dry_run": false,
  "summary": {
    "total_files": 95,
    "migrated": 28,
    "already_canonical": 67,
    "skipped_unknown": 0
  },
  "files": [
    {
      "path": "rac/decisions/adr-001-markdown-first.md",
      "status": "migrated",
      "id": "RAC-01JY4M8X2QZ7",
      "type": "decision"
    }
  ]
}
```

- Per-file `status` is one of `migrated`, `already-canonical`,
  `skipped-unknown` — every walked file is listed individually, in sorted
  path order (matching `rac validate` directory semantics).
- `id` and `type` are null for files that were not migrated.
- A dry run emits the same shape with `dry_run: true`; generated IDs shown
  in a dry run are illustrative (each run generates fresh IDs).

### Human Output

Migrated files are listed with their assigned IDs; already-canonical and
unknown files are counted, with unknown paths listed so users know what
remains. Dry runs are explicitly marked.

### Testing

Golden tests inject a deterministic ID generator through a module-level seam
(the same pattern as v0.7.11's creation tests), keeping golden output
byte-stable.

## Success Measures

- Running `rac migrate metadata` twice in a row changes nothing the second
  time.
- A repaired document (unknown → classified) is migrated by the next run.
- Migrated artifacts pass `rac validate`, classify unchanged, and resolve
  through `rac resolve` by both canonical ID and legacy alias.
- `--dry-run` writes nothing and reports the same file set as a real run.
- RAC's remaining corpus documents can be migrated with the command alone,
  with no scripts.

## Assumptions

- v0.7.11 identity (frontmatter, ID generation, `rac init`) and v0.7.12
  lookup are merged and stable.
- The recognized-artifact boundary (ADR-010) stays the migration boundary:
  unknown documents are out of scope until they classify.

## Risks

- Bulk rewrites can surprise users: mitigated by `--dry-run`, the
  no-overwrite guarantees, and byte-preservation of the body.
- Type misclassification would write a wrong `type`: mitigated by migrating
  only confidently classified artifacts and reporting everything else.
- ID collisions in large batches: mitigated by per-run dedupe plus the
  repository index check (the same contract as `rac new`).

## Non-Goals

- Migrating relationship sections into frontmatter `relationships` (staged
  separately).
- Editing or restructuring Markdown content to make documents classify.
- Removing legacy `## ID` sections or rewriting references.
- Mandatory frontmatter for legacy artifacts.

## Related Decisions

- ADR-025
- ADR-026
- ADR-010

## Dependencies

- v0.7.11 Hybrid Metadata and System-Assigned Identity
- v0.7.12 Artifact Lookup and Resolution
- Repository index and identity alias model

## Follow-On Work

- Relationship metadata migration (frontmatter `relationships`).
- Re-run of the corpus retrofit once the remaining unknown-type documents
  classify (tracked on PR #46).
