You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
| TXID | Engine-issued transaction id (`Value::TxId`). Populate only via the library API with a bound parameter; SQL literals are rejected. Sync-apply advances the local TxId allocator past incoming peer values. |`Value::TxId(tx.id())`|
137
138
138
139
NULL values display as `NULL`. Vectors display as `[0.1, 0.2, ...]`.
139
140
@@ -159,6 +160,39 @@ CREATE TABLE decisions (
159
160
|`UNIQUE`| No duplicate values (single column). A duplicate INSERT on a `UNIQUE` column is a silent no-op (returns `Ok(rows_affected=0)`), matching the composite-uniqueness contract. |
160
161
|`DEFAULT expr`| Default value for inserts |
161
162
|`REFERENCES table(col)`| Foreign key — writes are rejected if the referenced row does not exist; in explicit transactions the error may surface at `COMMIT`|
163
+
|`IMMUTABLE`| Column is audit-frozen — INSERT sets the value once; `UPDATE`, `ON CONFLICT DO UPDATE`, sync-apply mutations, and schema-altering DDL against the column are rejected with `Error::ImmutableColumn`|
164
+
165
+
### Audit-Frozen Columns
166
+
167
+
An audit-frozen column carries data that must not be silently rewritten by anyone, through any path. Declare it with `IMMUTABLE`:
168
+
169
+
```sql
170
+
CREATETABLEdecisions (
171
+
id UUID PRIMARY KEY,
172
+
decision_type TEXTNOT NULL IMMUTABLE,
173
+
description TEXTNOT NULL IMMUTABLE,
174
+
reasoning JSON,
175
+
confidence REAL,
176
+
status TEXTNOT NULL DEFAULT 'active'
177
+
) STATE MACHINE (status: active -> [superseded, archived])
178
+
```
179
+
180
+
`decision_type` and `description` are provenance — set once at INSERT and never rewritten. `status` and `confidence` remain mutable. An `UPDATE decisions SET decision_type = '…'` returns `Error::ImmutableColumn`; the row is unchanged. Sync-apply across a NATS edge enforces the same rule on the peer: incoming row-changes that mutate a flagged column are rejected and surface in `ApplyResult.conflicts`. `ALTER TABLE ... DROP COLUMN`, `RENAME COLUMN`, and column-type-altering ALTER against a flagged column are refused.
181
+
182
+
Correction without rewrite — the supersede pattern. When a recorded decision turns out to be wrong, insert a new row with the corrected values and mark the original `superseded`:
183
+
184
+
```sql
185
+
-- Original (frozen)
186
+
INSERT INTO decisions (id, decision_type, description, status)
Copy file name to clipboardExpand all lines: docs/usage-scenarios.md
+3-1Lines changed: 3 additions & 1 deletion
Display the source diff
Display the rich diff
Original file line number
Diff line number
Diff line change
@@ -75,10 +75,12 @@ The `DAG('DEPENDS_ON', 'BASED_ON')` declaration means these specific edge types
75
75
76
76
**Problem:** Decisions have a lifecycle — draft, active, superseded, invalidated. Invalid transitions (draft directly to superseded) should be impossible.
77
77
78
+
**Correction-via-supersede.** Provenance columns — what was decided, what it was based on — are marked `IMMUTABLE`. A recorded decision is never silently rewritten. When a correction is needed, insert a *new* row with the corrected values and transition the original `status` to `superseded`. Nothing disappears from the audit trail.
0 commit comments