Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ this project aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0

### Added

- feat(sidecar): JSON-family sidecar storage backend — plain JSON / JSON-LD / NDJSON with SQLite-parity octad runtime (provenance incl. forks, temporal, drift, gc); new `[sidecar].format` key and a single `StorageKind::resolve` backend resolver (V-L2-F3, ADR-0012, closes #146) (#148)
- feat(codegen): split sidecar DDL by dialect; reject json sidecar (#45) (#133)
- feat(codegen): split sidecar DDL by dialect; reject json sidecar (#45) (#131)
- feat(logging): tracing diagnostics with --log-format/--log-level (#51) (#124)
Expand All @@ -31,10 +32,6 @@ this project aims to follow [Semantic Versioning](https://semver.org/spec/v2.0.0
- feat(codegen): split sidecar DDL by dialect; reject json sidecar (#45) (#113)
- feat(provenance): fork-first-class chain model — ADR-0010 (#31; supersedes #32) (#109)

### Removed

- feat(manifest): drop the never-implemented `json` sidecar store; close `[sidecar].storage` to sqlite + postgres and reject unknown values at validate/doctor/generate (V-L2-F2, ADR-0011, closes #112) (#144, #147)

### Fixed

- fix(rhodibot): automated RSR compliance fixes (#135)
Expand Down
2 changes: 1 addition & 1 deletion README.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -239,7 +239,7 @@ temporal-versioning = true # automatic version history
drift-detection = true # cross-modal observer (Constraints symptom)

[tier1.provenance]
sidecar = "sqlite" # sqlite | postgres (V-L2-F2 / ADR-0011)
sidecar = "sqlite" # sqlite | postgres | json (V-L2-F3 / ADR-0012)
sidecar-path = ".verisimiser/provenance.db"

[tier1.temporal]
Expand Down
17 changes: 13 additions & 4 deletions docs/decisions/0011-sidecar-storage-is-relational.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,19 @@ Date: 2026-05-30

## Status

Accepted (2026-05-30) — implemented in #144 (json dropped from the storage
enum, docs, template, and validate/doctor messaging) with test coverage in
#147. Closes #112 (V-L2-F2). Builds on #45 (V-L2-F1, the per-dialect DDL
split) and ADR-0004 (octad ontology).
**Superseded by [ADR-0012](0012-json-family-sidecar-storage.adoc)
(V-L2-F3, #146 / #148).**

Originally Accepted (2026-05-30) and shipped as the V-L2-F2 drop — #144,
test coverage in #147, closing #112. It was then reversed *within the same
development cycle* by V-L2-F3 (#146), which re-opened and **implemented** the
JSON sidecar capability as a deliberately-scoped family (plain / JSON-LD /
NDJSON) at the maintainer's request. The "no storage-abstraction layer /
relational-only" premise below no longer holds: #148 introduced
`sidecar::StorageKind` and a format-independent `SidecarData` codec. This ADR
is retained as the historical record of the F2 decision; see ADR-0012 for the
current state. Builds on #45 (V-L2-F1, the per-dialect DDL split) and
ADR-0004 (octad ontology).

## Context

Expand Down
87 changes: 87 additions & 0 deletions docs/decisions/0012-json-family-sidecar-storage.adoc
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
= Architecture Decision Record: 0012-json-family-sidecar-storage
<!-- SPDX-License-Identifier: PMPL-1.0-or-later -->
<!-- Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) <j.d.a.jewell@open.ac.uk> -->

# 12. Implement the JSON-family sidecar store (supersedes ADR-0011)

Date: 2026-05-31

## Status

Accepted (2026-05-31) — implemented in #148. Closes #146 (V-L2-F3).
**Supersedes [ADR-0011](0011-sidecar-storage-is-relational.adoc)** (V-L2-F2,
the json *drop*), reversing it at the maintainer's request.

## Context

ADR-0011 (V-L2-F2, shipped in #144) dropped the never-implemented `json`
sidecar store on the grounds that the octad runtime is relational and no
storage-abstraction layer existed to host a second backend. That reasoning
was sound *at the time*, but it rested on an absence (no abstraction) rather
than an impossibility.

The maintainer subsequently asked for a real JSON sidecar capability (#146,
V-L2-F3) — deliberately reversing the drop — scoped as a *family* of on-disk
encodings with **full parity** to the runtime operations the SQLite path
implements today.

## Decision

Accept `[sidecar].storage = "json"` again, backed by a real implementation
(#148):

* **`[sidecar].format`** selects the on-disk encoding — `plain` (default) |
`ld` (JSON-LD) | `ndjson`. It is meaningful only for `json` and ignored
for `sqlite`/`postgres`.
* **One resolver.** `sidecar::StorageKind::resolve(storage, format)` is the
single source of truth (`Sqlite | Postgres | Json(JsonFormat)`);
`SqlDialect::from_storage` is folded into it, and `validate`/`doctor`,
`generate`, `drift`, and `gc` all dispatch on it.
* **Storage abstraction.** A single `SidecarData` model mirrors the
`verisimdb_*` tables (provenance log + chain-head set, temporal versions,
lineage edges, access policies). **Format is purely a codec** over that
model — every octad operation is written once and is format-independent
(plain = table→rows object; ld = `@context` + typed `@graph`; ndjson =
one record per line). Writes are crash-safe (temp file + atomic rename).
* **Octad parity.** Provenance (`append`, `append_fork`, `verify_chain`,
`fork_points`) reuses `abi::ProvenanceEntry::compute_hash`, so hashing is
identical across backends; temporal enforces monotonic versions and the
exactly-one-current invariant in code; drift reuses the `tier1` temporal
kernel; `gc` purges by age; `generate` emits a
`sidecar_schema.{json,jsonld,ndjson}` scaffold instead of SQL DDL.

### Relationship to ADR-0011

ADR-0011's premise — *no abstraction; a JSON store would re-encode
relational invariants in application code with weaker guarantees* — is
answered rather than simply contradicted: #148 introduces the abstraction
(`StorageKind` + the `SidecarData` codec) and re-encodes those invariants in
Rust with documented parity. The one acknowledged difference is concurrency:
the JSON store is single-writer (atomic whole-file rewrite) where SQLite
serialises via `BEGIN IMMEDIATE` locks. The guarantees SQL enforced
structurally (one current temporal row, monotonic versions, fork-aware
chains, hash identity) are now enforced in code for the json path and
covered by tests mirroring the SQLite suite, across all three formats.

## Consequences

### Positive

- `json` / `jsonld` / `ndjson` sidecars with octad parity — human-diffable,
portable, dependency-light stores, plus a genuine linked-data (JSON-LD)
option.
- A single backend resolver (`StorageKind::resolve`) for every storage value.

### Negative

- The json path enforces invariants in application code rather than via SQL
constraints, so it carries a heavier test burden and a documented
single-writer limitation.
- Two storage families to maintain in lockstep (the hash function is shared
via `abi`, which mitigates the highest-risk drift).

## References

- Issue #146 (V-L2-F3); PR #148.
- Supersedes ADR-0011 (V-L2-F2; issue #112, shipped in #144/#147).
- ADR-0004 (octad ontology), ADR-0010 (provenance forks are first-class).
Loading