From fd02ee56f1450dfc19b687b43604f7f7972096c2 Mon Sep 17 00:00:00 2001 From: gregoryfoster Date: Mon, 19 Oct 2026 17:33:51 +0000 Subject: [PATCH 1/8] #146 docs: curate COMMANDS.md, SKILLS.md, ENVIRONMENT.md under budget MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Exact counts, budget 10000 per doc: - COMMANDS.md 10378 -> 9505 - SKILLS.md 10059 -> 8959 - ENVIRONMENT.md 10014 -> 9298 Destinations: POLITENESS.md 6943 -> 7944, SOCRATICODE.md 4447 -> 5662, DEPLOYMENT.md 7570 -> 8550. AGENTS.md unchanged (5991). Relocated, verbatim (prove-no-loss --claims: lost 0 on all seven docs): - COMMANDS.md "### Politeness — content.fetch-policy (#19)": the stream reads, the journal greps and the four paragraphs reading them (~1000 tokens) -> POLITENESS.md "### Inspecting the stream", directly above "### Reading the apply log", which already opened by pointing back at them. The heading stays in COMMANDS.md as a pointer; the handler-hold greps (broker#20) stay under it, since they are not politeness. - SKILLS.md "The SocratiCode plugin is installed..." and "The health hook used to lie..." paragraphs, plus "### After an init-socraticode re-run" (~1210 tokens) -> SOCRATICODE.md "## Repo-specific notes", as "### The plugin and its hooks" and the re-run subsection. SKILLS.md keeps a pointer. - ENVIRONMENT.md bullets REPLICATOR_REDIS_FLOOR_TIMEOUT, REPLICATOR_REDIS_FLOOR_WAIT, REPLICATOR_ALLOW_ANY_CHECKOUT (~915 tokens) -> DEPLOYMENT.md "## Environment Variables", beside the guards they set: the same pattern as STORAGE.md, POLITENESS.md and FAILURE-NOTIFICATION.md. ENVIRONMENT.md keeps a pointer bullet and its preamble names the new home. Rewritten (judged in .skills/context-loss-ok / context-claims-ok): - retarget: POLITENESS.md's "Of the journal greps" lead-in, the moved block's "Why:" link, the moved plugin paragraph's "are in SOCRATICODE.md", the ENVIRONMENT.md preamble's list of homes, and tailscale.md's FLOOR_WAIT link. - rename: "### The patching gate's in-flight count (#131)" drops its provenance suffix, as the seams ack asked of the next COMMANDS.md edit; the number moves into a lead-in line. No deletions. Seams 0 (default sweep and --file on each curated doc), counts introduced 0. Manifest descriptions updated for the four docs whose content changed; codebase_update follows the merge. Co-Authored-By: Claude Opus 5.5 --- .skills/context-claims-ok | 4 +++ .skills/context-counts-ok | 5 +++ .skills/context-loss-ok | 24 +++++++++++++++ .skills/context-metrics.jsonl | 3 ++ .skills/context-seams-ok | 31 ++++++++++++++++--- .socraticodecontextartifacts.json | 8 ++--- docs/COMMANDS.md | 50 ++++-------------------------- docs/DEPLOYMENT.md | 8 +++++ docs/ENVIRONMENT.md | 9 +++--- docs/POLITENESS.md | 51 ++++++++++++++++++++++++++++++- docs/SKILLS.md | 43 +++----------------------- docs/SOCRATICODE.md | 42 +++++++++++++++++++++++++ docs/reference/tailscale.md | 2 +- 13 files changed, 182 insertions(+), 98 deletions(-) diff --git a/.skills/context-claims-ok b/.skills/context-claims-ok index 81c7f3f..a05870a 100644 --- a/.skills/context-claims-ok +++ b/.skills/context-claims-ok @@ -38,3 +38,7 @@ docs/SKILLS.md :: disproven :: skills/init-socraticode docs/contracts/content-fetch-outcome-reference.md :: disproven :: replicator:cmd: docs/contracts/content-fetch-issuer-contract.md :: disproven :: replicator:cmd: docs/contracts/content-fetch-issuer-contract.md :: disproven :: replicator:cmd: +# ---- #146: link targets of the pointers retargeted above (context-loss-ok) ---- +docs/COMMANDS.md :: retarget :: POLITENESS.md#reading-the-apply-log +docs/POLITENESS.md :: retarget :: COMMANDS.md#politeness--contentfetch-policy-19 +docs/SKILLS.md :: retarget :: SOCRATICODE.md diff --git a/.skills/context-counts-ok b/.skills/context-counts-ok index eb33270..a042422 100644 --- a/.skills/context-counts-ok +++ b/.skills/context-counts-ok @@ -40,3 +40,8 @@ docs/DEPLOYMENT.md :: stable :: `XAUTOCLAIM`'s three-element reply # The two _TRANSIENT_ERRORS members — src/worker/loop.py:80, imported and # asserted by tests/worker/test_oom_integration.py against a live broker. AGENTS.md :: gated :: `OutOfMemoryError` and `NoPermissionError`, the two `ResponseError` subclasses +# #146: the 'two' is the anchor slug of SOCRATICODE.md's own "### Two launch +# paths, pinned separately" heading, which the moved plugin paragraph now links +# in-doc. It cannot drift from the heading without a dead anchor, which +# measure-context.sh's links.dead_anchors reports. +docs/SOCRATICODE.md :: stable :: both launch paths and their pins are [above](#two-launch-paths-pinned-separately). diff --git a/.skills/context-loss-ok b/.skills/context-loss-ok index 43bf880..25ef7c0 100644 --- a/.skills/context-loss-ok +++ b/.skills/context-loss-ok @@ -763,3 +763,27 @@ AGENTS.md :: tighten :: # Integration tests (a scratch redis-server, or one the # 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 + +# ---- #146: the three over-budget reference docs ------------------------------ +# Every entry is a pointer this run retargeted because the same change moved +# what it points at; each read against its replacement. +# COMMANDS.md's politeness block moved into POLITENESS.md, so its "Why:" link +# into POLITENESS.md is now a same-doc anchor ([below](#reading-the-apply-log)). +docs/COMMANDS.md :: retarget :: [POLITENESS.md](POLITENESS.md#reading-the-apply-log). +# POLITENESS.md's lead-in pointed at that block's COMMANDS.md anchor; the greps +# now sit directly above it ([above](#inspecting-the-stream)). +docs/POLITENESS.md :: retarget :: Of the journal greps for this stream in [COMMANDS.md](COMMANDS.md#politeness--contentfetch-policy-19): +# SKILLS.md's plugin paragraph moved into SOCRATICODE.md, so "are in +# [SOCRATICODE.md]" became "are [above](#two-launch-paths-pinned-separately)". +docs/SKILLS.md :: retarget :: pins it to a literal version; both launch paths and their pins are in +docs/SKILLS.md :: retarget :: [SOCRATICODE.md](SOCRATICODE.md). The driver stays fenced: every invocation in +# ENVIRONMENT.md's preamble lists where variable reasoning lives outside it; the +# start-guard variables moved to DEPLOYMENT.md, so the list gains that pointer. +docs/ENVIRONMENT.md :: retarget :: fallback host interval in [POLITENESS.md](POLITENESS.md#variables), and the +docs/ENVIRONMENT.md :: retarget :: `OnFailure=` handler's in [FAILURE-NOTIFICATION.md](FAILURE-NOTIFICATION.md). Two env +# The `(#131)` provenance heading the seams ack asked to rename with the next +# COMMANDS.md edit; the issue number moved into a lead-in line beneath it. +docs/COMMANDS.md :: rename :: ### The patching gate's in-flight count (#131) +# tailscale.md pointed REPLICATOR_REDIS_FLOOR_WAIT at ENVIRONMENT.md; the +# variable's entry moved to DEPLOYMENT.md in this change. +docs/reference/tailscale.md :: retarget :: `REPLICATOR_REDIS_FLOOR_WAIT` ([ENVIRONMENT.md](../ENVIRONMENT.md)). It is diff --git a/.skills/context-metrics.jsonl b/.skills/context-metrics.jsonl index ae77e9e..a66e3b6 100644 --- a/.skills/context-metrics.jsonl +++ b/.skills/context-metrics.jsonl @@ -66,3 +66,6 @@ {"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"} +{"actions": ["baseline:pre-curation"], "budget": 10000, "bytes": 26738, "claims_dropped": null, "claims_warranted": null, "counts": null, "counts_acked": null, "delta_days": 20, "delta_tokens": 584, "docs_orphaned": 4, "docs_total": 22, "docs_unindexed": 13, "file": "docs/COMMANDS.md", "lines": 480, "links_dead": 0, "links_dead_anchors": 0, "no_loss": null, "no_loss_warrants": null, "note": null, "over_budget": true, "repo": "replicator", "repo_commit": "beda65d", "seams": null, "seams_acked": null, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 10378, "tokens_exact": true, "tokens_live": 144271, "top_section": "Worker", "top_section_share": 45, "ts": "2026-10-19"} +{"actions": ["baseline:pre-curation"], "budget": 10000, "bytes": 26669, "claims_dropped": null, "claims_warranted": null, "counts": null, "counts_acked": null, "delta_days": 25, "delta_tokens": 487, "docs_orphaned": 2, "docs_total": 22, "docs_unindexed": 18, "file": "docs/SKILLS.md", "lines": 410, "links_dead": 0, "links_dead_anchors": 0, "no_loss": null, "no_loss_warrants": null, "note": null, "over_budget": true, "repo": "replicator", "repo_commit": "beda65d", "seams": null, "seams_acked": null, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 10059, "tokens_exact": true, "tokens_live": 158777, "top_section": "The doc-check lists", "top_section_share": 18, "ts": "2026-10-19"} +{"actions": ["baseline:pre-curation"], "budget": 10000, "bytes": 25412, "claims_dropped": null, "claims_warranted": null, "counts": null, "counts_acked": null, "delta_days": 20, "delta_tokens": 75, "docs_orphaned": 4, "docs_total": 22, "docs_unindexed": 7, "file": "docs/ENVIRONMENT.md", "lines": 92, "links_dead": 0, "links_dead_anchors": 0, "no_loss": null, "no_loss_warrants": null, "note": null, "over_budget": true, "repo": "replicator", "repo_commit": "beda65d", "seams": null, "seams_acked": null, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 10014, "tokens_exact": true, "tokens_live": 144271, "top_section": "(preamble)", "top_section_share": 100, "ts": "2026-10-19"} diff --git a/.skills/context-seams-ok b/.skills/context-seams-ok index 6387b77..8039c81 100644 --- a/.skills/context-seams-ok +++ b/.skills/context-seams-ok @@ -127,7 +127,8 @@ docs/DEPLOYMENT.md :: and the boundary between them: [ENVIRONMENT.md](ENVIRONMEN docs/INFRASTRUCTURE.md :: | Key | `QDRANT_API_KEY`, `.claude/settings.local.json` | See [ENVIRONMENT.md](ENVIRONMENT.md). | docs/SOCRATICODE.md :: and nowhere else ([ENVIRONMENT.md](ENVIRONMENT.md)). Capped invocations for docs/STORAGE.md :: configuration — [ENVIRONMENT.md](ENVIRONMENT.md) keeps the boundary between that -docs/reference/tailscale.md :: `REPLICATOR_REDIS_FLOOR_WAIT` ([ENVIRONMENT.md](../ENVIRONMENT.md)). It is +# #146 moved the variable to DEPLOYMENT.md; the line now points there. +docs/reference/tailscale.md :: `REPLICATOR_REDIS_FLOOR_WAIT` ([DEPLOYMENT.md](../DEPLOYMENT.md#environment-variables)). It is .socraticodecontextartifacts.json :: "description": "The blob tree: file://///.bin with a constant .bin extension, temp-file + os.replace so presence means complete, and creation-only 0644/0755 modes with the startup reachability warning. Retention: the TTL runs from last reference (os.utime on the store short-circuit), the three populations under REPLICATOR_BLOB_DIR and why in-flight .tmp files are not garbage, the byte ceiling as backpressure rather than a faster clock, the single BlobUsage with two writers, orphan recording, and why the sweep runs in the worker rather than a systemd timer. Carries the REPLICATOR_BLOB_* variables and REPLICATOR_MAX_BLOB_BYTES themselves since #92, under ## Variables; docs/ENVIRONMENT.md indexes them by name." .socraticodecontextartifacts.json :: "description": "VM topology and ports, the systemd unit's lifecycle and its ExecStartPre guards, the checkout guard, and the co-core pin. The environment variables themselves are in docs/ENVIRONMENT.md. Also OOMScoreAdjust=-900 on both units (#92): on exe.dev everything descended from a session inherits oom_score_adj=-1000 and is exempt from the OOM killer, so an unprotected worker at ~670 was what the kernel would pick under pressure the dev tooling created." .socraticodecontextartifacts.json :: "path": "./docs/ENVIRONMENT.md", @@ -475,9 +476,8 @@ docs/COMMANDS.md :: AGENTS.md), then source this once per shell from the repo ro # Design Preferences stays in AGENTS.md; both SKILLS.md lines point at it by name. docs/SKILLS.md :: None — `brainstorming` runs upstream's, under AGENTS.md's **Design Preferences**. docs/SKILLS.md :: | `brainstorming` | Intent and design before building; this repo's overrides are AGENTS.md's Design Preferences | -# Provenance heading from #131, not this run's; nothing links its anchor (grep "in-flight-count"), -# so the slug is not load-bearing. Rename it with the next COMMANDS.md edit rather than widen this one. -docs/COMMANDS.md :: The patching gate's in-flight count (#131) +# (#146 renamed the #131 provenance heading, as the entry here asked: its +# number moved into the section's lead-in line.) # --- #140 curation: Memory protection split out of DEPLOYMENT.md ---------- # Judged one by one; each points at content that STAYED in DEPLOYMENT.md or @@ -488,3 +488,26 @@ docs/ENVIRONMENT.md :: `REPLICATOR_ALLOW_ANY_CHECKOUT` — bypasses the `main`-c docs/MEMORY-PROTECTION.md :: [DEPLOYMENT.md](DEPLOYMENT.md) (#140), which keeps the unit's lifecycle and the docs/MEMORY-PROTECTION.md :: from [DEPLOYMENT.md](DEPLOYMENT.md#server-lifecycle)'s table .socraticodecontextartifacts.json :: "description": "Memory protection for co-replicator, split out of docs/DEPLOYMENT.md (#140). + +# --- #146 curation: COMMANDS.md, SKILLS.md, ENVIRONMENT.md under budget ------ +# Judged one by one. Each points at content that STAYED where it points, or is +# this run's own pointer to where a block went. +# SKILLS.md's pointer to the SocratiCode notes it handed to SOCRATICODE.md. +docs/SKILLS.md :: check after an `init-socraticode` re-run are in +# The re-run checklist, moved verbatim into SOCRATICODE.md, names AGENTS.md's +# policy block and its Code Exploration Notes; neither moved. +docs/SOCRATICODE.md :: The skill replaces `docs/SOCRATICODE.md` between its `socraticode-doc` markers and the `AGENTS.md` +docs/SOCRATICODE.md :: | `codebase_update` | `AGENTS.md` and every doc here are registered artifacts; nothing re-embeds them | +docs/SOCRATICODE.md :: silent-skip modes with its pointer into `docs/INFRASTRUCTURE.md` (#92) — and `AGENTS.md`'s under +# The capped SocratiCode invocations stay in COMMANDS.md; both moved paragraphs +# and MEMORY-PROTECTION.md point at them there. +docs/SOCRATICODE.md :: [COMMANDS.md](COMMANDS.md) is wrapped in a `systemd-run` memory cap, and +docs/SOCRATICODE.md :: 1 in the hook's resolution order, and the name every invocation in [COMMANDS.md](COMMANDS.md) uses. +docs/MEMORY-PROTECTION.md :: [COMMANDS.md](COMMANDS.md). +docs/MEMORY-PROTECTION.md :: unit's half; the launch's, the capped invocation, is in [COMMANDS.md](COMMANDS.md). +# `rcli` and the denied-command table stay under COMMANDS.md's Inspecting the +# consume path; POLITENESS.md's new section and TESTING.md point there. +docs/POLITENESS.md :: [COMMANDS.md → Inspecting the consume path](COMMANDS.md#inspecting-the-consume-path). +docs/TESTING.md :: The operator half of that split is tabulated in [COMMANDS.md](COMMANDS.md#inspecting-the-consume-path) +# The SOCRATICODE.md manifest description's own provenance for what #146 moved in. +.socraticodecontextartifacts.json :: "description": "The on-demand half of the Code Exploration Policy diff --git a/.socraticodecontextartifacts.json b/.socraticodecontextartifacts.json index 54b19a2..95b4adb 100644 --- a/.socraticodecontextartifacts.json +++ b/.socraticodecontextartifacts.json @@ -63,7 +63,7 @@ { "name": "politeness", "path": "./docs/POLITENESS.md", - "description": "Per-host politeness, the consumer-side mechanism; the boundary decision (Replicator enforces, the issuer decides, the numbers travel over the bus) and the producer's obligations are the charter's. Spending a wait: src/worker/pacing.py's host -> last-request map and handler.py::_pace — a wait up to REPLICATOR_READ_BLOCK_MS is slept through, a longer one raises TransientFetchError and parks for claim_stale, because park-only misses a 1 s interval by 60x and sleep-only blocks every other host; only a request that goes out calls record(); keyed on the host asked for, not the host reached. The content.fetch-policy stream (src/worker/policy.py): read groupless, replayed from 0-0 at boot in REPLAY_COUNT batches that degrade to count=1 around a poison frame, tailed at count=1; AsyncBusTailReader.replay() is unusable; the replay runs before the consume loop and worker ready is logged after it; four ways to apply a policy wrongly (branch on revoked first, 0.0 is legal and falsy, the default's strictness is reported per host at apply time, order per host through co-core's fetch_policy_supersedes — a higher generation applies, an equal one is a silent republish skip, a frame without one (all of Watcher's, until F1) keeps the occurred_at >= guard, and mixed is a ratchet, with tombstones held as frames so it cannot forget them, #136), the global from_wire table, bounded poison recovery. Escalation on 429 and 503 (#25): HostPacer.report_rate_limited raises the interval by BACKOFF_MULTIPLIER up to BACKOFF_MAX_HEADROOM above the published floor, drops it after BACKOFF_DECAY_SECONDS measured from the last refusal, honours Retry-After only upward, and publishes no fact, adds no setting and keeps no second map. Reading the apply log: the two apply greps fire only on a change (#85), so hosts_stricter_than_default on the replay summary is the standing signal. Carries REPLICATOR_MIN_HOST_INTERVAL_SECONDS under ## Variables; docs/ENVIRONMENT.md indexes it by name." + "description": "Per-host politeness, the consumer-side mechanism; the boundary decision (Replicator enforces, the issuer decides, the numbers travel over the bus) and the producer's obligations are the charter's. Spending a wait: src/worker/pacing.py's host -> last-request map and handler.py::_pace — a wait up to REPLICATOR_READ_BLOCK_MS is slept through, a longer one raises TransientFetchError and parks for claim_stale, because park-only misses a 1 s interval by 60x and sleep-only blocks every other host; only a request that goes out calls record(); keyed on the host asked for, not the host reached. The content.fetch-policy stream (src/worker/policy.py): read groupless, replayed from 0-0 at boot in REPLAY_COUNT batches that degrade to count=1 around a poison frame, tailed at count=1; AsyncBusTailReader.replay() is unusable; the replay runs before the consume loop and worker ready is logged after it; four ways to apply a policy wrongly (branch on revoked first, 0.0 is legal and falsy, the default's strictness is reported per host at apply time, order per host through co-core's fetch_policy_supersedes — a higher generation applies, an equal one is a silent republish skip, a frame without one (all of Watcher's, until F1) keeps the occurred_at >= guard, and mixed is a ratchet, with tombstones held as frames so it cannot forget them, #136), the global from_wire table, bounded poison recovery. Escalation on 429 and 503 (#25): HostPacer.report_rate_limited raises the interval by BACKOFF_MULTIPLIER up to BACKOFF_MAX_HEADROOM above the published floor, drops it after BACKOFF_DECAY_SECONDS measured from the last refusal, honours Retry-After only upward, and publishes no fact, adds no setting and keeps no second map. Reading the apply log: the two apply greps fire only on a change (#85), so hosts_stricter_than_default on the replay summary is the standing signal. Inspecting the stream: the rcli reads of content.fetch-policy and the journal greps for its replay and applies, moved from docs/COMMANDS.md (#146). Carries REPLICATOR_MIN_HOST_INTERVAL_SECONDS under ## Variables; docs/ENVIRONMENT.md indexes it by name." }, { "name": "bus-conventions", @@ -88,7 +88,7 @@ { "name": "deployment-reference", "path": "./docs/DEPLOYMENT.md", - "description": "How the systemd unit runs and the guards it starts behind; where it runs is docs/INFRASTRUCTURE.md. Every deploy situation as one table — pull or push by where the merge happened, the cp each installed file under deploy/ needs, reset-failed after start-limit-hit — why the installed units are copies, the dev-server invocation, and the start budget: StartLimitBurst=8 in two hours, sized against the worst observed cluster outage (#94, #134). The checkout guard's verdict table (#37, #48) and the dev worker's check at the writer (#52). The co-core pin and why its patch floor is load-bearing. The host's memory tunables are docs/MEMORY-PROTECTION.md. Also: a co-core bump that makes a field required is a two-repo deploy (#28), and the dedupe keys' stream segment (#29). The variables are in docs/ENVIRONMENT.md, the OnFailure= handler in docs/FAILURE-NOTIFICATION.md, the reconnection rehearsal in docs/TESTING.md." + "description": "How the systemd unit runs and the guards it starts behind; where it runs is docs/INFRASTRUCTURE.md. Every deploy situation as one table — pull or push by where the merge happened, the cp each installed file under deploy/ needs, reset-failed after start-limit-hit — why the installed units are copies, the dev-server invocation, and the start budget: StartLimitBurst=8 in two hours, sized against the worst observed cluster outage (#94, #134). The checkout guard's verdict table (#37, #48) and the dev worker's check at the writer (#52). The co-core pin and why its patch floor is load-bearing. The host's memory tunables are docs/MEMORY-PROTECTION.md. Also: a co-core bump that makes a field required is a two-repo deploy (#28), and the dedupe keys' stream segment (#29). The variables are in docs/ENVIRONMENT.md, bar the three the start guards read (REPLICATOR_REDIS_FLOOR_TIMEOUT, REPLICATOR_REDIS_FLOOR_WAIT, REPLICATOR_ALLOW_ANY_CHECKOUT), kept under ## Environment Variables here (#146); the OnFailure= handler in docs/FAILURE-NOTIFICATION.md, the reconnection rehearsal in docs/TESTING.md." }, { "name": "memory-protection-reference", @@ -103,7 +103,7 @@ { "name": "environment-variables", "path": "./docs/ENVIRONMENT.md", - "description": "Every environment variable Replicator reads, indexed in one place, with the reasoning behind each default — except the blob and temp-store settings, whose reasoning sits beside the behaviour it argues about in docs/STORAGE.md (#92), and REPLICATOR_MIN_HOST_INTERVAL_SECONDS, in docs/POLITENESS.md. Also the hard boundary between /etc/replicator/.env (the only file the service reads) and the repo .env (dev/agent secrets). Split out of docs/DEPLOYMENT.md. Since #92 a third carrier is documented alongside them: .claude/settings.json holds the six non-secret values addressing the shared SocratiCode store and .claude/settings.local.json holds QDRANT_API_KEY, both read by the agent harness and by nothing the service starts." + "description": "Every environment variable Replicator reads, indexed in one place, with the reasoning behind each default — except the blob and temp-store settings, whose reasoning sits beside the behaviour it argues about in docs/STORAGE.md (#92), REPLICATOR_MIN_HOST_INTERVAL_SECONDS, in docs/POLITENESS.md, and the start-guard variables, in docs/DEPLOYMENT.md (#146). Also the hard boundary between /etc/replicator/.env (the only file the service reads) and the repo .env (dev/agent secrets). Split out of docs/DEPLOYMENT.md. Since #92 a third carrier is documented alongside them: .claude/settings.json holds the six non-secret values addressing the shared SocratiCode store and .claude/settings.local.json holds QDRANT_API_KEY, both read by the agent harness and by nothing the service starts." }, { "name": "testing-reference", @@ -128,7 +128,7 @@ { "name": "socraticode-reference", "path": "./docs/SOCRATICODE.md", - "description": "The on-demand half of the Code Exploration Policy, now describing a store this VM does not host: Qdrant and Ollama run on co-index and Docker is disabled here (#92). Split out of AGENTS.md in #35 to match gregoryfoster/skills#115, and regenerated from the skills#323-#330 template in #117. Between its socraticode-doc markers, the generated half: the full tool-selection table (its context and schema rows adapted to this repo's contracts), a pointer to the SessionStart prefetch hook rather than a copy of its query, per-tool gotchas (codebase_search ranks by embedding similarity so an empty result is a threshold miss not an absence; the graph answers empty rather than erroring when stale; codebase_context_search sees only indexed manifest artifacts; codebase_update not codebase_context_index repairs a stale one; the file watcher is ephemeral), graph health read from the server's import-resolution ruling and Built by stamp, and the two index-scope controls. Below END, repo-specific notes a re-run keeps: the external store and the capped health-check, what the manifest registers, the two launch paths and their separate pins (SOCRATICODE_SPEC for the plugin, ~/.socraticode/pin for the driver), and cross-repo search over the four cohort siblings via linkedProjects in .socraticode.json with the three ways a linked repo contributes nothing without saying so." + "description": "The on-demand half of the Code Exploration Policy, now describing a store this VM does not host: Qdrant and Ollama run on co-index and Docker is disabled here (#92). Split out of AGENTS.md in #35 to match gregoryfoster/skills#115, and regenerated from the skills#323-#330 template in #117. Between its socraticode-doc markers, the generated half: the full tool-selection table (its context and schema rows adapted to this repo's contracts), a pointer to the SessionStart prefetch hook rather than a copy of its query, per-tool gotchas (codebase_search ranks by embedding similarity so an empty result is a threshold miss not an absence; the graph answers empty rather than erroring when stale; codebase_context_search sees only indexed manifest artifacts; codebase_update not codebase_context_index repairs a stale one; the file watcher is ephemeral), graph health read from the server's import-resolution ruling and Built by stamp, and the two index-scope controls. Below END, repo-specific notes a re-run keeps: the external store and the capped health-check, what the manifest registers, the two launch paths and their separate pins (SOCRATICODE_SPEC for the plugin, ~/.socraticode/pin for the driver), cross-repo search over the four cohort siblings via linkedProjects in .socraticode.json with the three ways a linked repo contributes nothing without saying so, and — moved from docs/SKILLS.md (#146) — the plugin's pinned launch, the two ways the health hook used to report a healthy index falsely, and what to check after an init-socraticode re-run." }, { "name": "deployment-topology", diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index b05ce42..b1021e6 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -106,7 +106,9 @@ worth knowing before seeding a local test server. The full list is in Watch the live side with `sudo journalctl -u replicator -f`: each `stored a blob and published blob_available` is one of Watcher's commands closing. -### The patching gate's in-flight count (#131) +### The patching gate's in-flight count + +What `patching-hosts` asks before each step (#131): ```bash .venv/bin/python -m scripts.inflight # from the repo root; prints this worker's unacked commands @@ -306,36 +308,9 @@ things to know before asking (#85): ### Politeness — `content.fetch-policy` (#19) -Where to start when a host is being fetched more or less often than expected. The stream is -config/state: last-write-wins per host, read **without a consumer group**, replayed from the -beginning at every worker boot. - -```bash -# Has the producer published anything at all? An empty stream is not an error — -# it means every host resolves to REPLICATOR_MIN_HOST_INTERVAL_SECONDS — but it -# is the first thing to rule out, and it looks identical to a working consumer. -rcli XLEN content.fetch-policy -rcli XRANGE content.fetch-policy - + COUNT 10 - -# What one host is actually paced at. `revoked: true` is a tombstone meaning -# "no explicit policy", not "no limit" — it falls back to the env default. -rcli XRANGE content.fetch-policy - + COUNT 500 | grep '"host":"example.test"' - -# Expected EMPTY. A group here is a bug: every worker needs every message, so a -# group would compete for them and grow a PEL nothing acks or drains. Denied to -# the `replicator` user (#85) — ask the broker operator, or infer it the way -# tests/worker/test_policy_integration.py does, from the absence of a PEL. -rcli XINFO GROUPS content.fetch-policy # NOPERM as replicator -``` - -The worker's own view, from the journal — what it rebuilt at boot and what it has applied since: - -```bash -sudo journalctl -u replicator | grep 'replaying the fetch policy stream' # the boot replay started -sudo journalctl -u replicator | grep 'fetch policy replay complete' # tracked_hosts, messages, hosts_stricter_than_default, duration_ms -sudo journalctl -u replicator -f | grep 'applied a host fetch policy' # host, min_interval, and the default beside it -sudo journalctl -u replicator | grep 'stricter than the fallback' # raise REPLICATOR_MIN_HOST_INTERVAL_SECONDS -``` +The stream's inspection commands — what the producer published, one host's policy, and the +worker's journal view of the replay — are in +[POLITENESS.md](POLITENESS.md#inspecting-the-stream), beside the mechanism they inspect (#146). How long a handler holds its group — the number CannObserv/broker#20 sizes its undelivered-age warning from, because this consume path reads nothing while a handler runs: @@ -353,19 +328,6 @@ corpus and ~5.3 s for a blob at the `REPLICATOR_MAX_BLOB_BYTES` ceiling; the sec the one that finds a hold longer than that. One repeating on the same `command_id` can go on indefinitely, but since #98 it no longer stops the group reading. -`tracked_hosts: 0` with a non-empty `XLEN` means messages arrived and none applied — check for -`ignoring a ...` warnings on the same boot. The last grep is the one that needs acting on: it -names a host whose real policy is stricter than the fallback that would replace it if the -policy were revoked or missed on a replay. - -`applied a host fetch policy` and `stricter than the fallback` fire on a *change*, not on every apply — so an empty grep is not "resolved"; -read `hosts_stricter_than_default` on the replay summary instead. Why: -[POLITENESS.md](POLITENESS.md#reading-the-apply-log). - -To confirm the map is populated rather than to watch it move, read `tracked_hosts` on the same -line with `XLEN` beside it; `worker ready` follows the replay, so seeing it means the consume -loops are about to make their first read. - ## Submodules ```bash diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index a7dec55..5f33dc3 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -135,3 +135,11 @@ including an empty namespace after a flush: Every variable either env file carries, with the reasoning behind each default, and the boundary between them: [ENVIRONMENT.md](ENVIRONMENT.md). + +The variables the unit's start guards read are kept here, beside the guards they set — the +Redis floor check's timeout and wait, and the checkout guard's override. They live in `/etc/replicator/.env` +like the rest (#146): + +- `REPLICATOR_REDIS_FLOOR_TIMEOUT` — seconds one `check_redis_floor.sh` probe may run before it is killed; default `5`. Read by the unit's `ExecStartPre` only, never by the worker. Exists because `redis-cli` has no connect timeout, and a `rediss://` URL against a plaintext endpoint hangs on the TLS handshake +- `REPLICATOR_REDIS_FLOOR_WAIT` — how long `check_redis_floor.sh` keeps retrying a broker it cannot reach before reporting the floor `UNVERIFIED`; default `30`, and `0` probes once. Exists because on a cold boot MagicDNS answers `broker` with no address for a moment after tailscaled starts, which `After=tailscaled.service` cannot see (#88; a wait for the tailnet *address* was tried and did not help). Only `unreachable` is retried — an auth refusal is an answer, and a silent failure has already spent the timeout. `WAIT + TIMEOUT` is held to half the unit's 90 s start budget by `tests/test_deploy.py`, because every `ExecStartPre` shares it. A non-integer is named at start and the default used +- `REPLICATOR_ALLOW_ANY_CHECKOUT` — bypasses the `main`-checkout guard; **unset by default, which is the enforcing posture**. `scripts/check_main_checkout.sh` runs as the unit's first non-privileged `ExecStartPre` and refuses to start on any branch but `main` — see [**The checkout guard**](DEPLOYMENT.md#the-checkout-guard--the-service-refuses-to-start-off-main-37-or-off-unpushed-commits-48). Only the literal `1` bypasses it; anything else non-empty logs that it is *not* bypassing rather than silently failing to, because an override that quietly does nothing is worse than none. Exists at all because a guard with no override gets removed rather than overridden — but it is not the way to test a branch on this VM. That is `uv run python -m src.worker.main` under distinct `REPLICATOR_CONSUMER_NAME` and `REPLICATOR_REPLICATE_CONSUMER_NAME`, which never touches the unit and never needs this. Set it only when the thing under test *is* the unit — or when `main` carries a commit that cannot be pushed because the remote is unreachable, which is the one incident-time use the ahead refusal leaves (#37, #48). It has a second reader since #52: `build_writers` refuses to build a provider writer on a checkout the same script rejects, so this is also what a dev worker needs before it can write to the test bucket from a branch diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 6e4ebfb..8af810e 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -3,8 +3,9 @@ Every variable Replicator reads, indexed here, with the reasoning behind each default — except where the reasoning sits beside the behaviour it argues about: the blob and temp-store settings in [STORAGE.md](STORAGE.md#variables), the -fallback host interval in [POLITENESS.md](POLITENESS.md#variables), and the -`OnFailure=` handler's in [FAILURE-NOTIFICATION.md](FAILURE-NOTIFICATION.md). Two env +fallback host interval in [POLITENESS.md](POLITENESS.md#variables), the +`OnFailure=` handler's in [FAILURE-NOTIFICATION.md](FAILURE-NOTIFICATION.md), and the start guards' in +[DEPLOYMENT.md](DEPLOYMENT.md#environment-variables). Two env files carry them, and the boundary between the two is hard: `/etc/replicator/.env` is the **only** file `replicator.service` reads, and the repo `.env` holds dev/agent secrets the worker has no use for — org-wide PATs @@ -34,8 +35,7 @@ Read by neither env file — test-only, defined in `tests/conftest.py`: In `/etc/replicator/.env` (read by the service): - `GOOGLE_APPLICATION_CREDENTIALS` — the worker's ADC: the writer SA key (`/etc/replicator/co-gcs-replicator-writer.json`, `co-gcs-replicator-writer` since #114; it replaced `co-gcs-replicator`). **Not** the wheelhouse reader — that is `REPLICATOR_WHEELHOUSE_CREDENTIALS` below, split off precisely so the boot step and the worker never share an identity (#29); this line said otherwise until #86 verified the file - `REPLICATOR_REDIS_URL` — change-bus client URL; default `redis://localhost:6379/0`. On `co-replicator` it names the `replicator` ACL user at `broker:6379/0` over the tailnet, with the username explicit — without one the login goes to `default` and is refused (broker#46) -- `REPLICATOR_REDIS_FLOOR_TIMEOUT` — seconds one `check_redis_floor.sh` probe may run before it is killed; default `5`. Read by the unit's `ExecStartPre` only, never by the worker. Exists because `redis-cli` has no connect timeout, and a `rediss://` URL against a plaintext endpoint hangs on the TLS handshake -- `REPLICATOR_REDIS_FLOOR_WAIT` — how long `check_redis_floor.sh` keeps retrying a broker it cannot reach before reporting the floor `UNVERIFIED`; default `30`, and `0` probes once. Exists because on a cold boot MagicDNS answers `broker` with no address for a moment after tailscaled starts, which `After=tailscaled.service` cannot see (#88; a wait for the tailnet *address* was tried and did not help). Only `unreachable` is retried — an auth refusal is an answer, and a silent failure has already spent the timeout. `WAIT + TIMEOUT` is held to half the unit's 90 s start budget by `tests/test_deploy.py`, because every `ExecStartPre` shares it. A non-integer is named at start and the default used +- The **start-guard variables** — `REPLICATOR_REDIS_FLOOR_TIMEOUT`, `REPLICATOR_REDIS_FLOOR_WAIT` and `REPLICATOR_ALLOW_ANY_CHECKOUT` — are in [DEPLOYMENT.md](DEPLOYMENT.md#environment-variables), beside the guards they set. They are read from this same file; only the prose moved. - The **blob and temp-store variables** — `REPLICATOR_BLOB_BACKEND`, `_BUCKET`, `_PREFIX`, `_TIMEOUT_SECONDS`, `_DIR`, `_TTL_SECONDS`, `_SWEEP_INTERVAL_SECONDS`, `_TEMP_GRACE_SECONDS`, `_MAX_TOTAL_BYTES` and `REPLICATOR_MAX_BLOB_BYTES` — are in [STORAGE.md](STORAGE.md#variables), beside the store whose behaviour they set. They are read from this same file; only the prose moved. - `REPLICATOR_MAX_FETCH_TIMEOUT_SECONDS` — the most a command's own `timeout_seconds` may ask for; default `120`, **positive and finite** (`nan` would disarm this ceiling, since every comparison against it is false, and `inf` would disarm the whole-fetch one below, which derives from this — both are refused at startup). Not a default (an omitted field still gets the driver's 30 s) but a ceiling, and a guard rather than a preference: the consume path is serial, so one issuer's timeout is a lien on every other command in the group. Over it ⇒ `PermanentFetchError`. **Per operation, not per fetch** — httpx applies it to the connect and to each read separately, so it never bounded a fetch (#104); what does is the next variable - `REPLICATOR_MAX_FETCH_SECONDS` — one deadline around a fetch's **whole** wall time: the destination guard's resolve on every hop, every connect, every read (#104); default `120`, positive and finite, and **never under `REPLICATOR_MAX_FETCH_TIMEOUT_SECONDS`** — a command may ask for any per-operation timeout up to that, and a whole-fetch ceiling below it would clamp one silently, which #11 refuses to do. Held two ways: **unset, it is derived** as the larger of `120` and the per-operation ceiling, so a host that raised only `REPLICATOR_MAX_FETCH_TIMEOUT_SECONDS` keeps booting and keeps the fetches it allowed; **set under it, startup fails**, since that is two operator-named numbers contradicting each other. The operator's number, not the issuer's: `timeout_seconds` keeps its per-operation meaning. Exceeded ⇒ `TransientFetchError`, like any timeout, so the command retries at the reclaim cadence — a corpus whose fetches genuinely run longer needs this raised, and the unit's `TimeoutStopSec` with it (`tests/test_deploy.py` sums it). 50× the slowest fetch the journal recorded before it existed (2.3 s over 293) @@ -86,7 +86,6 @@ In `/etc/replicator/.env` (read by the service): **Each alias gets its own writer, so two aliases may safely name two buckets.** The worker builds one `AsyncGcsDriver` per binding at startup and keys them by alias — a driver *is* a bucket, and keying them by provider once let a command land in a bucket its binding never named (CR #26). A binding whose driver cannot be built — ADC resolves inside that constructor, so an expired key file or a revoked SA raises there — is **skipped and logged at ERROR**, never fatal: commands naming it are refused `provider_disabled`, and the fetch path keeps running. Grep the journal for `could not build a provider writer` after any credential change. - `REPLICATOR_LOG_LEVEL` — default `INFO`. Governs the **root** logger only, which is the whole tree for the worker. Under the dev server's `--log-config`, uvicorn's own `uvicorn` / `uvicorn.access` / `uvicorn.error` loggers are pinned `INFO` by `src/core/log_config.json` and do not follow it (nor did they under uvicorn's built-in config), so setting `WARNING` will not silence access lines; root itself is `INFO` from boot until the lifespan's `configure_logging()` applies this value -- `REPLICATOR_ALLOW_ANY_CHECKOUT` — bypasses the `main`-checkout guard; **unset by default, which is the enforcing posture**. `scripts/check_main_checkout.sh` runs as the unit's first non-privileged `ExecStartPre` and refuses to start on any branch but `main` — see [**The checkout guard**](DEPLOYMENT.md#the-checkout-guard--the-service-refuses-to-start-off-main-37-or-off-unpushed-commits-48). Only the literal `1` bypasses it; anything else non-empty logs that it is *not* bypassing rather than silently failing to, because an override that quietly does nothing is worse than none. Exists at all because a guard with no override gets removed rather than overridden — but it is not the way to test a branch on this VM. That is `uv run python -m src.worker.main` under distinct `REPLICATOR_CONSUMER_NAME` and `REPLICATOR_REPLICATE_CONSUMER_NAME`, which never touches the unit and never needs this. Set it only when the thing under test *is* the unit — or when `main` carries a commit that cannot be pushed because the remote is unreachable, which is the one incident-time use the ahead refusal leaves (#37, #48). It has a second reader since #52: `build_writers` refuses to build a provider writer on a checkout the same script rejects, so this is also what a dev worker needs before it can write to the test bucket from a branch - `BUILD_ID` — git SHA stamped by the systemd unit's `ExecStartPre`; defaults to `"dev"` outside systemd Replicator-owned settings carry the `REPLICATOR_` prefix — the cohort convention, kept from when four services shared one VM (#88). `BUILD_ID` is deliberately unprefixed — the systemd unit stamps it generically. diff --git a/docs/POLITENESS.md b/docs/POLITENESS.md index 43d5204..63b60c8 100644 --- a/docs/POLITENESS.md +++ b/docs/POLITENESS.md @@ -101,9 +101,58 @@ replay gives up (the tail resumes from the same cursor) and the tail parks. The rides the worker's stop event, because how long it runs is the producer's business: the charter asks the producer to `MAXLEN`, and Replicator cannot enforce it. +### Inspecting the stream + +`rcli` is the function sourced under +[COMMANDS.md → Inspecting the consume path](COMMANDS.md#inspecting-the-consume-path). + +Where to start when a host is being fetched more or less often than expected. The stream is +config/state: last-write-wins per host, read **without a consumer group**, replayed from the +beginning at every worker boot. + +```bash +# Has the producer published anything at all? An empty stream is not an error — +# it means every host resolves to REPLICATOR_MIN_HOST_INTERVAL_SECONDS — but it +# is the first thing to rule out, and it looks identical to a working consumer. +rcli XLEN content.fetch-policy +rcli XRANGE content.fetch-policy - + COUNT 10 + +# What one host is actually paced at. `revoked: true` is a tombstone meaning +# "no explicit policy", not "no limit" — it falls back to the env default. +rcli XRANGE content.fetch-policy - + COUNT 500 | grep '"host":"example.test"' + +# Expected EMPTY. A group here is a bug: every worker needs every message, so a +# group would compete for them and grow a PEL nothing acks or drains. Denied to +# the `replicator` user (#85) — ask the broker operator, or infer it the way +# tests/worker/test_policy_integration.py does, from the absence of a PEL. +rcli XINFO GROUPS content.fetch-policy # NOPERM as replicator +``` + +The worker's own view, from the journal — what it rebuilt at boot and what it has applied since: + +```bash +sudo journalctl -u replicator | grep 'replaying the fetch policy stream' # the boot replay started +sudo journalctl -u replicator | grep 'fetch policy replay complete' # tracked_hosts, messages, hosts_stricter_than_default, duration_ms +sudo journalctl -u replicator -f | grep 'applied a host fetch policy' # host, min_interval, and the default beside it +sudo journalctl -u replicator | grep 'stricter than the fallback' # raise REPLICATOR_MIN_HOST_INTERVAL_SECONDS +``` + +`tracked_hosts: 0` with a non-empty `XLEN` means messages arrived and none applied — check for +`ignoring a ...` warnings on the same boot. The last grep is the one that needs acting on: it +names a host whose real policy is stricter than the fallback that would replace it if the +policy were revoked or missed on a replay. + +`applied a host fetch policy` and `stricter than the fallback` fire on a *change*, not on every apply — so an empty grep is not "resolved"; +read `hosts_stricter_than_default` on the replay summary instead. Why: +[below](#reading-the-apply-log). + +To confirm the map is populated rather than to watch it move, read `tracked_hosts` on the same +line with `XLEN` beside it; `worker ready` follows the replay, so seeing it means the consume +loops are about to make their first read. + ### Reading the apply log -Of the journal greps for this stream in [COMMANDS.md](COMMANDS.md#politeness--contentfetch-policy-19): +Of the journal greps for this stream [above](#inspecting-the-stream): **The last two greps are silent while nothing changes, by design (#85).** `applied a host fetch policy` and the `stricter than the fallback` warning beneath it both fire on a *change*, not on diff --git a/docs/SKILLS.md b/docs/SKILLS.md index ba7d414..b3a0f0d 100644 --- a/docs/SKILLS.md +++ b/docs/SKILLS.md @@ -304,45 +304,10 @@ unnoticed for another nine days. | `socraticode-health.sh` | SessionStart | Once-per-UTC-day SocratiCode infra check: graph yield, `codebase_health`, a failed last operation, and — from the #72 pin bump ([skills#214](https://github.com/gregoryfoster/skills/issues/214)) — the manifest's declared artifact count against how many are actually indexed, naming the shortfall. **Reports only — never re-indexes, never edits a file, never starts Docker.** Silent when clean; logs to `.git/socraticode-health.log`. **Caps its own launch where user systemd can** ([skills#330](https://github.com/gregoryfoster/skills/issues/330)) — driver and server in one `systemd-run --user --scope` at [COMMANDS.md](COMMANDS.md)'s ceiling, under `choom -n 500`; where it cannot, it runs uncapped and says so only in its log (`memory cap unavailable: …`). So it is registered plain, with no `settings.json` wrapper (#115); a host's own ceiling is `SOCRATICODE_HEALTH_CAP` in `settings.local.json`. Symlink into the vendored skill for the #16 reason above. `init-socraticode` used to instruct a *copy* here; [skills#179](https://github.com/gregoryfoster/skills/issues/179) and [skills#186](https://github.com/gregoryfoster/skills/issues/186) are both closed and it now symlinks both hooks through `managing-skills`' `install-hook.sh`. | | `context-budget-guard.sh` | PostToolUse | `curating-context`'s write guard: warns when an edit pushes `AGENTS.md` over the token budget. Non-blocking. | -**The SocratiCode plugin is installed, and its launch is pinned (since 2026-09-23).** -From #92 until then it was deliberately absent — its launch was an uncapped -`npx -y --prefer-online socraticode@latest` on a 3.9 GB host with no swap — so the -prefetch `socraticode-reminder.sh` prints answered "No matching deferred tools found". -The plugin parameterises its spec, so `SOCRATICODE_SPEC` in `.claude/settings.json` -pins it to a literal version; both launch paths and their pins are in -[SOCRATICODE.md](SOCRATICODE.md). The driver stays fenced: every invocation in -[COMMANDS.md](COMMANDS.md) is wrapped in a `systemd-run` memory cap, and -`validate-store`, `validate-manifest` and `resolve` need no server at all. - -**The health hook used to lie in two situations, and both looked like a healthy report.** Both are -fixed upstream (closed 2026-08-17). `mcp-driver.mjs` dispatched only when `process.argv[1]` was its -own module path, so through the `skills/` symlink it **exited 0 having printed nothing** -([gregoryfoster/skills#177](https://github.com/gregoryfoster/skills/issues/177)); it now compares -realpaths. And the hook measured `.`, so every worktree session reported `graph is not READY` -against a healthy index ([gregoryfoster/skills#180](https://github.com/gregoryfoster/skills/issues/180)); -it now measures the main checkout. `SOCRATICODE_DRIVER` in `.claude/settings.json` stays — candidate -1 in the hook's resolution order, and the name every invocation in [COMMANDS.md](COMMANDS.md) uses. -And since [skills#226](https://github.com/gregoryfoster/skills/issues/226) the driver itself resolves a -relative path (`.` included) from a worktree to the main checkout, and says so on stderr — only an -absolute worktree path measures the worktree. - -### After an `init-socraticode` re-run - -The skill replaces `docs/SOCRATICODE.md` between its `socraticode-doc` markers and the `AGENTS.md` -policy block between its own, and keeps what follows each `END`. The doc had no markers until #117, -so a re-run used to overwrite it wholesale and this list was three things to re-apply by hand. Check -these before committing its output: - -| Check | Why it matters | -|---|---| -| `.claude/hooks/socraticode-health.sh` is still a symlink | skills#179; `install-hook.sh` copies only where there is no `skills-vendor/`, and `tests/test_skills_hook.py` fails on a copy | -| The doc's tool table names this repo's artifacts in its context and schema rows | the template's rows are generic, and the schema row's is a database; here it names the contracts that list each stream's `reason` values, because an unfiltered context search ranks the dated design plans with them (skills#315) | -| `codebase_update` | `AGENTS.md` and every doc here are registered artifacts; nothing re-embeds them | - -Repo-specific *prose* needs no re-applying. The doc's lives under `## Repo-specific notes` below its -`END` marker — the external store, both launch pins, and the Cross-repo search section's three -silent-skip modes with its pointer into `docs/INFRASTRUCTURE.md` (#92) — and `AGENTS.md`'s under -`## Code Exploration Notes (repo-specific)`, outside the policy markers. +The SocratiCode plugin's launch pin, the ways the health hook used to lie, and what to +check after an `init-socraticode` re-run are in +[SOCRATICODE.md](SOCRATICODE.md#the-plugin-and-its-hooks), beside the rest of that tool's +repo-specific notes (#146). ## Every hook dangles on a submodule-less checkout diff --git a/docs/SOCRATICODE.md b/docs/SOCRATICODE.md index 52eb4da..942be55 100644 --- a/docs/SOCRATICODE.md +++ b/docs/SOCRATICODE.md @@ -215,3 +215,45 @@ So a green `codebase_search` is never evidence that every sibling answered. Whic sibling is in which state on this VM — and the drift a real checkout is exposed to that a one-file stub is not — is in [INFRASTRUCTURE.md](INFRASTRUCTURE.md#the-semantic-index-is-co-index--a-store-this-repo-is-a-client-of). + +### The plugin and its hooks + +**The SocratiCode plugin is installed, and its launch is pinned (since 2026-09-23).** +From #92 until then it was deliberately absent — its launch was an uncapped +`npx -y --prefer-online socraticode@latest` on a 3.9 GB host with no swap — so the +prefetch `socraticode-reminder.sh` prints answered "No matching deferred tools found". +The plugin parameterises its spec, so `SOCRATICODE_SPEC` in `.claude/settings.json` +pins it to a literal version; both launch paths and their pins are +[above](#two-launch-paths-pinned-separately). The driver stays fenced: every invocation in +[COMMANDS.md](COMMANDS.md) is wrapped in a `systemd-run` memory cap, and +`validate-store`, `validate-manifest` and `resolve` need no server at all. + +**The health hook used to lie in two situations, and both looked like a healthy report.** Both are +fixed upstream (closed 2026-08-17). `mcp-driver.mjs` dispatched only when `process.argv[1]` was its +own module path, so through the `skills/` symlink it **exited 0 having printed nothing** +([gregoryfoster/skills#177](https://github.com/gregoryfoster/skills/issues/177)); it now compares +realpaths. And the hook measured `.`, so every worktree session reported `graph is not READY` +against a healthy index ([gregoryfoster/skills#180](https://github.com/gregoryfoster/skills/issues/180)); +it now measures the main checkout. `SOCRATICODE_DRIVER` in `.claude/settings.json` stays — candidate +1 in the hook's resolution order, and the name every invocation in [COMMANDS.md](COMMANDS.md) uses. +And since [skills#226](https://github.com/gregoryfoster/skills/issues/226) the driver itself resolves a +relative path (`.` included) from a worktree to the main checkout, and says so on stderr — only an +absolute worktree path measures the worktree. + +### After an `init-socraticode` re-run + +The skill replaces `docs/SOCRATICODE.md` between its `socraticode-doc` markers and the `AGENTS.md` +policy block between its own, and keeps what follows each `END`. The doc had no markers until #117, +so a re-run used to overwrite it wholesale and this list was three things to re-apply by hand. Check +these before committing its output: + +| Check | Why it matters | +|---|---| +| `.claude/hooks/socraticode-health.sh` is still a symlink | skills#179; `install-hook.sh` copies only where there is no `skills-vendor/`, and `tests/test_skills_hook.py` fails on a copy | +| The doc's tool table names this repo's artifacts in its context and schema rows | the template's rows are generic, and the schema row's is a database; here it names the contracts that list each stream's `reason` values, because an unfiltered context search ranks the dated design plans with them (skills#315) | +| `codebase_update` | `AGENTS.md` and every doc here are registered artifacts; nothing re-embeds them | + +Repo-specific *prose* needs no re-applying. The doc's lives under `## Repo-specific notes` below its +`END` marker — the external store, both launch pins, and the Cross-repo search section's three +silent-skip modes with its pointer into `docs/INFRASTRUCTURE.md` (#92) — and `AGENTS.md`'s under +`## Code Exploration Notes (repo-specific)`, outside the policy markers. diff --git a/docs/reference/tailscale.md b/docs/reference/tailscale.md index f3df6e8..4da18ce 100644 --- a/docs/reference/tailscale.md +++ b/docs/reference/tailscale.md @@ -95,7 +95,7 @@ not buy the floor check. Offsets are from tailscaled starting: `After=` orders only against tailscaled starting, and the second boot proved an address wait guards the wrong thing, since the address was local while the name still failed. So the check retries the dependency itself, for -`REPLICATOR_REDIS_FLOOR_WAIT` ([ENVIRONMENT.md](../ENVIRONMENT.md)). It is +`REPLICATOR_REDIS_FLOOR_WAIT` ([DEPLOYMENT.md](../DEPLOYMENT.md#environment-variables)). It is notifier#43's R1 on the name side: that reboot measured a 2.4 s margin for a bind and warned it could invert; this one was ~0.2 s for a lookup, and did. The worker itself was never affected — its backoff absorbs a slow tailnet. From 2ed6af2a94e13527485b420a14f46903a8201984 Mon Sep 17 00:00:00 2001 From: gregoryfoster Date: Mon, 19 Oct 2026 17:34:46 +0000 Subject: [PATCH 2/8] #146 chore: record curation telemetry (COMMANDS.md 10378 -> 9505, SKILLS.md 10059 -> 8959, ENVIRONMENT.md 10014 -> 9298) Co-Authored-By: Claude Opus 5.5 --- .skills/context-metrics.jsonl | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.skills/context-metrics.jsonl b/.skills/context-metrics.jsonl index a66e3b6..8450ec8 100644 --- a/.skills/context-metrics.jsonl +++ b/.skills/context-metrics.jsonl @@ -69,3 +69,6 @@ {"actions": ["baseline:pre-curation"], "budget": 10000, "bytes": 26738, "claims_dropped": null, "claims_warranted": null, "counts": null, "counts_acked": null, "delta_days": 20, "delta_tokens": 584, "docs_orphaned": 4, "docs_total": 22, "docs_unindexed": 13, "file": "docs/COMMANDS.md", "lines": 480, "links_dead": 0, "links_dead_anchors": 0, "no_loss": null, "no_loss_warrants": null, "note": null, "over_budget": true, "repo": "replicator", "repo_commit": "beda65d", "seams": null, "seams_acked": null, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 10378, "tokens_exact": true, "tokens_live": 144271, "top_section": "Worker", "top_section_share": 45, "ts": "2026-10-19"} {"actions": ["baseline:pre-curation"], "budget": 10000, "bytes": 26669, "claims_dropped": null, "claims_warranted": null, "counts": null, "counts_acked": null, "delta_days": 25, "delta_tokens": 487, "docs_orphaned": 2, "docs_total": 22, "docs_unindexed": 18, "file": "docs/SKILLS.md", "lines": 410, "links_dead": 0, "links_dead_anchors": 0, "no_loss": null, "no_loss_warrants": null, "note": null, "over_budget": true, "repo": "replicator", "repo_commit": "beda65d", "seams": null, "seams_acked": null, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 10059, "tokens_exact": true, "tokens_live": 158777, "top_section": "The doc-check lists", "top_section_share": 18, "ts": "2026-10-19"} {"actions": ["baseline:pre-curation"], "budget": 10000, "bytes": 25412, "claims_dropped": null, "claims_warranted": null, "counts": null, "counts_acked": null, "delta_days": 20, "delta_tokens": 75, "docs_orphaned": 4, "docs_total": 22, "docs_unindexed": 7, "file": "docs/ENVIRONMENT.md", "lines": 92, "links_dead": 0, "links_dead_anchors": 0, "no_loss": null, "no_loss_warrants": null, "note": null, "over_budget": true, "repo": "replicator", "repo_commit": "beda65d", "seams": null, "seams_acked": null, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 10014, "tokens_exact": true, "tokens_live": 144271, "top_section": "(preamble)", "top_section_share": 100, "ts": "2026-10-19"} +{"actions": ["demote:Politeness — content.fetch-policy", "retarget:apply-log link", "rename:patching gate heading"], "budget": 10000, "bytes": 24448, "claims_dropped": 0, "claims_warranted": 1, "counts": 9, "counts_acked": 0, "delta_days": 0, "delta_tokens": -873, "docs_orphaned": 4, "docs_total": 22, "docs_unindexed": 13, "file": "docs/COMMANDS.md", "lines": 442, "links_dead": 0, "links_dead_anchors": 0, "no_loss": "ok", "no_loss_warrants": 2, "note": "#146: politeness inspection block -> docs/POLITENESS.md#inspecting-the-stream (~1000 tokens, class B); counts pre-existing, none introduced", "over_budget": false, "repo": "replicator", "repo_commit": "fd02ee5", "seams": 0, "seams_acked": 38, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 9505, "tokens_exact": true, "tokens_live": 144669, "top_section": "Worker", "top_section_share": 50, "ts": "2026-10-19"} +{"actions": ["demote:SocratiCode plugin and health hook", "demote:After an init-socraticode re-run", "retarget:launch-paths link"], "budget": 10000, "bytes": 23772, "claims_dropped": 0, "claims_warranted": 1, "counts": 17, "counts_acked": 0, "delta_days": 0, "delta_tokens": -1100, "docs_orphaned": 2, "docs_total": 22, "docs_unindexed": 18, "file": "docs/SKILLS.md", "lines": 375, "links_dead": 0, "links_dead_anchors": 0, "no_loss": "ok", "no_loss_warrants": 2, "note": "#146: SocratiCode plugin/hook notes + re-run checklist -> docs/SOCRATICODE.md#the-plugin-and-its-hooks (~1210 tokens, class B)", "over_budget": false, "repo": "replicator", "repo_commit": "fd02ee5", "seams": 0, "seams_acked": 24, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 8959, "tokens_exact": true, "tokens_live": 159290, "top_section": "The doc-check lists", "top_section_share": 21, "ts": "2026-10-19"} +{"actions": ["demote:start-guard variables", "retarget:preamble", "retarget:tailscale.md FLOOR_WAIT link"], "budget": 10000, "bytes": 23419, "claims_dropped": 0, "claims_warranted": 0, "counts": 16, "counts_acked": 0, "delta_days": 0, "delta_tokens": -716, "docs_orphaned": 4, "docs_total": 22, "docs_unindexed": 7, "file": "docs/ENVIRONMENT.md", "lines": 91, "links_dead": 0, "links_dead_anchors": 0, "no_loss": "ok", "no_loss_warrants": 2, "note": "#146: REDIS_FLOOR_TIMEOUT/_WAIT, ALLOW_ANY_CHECKOUT -> docs/DEPLOYMENT.md#environment-variables (~915 tokens, class B)", "over_budget": false, "repo": "replicator", "repo_commit": "fd02ee5", "seams": 0, "seams_acked": 26, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 9298, "tokens_exact": true, "tokens_live": 144669, "top_section": "(preamble)", "top_section_share": 100, "ts": "2026-10-19"} From 1af75e4e8f0cd351dad9883288bb98ccfb608a46 Mon Sep 17 00:00:00 2001 From: gregoryfoster Date: Mon, 19 Oct 2026 17:38:39 +0000 Subject: [PATCH 3/8] =?UTF-8?q?#146=20docs:=20CR=201=20=E2=80=94=20say=20w?= =?UTF-8?q?here=20the=20start=20guards'=20variables=20sit,=20give=20the=20?= =?UTF-8?q?hold=20greps=20a=20heading?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Fresh-eyes review of the curation: - DEPLOYMENT.md / ENVIRONMENT.md: "beside the guards they set" was false for the Redis floor check, which DEPLOYMENT.md does not describe. The lead-in now says the variables sit with the unit they gate, points at INFRASTRUCTURE.md for the floor itself, and no longer reads as contradicting "Every variable ... ENVIRONMENT.md" just above it (they stay indexed there by name). - COMMANDS.md: the handler-hold greps sat under the Politeness pointer heading once its body moved; they get "### How long a handler holds its group". - Manifest: the health hook's two past lies were not both "a healthy index reported falsely" (skills#180 reported a healthy one as not ready). - .skills/context-seams-ok: the #140 entry for ENVIRONMENT.md's ALLOW_ANY_CHECKOUT line matched nothing once the line moved into DEPLOYMENT.md, where its link is same-doc; dropped with a note. COMMANDS.md 9505 -> 9516, ENVIRONMENT.md 9298 -> 9299, DEPLOYMENT.md 8550 -> 8628. lost 0, seams 0, counts introduced 0. Co-Authored-By: Claude Opus 5.5 --- .skills/context-seams-ok | 3 ++- .socraticodecontextartifacts.json | 2 +- docs/COMMANDS.md | 2 ++ docs/DEPLOYMENT.md | 7 ++++--- docs/ENVIRONMENT.md | 2 +- 5 files changed, 10 insertions(+), 6 deletions(-) diff --git a/.skills/context-seams-ok b/.skills/context-seams-ok index 8039c81..818d792 100644 --- a/.skills/context-seams-ok +++ b/.skills/context-seams-ok @@ -484,7 +484,8 @@ docs/SKILLS.md :: | `brainstorming` | Intent and design before building; this re # is the new doc's own split note. ENVIRONMENT.md's two name the start budget # (#134) and the checkout guard, neither of which moved. docs/ENVIRONMENT.md :: `REPLICATOR_MAX_CONSECUTIVE_CYCLE_FAILURES` — consecutive failed cycles -docs/ENVIRONMENT.md :: `REPLICATOR_ALLOW_ANY_CHECKOUT` — bypasses the `main`-checkout guard +# (#146 moved the ALLOW_ANY_CHECKOUT bullet into DEPLOYMENT.md itself, where its +# checkout-guard link is same-doc and no longer a seam; its entry here was dropped.) docs/MEMORY-PROTECTION.md :: [DEPLOYMENT.md](DEPLOYMENT.md) (#140), which keeps the unit's lifecycle and the docs/MEMORY-PROTECTION.md :: from [DEPLOYMENT.md](DEPLOYMENT.md#server-lifecycle)'s table .socraticodecontextartifacts.json :: "description": "Memory protection for co-replicator, split out of docs/DEPLOYMENT.md (#140). diff --git a/.socraticodecontextartifacts.json b/.socraticodecontextartifacts.json index 95b4adb..0bf6f81 100644 --- a/.socraticodecontextartifacts.json +++ b/.socraticodecontextartifacts.json @@ -128,7 +128,7 @@ { "name": "socraticode-reference", "path": "./docs/SOCRATICODE.md", - "description": "The on-demand half of the Code Exploration Policy, now describing a store this VM does not host: Qdrant and Ollama run on co-index and Docker is disabled here (#92). Split out of AGENTS.md in #35 to match gregoryfoster/skills#115, and regenerated from the skills#323-#330 template in #117. Between its socraticode-doc markers, the generated half: the full tool-selection table (its context and schema rows adapted to this repo's contracts), a pointer to the SessionStart prefetch hook rather than a copy of its query, per-tool gotchas (codebase_search ranks by embedding similarity so an empty result is a threshold miss not an absence; the graph answers empty rather than erroring when stale; codebase_context_search sees only indexed manifest artifacts; codebase_update not codebase_context_index repairs a stale one; the file watcher is ephemeral), graph health read from the server's import-resolution ruling and Built by stamp, and the two index-scope controls. Below END, repo-specific notes a re-run keeps: the external store and the capped health-check, what the manifest registers, the two launch paths and their separate pins (SOCRATICODE_SPEC for the plugin, ~/.socraticode/pin for the driver), cross-repo search over the four cohort siblings via linkedProjects in .socraticode.json with the three ways a linked repo contributes nothing without saying so, and — moved from docs/SKILLS.md (#146) — the plugin's pinned launch, the two ways the health hook used to report a healthy index falsely, and what to check after an init-socraticode re-run." + "description": "The on-demand half of the Code Exploration Policy, now describing a store this VM does not host: Qdrant and Ollama run on co-index and Docker is disabled here (#92). Split out of AGENTS.md in #35 to match gregoryfoster/skills#115, and regenerated from the skills#323-#330 template in #117. Between its socraticode-doc markers, the generated half: the full tool-selection table (its context and schema rows adapted to this repo's contracts), a pointer to the SessionStart prefetch hook rather than a copy of its query, per-tool gotchas (codebase_search ranks by embedding similarity so an empty result is a threshold miss not an absence; the graph answers empty rather than erroring when stale; codebase_context_search sees only indexed manifest artifacts; codebase_update not codebase_context_index repairs a stale one; the file watcher is ephemeral), graph health read from the server's import-resolution ruling and Built by stamp, and the two index-scope controls. Below END, repo-specific notes a re-run keeps: the external store and the capped health-check, what the manifest registers, the two launch paths and their separate pins (SOCRATICODE_SPEC for the plugin, ~/.socraticode/pin for the driver), cross-repo search over the four cohort siblings via linkedProjects in .socraticode.json with the three ways a linked repo contributes nothing without saying so, and — moved from docs/SKILLS.md (#146) — the plugin's pinned launch, the two ways the health hook used to misreport, and what to check after an init-socraticode re-run." }, { "name": "deployment-topology", diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index b1021e6..7138fdb 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -312,6 +312,8 @@ The stream's inspection commands — what the producer published, one host's pol worker's journal view of the replay — are in [POLITENESS.md](POLITENESS.md#inspecting-the-stream), beside the mechanism they inspect (#146). +### How long a handler holds its group + How long a handler holds its group — the number CannObserv/broker#20 sizes its undelivered-age warning from, because this consume path reads nothing while a handler runs: diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 5f33dc3..5238e16 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -136,9 +136,10 @@ including an empty namespace after a flush: Every variable either env file carries, with the reasoning behind each default, and the boundary between them: [ENVIRONMENT.md](ENVIRONMENT.md). -The variables the unit's start guards read are kept here, beside the guards they set — the -Redis floor check's timeout and wait, and the checkout guard's override. They live in `/etc/replicator/.env` -like the rest (#146): +The variables the unit's start guards read are the exception, kept here with the unit they gate +and indexed there by name (#146): the Redis floor check's timeout and wait — the floor itself is +[INFRASTRUCTURE.md](INFRASTRUCTURE.md#the-broker-is-co-broker--replicator-connects-it-does-not-run-one)'s +— and the checkout guard's override. They live in `/etc/replicator/.env` like the rest: - `REPLICATOR_REDIS_FLOOR_TIMEOUT` — seconds one `check_redis_floor.sh` probe may run before it is killed; default `5`. Read by the unit's `ExecStartPre` only, never by the worker. Exists because `redis-cli` has no connect timeout, and a `rediss://` URL against a plaintext endpoint hangs on the TLS handshake - `REPLICATOR_REDIS_FLOOR_WAIT` — how long `check_redis_floor.sh` keeps retrying a broker it cannot reach before reporting the floor `UNVERIFIED`; default `30`, and `0` probes once. Exists because on a cold boot MagicDNS answers `broker` with no address for a moment after tailscaled starts, which `After=tailscaled.service` cannot see (#88; a wait for the tailnet *address* was tried and did not help). Only `unreachable` is retried — an auth refusal is an answer, and a silent failure has already spent the timeout. `WAIT + TIMEOUT` is held to half the unit's 90 s start budget by `tests/test_deploy.py`, because every `ExecStartPre` shares it. A non-integer is named at start and the default used diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md index 8af810e..99c0879 100644 --- a/docs/ENVIRONMENT.md +++ b/docs/ENVIRONMENT.md @@ -35,7 +35,7 @@ Read by neither env file — test-only, defined in `tests/conftest.py`: In `/etc/replicator/.env` (read by the service): - `GOOGLE_APPLICATION_CREDENTIALS` — the worker's ADC: the writer SA key (`/etc/replicator/co-gcs-replicator-writer.json`, `co-gcs-replicator-writer` since #114; it replaced `co-gcs-replicator`). **Not** the wheelhouse reader — that is `REPLICATOR_WHEELHOUSE_CREDENTIALS` below, split off precisely so the boot step and the worker never share an identity (#29); this line said otherwise until #86 verified the file - `REPLICATOR_REDIS_URL` — change-bus client URL; default `redis://localhost:6379/0`. On `co-replicator` it names the `replicator` ACL user at `broker:6379/0` over the tailnet, with the username explicit — without one the login goes to `default` and is refused (broker#46) -- The **start-guard variables** — `REPLICATOR_REDIS_FLOOR_TIMEOUT`, `REPLICATOR_REDIS_FLOOR_WAIT` and `REPLICATOR_ALLOW_ANY_CHECKOUT` — are in [DEPLOYMENT.md](DEPLOYMENT.md#environment-variables), beside the guards they set. They are read from this same file; only the prose moved. +- The **start-guard variables** — `REPLICATOR_REDIS_FLOOR_TIMEOUT`, `REPLICATOR_REDIS_FLOOR_WAIT` and `REPLICATOR_ALLOW_ANY_CHECKOUT` — are in [DEPLOYMENT.md](DEPLOYMENT.md#environment-variables), with the unit's start guards. They are read from this same file; only the prose moved. - The **blob and temp-store variables** — `REPLICATOR_BLOB_BACKEND`, `_BUCKET`, `_PREFIX`, `_TIMEOUT_SECONDS`, `_DIR`, `_TTL_SECONDS`, `_SWEEP_INTERVAL_SECONDS`, `_TEMP_GRACE_SECONDS`, `_MAX_TOTAL_BYTES` and `REPLICATOR_MAX_BLOB_BYTES` — are in [STORAGE.md](STORAGE.md#variables), beside the store whose behaviour they set. They are read from this same file; only the prose moved. - `REPLICATOR_MAX_FETCH_TIMEOUT_SECONDS` — the most a command's own `timeout_seconds` may ask for; default `120`, **positive and finite** (`nan` would disarm this ceiling, since every comparison against it is false, and `inf` would disarm the whole-fetch one below, which derives from this — both are refused at startup). Not a default (an omitted field still gets the driver's 30 s) but a ceiling, and a guard rather than a preference: the consume path is serial, so one issuer's timeout is a lien on every other command in the group. Over it ⇒ `PermanentFetchError`. **Per operation, not per fetch** — httpx applies it to the connect and to each read separately, so it never bounded a fetch (#104); what does is the next variable - `REPLICATOR_MAX_FETCH_SECONDS` — one deadline around a fetch's **whole** wall time: the destination guard's resolve on every hop, every connect, every read (#104); default `120`, positive and finite, and **never under `REPLICATOR_MAX_FETCH_TIMEOUT_SECONDS`** — a command may ask for any per-operation timeout up to that, and a whole-fetch ceiling below it would clamp one silently, which #11 refuses to do. Held two ways: **unset, it is derived** as the larger of `120` and the per-operation ceiling, so a host that raised only `REPLICATOR_MAX_FETCH_TIMEOUT_SECONDS` keeps booting and keeps the fetches it allowed; **set under it, startup fails**, since that is two operator-named numbers contradicting each other. The operator's number, not the issuer's: `timeout_seconds` keeps its per-operation meaning. Exceeded ⇒ `TransientFetchError`, like any timeout, so the command retries at the reclaim cadence — a corpus whose fetches genuinely run longer needs this raised, and the unit's `TimeoutStopSec` with it (`tests/test_deploy.py` sums it). 50× the slowest fetch the journal recorded before it existed (2.3 s over 293) From 8571df1d14a91333419ae08b554f98e1fcfc007a Mon Sep 17 00:00:00 2001 From: gregoryfoster Date: Mon, 19 Oct 2026 17:39:01 +0000 Subject: [PATCH 4/8] =?UTF-8?q?#146=20chore:=20CR=201=20=E2=80=94=20amend?= =?UTF-8?q?=20the=20COMMANDS.md=20and=20ENVIRONMENT.md=20rows=20to=20the?= =?UTF-8?q?=20corrected=20tree=20(9505=20->=209516,=209298=20->=209299)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .skills/context-metrics.jsonl | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.skills/context-metrics.jsonl b/.skills/context-metrics.jsonl index 8450ec8..25a92db 100644 --- a/.skills/context-metrics.jsonl +++ b/.skills/context-metrics.jsonl @@ -69,6 +69,6 @@ {"actions": ["baseline:pre-curation"], "budget": 10000, "bytes": 26738, "claims_dropped": null, "claims_warranted": null, "counts": null, "counts_acked": null, "delta_days": 20, "delta_tokens": 584, "docs_orphaned": 4, "docs_total": 22, "docs_unindexed": 13, "file": "docs/COMMANDS.md", "lines": 480, "links_dead": 0, "links_dead_anchors": 0, "no_loss": null, "no_loss_warrants": null, "note": null, "over_budget": true, "repo": "replicator", "repo_commit": "beda65d", "seams": null, "seams_acked": null, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 10378, "tokens_exact": true, "tokens_live": 144271, "top_section": "Worker", "top_section_share": 45, "ts": "2026-10-19"} {"actions": ["baseline:pre-curation"], "budget": 10000, "bytes": 26669, "claims_dropped": null, "claims_warranted": null, "counts": null, "counts_acked": null, "delta_days": 25, "delta_tokens": 487, "docs_orphaned": 2, "docs_total": 22, "docs_unindexed": 18, "file": "docs/SKILLS.md", "lines": 410, "links_dead": 0, "links_dead_anchors": 0, "no_loss": null, "no_loss_warrants": null, "note": null, "over_budget": true, "repo": "replicator", "repo_commit": "beda65d", "seams": null, "seams_acked": null, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 10059, "tokens_exact": true, "tokens_live": 158777, "top_section": "The doc-check lists", "top_section_share": 18, "ts": "2026-10-19"} {"actions": ["baseline:pre-curation"], "budget": 10000, "bytes": 25412, "claims_dropped": null, "claims_warranted": null, "counts": null, "counts_acked": null, "delta_days": 20, "delta_tokens": 75, "docs_orphaned": 4, "docs_total": 22, "docs_unindexed": 7, "file": "docs/ENVIRONMENT.md", "lines": 92, "links_dead": 0, "links_dead_anchors": 0, "no_loss": null, "no_loss_warrants": null, "note": null, "over_budget": true, "repo": "replicator", "repo_commit": "beda65d", "seams": null, "seams_acked": null, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 10014, "tokens_exact": true, "tokens_live": 144271, "top_section": "(preamble)", "top_section_share": 100, "ts": "2026-10-19"} -{"actions": ["demote:Politeness — content.fetch-policy", "retarget:apply-log link", "rename:patching gate heading"], "budget": 10000, "bytes": 24448, "claims_dropped": 0, "claims_warranted": 1, "counts": 9, "counts_acked": 0, "delta_days": 0, "delta_tokens": -873, "docs_orphaned": 4, "docs_total": 22, "docs_unindexed": 13, "file": "docs/COMMANDS.md", "lines": 442, "links_dead": 0, "links_dead_anchors": 0, "no_loss": "ok", "no_loss_warrants": 2, "note": "#146: politeness inspection block -> docs/POLITENESS.md#inspecting-the-stream (~1000 tokens, class B); counts pre-existing, none introduced", "over_budget": false, "repo": "replicator", "repo_commit": "fd02ee5", "seams": 0, "seams_acked": 38, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 9505, "tokens_exact": true, "tokens_live": 144669, "top_section": "Worker", "top_section_share": 50, "ts": "2026-10-19"} {"actions": ["demote:SocratiCode plugin and health hook", "demote:After an init-socraticode re-run", "retarget:launch-paths link"], "budget": 10000, "bytes": 23772, "claims_dropped": 0, "claims_warranted": 1, "counts": 17, "counts_acked": 0, "delta_days": 0, "delta_tokens": -1100, "docs_orphaned": 2, "docs_total": 22, "docs_unindexed": 18, "file": "docs/SKILLS.md", "lines": 375, "links_dead": 0, "links_dead_anchors": 0, "no_loss": "ok", "no_loss_warrants": 2, "note": "#146: SocratiCode plugin/hook notes + re-run checklist -> docs/SOCRATICODE.md#the-plugin-and-its-hooks (~1210 tokens, class B)", "over_budget": false, "repo": "replicator", "repo_commit": "fd02ee5", "seams": 0, "seams_acked": 24, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 8959, "tokens_exact": true, "tokens_live": 159290, "top_section": "The doc-check lists", "top_section_share": 21, "ts": "2026-10-19"} -{"actions": ["demote:start-guard variables", "retarget:preamble", "retarget:tailscale.md FLOOR_WAIT link"], "budget": 10000, "bytes": 23419, "claims_dropped": 0, "claims_warranted": 0, "counts": 16, "counts_acked": 0, "delta_days": 0, "delta_tokens": -716, "docs_orphaned": 4, "docs_total": 22, "docs_unindexed": 7, "file": "docs/ENVIRONMENT.md", "lines": 91, "links_dead": 0, "links_dead_anchors": 0, "no_loss": "ok", "no_loss_warrants": 2, "note": "#146: REDIS_FLOOR_TIMEOUT/_WAIT, ALLOW_ANY_CHECKOUT -> docs/DEPLOYMENT.md#environment-variables (~915 tokens, class B)", "over_budget": false, "repo": "replicator", "repo_commit": "fd02ee5", "seams": 0, "seams_acked": 26, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 9298, "tokens_exact": true, "tokens_live": 144669, "top_section": "(preamble)", "top_section_share": 100, "ts": "2026-10-19"} +{"actions": ["demote:Politeness — content.fetch-policy", "retarget:apply-log link", "rename:patching gate heading"], "budget": 10000, "bytes": 24488, "claims_dropped": 0, "claims_warranted": 1, "counts": 9, "counts_acked": 0, "delta_days": 0, "delta_tokens": -862, "docs_orphaned": 4, "docs_total": 22, "docs_unindexed": 13, "file": "docs/COMMANDS.md", "lines": 444, "links_dead": 0, "links_dead_anchors": 0, "no_loss": "ok", "no_loss_warrants": 2, "note": "#146: politeness inspection block -> docs/POLITENESS.md#inspecting-the-stream (~1000 tokens, class B); counts pre-existing, none introduced", "over_budget": false, "repo": "replicator", "repo_commit": "1af75e4", "seams": 0, "seams_acked": 38, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 9516, "tokens_exact": true, "tokens_live": 144759, "top_section": "Worker", "top_section_share": 50, "ts": "2026-10-19"} +{"actions": ["demote:start-guard variables", "retarget:preamble", "retarget:tailscale.md FLOOR_WAIT link"], "budget": 10000, "bytes": 23421, "claims_dropped": 0, "claims_warranted": 0, "counts": 16, "counts_acked": 0, "delta_days": 0, "delta_tokens": -715, "docs_orphaned": 4, "docs_total": 22, "docs_unindexed": 7, "file": "docs/ENVIRONMENT.md", "lines": 91, "links_dead": 0, "links_dead_anchors": 0, "no_loss": "ok", "no_loss_warrants": 2, "note": "#146: REDIS_FLOOR_TIMEOUT/_WAIT, ALLOW_ANY_CHECKOUT -> docs/DEPLOYMENT.md#environment-variables (~915 tokens, class B)", "over_budget": false, "repo": "replicator", "repo_commit": "1af75e4", "seams": 0, "seams_acked": 26, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 9299, "tokens_exact": true, "tokens_live": 144759, "top_section": "(preamble)", "top_section_share": 100, "ts": "2026-10-19"} From 0f378d9fa678289c548a163385d59ee18c80e779 Mon Sep 17 00:00:00 2001 From: gregoryfoster Date: Mon, 19 Oct 2026 17:45:20 +0000 Subject: [PATCH 5/8] =?UTF-8?q?#146=20docs:=20CR=209=20=E2=80=94=20DEPLOYM?= =?UTF-8?q?ENT.md's=20preamble=20names=20the=20variables=20it=20now=20keep?= =?UTF-8?q?s?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- docs/DEPLOYMENT.md | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/DEPLOYMENT.md b/docs/DEPLOYMENT.md index 5238e16..bd314d9 100644 --- a/docs/DEPLOYMENT.md +++ b/docs/DEPLOYMENT.md @@ -10,6 +10,7 @@ themselves are in [ENVIRONMENT.md](ENVIRONMENT.md), which indexes every one and sends the blob and temp-store settings on to [STORAGE.md](STORAGE.md); the VM, broker and bucket topology this unit runs on is in [INFRASTRUCTURE.md](INFRASTRUCTURE.md). +The start guards' own variables are the exception, kept [below](#environment-variables) (#146). ## Server Lifecycle From 85896522ca1fc9c138c76c39a1907522e4c52fb7 Mon Sep 17 00:00:00 2001 From: gregoryfoster Date: Mon, 19 Oct 2026 17:45:20 +0000 Subject: [PATCH 6/8] =?UTF-8?q?#146=20docs:=20CR=2010=20=E2=80=94=20the=20?= =?UTF-8?q?politeness=20pointer=20names=20the=20live-apply=20greps=20too?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- docs/COMMANDS.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/COMMANDS.md b/docs/COMMANDS.md index 7138fdb..a42ff33 100644 --- a/docs/COMMANDS.md +++ b/docs/COMMANDS.md @@ -309,7 +309,7 @@ things to know before asking (#85): ### Politeness — `content.fetch-policy` (#19) The stream's inspection commands — what the producer published, one host's policy, and the -worker's journal view of the replay — are in +worker's journal view of the replay and of each apply — are in [POLITENESS.md](POLITENESS.md#inspecting-the-stream), beside the mechanism they inspect (#146). ### How long a handler holds its group From bd689edbed0832cd46de61168d13f16d769dfb83 Mon Sep 17 00:00:00 2001 From: gregoryfoster Date: Mon, 19 Oct 2026 17:45:20 +0000 Subject: [PATCH 7/8] =?UTF-8?q?#146=20chore:=20CR=2011=20=E2=80=94=20doc-c?= =?UTF-8?q?heck=20advice=20routes=20SOCRATICODE=5FSPEC=20to=20SOCRATICODE.?= =?UTF-8?q?md,=20where=20its=20pin=20now=20lives?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .skills/doc-sections | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.skills/doc-sections b/.skills/doc-sections index 7409e37..653f5cd 100644 --- a/.skills/doc-sections +++ b/.skills/doc-sections @@ -24,4 +24,4 @@ docs/TESTING.md: what the test and gcs jobs run and why; a checkout change also docs/contracts/: normative and linked from sibling repos, so drift here is cross-repo — change a charter and tests/test_boundaries.py together, file a changed issuer obligation in each issuer's repo, and check README.md's two Issuing paragraphs, which summarize the obligations docs/SKILLS.md: the skill and vendor inventory, hook registration and suspension, and The doc-check lists section (skills/, .claude/, .gitmodules, doc-sensitive-paths, doc-sections) AGENTS.md: Code Exploration Notes — run codebase_update in the same change as a manifest description edit; NOT codebase_context_index, which re-embeds every artifact (.socraticodecontextartifacts.json) -docs/SOCRATICODE.md: the store's endpoints, the collection names projectId fixes, and which siblings a cross-repo search reaches (.socraticode.json, the store block in .claude/settings.json) — docs/INFRASTRUCTURE.md carries co-index itself, and docs/ENVIRONMENT.md the key's env file +docs/SOCRATICODE.md: the store's endpoints, the collection names projectId fixes, which siblings a cross-repo search reaches, the plugin's SOCRATICODE_SPEC pin and the checks after an init-socraticode re-run (.socraticode.json, the store block and SOCRATICODE_SPEC in .claude/settings.json) — docs/INFRASTRUCTURE.md carries co-index itself, and docs/ENVIRONMENT.md the key's env file From d46cb16aad7bc043842c84023f34c14d6bc147cf Mon Sep 17 00:00:00 2001 From: gregoryfoster Date: Mon, 19 Oct 2026 17:46:53 +0000 Subject: [PATCH 8/8] =?UTF-8?q?#146=20chore:=20CR=2010=20=E2=80=94=20amend?= =?UTF-8?q?=20the=20COMMANDS.md=20row=20to=20the=20corrected=20tree=20(951?= =?UTF-8?q?6=20->=209521)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-Authored-By: Claude Opus 5.5 --- .skills/context-metrics.jsonl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.skills/context-metrics.jsonl b/.skills/context-metrics.jsonl index 25a92db..3b10282 100644 --- a/.skills/context-metrics.jsonl +++ b/.skills/context-metrics.jsonl @@ -70,5 +70,5 @@ {"actions": ["baseline:pre-curation"], "budget": 10000, "bytes": 26669, "claims_dropped": null, "claims_warranted": null, "counts": null, "counts_acked": null, "delta_days": 25, "delta_tokens": 487, "docs_orphaned": 2, "docs_total": 22, "docs_unindexed": 18, "file": "docs/SKILLS.md", "lines": 410, "links_dead": 0, "links_dead_anchors": 0, "no_loss": null, "no_loss_warrants": null, "note": null, "over_budget": true, "repo": "replicator", "repo_commit": "beda65d", "seams": null, "seams_acked": null, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 10059, "tokens_exact": true, "tokens_live": 158777, "top_section": "The doc-check lists", "top_section_share": 18, "ts": "2026-10-19"} {"actions": ["baseline:pre-curation"], "budget": 10000, "bytes": 25412, "claims_dropped": null, "claims_warranted": null, "counts": null, "counts_acked": null, "delta_days": 20, "delta_tokens": 75, "docs_orphaned": 4, "docs_total": 22, "docs_unindexed": 7, "file": "docs/ENVIRONMENT.md", "lines": 92, "links_dead": 0, "links_dead_anchors": 0, "no_loss": null, "no_loss_warrants": null, "note": null, "over_budget": true, "repo": "replicator", "repo_commit": "beda65d", "seams": null, "seams_acked": null, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 10014, "tokens_exact": true, "tokens_live": 144271, "top_section": "(preamble)", "top_section_share": 100, "ts": "2026-10-19"} {"actions": ["demote:SocratiCode plugin and health hook", "demote:After an init-socraticode re-run", "retarget:launch-paths link"], "budget": 10000, "bytes": 23772, "claims_dropped": 0, "claims_warranted": 1, "counts": 17, "counts_acked": 0, "delta_days": 0, "delta_tokens": -1100, "docs_orphaned": 2, "docs_total": 22, "docs_unindexed": 18, "file": "docs/SKILLS.md", "lines": 375, "links_dead": 0, "links_dead_anchors": 0, "no_loss": "ok", "no_loss_warrants": 2, "note": "#146: SocratiCode plugin/hook notes + re-run checklist -> docs/SOCRATICODE.md#the-plugin-and-its-hooks (~1210 tokens, class B)", "over_budget": false, "repo": "replicator", "repo_commit": "fd02ee5", "seams": 0, "seams_acked": 24, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 8959, "tokens_exact": true, "tokens_live": 159290, "top_section": "The doc-check lists", "top_section_share": 21, "ts": "2026-10-19"} -{"actions": ["demote:Politeness — content.fetch-policy", "retarget:apply-log link", "rename:patching gate heading"], "budget": 10000, "bytes": 24488, "claims_dropped": 0, "claims_warranted": 1, "counts": 9, "counts_acked": 0, "delta_days": 0, "delta_tokens": -862, "docs_orphaned": 4, "docs_total": 22, "docs_unindexed": 13, "file": "docs/COMMANDS.md", "lines": 444, "links_dead": 0, "links_dead_anchors": 0, "no_loss": "ok", "no_loss_warrants": 2, "note": "#146: politeness inspection block -> docs/POLITENESS.md#inspecting-the-stream (~1000 tokens, class B); counts pre-existing, none introduced", "over_budget": false, "repo": "replicator", "repo_commit": "1af75e4", "seams": 0, "seams_acked": 38, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 9516, "tokens_exact": true, "tokens_live": 144759, "top_section": "Worker", "top_section_share": 50, "ts": "2026-10-19"} {"actions": ["demote:start-guard variables", "retarget:preamble", "retarget:tailscale.md FLOOR_WAIT link"], "budget": 10000, "bytes": 23421, "claims_dropped": 0, "claims_warranted": 0, "counts": 16, "counts_acked": 0, "delta_days": 0, "delta_tokens": -715, "docs_orphaned": 4, "docs_total": 22, "docs_unindexed": 7, "file": "docs/ENVIRONMENT.md", "lines": 91, "links_dead": 0, "links_dead_anchors": 0, "no_loss": "ok", "no_loss_warrants": 2, "note": "#146: REDIS_FLOOR_TIMEOUT/_WAIT, ALLOW_ANY_CHECKOUT -> docs/DEPLOYMENT.md#environment-variables (~915 tokens, class B)", "over_budget": false, "repo": "replicator", "repo_commit": "1af75e4", "seams": 0, "seams_acked": 26, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 9299, "tokens_exact": true, "tokens_live": 144759, "top_section": "(preamble)", "top_section_share": 100, "ts": "2026-10-19"} +{"actions": ["demote:Politeness — content.fetch-policy", "retarget:apply-log link", "rename:patching gate heading"], "budget": 10000, "bytes": 24506, "claims_dropped": 0, "claims_warranted": 1, "counts": 9, "counts_acked": 0, "delta_days": 0, "delta_tokens": -857, "docs_orphaned": 4, "docs_total": 22, "docs_unindexed": 13, "file": "docs/COMMANDS.md", "lines": 444, "links_dead": 0, "links_dead_anchors": 0, "no_loss": "ok", "no_loss_warrants": 2, "note": "#146: politeness inspection block -> docs/POLITENESS.md#inspecting-the-stream (~1000 tokens, class B); counts pre-existing, none introduced", "over_budget": false, "repo": "replicator", "repo_commit": "bd689ed", "seams": 0, "seams_acked": 38, "skill_commit": "ae5338c", "skill_version": "1.31", "tokens": 9521, "tokens_exact": true, "tokens_live": 144794, "top_section": "Worker", "top_section_share": 50, "ts": "2026-10-19"}