diff --git a/.skills/context-counts-ok b/.skills/context-counts-ok index 1157ec7..eb33270 100644 --- a/.skills/context-counts-ok +++ b/.skills/context-counts-ok @@ -15,7 +15,7 @@ # --- enumerated: the counted things are named in the same clause or the list # --- immediately under it, so the number is checkable by reading the section. docs/ARCHITECTURE.md :: enumerated :: **two backends** (`local`, `gcs`) -AGENTS.md :: enumerated :: Two that bite, each symptomless until it matters: +AGENTS.md :: enumerated :: Two that bite, silently until they matter: AGENTS.md :: enumerated :: Two env files, split by a security boundary AGENTS.md :: enumerated :: **Three stream kinds, three sets of rules.** # The three contracts and their four documents are the four `docs/contracts/` diff --git a/.skills/context-loss-ok b/.skills/context-loss-ok index b618f0a..43bf880 100644 --- a/.skills/context-loss-ok +++ b/.skills/context-loss-ok @@ -734,3 +734,32 @@ AGENTS.md :: retarget :: - [DEPLOYMENT.md](docs/DEPLOYMENT.md) — the unit's li docs/DEPLOYMENT.md :: retarget :: The systemd unit's lifecycle, the guards it starts behind, the host's memory docs/DEPLOYMENT.md :: retarget :: tunables, and the co-core pin. What it reports when it *fails* is in docs/DEPLOYMENT.md :: retarget :: from the table above — `daemon-reload` alone silently keeps the old values, and +# ---- #137: AGENTS.md headroom for the cohort charter link (5991 -> 5991) ---- +# A new Detail Docs line links cannobserv's docs/BOUNDARIES.md, the parent of +# replicator-boundaries.md since cannobserv#562 — the charter asks each +# sibling to link it from AGENTS.md. It costs ~16 tokens against 9 of +# headroom, so the lines below are tightened in place to pay for it (nine +# here, one each from CR 2 and CR 5). Extending the boundaries line instead +# fails `index`: the check counts only docs in this repo, and the URL is +# cannobserv's. Each was read against its replacement; --claims passes with +# no atom dropped. The two judged by meaning rather than wording: +# "Consumers must be idempotent" is the first Bus Conventions bullet's +# "At-least-once ⇒ idempotent"; "the dev-server invocation" is Common +# Commands' uvicorn line, inline in this file. +AGENTS.md :: tighten :: **Cap anything that launches a SocratiCode server** — uncapped, one cost this cluster 58 min of bus (#94). Invocations in [docs/COMMANDS.md](docs/COMMANDS.md). A green `codebase_search` is also not evidence every linked sibling answered: the silent-skip modes, and which sibling is in one, are in [docs/SOCRATICODE.md](docs/SOCRATICODE.md). +AGENTS.md :: tighten :: Two that bite, each symptomless until it matters: +AGENTS.md :: tighten :: Every deploy situation, the guard's verdict table, and the dev-server invocation: +AGENTS.md :: tighten :: Every variable, which file carries it, and each default's reasoning: +AGENTS.md :: tighten :: - **Consumers must be idempotent; producers own the outbox.** No DB here — the durable +AGENTS.md :: tighten :: # Mirror the private index first — command under Environment & Tooling +AGENTS.md :: tighten :: # Run a subset of tests (skip the coverage gate, which measures all of src/) +AGENTS.md :: tighten :: Entry points only: `configure_logging()` is called once inside the FastAPI `lifespan` or the worker's `run()`. Never in library modules. +# #137 CR 2: the charter line nests under the boundaries line, so "its parent" +# names the line above structurally; the one token the indent costs comes +# off the integration-tests comment. +AGENTS.md :: tighten :: # Integration tests (a scratch redis-server, or one the test spawns; --no-cov again) +# #137 CR 5: round 1's STREAMS.md tighten ("Bus Conventions in full") changed +# the claim -- the cross-stream rules are CONVENTIONS.md's, and STREAMS.md +# opens "Each rule `AGENTS.md` states in one line, with the reasoning". This +# rewording carries that claim; `AGENTS.md` stays an atom of the file. +AGENTS.md :: tighten :: - [STREAMS.md](docs/STREAMS.md) — what each stream carries, one bullet per rule `AGENTS.md` states in a line diff --git a/.skills/context-metrics.jsonl b/.skills/context-metrics.jsonl index 42f8157..ae77e9e 100644 --- a/.skills/context-metrics.jsonl +++ b/.skills/context-metrics.jsonl @@ -64,3 +64,5 @@ {"actions": ["baseline:pre-curation"], "budget": 10000, "bytes": 27998, "claims_dropped": null, "claims_warranted": null, "counts": null, "counts_acked": null, "delta_days": 23, "delta_tokens": 1533, "docs_orphaned": 4, "docs_total": 21, "docs_unindexed": 10, "file": "docs/DEPLOYMENT.md", "lines": 265, "links_dead": 0, "links_dead_anchors": 0, "no_loss": null, "no_loss_warrants": null, "note": null, "over_budget": true, "repo": "replicator", "repo_commit": "f638266", "seams": null, "seams_acked": null, "skill_commit": "3cea77a", "skill_version": "1.31", "tokens": 10889, "tokens_exact": true, "tokens_live": 142762, "top_section": "Server Lifecycle", "top_section_share": 97, "ts": "2026-10-17"} {"actions": ["index:MEMORY-PROTECTION.md", "retarget:Detail Docs DEPLOYMENT.md line"], "budget": 6000, "bytes": 14154, "claims_dropped": 0, "claims_warranted": 0, "counts": 0, "counts_acked": 4, "delta_days": 0, "delta_tokens": 36, "docs_orphaned": 0, "docs_total": 23, "docs_unindexed": 0, "file": "AGENTS.md", "lines": 252, "links_dead": 0, "links_dead_anchors": 0, "no_loss": "ok", "no_loss_warrants": 1, "note": "#140: pointer for the doc demoted out of DEPLOYMENT.md; 9 tokens headroom left", "over_budget": false, "repo": "replicator", "repo_commit": "2b1fd5b", "seams": 0, "seams_acked": 39, "skill_commit": "3cea77a", "skill_version": "1.31", "tokens": 5991, "tokens_exact": true, "tokens_live": 167285, "top_section": "Bus Conventions", "top_section_share": 24, "ts": "2026-10-17"} {"actions": ["demote:Memory protection", "retarget:preamble", "fix:manifest StartLimitBurst"], "budget": 10000, "bytes": 19715, "claims_dropped": 0, "claims_warranted": 0, "counts": 17, "counts_acked": 0, "delta_days": 0, "delta_tokens": -3319, "docs_orphaned": 4, "docs_total": 22, "docs_unindexed": 11, "file": "docs/DEPLOYMENT.md", "lines": 137, "links_dead": 0, "links_dead_anchors": 0, "no_loss": "ok", "no_loss_warrants": 3, "note": "#140: Memory protection -> docs/MEMORY-PROTECTION.md (3449 tokens, class B)", "over_budget": false, "repo": "replicator", "repo_commit": "2b1fd5b", "seams": 0, "seams_acked": 29, "skill_commit": "3cea77a", "skill_version": "1.31", "tokens": 7570, "tokens_exact": true, "tokens_live": 143227, "top_section": "Server Lifecycle", "top_section_share": 95, "ts": "2026-10-17"} +{"actions": ["baseline:pre-curation"], "budget": 6000, "bytes": 14154, "claims_dropped": null, "claims_warranted": null, "counts": null, "counts_acked": null, "delta_days": 1, "delta_tokens": 0, "docs_orphaned": 0, "docs_total": 23, "docs_unindexed": 0, "file": "AGENTS.md", "lines": 252, "links_dead": 0, "links_dead_anchors": 0, "no_loss": null, "no_loss_warrants": null, "note": null, "over_budget": false, "repo": "replicator", "repo_commit": "e924f97", "seams": null, "seams_acked": null, "skill_commit": "9851de0", "skill_version": "1.31", "tokens": 5991, "tokens_exact": true, "tokens_live": 167770, "top_section": "Bus Conventions", "top_section_share": 24, "ts": "2026-10-18"} +{"actions": ["relink:cohort-charter", "prune:Code Exploration Notes", "prune:Project Layout", "prune:Server Lifecycle", "prune:Environment Variables", "prune:Bus Conventions", "prune:Common Commands", "prune:Conventions", "prune:Detail Docs"], "budget": 6000, "bytes": 14090, "claims_dropped": 0, "claims_warranted": 0, "counts": 0, "counts_acked": 4, "delta_days": 1, "delta_tokens": 0, "docs_orphaned": 0, "docs_total": 23, "docs_unindexed": 0, "file": "AGENTS.md", "lines": 253, "links_dead": 0, "links_dead_anchors": 0, "no_loss": "ok", "no_loss_warrants": 12, "note": null, "over_budget": false, "repo": "replicator", "repo_commit": "19955ac", "seams": 0, "seams_acked": 39, "skill_commit": "9851de0", "skill_version": "1.31", "tokens": 5991, "tokens_exact": true, "tokens_live": 168329, "top_section": "Bus Conventions", "top_section_share": 24, "ts": "2026-10-19"} diff --git a/.skills/context-token-counts b/.skills/context-token-counts index 26c44b7..9dc3bc7 100644 --- a/.skills/context-token-counts +++ b/.skills/context-token-counts @@ -3,7 +3,7 @@ # The offline estimators prefer a file's own last exact measurement to the # repo-wide figure in .skills/context-token-ratio, falling back to it for a # file never counted exactly or since drifted far from the size recorded here. -14154 5991 AGENTS.md +14090 5991 AGENTS.md 5692 2194 docs/ARCHITECTURE.md 26738 10378 docs/COMMANDS.md 26391 9745 docs/CONVENTIONS.md @@ -12,7 +12,7 @@ 8941 3330 docs/FAILURE-NOTIFICATION.md 21824 8554 docs/INFRASTRUCTURE.md 9228 3778 docs/MEMORY-PROTECTION.md -18208 6458 docs/POLITENESS.md +19603 6943 docs/POLITENESS.md 26669 10059 docs/SKILLS.md 11592 4447 docs/SOCRATICODE.md 22502 8233 docs/STORAGE.md @@ -25,5 +25,5 @@ 9039 3348 docs/contracts/content-persist-issuer-contract.md 27798 9893 docs/contracts/content-replicate-issuer-contract.md 12475 4331 docs/contracts/content-replicate-issuer-reference.md -25972 9167 docs/contracts/replicator-boundaries.md +27305 9726 docs/contracts/replicator-boundaries.md 11391 4818 docs/reference/tailscale.md diff --git a/.socraticodecontextartifacts.json b/.socraticodecontextartifacts.json index 5a75b1f..54b19a2 100644 --- a/.socraticodecontextartifacts.json +++ b/.socraticodecontextartifacts.json @@ -48,7 +48,7 @@ { "name": "boundaries-charter", "path": "./docs/contracts/replicator-boundaries.md", - "description": "Normative charter for what Replicator is allowed to become — sibling to the issuer contract, which settles the wire while this settles the service. The rule: Replicator owns the mechanics of acquiring bytes from a network and holding them briefly, never why, when, or what they mean. Three tests for any proposed capability, field, or setting (durable per-resource history -> issuer; cross-command coordination over a resource only the fetcher can see -> Replicator; expressible without domain vocabulary or it is the issuer's), resolved when both fire by mechanism-to-Replicator / policy-to-the-issuer / config-over-the-bus. Worked examples: per-host politeness (the hard case, where the tests do not resolve to one side) and conditional GET (ETag/Last-Modified are durable per-resource history, so the fact returns them and the next command replays them as headers — conditional GET with zero per-URL state here). Config taxonomy of three channels (env for host facts, command for the occasion, a policy stream for cluster policy needing cross-command state) with a fourth — an inbound admin HTTP API — rejected by name; ingress is read-only liveness and self-description, and the invariant that matters is asserted about the worker, which binds no port, not the dev-only FastAPI app. The policy stream SHIPPED in #19 on co-core v0.7.7 (cannobserv#285) and is named **content.fetch-policy with a hyphen** — the dotted content.fetch.policy this document originally proposed collides with the .dlq derivation of the command stream. Last-write-wins per host, replayed from 0-0 at boot via the groupless AsyncBusTailReader and tailed thereafter, MAXLEN-trimmed by the producer because periodic full republication onto an untrimmed stream is unbounded growth, a Redis hash rejected for having no schema_version and coupling to a key name. Four properties that must hold (the producer republishes the full set and MAXLEN-trims it; an unknown host resolves to a conservative default; enforcement has a rate floor the consumer must answer); the consumer-side rules that fail silently if missed, and the sleep-or-park enforcement mechanism, live in docs/POLITENESS.md. REPLICATOR_MIN_HOST_INTERVAL_SECONDS is now the FALLBACK rather than the rule, and 0 no longer disables pacing outright. Two known limitations recorded rather than omitted: blob_uri's host-local file:// coupling (#7), and the host asked for not being the host reached under redirects, which #19 did NOT resolve. Enforced by tests/test_boundaries.py in CI: eight invariants including an AST scan of src/ for domain nouns in identifiers and string literals, a recursive route walk, no locally-defined wire models (payload shapes, not producer-owned token vocabularies like FailureReason), and REPLICATOR_-prefixed settings with BUILD_ID exempted by name. Written for #12, updated for #19." + "description": "Normative charter for what Replicator is allowed to become — the Replicator annex to the cohort charter (CannObserv/cannobserv docs/BOUNDARIES.md), whose cohort-wide rules it applies, and sibling to the issuer contract, which settles the wire while this settles the service. The rule: Replicator owns the mechanics of acquiring bytes from a network and holding them briefly, never why, when, or what they mean. Three tests for any proposed capability, field, or setting (durable per-resource history -> issuer; cross-command coordination over a resource only the fetcher can see -> Replicator; expressible without domain vocabulary or it is the issuer's), resolved when both fire by mechanism-to-Replicator / policy-to-the-issuer / config-over-the-bus. Worked examples: per-host politeness (the hard case, where the tests do not resolve to one side) and conditional GET (ETag/Last-Modified are durable per-resource history, so the fact returns them and the next command replays them as headers — conditional GET with zero per-URL state here). Config taxonomy of three channels (env for host facts, command for the occasion, a policy stream for cluster policy needing cross-command state) with a fourth — an inbound admin HTTP API — rejected by name; ingress is read-only liveness and self-description, and the invariant that matters is asserted about the worker, which binds no port, not the dev-only FastAPI app. The policy stream SHIPPED in #19 on co-core v0.7.7 (cannobserv#285) and is named **content.fetch-policy with a hyphen** — the dotted content.fetch.policy this document originally proposed collides with the .dlq derivation of the command stream. Last-write-wins per host, ordered by the optional generation through co-core's fetch_policy_supersedes (#136; the rule is docs/POLITENESS.md's), replayed from 0-0 at boot via the groupless AsyncBusTailReader and tailed thereafter, MAXLEN-trimmed by the producer because periodic full republication onto an untrimmed stream is unbounded growth, a Redis hash rejected for having no schema_version and coupling to a key name. Four properties that must hold (the producer republishes the full set and MAXLEN-trims it; an unknown host resolves to a conservative default; enforcement has a rate floor the consumer must answer); the consumer-side rules that fail silently if missed, and the sleep-or-park enforcement mechanism, live in docs/POLITENESS.md. REPLICATOR_MIN_HOST_INTERVAL_SECONDS is now the FALLBACK rather than the rule, and 0 no longer disables pacing outright. Two known limitations recorded rather than omitted: blob_uri's host-local file:// coupling (#7), and the host asked for not being the host reached under redirects, which #19 did NOT resolve. Enforced by tests/test_boundaries.py in CI: eight invariants including an AST scan of src/ for domain nouns in identifiers and string literals, a recursive route walk, no locally-defined wire models (payload shapes, not producer-owned token vocabularies like FailureReason), and REPLICATOR_-prefixed settings with BUILD_ID exempted by name. The producer is Watcher until the headless-Watcher flip F1 (watcher#374), Archiver after it. Written for #12, updated for #19 and #137." }, { "name": "architecture", diff --git a/AGENTS.md b/AGENTS.md index a152352..4660c93 100644 --- a/AGENTS.md +++ b/AGENTS.md @@ -65,12 +65,12 @@ Full tool table, prefetch hook, per-tool guidance: [`docs/SOCRATICODE.md`](docs/ **The manifest is a source, not the artifact.** Nothing re-embeds it — run `codebase_update` in the same change as a `description` edit, or the stalest answer carries the most authority (#19 CR #17). -**Cap anything that launches a SocratiCode server** — uncapped, one cost this cluster 58 min of bus (#94). Invocations in [docs/COMMANDS.md](docs/COMMANDS.md). A green `codebase_search` is also not evidence every linked sibling answered: the silent-skip modes, and which sibling is in one, are in [docs/SOCRATICODE.md](docs/SOCRATICODE.md). +**Cap anything that launches a SocratiCode server** — uncapped, one cost this cluster 58 min of bus (#94). Invocations in [docs/COMMANDS.md](docs/COMMANDS.md). A green `codebase_search` doesn't prove every linked sibling answered: the silent-skip modes, and which sibling is in one, are in [docs/SOCRATICODE.md](docs/SOCRATICODE.md). ## Project Layout `src/worker/` is the bus consumer; `tests/` mirrors `src/`. Each module's job, its -seams, and the file map: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). +seams and the file map: [docs/ARCHITECTURE.md](docs/ARCHITECTURE.md). ## Infrastructure @@ -88,7 +88,7 @@ critical, guarded by `scripts/check_redis_floor.sh`. Why, ports, redis-py pin: && uv sync --frozen && sudo systemctl restart replicator` — `git push` instead of the pull when the merge happened here. -Two that bite, each symptomless until it matters: +Two that bite, silently until they matter: - **The service refuses to start off `main`, or off unpushed commits** (#37, #48). `REPLICATOR_ALLOW_ANY_CHECKOUT=1` overrides; a dev worker asks the same question @@ -99,7 +99,7 @@ Two that bite, each symptomless until it matters: `OnFailure=` handler's missed `cp` is invisible until the first failure; read what it recorded with `journalctl -t replicator-failure`, never `-u`. -Every deploy situation, the guard's verdict table, and the dev-server invocation: +Every deploy situation and the guard's verdict table: [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md). ## Environment Variables @@ -116,7 +116,7 @@ radius. Anything the service needs goes in `/etc/replicator/.env`. New settings take the `REPLICATOR_` prefix (cohort convention). `BUILD_ID` is the one deliberate exception, stamped generically by the unit. -Every variable, which file carries it, and each default's reasoning: +Every variable and each default's reasoning: [docs/ENVIRONMENT.md](docs/ENVIRONMENT.md). ## Bus Conventions @@ -158,7 +158,7 @@ Replicator is a **consumer** first — follow what co-core and the archiver prod per-stream dedupe, `EXISTS` before the handler, `SET NX EX` after a *completing* close; losing them costs one TTL window of re-fetches, never correctness: [docs/CONVENTIONS.md](docs/CONVENTIONS.md#the-replicatorcmd-keys). -- **Consumers must be idempotent; producers own the outbox.** No DB here — the durable +- **Producers own the outbox.** No DB here — the durable record of intent is the group's PEL; never add a Postgres outbox to the consume path. - **Three stream kinds, three sets of rules.** `content.fetch`, `content.replicate` and `content.persist` are command streams (one group each, @@ -174,7 +174,7 @@ Replicator is a **consumer** first — follow what co-core and the archiver prod ## Common Commands ```bash -# Mirror the private index first — command under Environment & Tooling +# Mirror the private index first (Environment & Tooling) uv sync # Load environment — dev only (required before the worker, gh, or rcli) @@ -183,10 +183,10 @@ set -a; . /etc/replicator/.env 2>/dev/null; . .env 2>/dev/null; set +a; . script # Run tests uv run pytest -# Run a subset of tests (skip the coverage gate, which measures all of src/) +# Run a subset of tests (--no-cov: the coverage gate measures all of src/) uv run pytest --no-cov tests/path/to/test.py -# Integration tests (a scratch redis-server, or one the test spawns; --no-cov again) +# Integration tests (a scratch redis-server or one the test spawns; --no-cov) uv run pytest --no-cov -m integration # Run linter @@ -215,7 +215,7 @@ Skills in `skills/` (agentskills.io) and `.claude/skills/` (Claude Code). Refere Types: feat, fix, refactor, docs, test, chore **Logging:** `from src.core.logging import get_logger`, then `logger = get_logger(__name__)`. -Entry points only: `configure_logging()` is called once inside the FastAPI `lifespan` or the worker's `run()`. Never in library modules. +`configure_logging()` runs once, in the FastAPI `lifespan` or the worker's `run()` — never in library modules. The stack itself: [docs/STYLE.md](docs/STYLE.md). **Date & Time:** @@ -223,12 +223,12 @@ The stack itself: [docs/STYLE.md](docs/STYLE.md). - ISO 8601: `YYYY-MM-DDTHH:MM:SS.ffffffZ` (timestamps), `YYYY-MM-DD` (dates) **General:** house style — imports, docstrings, function size, tests mirroring -source — each with its rationale and ruff gate in [docs/STYLE.md](docs/STYLE.md). +source — with rationale and ruff gates: [docs/STYLE.md](docs/STYLE.md). ## Detail Docs - [ARCHITECTURE.md](docs/ARCHITECTURE.md) — founding design, the command → fact flow, module by module; read before changing one -- [STREAMS.md](docs/STREAMS.md) — what each stream carries, one bullet per rule `AGENTS.md` states in a line +- [STREAMS.md](docs/STREAMS.md) — what each stream carries, and why each one-liner here holds - [POLITENESS.md](docs/POLITENESS.md) — per-host pacing: the policy stream, sleep vs park, 429/503 escalation - [CONVENTIONS.md](docs/CONVENTIONS.md) — rules common to every stream: idempotency, validation, DLQ, `claim_stale`, the `replicator:cmd:*` keys (#80) - [STORAGE.md](docs/STORAGE.md) — blob paths and modes, the populations under `REPLICATOR_BLOB_DIR`, TTL and ceilings @@ -247,6 +247,7 @@ source — each with its rationale and ruff gate in [docs/STYLE.md](docs/STYLE.m - [content-fetch-issuer-reference.md](docs/contracts/content-fetch-issuer-reference.md) — its lookup half, request side: refusal list, trust posture, envelope keys - [content-fetch-outcome-reference.md](docs/contracts/content-fetch-outcome-reference.md) — its result side: both facts field by field, failure taxonomy, silent conditions, the DLQ - [replicator-boundaries.md](docs/contracts/replicator-boundaries.md) — what Replicator may become; run its three tests against any proposed capability + - [cohort charter](https://github.com/CannObserv/cannobserv/blob/main/docs/BOUNDARIES.md) — its parent - [content-replicate-issuer-contract.md](docs/contracts/content-replicate-issuer-contract.md) — the replicate trust model and issuer obligations (#34) - [content-replicate-issuer-reference.md](docs/contracts/content-replicate-issuer-reference.md) — the why behind trust, T3a, T4, T6; triggers, Charter check, open questions - [content-persist-issuer-contract.md](docs/contracts/content-persist-issuer-contract.md) — keeping a blob in the permanent store by digest: obligations, refusal registry, enabling it on a host (#114) diff --git a/docs/contracts/replicator-boundaries.md b/docs/contracts/replicator-boundaries.md index e1457e8..afe4a3a 100644 --- a/docs/contracts/replicator-boundaries.md +++ b/docs/contracts/replicator-boundaries.md @@ -1,7 +1,11 @@ # Replicator boundaries charter -**Status:** normative. **Home:** this file, in the Replicator repo — the invariants are -about this repo's code and are enforced by +**Status:** normative, as the Replicator annex to the cohort charter, CannObserv/cannobserv +[`docs/BOUNDARIES.md`](https://github.com/CannObserv/cannobserv/blob/main/docs/BOUNDARIES.md). +The cohort-wide rules — which service owns what, which channel carries what — live and change +there; this file applies them to Replicator. +**Home:** this file, in the Replicator repo, for what is its own — the three tests and the +invariants, which are about this repo's code and are enforced by [`tests/test_boundaries.py`](../../tests/test_boundaries.py). Sibling repos link here rather than copying. @@ -62,9 +66,10 @@ mechanism/policy split is what finishes the job. | 3 — expressible without domain vocabulary? | **Yes.** A hostname is a network fact, not an InfoSource. | Test 2 fires alone, but a naive reading of it — "politeness is Replicator's, done" — would -move the *numbers* here too, and the numbers are operator policy that lives in Watcher's -`Domain` table today. The resolution is the split: Replicator **enforces**, the issuer -**decides**, and the decision travels over the bus (see **The policy stream**). +move the *numbers* here too, and the numbers are operator policy: in Watcher's `Domain` table +until the flip F1, in Archiver after it, which owns all operator intent under the cohort charter. +The resolution is the split: Replicator **enforces**, the issuer **decides**, and the decision +travels over the bus (see **The policy stream**). A reader who only skims the easy case below learns the pattern and misses the rule. @@ -136,7 +141,8 @@ groupless replay-then-tail driver whose absence this section previously recorded this document used to name collides with the `.dlq` derivation of the command stream. Use the constant, never the literal. -Last-write-wins per host key. Replicator replays it from `0-0` at boot into memory and tails +Last-write-wins per host key, ordered as [applying a policy](../POLITENESS.md#applying-a-policy) +states (#136). Replicator replays it from `0-0` at boot into memory and tails it thereafter. No DB, rebuildable, no inbound calls, and the data stays owned by its producer. `src/worker/policy.py`. @@ -162,10 +168,15 @@ because it has no `schema_version`, no co-core model, and it couples Replicator another service writes instead of to a payload contract. The stream keeps the same validation posture as every other wire input. -**Producer:** Watcher for Phase 4 (the numbers live in its `Domain` table today). Because it -is bus-delivered, the producer can later move to Archiver — the natural home if a second -issuer ever exists — with no Replicator change at all. That portability is the reason for the -indirection. +**Producer:** Watcher, from its `Domain` table, until the headless-Watcher flip F1 +([CannObserv/watcher#374](https://github.com/CannObserv/watcher/issues/374)); Archiver after +it. The payload is unchanged but for an optional `generation`, which Archiver sets because its +outbox can reorder frames ([CannObserv/cannobserv#566](https://github.com/CannObserv/cannobserv/issues/566)). +Replicator applies it through co-core's `fetch_policy_supersedes`; the rule is +[POLITENESS.md](../POLITENESS.md#applying-a-policy)'s and the code of record +[`src/worker/policy.py`](../../src/worker/policy.py). Nothing there branches on which producer +sent a frame — #136 ordered frames, it did not switch producers. That portability is the reason +for the indirection. ### The fallback default (was the interim, #12 → #19) @@ -387,8 +398,11 @@ files passes forever while enforcing nothing — worse than no test, because thi - #7 — object-store blob backend (the tracked violation) - #9, #10, #11 — the Phase 4 contract additions - #12 — this charter +- [CannObserv/cannobserv `docs/BOUNDARIES.md`](https://github.com/CannObserv/cannobserv/blob/main/docs/BOUNDARIES.md) — the cohort charter this applies - #19 — the policy stream's consumer half +- #136 — `generation` ordering on the policy stream (cannobserv#566) - [CannObserv/cannobserv#285](https://github.com/CannObserv/cannobserv/issues/285) — the policy stream contract (co-core v0.7.7) - [CannObserv/watcher#241](https://github.com/CannObserv/watcher/issues/241) — Phase 4 issuer - [CannObserv/watcher#245](https://github.com/CannObserv/watcher/issues/245) — the politeness gap at cutover +- [CannObserv/watcher#374](https://github.com/CannObserv/watcher/issues/374) — F1, the policy stream's producer moves to Archiver - [CannObserv/archiver#72](https://github.com/CannObserv/archiver/issues/72) — cluster integration strategy