Files
road-compiler/.trellis/spec/backend/road-revision-storage.md

4.3 KiB

Road Revision Storage

1. Scope / Trigger

src/compile/road-revisions.js owns the persistent v2 workbench layout. It is called after a successful import and when an existing workbench area is read, so legacy import-* directories migrate lazily without an administrative command.

2. Signatures

ensureRevisionStore(workspace) // -> { paths, active, baseline }
setActiveAreaConfig(workspace, options) // -> { paths, areaConfig }
readAreaConfigSnapshot(file) // -> compiler options
createCheckpoint(workspace, label) // -> restored revision
readRevision(workspace, revisionId) // -> { manifest, osm, areaConfig, nativeRoadOverrides, directEdits, trafficSignals }

3. Contracts

  • Legacy inputs remain at source.osm, native-road-overrides.json, and native-traffic-signals.json; migration must never rewrite them.
  • OSM bytes are stored once under osm/<sha256>.osm.
  • active/native-road-edits.json starts as a validated v2 document at version 0; active/state.json points to the active revision.
  • The active config is { schema: 'road-workbench-area-config/v1', options } at active/area-config.snapshot.json. Updating it must also update base.areaConfigSha256 through a versioned v2 document write.
  • A revision is immutable at revisions/rev-NNNN/. Its manifest.json uses road-workbench-revision/v1, references the content-addressed OSM and frozen area-config.snapshot.json, lists frozen JSON documents, and records SHA-256 digests.
  • compileInput() uses areaConfigSnapshotFile when present. Its external options are ignored in that mode; a missing or invalid snapshot is an error, never a fallback.

4. Validation And Error Matrix

Condition Error / behavior
Required legacy input missing Revision source is missing: <path>
Snapshot file is missing Area config snapshot is missing: <path>
Snapshot schema or options are invalid Invalid area config snapshot: <path>
Content-addressed OSM differs from its filename digest Content-addressed OSM is corrupt
Revision manifest schema or ID mismatch Invalid revision manifest
Revision ID is not rev-NNNN Reject with Invalid revision id
Manifest path leaves its OSM or revision directory Reject the read; never resolve an external path
Referenced frozen OSM or JSON digest mismatch Reject the read with a digest mismatch error
Empty checkpoint label Reject with label must be a non-empty string

5. Good / Base / Bad Cases

  • Good: first open of a legacy directory adds osm/, active/, and revisions/rev-0001/, leaving old files byte-identical.
  • Base: opening an already migrated directory is idempotent; an interrupted migration with a baseline but no active state repairs only the missing state file.
  • Bad: mutating a frozen revision document, config snapshot, or referenced OSM must make readRevision() fail rather than returning altered input.

6. Tests Required

test/road-revisions.js must cover:

  • legacy-byte preservation and baseline creation;
  • equal OSM bytes yielding one content-addressed copy;
  • named checkpoint creation, parent linkage, and complete restore;
  • manifest digest equality with the actual frozen files, including area config;
  • active config writes incrementing documentVersion and preserving old revision snapshots;
  • compiler snapshot precedence and an explicit missing-snapshot error;
  • invalid checkpoint labels.

test/fixtures.js compares normalized current compiler outputs with the checked-in native-road-package/v1.1 fixture baselines. Regenerate them only through npm run test:fixtures:update-baseline after an intentional compiler output change.

7. Wrong Vs Correct

Wrong

fs.copyFileSync(sourceOsm, path.join(revisionDirectory, 'source.osm'));

This duplicates equal OSM files and provides no integrity check.

Correct

const { file, digest } = storeOsm(paths);
manifest.source = { osmFile: path.relative(paths.workspace, file), osmSha256: digest };

The content address is both the deduplication key and the integrity contract.

Wrong

compileInput({ ...input, options: externalOptions });

This allows later external config changes to alter a frozen revision.

Correct

compileInput({ ...input, areaConfigSnapshotFile: revisionSnapshot });

The compiler validates and consumes the frozen snapshot exclusively.