Files
road-compiler/.trellis/spec/backend/direct-edit-api.md

97 lines
4.1 KiB
Markdown

# Direct Edit Solver And API
## 1. Scope / Trigger
This contract covers the pure direct-edit resolver and the workbench endpoints
that preview, persist, checkpoint, and rebase `native-road-edits/v2` documents.
It is required because the feature crosses compiler, storage, and HTTP layers.
## 2. Signatures
```js
resolveDirectEditConstraints(model, editDocument, context)
// -> { roadProfiles, junctionPlans, constraintStates, handles, diagnostics }
GET /api/edit-state
POST /api/edit-preview
POST /api/edits
POST /api/revisions
POST /api/revisions/:id/rebase
```
## 3. Contracts
- The resolver is deterministic and must not import `fs`, `path`, `http`, or
write files. It accepts a baseline model, an optional validated v2 document,
and `{ revisionId, compilerGeometryVersion, previewSeq }` context.
- `GET /api/edit-state` returns the active document, `documentVersion`, active
revision, constraint states, diagnostics, and `road-edit-handles/v1` manifest.
- `POST /api/edit-preview` accepts `document` (or replacement `constraints`
and `operations`) and optional integer `previewSeq`. It returns the same
manifest/state data plus compiled layers, echoes `previewSeq`, and never
changes active files. Work over the 300ms budget sets `degraded: true`.
- `POST /api/edits` requires integer `expectedDocumentVersion` and either a
complete `document` or replacement arrays. Saves through a staging file and
rename, increments `documentVersion`, and returns the active revision.
- A version mismatch returns HTTP 409 with `{ ok: false, error, current }` and
leaves the active document byte-identical.
- `POST /api/revisions` requires a non-empty `label`; it creates an immutable
checkpoint and returns its manifest plus current edit state.
- `POST /api/revisions/:id/rebase` is read-only and returns status counts plus
per-constraint details. `pending`, `conflicted`, and `stale` constraints do
not enter geometry solving.
## 4. Validation & Error Matrix
| Condition | Required behavior |
| --- | --- |
| Missing or invalid v2 document fields | Reject with a field-specific 400 error |
| Missing `expectedDocumentVersion` | Reject with 400; do not write |
| Expected version differs from active version | Return 409 and current version; do not write |
| Empty checkpoint label | Reject with 400; no revision is created |
| Missing active workspace for preview | Reject with 400; no files are created |
| Solver replay anchor cannot resolve | Mark `stale` or `conflicted` with diagnostic; never silently apply |
| Compiler geometry version differs | Mark all replayable constraints `recheck` without changing values |
| Invariant violation (lane width, crossing boundaries, self-intersection, connector bounds) | Return a blocking diagnostic; do not clamp or silently repair |
## 5. Good / Base / Bad Cases
- Good: preview the same document later saved with the same active baseline;
compiled layers and solver diagnostics agree, while tracked file bytes and
mtimes remain unchanged.
- Base: an empty v2 document yields byte-for-byte baseline geometry and an
empty constraint-state list.
- Bad: two tabs save version `N`; the second save with `N` receives 409 and
cannot overwrite the first tab's document.
## 6. Tests Required
- Unit: resolver purity, empty-document identity, five replay statuses, handle
manifest fields, and blocking invariant diagnostics.
- Integration: preview file bytes/mtimes unchanged, `previewSeq` echoed,
preview layers matching formal compilation, and `degraded` represented as a
boolean.
- Persistence: atomic save, version-409 no-write path, checkpoint parent and
digest integrity, and rebase counts/details.
## 7. Wrong vs Correct
### Wrong
```js
fs.writeFileSync(activeEdits, JSON.stringify(draft));
```
This permits half-written documents and allows a stale browser tab to overwrite
newer edits.
### Correct
```js
if (expectedDocumentVersion !== current.documentVersion) throw conflict409();
saveEditDocument(activeEdits, { ...draft, documentVersion: current.documentVersion });
```
The service checks the version before the atomic version-bumping write, while
the pure resolver remains reusable by preview and formal compilation.