# Implementation Plan: Constrained translation of novel content

## Approach

Build the constraint harness, leave the renderer pluggable, and default it to refusing.

None of the four acceptance criteria require a model. They ask that an agreed rendering
be used and no other, that protected terms survive byte-identical, that every rendering
names its authority, and that removing a term from the terminology authority escalates
rather than silently changes output. All four are properties of the harness around the
renderer, not of the renderer. Building them first means the risky part arrives into a
structure that can already catch it misbehaving.

So: a renderer is a function from a segment and its constraints to a candidate string.
The harness assembles the constraints, calls the renderer, and then **verifies the
candidate against the same constraints it supplied**. A renderer that ignores an agreed
rendering, drops an identifier or alters a code span is rejected, not trusted. This is
the part that matters, because an unverified generative step is indistinguishable from
guessing.

The default renderer refuses. Wiring a real one is a separate decision with
consequences this plan does not get to make: credentials in CI, cost, which model, and
where it runs. Until then the pipeline is safe by construction, and spec 004 already
covers everything resolvable without a renderer.

Provenance is recorded per segment, not per file, with the authority named from the
order CLAUDE.md §10 and the project already use: terminology authority, then the target
page title, then approved memory, then fresh. AC-3 asks for the authority, and a
reviewer reading a proposal needs to know which renderings rest on nothing stronger
than the model.

AC-4 falls out of recording provenance properly. A rendering attributed to the
terminology authority is checked against it on every run, so removing the entry turns
that rendering into an escalation rather than letting the next run quietly produce
something else.

## Decisions (2026-10-02)

**Where it runs.** On demand, invoked by whichever writer is making the documentation
change. Not in CI: that would need credentials as pipeline variables and cost something
on every run, to automate a step that only happens when a person is already sitting with
the change. This also matches the decision that translation is the final step of the
work that causes it.

**On rejection.** One retry, with the violated constraint fed back to the renderer, then
escalate. Retrying is cheap and often works, and an uncapped loop would let a renderer
that cannot satisfy a constraint burn attempts before escalating anyway.

**What the renderer is.** An agent, invoked on demand by the writer making the change.
This project has never used a human translator: every translation has been produced by
an agent and reviewed afterwards by native speakers. So the renderer interface is not
an API client but the two halves of an exchange — `--request` emits each unresolved
segment with the constraints binding it, `--apply` takes the renderings back and
verifies each one before anything is written. No credentials, no per-run cost, and the
writer is already in a session when the work arises.

## Rollback

The harness writes nothing on its own; it returns candidates and escalations to its
caller, which is the writer from spec 004. With the default renderer in place the
behaviour is identical to 004 alone.

## Appendix

### Alternatives considered

**Calling a model directly from the harness.** Rejected for now. It makes the module
untestable without credentials, and it settles questions about cost and placement that
belong to whoever operates the pipeline.

**Trusting the renderer and verifying only at the gate.** Rejected. The gate checks a
finished file, so a constraint violation would be reported as a structural defect with
no indication that a renderer caused it. Verifying at the point of generation names the
segment and the constraint it broke.

**Letting a violated constraint fall back to the source text.** Rejected outright. That
is the defect spec 006 exists to catch, and 004 already refuses rather than doing it.