From 1e7334275af3240bbd3ed78bac2a341c94445847 Mon Sep 17 00:00:00 2001 From: Ragnor Comerford Date: Tue, 28 Apr 2026 23:38:24 +0200 Subject: [PATCH] Trim always-on rules to architectural-level invariants MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Drops four rules whose phrasing leaned on implementation specifics (nearest LIMIT, __run__ branches, __schema_apply_lock__, branch_list filter convention) — those are real constraints, but they live at the implementation layer and would go stale if internals are renamed or refactored. The architectural intent is captured by the remaining six rules and by the per-area docs. Reframes the kept rules at the survives-a-rename level: "multi-dataset publish is atomic across the whole graph" rather than naming the manifest table or the publisher type, etc. Co-Authored-By: Claude Opus 4.7 --- AGENTS.md | 18 +++++++----------- 1 file changed, 7 insertions(+), 11 deletions(-) diff --git a/AGENTS.md b/AGENTS.md index 251d0f5..6410a43 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -84,18 +84,14 @@ Full diagram and concurrency model: [docs/architecture.md](docs/architecture.md) ## Always-on rules (load these into your working memory) -These invariants need to be in scope on every change — they're the ones that quietly break if forgotten. The full architectural invariants and deny-list live in [docs/invariants.md](docs/invariants.md); §IX (deny-list) is the fastest first-pass when reviewing any change. +These are architectural rules that need to be in scope on every change. They're framed at the level that survives renames and refactors — the deeper implementation specifics (function names, lock names, branch-prefix conventions, enforcement points) live in the per-area docs and may evolve. The full architectural invariants and deny-list are in [docs/invariants.md](docs/invariants.md); §IX (deny-list) is the fastest first-pass when reviewing any change. -1. **`__manifest` is the atomic-publish boundary.** Multi-dataset commits flip via a single `ManifestBatchPublisher` write. Don't introduce code paths that publish per sub-table outside the batch publisher — you'll lose snapshot isolation across tables. -2. **`nearest($x.vec, $q)` requires a `LIMIT`.** The compiler enforces it, but if you're touching the query lowering or executor, don't break this rule. ANN without a limit is unbounded. -3. **Snapshot isolation per query.** A query holds one `Snapshot` for its lifetime. Don't read against `db.head()` mid-query; use the snapshot bound at lowering time. -4. **Run isolation lives on `__run__` branches.** Mutations inside `begin_run` … `publish_run` must go through `run_branch`, not `target_branch`. Publish picks fast-path (target unmoved) or merge-path (three-way). -5. **Schema apply is serialized via `__schema_apply_lock__`.** Concurrent `apply_schema` is not safe. Don't bypass the lock. -6. **`branch_list()` filters internal branches.** `__run__…` and `__schema_apply_lock__` must not appear in user-visible listings, exports, or policy-scoped operations. If you add a new system branch, follow the `__name__` prefix convention and add it to the filter. -7. **Bearer-token plaintext never persists in process memory.** Tokens are SHA-256 hashed at startup; comparison uses `subtle::ConstantTimeEq`. The actor id is server-resolved from the hash match — it must not be settable by the client. -8. **Mutations are atomic at the manifest commit boundary.** Multi-statement `change` queries publish one commit. Don't commit per-statement. -9. **Indexes are built on the branch head, not on a snapshot.** Reads always see the current index state. Lazy fork: a branch that hasn't mutated a sub-table reuses the source's index until the first write. -10. **Stable type IDs survive renames.** Schema migration uses `stable_type_id` (kind+name hashed at first sight). Don't mint new IDs on rename. +1. **Multi-dataset publish is atomic across the whole graph.** A graph commit flips every relevant sub-table version visible together, in one manifest write. Don't introduce code paths that publish per sub-table outside the unified publish path — that loses cross-table snapshot isolation. +2. **Snapshot isolation per query.** A query holds one snapshot for its lifetime. Don't re-read the current head mid-query. +3. **Mutations are atomic at the commit boundary.** Multi-statement change queries publish one commit. Don't commit per-statement. +4. **Bearer-token plaintext never persists in process memory.** Tokens are hashed at startup; auth uses constant-time comparison; the actor id is server-resolved from the hash match and must not be settable by the client. +5. **Reads always see the current index state for the branch they're reading.** Indexes track the branch head, not historical snapshots. If you change index lifecycle, preserve this guarantee. +6. **Stable type IDs survive renames.** Schema migration relies on identity that's stable across rename — don't mint new IDs on rename. ### Deny-list (fast-pass review filter — full reasoning in [docs/invariants.md §IX](docs/invariants.md))