diff --git a/.claude/hooks/session-start.sh b/.claude/hooks/session-start.sh new file mode 100755 index 0000000..d284bc5 --- /dev/null +++ b/.claude/hooks/session-start.sh @@ -0,0 +1,28 @@ +#!/bin/sh +# SPDX-License-Identifier: PMPL-1.0-or-later +# Copyright (c) 2026 Jonathan D.A. Jewell (hyperpolymath) +# +# session-start.sh — Claude Code (web) SessionStart hook for verisimiser. +# +# Warms the Cargo dependency + build cache so `cargo build`, `cargo test`, +# `cargo clippy`, and `cargo fmt` are ready the moment a web session starts, +# instead of paying the cold-compile cost on the first tool call. Runs +# synchronously; the container state is cached after it completes. +# Idempotent and non-interactive. +set -eu + +# Only run in the remote (Claude Code on the web) environment. Local +# developers manage their own toolchain via setup.sh / direnv (.envrc). +if [ "${CLAUDE_CODE_REMOTE:-}" != "true" ]; then + exit 0 +fi + +cd "${CLAUDE_PROJECT_DIR:-.}" + +# Fetch the pinned dependency graph (Cargo.lock is committed), then compile +# all targets including tests. A transient compile error in a work-in-progress +# tree must not block the session from starting, so the warm build is +# best-effort; the fetch is the part that genuinely needs to succeed. +cargo fetch +cargo build --all-targets \ + || echo "session-start: cargo build did not complete cleanly (continuing)" >&2 diff --git a/.claude/settings.json b/.claude/settings.json new file mode 100644 index 0000000..4aea0ed --- /dev/null +++ b/.claude/settings.json @@ -0,0 +1,33 @@ +{ + "permissions": { + "allow": [ + "Bash(git status:*)", + "Bash(git log:*)", + "Bash(git diff:*)", + "Bash(git show:*)", + "Bash(git branch:*)", + "Bash(git fetch:*)", + "Bash(cargo build:*)", + "Bash(cargo test:*)", + "Bash(cargo check:*)", + "Bash(cargo clippy:*)", + "Bash(cargo fmt:*)", + "Bash(cargo fetch:*)", + "mcp__github__pull_request_read", + "mcp__github__issue_read", + "mcp__github__list_pull_requests" + ] + }, + "hooks": { + "SessionStart": [ + { + "hooks": [ + { + "type": "command", + "command": "$CLAUDE_PROJECT_DIR/.claude/hooks/session-start.sh" + } + ] + } + ] + } +} diff --git a/CHANGELOG.md b/CHANGELOG.md index 6386fea..849767d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -31,6 +31,10 @@ 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) diff --git a/README.adoc b/README.adoc index a7f1bd4..0c152fb 100644 --- a/README.adoc +++ b/README.adoc @@ -239,7 +239,7 @@ temporal-versioning = true # automatic version history drift-detection = true # cross-modal observer (Constraints symptom) [tier1.provenance] -sidecar = "sqlite" # sqlite | file | verisim +sidecar = "sqlite" # sqlite | postgres (V-L2-F2 / ADR-0011) sidecar-path = ".verisimiser/provenance.db" [tier1.temporal] diff --git a/docs/decisions/0011-sidecar-storage-is-relational.adoc b/docs/decisions/0011-sidecar-storage-is-relational.adoc new file mode 100644 index 0000000..9e25583 --- /dev/null +++ b/docs/decisions/0011-sidecar-storage-is-relational.adoc @@ -0,0 +1,93 @@ += Architecture Decision Record: 0011-sidecar-storage-is-relational + + + +# 11. The sidecar octad store is relational; drop the JSON backend + +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). + +## Context + +The manifest's `[sidecar].storage` historically advertised `sqlite | json`, +but only the SQLite path — and later a PostgreSQL DDL dialect — had code. + +* **#45 (V-L2-F1)** split the sidecar DDL by dialect and stopped silently + emitting SQLite DDL for a `json` store, rejecting `storage = "json"` at + `generate` time with a pointer to #112. +* **#112 (V-L2-F2)** posed the decision squarely: *implement* a JSON + document store mirroring the `verisimdb_*` tables (with the same + octad-dimension coverage as the SQLite path, honoured by + `generate`/`drift`/`gc`), or *drop* `json` from the schema, docs, and + validation. + +## Decision + +Drop `json`. The supported `[sidecar].storage` set is **closed** to: + +* `sqlite` — the reference runtime store, used by `generate`, `drift`, and + `gc`; and +* `postgres` / `postgresql` — a `generate`-time DDL dialect. + +`codegen::overlay::SqlDialect::from_storage` is the single source of truth +for accepted values. `validate`/`doctor` (the `sidecar-storage-supported` +check) and `generate` all defer to it, so an unsupported store is rejected +uniformly and up front rather than only at code-generation time, and with no +"coming soon" pointer. + +### Why relational, not document + +The octad data layer's correctness rests on invariants that SQL enforces +*structurally*, and that a JSON document store would have to re-implement in +application code with weaker guarantees: + +* **Provenance** is an append-only hash chain advanced under a + `BEGIN IMMEDIATE` write transaction; the multi-head tip set and the + duplicate guard are the `hash` PRIMARY KEY plus a non-unique predecessor + index (ADR-0010). +* **Temporal** versioning enforces "exactly one current version per + `(entity, table)`" with a partial `UNIQUE` index, and `valid_to >= + valid_from` with a `CHECK`. +* **Lineage** acyclicity begins with a self-edge `CHECK` at the storage + layer (ADR-0005). +* **Closed enums** (`operation`, `derivation_type`, `access_level`, + `status`) are `CHECK` constraints; simulation branch parentage is a + self-referencing foreign key. + +There is also no storage-abstraction layer to host a second *runtime* +backend — provenance, temporal, drift, and gc are all SQLite-specific — and +`postgres` itself is already `generate`-only. A half-built JSON store would +re-introduce exactly the advertised-but-unimplemented surface #45 set out to +remove. + +## Consequences + +### Positive + +- The advertised configuration surface is honest: every accepted + `[sidecar].storage` value has code behind it. +- One validation path — `validate`, `doctor`, and `generate` cannot + disagree on the accepted set. +- No application-layer re-encoding of invariants the relational engine + enforces for free. + +### Negative + +- No document-store option. Should one be wanted later (e.g. an embedded, + dependency-free flat-file deployment), it is a deliberate and larger piece + of work: introduce a real storage trait, re-encode every storage-layer + invariant listed above against it, and re-add the value to + `from_storage`. Until then, `json` is simply another unsupported value. + +## References + +- Issue #112 (V-L2-F2); issue context from #45 (V-L2-F1). +- PRs #144 (drop) and #147 (test coverage). +- ADR-0004 (octad ontology), ADR-0005 (lineage acyclicity), + ADR-0010 (provenance forks are first-class).