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
Copy file name to clipboardExpand all lines: docs/architecture.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
@@ -186,7 +186,7 @@ Two further per-type dimensions are resolved in Go and passed to every call **ex
186
186
187
187
## Block-type registry (migration 072)
188
188
189
-
`context_block_types` is the declarative per-type behaviour registry of the workflow-engine line. The four M035 block-type classes (`knowledge`, `audit-trail`, `reference`, `system-meta`) ship as builtin seed rows (migration 072) whose JSONB configs reproduce today's hardcoded behaviour (retrieval damping, guard participation, dream linkability, digest/overview inclusion, classify rules); Welle I-C adds two more builtin rows (migration 084), the `issue`/`comment` workflow types (see below). The daemon starts on a compiled-in builtin set, overlays the DB rows at boot (merge — a deleted builtin row can never blank the default type) and hot-reloads on any table write via the same NOTIFY channel the settings use; edits via `psql` take effect without a restart.
189
+
`context_block_types` is the declarative per-type behaviour registry of the workflow-engine line. The four M035 block-type classes (`knowledge`, `audit-trail`, `reference`, `system-meta`) ship as builtin seed rows (migration 072) whose JSONB configs reproduce today's hardcoded behaviour (retrieval damping, guard participation, dream linkability, digest/overview inclusion, classify rules); Welle I-C adds two more builtin rows (migration 084), the `issue`/`comment` workflow types, and migration 107 adds the `checkpoint` evidence type (see below). The daemon starts on a compiled-in builtin set, overlays the DB rows at boot (merge — a deleted builtin row can never blank the default type) and hot-reloads on any table write via the same NOTIFY channel the settings use; edits via `psql` take effect without a restart.
190
190
191
191
A boot that finds the table but cannot load it (corrupt row) degrades **loudly**: `/health` carries `blocktype_registry: "builtin-fallback"`, the overall status drops to `degraded`, and a 30s retry loop self-heals once the row is fixed.
192
192
@@ -204,6 +204,8 @@ A boot that finds the table but cannot load it (corrupt row) degrades **loudly**
204
204
205
205
**Issue/comment workflow types (Welle I-C, migration 084).** `issue` is `full-pass` retrieval; its guard participates in **flag** mode (a duplicate issue is surfaced via a `possible_duplicate` flag, never auto-archived — §4.7) restricted to `same-scope` candidates, with per-type thresholds 0.97/0.90; dream links issues, but `digest.include=false` **and** `overview.include=false` keep a 10k+-issues/repo out of the topic-map and the Louvain overview clustering. `issue` carries a `workflow` state machine (`backlog`→`in-progress`→`done`, forge `open`/`closed` mapping) and a `structural_link_classes` allowlist (`references`, `duplicate-of`) — the new top-level policy vocabulary field (deliberately **not** `link_classes`, which is the distinct `dream.link_classes` semantic axis) that gates writes into `context_structural_links`; the five dream relationship names (`topical`, `factual`, `causal`, `recurrent`, `supersedes`) are **reserved** and reject at validation (graph-structural GA6 — a colliding class would make the `link_class` filter ambiguous and let a structural `supersedes` traverse while dream-supersedes is display-only); migration 105 preflights the existing corpus for colliding rows before a deploy can poison a registry reload, and a colliding row planted past it degrades that tenant loudly onto base (GA7); it is a declarative write allowlist consumed by the link-create path AND (since migration 103) by the daily-synthesis pipeline: `audit-trail` declares `references`, and `writeReportSourceLinks` persists report→source edges (origin `system`) for the blocks the Tagesbericht enumerates — fail-closed, an operator removing the class switches the edges off. `comment` is kept out of every autonomous pipeline (guard/dream/digest/overview all off). **Welle I-E flipped `comment` to the §4.1 target** (migration 085, in lockstep with `builtin.go`): `retrieval=aggregate-to-parent` (a ranked comment folds onto its parent issue — see [Aggregate-to-parent fold](#aggregate-to-parent-fold-wf-t11) above) + `parent.mode=required`/`comment-of`. Both mechanisms the target needs are live: the T11 fold consumer and the `parent_id` write path (`store.InsertCommentBlock`/`PutBlockParent`, I-D). Migration 084 stays the interim seed (`ON CONFLICT DO NOTHING`); 085 is the correcting `UPDATE` of the builtin `_global` row, and the registry drift gate diffs the decoded DB row against the compiled-in builtin so the two never separate. The interim shape (`retrieval=excluded` + `parent.mode=none`) that 084 planted while the mechanisms were unbuilt is retired.
206
206
207
+
**Checkpoint evidence type (migration 107).** `checkpoint` structurally anchors ID-referenced evidence chains — compaction-checkpoint manifests plus their transcript source parts, written by external agent plugins before a lossy context compaction. Resolution runs **exclusively over exact block IDs** (the manifest carries `source_block_ids` and a `parent_manifest_id` chain), so the type is out of **every** autonomous pipeline: `retrieval=excluded` (token-dense transcript parts would flood retrieval candidate sets — hex-heavy prefixes tokenize at ~2 bytes/token and overflow reranker slot windows), `guard.check=false` **and** `guard.candidate=false`, dream/digest/overview all off. The guard exemption is the load-bearing part: consecutive checkpoints of one session are near-duplicates **by construction** (manifest = boilerplate + IDs, parts overlap in the transcript window), and the default knowledge lane (archive mode, 0.98) auto-archived manifests/parts — with every read path filtering `NOT is_archived`, active summaries were left pointing at dangling IDs while the data sat intact (2026-07-20 incident). Blocks classify via the stable writer title prefix (`compaction source`, priority 30); writers SHOULD still assert `type=checkpoint` explicitly on store (`type_source='manual'`). Migration 107 also repairs the existing corpus: retype plus un-archive of the guard-archived evidence rows (newest per `(category,title,scope)`, guarded against the partial unique index; guard metadata stays for auditability — re-archive is impossible on both axes, `guard_checked_at` stays set and the type left the guard allowlist).
208
+
207
209
Since T10 the registry is **API-exposed** (no schema change) via the `type-*` manage family: `type-list`/`type-get` are open reads (scoped to `_global` ∪ the caller's own tenant namespace), `type-create`/`type-update`/`type-delete` are server-admin (tier 1: only the shipped `_global` namespace is writable). Configs are validated by the SAME decoder the reload path uses (422 with the offending key path); builtin rows are undeletable (their config IS editable), a delete is refused while ANY block references the type, and every mutation runs in one attributed transaction (`via='api'` + acting key) followed by a synchronous reload. Since T9 (migration 075) the legacy `is_meta` column + index dropped — the third type axis consolidated into the registry (`metadata.is_meta` survives as a JSONB classify-input key only).
208
210
209
211
**Tenant overlays (Stufe 2, WF T12 — no migration; the schema carries `UNIQUE(name, scope)` since M072).** On top of the `_global` base generation the registry keeps a lazy per-tenant cache — the exact `config.Store` overlay mechanism (immutable snapshot behind an atomic pointer, a monotone `baseGen` stamp per cached generation, single-flighted MISS builds, O(1) cache wipe on a base reload). `SnapshotForTenant(scope)` / `SnapshotForRequest(ctx)` resolve `_global ∪ tenant`, where a **tenant row WINS** on a name collision (**D6: "Overlay gewinnt" v1** — no monotonic narrowing; the alternative that would forbid a tenant lifting a `_global`-excluded type is a named open decision, pinned by a semantic-fixier test so a later flip is a visible break). A tenant with no own rows inherits the base pointer byte-for-byte, so the single-tenant / default-tenant server is functionally identical (zero per-tenant generation footprint). The request path derives the tenant scope from the auth context (never a caller argument, §5.4); a tenant-scope registry write drops **only** that tenant's cached generation via the NOTIFY dispatch (`InvalidateTenant`), while a `_global` write bumps `baseGen` and wipes the whole tenant cache. **Consumer reach:** request handlers (RRF/graph) resolve per caller tenant; the background **dream** and **digest** loops resolve per iterated tenant — the dream `PickBlock` is bound to the iterating tenant's scopes (`scope = ANY(owned)`) so tenant A's `dream.linkable`/allowlist never governs tenant B's blocks. The type policy is a **ranking/visibility** steering, **not** a confidentiality boundary (that stays scope + grants + sensitivity): a grantee's overlay steers the ranking of granted FOREIGN blocks too (Reader-Overlay wins). **Known Stufe-2 boundaries (documented, no leak):** the guard batch and the overview rebuild stay single global runs on the base allowlist (guard-state counts are global telemetry; overview visibility overrides need a per-tenant overview) — reads on both remain scope-filtered.
0 commit comments