Skip to main content
Use this recipe to combine protected source records into destination files. It captures the sources, checks every mapping, and waits for approval of the exact proposed output. It saves a complete backup before it writes a target. The example uses four synthetic source kinds: document, current record, discussion comment, and historical entry. Each complete source record is one protected fact. That fact includes its metadata and exact body text. The fixture uses scripted proposal and review data and calls no model.

Graph

Each stage uses an ordinary node with a stored attempt record. The apply node changes one target and reads it back. A separate verification node checks that result before the next action. The final node checks that all protected records remain in their destinations.

Contract

A source record holds an ID, a declared kind, metadata, and a body string. A destination record holds an ID, a revision, an active flag, content, and the last action’s identity. The fixture stores these records as UTF-8 JSON files with mode 0600. The mapping must preserve each complete source record in an active destination. A change that drops metadata or alters a protected body is refused. The recipe does not extract semantic facts from prose or decide which facts can be discarded.

Approval and writes

The proposal covers destination content, expected revisions, mappings, and source hashes. Approval binds its exact bytes and the proof packet through the stored callback APIs. The host posts and answers the callback only while the executor is stopped. Before each write, the file adapter checks stored approval, live source hashes, the target’s version and active status, and the verified backup. Models can propose or review data; the deterministic adapter applies it. The runnable fixture’s approve() helper supplies a scripted answer. A real host must collect its decision through the stored callback client. The backup retains the original source and target file bytes, including all record fields needed to restore them. Artifact hashes protect these bytes. The recipe checks the backup before use. It provides no automatic restore. Each file merge has one stable action identity taken from its original stored node attempt. The target’s content, revision, and action witness are saved together through one file replacement. The witness identifies the write that produced the content. The recipe appends intent and result events to each target’s own stream. It keeps source event histories separate. It never imports a source’s event history into a target stream or the executor’s run stream.

Failure, pause, and recovery

A readback that succeeds with different bytes records a mismatch and pauses the graph before the next write. A person must decide what happens next. Changing the file back and calling resume does not clear that mismatch. A readback that cannot run fails the graph. An action that fails before an uncertain outward result also fails it. If a write succeeds but its response is lost, the recipe reads the target before deciding whether it completed. After a process crash leaves an action’s outcome unknown, the executor pauses that attempt for reconciliation. On resume, the recipe checks the saved intent, exact target bytes, and action witness. A matching completed write returns its evidence without another application. Unknown or mismatched state stays paused. Resume alone never authorizes a repeated write. This adapter assumes one writer owns the synthetic record directory. An external service requires its own conditional-write and action-status contract. The file example makes no guarantee about an arbitrary remote API.

Run the line

From an Obversa checkout, run:
The example writes only to its temporary directory and removes it when done. The report is:

Source

Copy all three files from examples/safe-change/: recipe.ts drives the stored graph, file-adapter.ts reads and changes records, and example.ts runs the fixture below. Keep them in one directory. They import public runtime exports.
The clean-consumer check compiles all three files against the packed runtime with TypeScript 6 and TypeScript 7. It runs the compiled and direct TypeScript forms and compares their reports with this page.