Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

ADR-036: Deterministic replay model + clock/identity seam

Date: 2026-06-18 Status: Accepted Author: Rômulo Febasi

SYN-266 (INV-4). The verify-first investigation the issue mandated: “ADR-027 assumes but never verified that audit replay reconstructs the store.” It does not. Companion to ADR-026 (audit), ADR-027/031 (bitemporal), ADR-035 (verify report). Grounds: docs/researchs/2026-06-16-provable-non-corruption-syn-verify.md.


Context

The brief’s INV-4 read: “replaying every accepted audit payload into an empty workspace reconstructs the current store.” Before building it the issue required verifying that assumption. It is false as stated, for three reasons found in the code:

  1. The audit log is hash-only. AuditEntry stores payload_sha256, not the payload. The replayable payloads live in the pending table: part of the disposable index, and prunable (syn audit prune). So the audit log is not an event journal you can replay.
  2. The wall clock is baked in, not journaled. apply stamps created/updated (via Frontmatter::now_iso) and the belief axis (x-recorded-at) from now() / the accept-time stamps, and accepted_by from the environment. Two of these are fresh now() calls recorded nowhere a replay can read, so a replay cannot reproduce them byte-for-byte.
  3. Direct CLI writes are not audited. syn project add, tag, rm, and manual Markdown edits never enter the queue. The decision journal therefore covers only the governed (AI-proposed → human-accepted) path.

The deeper point (3) exposes: Synapse is state-based, not event-sourced. The Markdown is the user-editable source of truth; the decision journal records what was proposed and accepted (provenance), not the current content (which the human may have since edited: legitimately). “Replay the decision journal == current store” is the wrong model: it would flag every legitimate post-accept edit as corruption.

Decision

1. Reframe INV-4 to what is sound and provable. Two separable claims:

  • Replay determinism (provable, shipped). apply is a deterministic function of (payload, stamps, clock). Given the same journal and the same pinned clock, replay reproduces the store exactly. This is proven by a property test (core::replay, ≥48 generated cases incl. a supersession).
  • Store reconstruction is NOT journal replay. It is INV-1 (the index is a reproducible build of the Markdown: ADR-035) and INV-7 (any past state is reconstructible from git via syn at: SYN-258). Those, not audit-replay, are Synapse’s reconstruction guarantees.

2. Introduce a clock/identity seam. Frontmatter::now_iso consults a per-thread pinned value (pin_clock, returning an RAII guard); production never pins, so behaviour is unchanged. Replay pins the recorded instant per op so timestamps are reproducible. This is the seam the issue asked for.

3. syn verify --deep is a bounded journal-integrity check, not store reconstruction. It replays the governed journal (accepted, still-present pending rows) through the same mutation engine the accept path uses, in two throwaway scratch workspaces, and confirms every op applies cleanly and the two replays agree (determinism on the real data). It catches a tampered or corrupt pending payload. It does not compare against the live store (unsound for an editable-Markdown store). The bound (pruned payloads and CLI-direct writes are outside the journal) is intrinsic and stated in the report evidence.

Consequences

  • Improves: an honest, sound INV-4 ships today; the clock seam makes the governed write path reproducible; the deep check catches journal corruption with no false positives on legitimate edits; core::replay reuses pending::apply so the replay and accept engines cannot drift.
  • Worsens / limits: --deep does not prove “the store equals its decision history”: that is not a property of a state-based store and is deliberately not claimed. Exact store reconstruction from a complete, durable op-journal would need the journal to record the payloads and the wall-clock stamps (today they are hash-only / fresh-now()); that is a larger event-sourcing refactor, not pursued because Markdown-as-truth + git already reconstruct state (INV-1/INV-7).
  • Open: if a future “shared/governed team” edition wants audit-log-only reconstruction (no shared Markdown), it must durably journal payloads + a clock/identity seam in the log: pull this then.

Alternatives considered

  • Replay the audit log into an empty workspace and compare to the store. Rejected: the audit log is hash-only (no payloads), and the comparison is unsound: a human editing a governed fact afterwards is legitimate, not corruption.
  • Replay the pending journal and compare a timestamp-normalised dump to the live store. Rejected: still false-positives on any legitimate post-accept Markdown edit (Markdown is the truth, not the journal); the normalisation needed to hide that is broad enough to risk fake-green.
  • Thread explicit created/updated/accepted_at parameters through every create_* and apply. Heavier and more invasive than a single thread-local clock seam, for the same determinism. Rejected in favour of pin_clock.
  • A full event-sourcing rewrite (the store is a fold over an op-log). Rejected: it contradicts Markdown-as-truth (ADR foundation) and git-native history; reconstruction is already INV-1/INV-7.