---
schema_version: 1
id: RAC-KTYSRDWMYHPJ
type: roadmap
---
# RAC v0.11.0 — Portal Export

## Status

Planned

## Context

The Guide (`rac mcp`) serves recorded knowledge to coding agents; humans
have no equivalent read-only surface short of browsing the Markdown
tree. ADR-014 establishes that RAC artifacts stay viewer-agnostic and
that machine-readable exports are the mechanism by which external
viewers consume a corpus; ADR-012 places import and export capabilities
in the open-source core. The Portal is that surface: `rac export`
produces a canonical JSON payload of the corpus, and `--html` wraps the
same payload in a vendored, self-contained HTML viewer shell (built
from `lore-web/src/viewer/`, which already implements list, filter,
search, detail, and relationship views that work from `file://` with
zero network requests).

This item starts the v0.11.x series. The naming follows the consumer
surfaces: the MCP server is the Guide; the exported HTML viewer is the
Portal.

## Outcomes

- A repository's corpus can be exported as a single deterministic JSON
  document (`rac export --json`) whose shape is a stable public
  contract.
- The same corpus can be exported as one self-contained HTML file
  (`rac export --html`) that opens from `file://` with no network
  requests — attachable to releases, sendable to stakeholders.
- The lore-web viewer contract graduates from proposal to reconciled
  v1: the viewer consumes exactly what Core emits.

## Initiatives

### Initiative 1 — Export service and JSON contract

`rac.services.export.build_corpus_export()` composes existing Core
services (corpus walk, identity/aliases, relationship extraction) into
a typed `CorpusExport` model. Bodies are rendered to HTML with the
already-vendored `markdown-it-py` CommonMark renderer (raw HTML in
sources is escaped). The payload is deterministic: no timestamps, no
environment-dependent fields, stable ordering.

### Initiative 2 — Portal shell vendoring

The lore-web viewer build gains a shell-only mode that emits the
single-file viewer with an empty data seam
(`<script type="application/json" id="lore-export">`). The built shell
is committed as package data at `src/rac/templates/portal/` together
with a provenance manifest (lore-web source commit, shell hash, viewer
source-tree hash). A drift-guard test fails when the viewer source
changes without re-vendoring.

### Initiative 3 — CLI surface

`rac export [directory] [--json | --html] [--out PATH]` with the
repository-wide exit-code convention. JSON is the default mode and
prints to stdout; `--html` injects the payload into the shell and
writes a file, confirming with one human-readable line.

## Constraints

- No new dependencies; rendering uses `markdown-it-py` already in the
  base dependency set (ADR-029 delivery pattern, ADR-002 offline).
- Export is read-only by construction and imports no write-capable API
  (ADR-031).
- The JSON payload is a public contract under ADR-007: golden-tested,
  field names stable, `schema_version` a string `"1"` matching the
  index contract.
- Classification stays separate from validation: invalid but
  recognizable artifacts export as classified; unknown-type files are
  skipped and counted, mirroring `rac index`.
- Relationships export as untyped `relates-to` edges with unresolved
  targets preserved verbatim; richer edge semantics are a future Core
  decision, not an export invention.
- Determinism: two exports of the same tree are byte-identical;
  `generated_at` is omitted from the payload.

## Non-Goals

- No graph visualisation in the Portal (explicitly deferred by the
  viewer contract).
- No hosted or multi-tenant viewing; the trust boundary is the
  repository itself.
- No `supersedes`/`refines`/`implements` edge typing — reserved for a
  future decision.
- No changes to the Guide tool surface.

## Implementation Contract

### Module Locations

- `src/rac/services/export.py` — `CorpusExport`, `ExportArtifact`,
  `ExportRelationship` dataclasses; `build_corpus_export(directory,
  recursive=True)`.
- `src/rac/output/` — `render_export_json(export)`; HTML injection
  helper for the shell seam.
- `src/rac/templates/portal/lore-portal-shell.html` — vendored shell;
  `src/rac/templates/portal/provenance.json` — vendoring manifest.
- `lore-web/scripts/build-viewer-artifact.mjs` — gains `--shell-only`;
  `lore-web/VIEWER_CONTRACT.md` — reconciled v1.

### Public APIs

- `build_corpus_export(directory: str, recursive: bool = True) ->
  CorpusExport` with `to_dict()`.

### Payload Shape

`schema_version` (string), `corpus {name, rac_version,
artifact_count}`, `artifacts [{id, aliases, type, status, title, path,
body_html}]`, `relationships [{from, to, type}]`. Artifacts ordered by
path; relationships by (from, to); aliases as emitted by Core identity.

### Dependencies

None added.

### CLI and Exit Codes

`rac export [directory] [--json | --html] [--out PATH]`; exit 0 on
success, 2 on usage/IO errors (missing directory, unwritable output).
`--out` applies to `--html` (default `lore-export.html`); `--json`
writes to stdout.

### Testing

`tests/test_export_cmd.py` in the command battery: golden JSON against
a fixture corpus; HTML test extracts the embedded JSON and asserts
equality with `--json` output and shell byte-identity around the seam;
determinism (two runs identical); boundary cases (exit 2 on missing
directory, empty corpus, unresolved reference preserved, raw HTML in a
body escaped); the vendoring drift-guard comparing the recorded viewer
source-tree hash with the working tree (skipped outside the repo).

## Success Measures

- `rac export --json rac/` on this repository emits a payload the
  Portal renders fully (every artifact reachable, cited aliases
  linkified).
- `rac export --html rac/` produces one file that opens from `file://`
  with zero network requests.
- Golden tests pin both contracts; `pytest`, `rac validate`,
  relationship validation, and review gates pass.

## Risks

- Vendored shell drifts from lore-web source. Mitigated: drift-guard
  test pins the viewer source-tree hash; re-vendoring is a recorded,
  reviewable change.
- Body rendering differences between markdown-it (JS) and
  markdown-it-py for edge-case Markdown. Accepted: CommonMark spec
  compliance on both sides; bodies are rendered once, by Core, so the
  Portal displays exactly what Core emitted.
- Large corpora inflate the single HTML file. Accepted for this item:
  the viewer is verified smooth at 500 artifacts; compression and
  pagination are future concerns.

## Assumptions

- The lore-web viewer remains in this repository so the shell can be
  rebuilt and re-vendored from source.
- `markdown-it-py` CommonMark rendering with raw HTML disabled is an
  acceptable body-HTML trust posture (escaping, not sanitising).

## Related Decisions

- ADR-002
- ADR-003
- ADR-005
- ADR-007
- ADR-012
- ADR-014
- ADR-027
- ADR-029
- ADR-031
- ADR-032

## Related Requirements

- rac-agent-context-guide

## Dependencies

- v0.10.x Guide series (shared Core read services)
