Skip to content

Commit f1e92dc

Browse files
committed
Merge Batch A: CI signal per commit (#44) and the main-checkout guard (#37)
Two guardrails on the path that deploys and verifies everything else, landed first so later batches inherit them: each main commit now gets its own CI run, and replicator.service refuses to start on any checkout but main. Includes CR round 1 (#1-#8) and the AGENTS.md curation that took the policy file from 6031 back to 5890 against its 6000 budget. Closes #44 Closes #37
2 parents 35fef91 + b7c826b commit f1e92dc

16 files changed

Lines changed: 779 additions & 28 deletions

‎.github/workflows/ci.yml‎

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,10 +10,17 @@ on:
1010
permissions:
1111
contents: read
1212

13-
# Cancel superseded runs on the same PR; never cancel runs on main (keep
14-
# CI signal for every commit that lands on the protected branch).
13+
# Two mechanisms, one per event kind:
14+
# - PR runs share a group per branch (`github.head_ref` is non-empty only on
15+
# `pull_request`), and `cancel-in-progress` cancels the superseded one.
16+
# - Everything else (`push`, `workflow_dispatch`) is keyed on `github.sha`,
17+
# unique per commit, so each `main` run sits alone in its own group.
18+
# The second is not redundant with the first. A group holds at most one
19+
# *pending* run regardless of `cancel-in-progress`, so under one branch-keyed
20+
# group a third push evicts the second's queued run before it ever starts —
21+
# a commit lands on the protected branch with no CI signal at all (#44).
1522
concurrency:
16-
group: ${{ github.workflow }}-${{ github.head_ref || github.ref }}
23+
group: ${{ github.workflow }}-${{ github.head_ref || github.sha }}
1724
cancel-in-progress: ${{ github.event_name == 'pull_request' }}
1825

1926
jobs:

‎.gitignore‎

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -42,8 +42,15 @@ coverage.xml
4242
*.swp
4343
*.swo
4444

45-
# Git worktrees
45+
# Git worktrees. Two roots, because two things provision them: the
46+
# using-git-worktrees scripts resolve to <repo>/.worktrees/, and the Claude Code
47+
# Agent harness (isolation: "worktree") provisions into .claude/worktrees/.
48+
# Both are ignored so that `git status --porcelain` on the main checkout stays
49+
# empty while agents run — that emptiness is the orchestrator's tripwire for an
50+
# agent falling through into the deployment checkout, and a tripwire that fires
51+
# on every clean run is one nobody reads.
4652
.worktrees/
53+
.claude/worktrees/
4754

4855
# Runtime (BUILD_ID stamp target — the systemd unit writes /run/replicator/build-id)
4956
/run/

‎.skills/context-loss-ok‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
# 2026-08-17 curation (#47). Two lines rewritten because a command refuted them.
2+
disproven :: Auth is ADC: on the VM the SA key at `GOOGLE_APPLICATION_CREDENTIALS` (`/etc/replicator/co-pypi-reader.json`), in CI a keyless WIF token. Pin the current minor
3+
disproven :: enforces eight charter invariants in CI; change a

‎.skills/context-metrics.jsonl‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,5 @@
22
{"actions": ["split:content-fetch-issuer-contract", "relocate:Settled->docs/plans", "restructure:payload headings", "index:Where the rest lives", "reindex:context-artifacts"], "budget": 6000, "bytes": 14582, "delta_days": 0, "delta_tokens": 123, "docs_orphaned": 0, "docs_total": 10, "file": "AGENTS.md", "lines": 234, "links_dead": 0, "no_loss": "ok", "note": "#24 doc split: issuer contract 14772->9966, new reference 7349; every live doc under the 10k per-doc budget", "over_budget": false, "repo": "replicator", "skill_commit": "3fc7b71", "skill_version": "1.2", "tokens": 5896, "tokens_exact": true, "tokens_live": 54697, "top_section": "Code Exploration Policy", "top_section_share": 18, "ts": "2026-08-06"}
33
{"actions": ["prune:Code Exploration Policy prefetch query (dup of SessionStart hook)", "add:content-replicate-issuer-contract"], "budget": 6000, "bytes": 14651, "delta_days": 5, "delta_tokens": -24, "docs_orphaned": 0, "docs_total": 11, "file": "AGENTS.md", "lines": 236, "links_dead": 0, "no_loss": "failed", "note": "prefetch select: string dropped from AGENTS.md — verbatim duplicate of the SessionStart hook in .claude/settings.json; issuer-reference was never over budget (guard estimate ran 9.4% high vs exact)", "over_budget": false, "repo": "replicator", "skill_commit": "3fc7b71", "skill_version": "1.2", "tokens": 5872, "tokens_exact": true, "tokens_live": 66108, "top_section": "Bus Conventions", "top_section_share": 19, "ts": "2026-08-11"}
44
{"actions": ["split:ARCHITECTURE->CONVENTIONS", "split:Code Exploration Policy->SOCRATICODE (skills#115)", "relocate:Why the issuer renders->docs/plans", "demote:test-layout->TESTING", "demote:founding-design->ARCHITECTURE", "rescue:repo-authored notes outside marker pair", "tighten:Bus Conventions", "prune:dup env-load snippet", "index:CONVENTIONS.md+SOCRATICODE.md", "reindex:context-artifacts"], "budget": 6000, "bytes": 14489, "delta_days": 4, "delta_tokens": -26, "docs_orphaned": 0, "docs_total": 13, "file": "AGENTS.md", "lines": 248, "links_dead": 0, "no_loss": "failed", "note": "GH #35, CR rounds 1-2 (findings 1-12). Issue premise partly refuted: replicator-boundaries.md 9152 exact, 848 UNDER — the guard estimator runs +13.5% high on contract docs because the ratio is fitted to the policy file alone (skills#172). Genuinely over and unlisted in the issue: ARCHITECTURE.md 11075 and AGENTS.md itself 6349. Code Exploration Policy brought onto init-socraticode's post-#115 Variant A shape (935 -> 446 tok); vendored pin 3fc7b71 predates that split, so a re-run before #41 reverts the block — recorded in docs/SOCRATICODE.md's header. Manifest description de-coupled from positional doc lists to stop it rotting. All prove-no-loss lines warranted: template-owned block regenerated, duplications, or reflows.", "over_budget": false, "repo": "replicator-ctx35", "skill_commit": "3fc7b71", "skill_version": "1.2", "tokens": 5846, "tokens_exact": true, "tokens_live": 74795, "top_section": "Bus Conventions", "top_section_share": 24, "ts": "2026-08-15"}
5+
{"actions": ["baseline:pre-curation"], "budget": 6000, "bytes": 14853, "delta_days": 2, "delta_tokens": 185, "docs_orphaned": 0, "docs_total": 13, "file": "AGENTS.md", "lines": 252, "links_dead": 0, "links_dead_anchors": 0, "no_loss": null, "no_loss_warrants": null, "note": null, "over_budget": true, "repo": "replicator", "seams": null, "seams_acked": null, "skill_commit": "1bf970b", "skill_version": "1.9", "tokens": 6031, "tokens_exact": true, "tokens_live": 78605, "top_section": "Bus Conventions", "top_section_share": 24, "ts": "2026-08-17"}
6+
{"actions": ["demote:Bus Conventions/replicate-gcs->docs/CONVENTIONS.md", "fix:co-core-pin-disproven", "fix:charter-invariant-count-disproven"], "budget": 6000, "bytes": 14446, "delta_days": 0, "delta_tokens": -141, "docs_orphaned": 0, "docs_total": 13, "file": "AGENTS.md", "lines": 247, "links_dead": 0, "links_dead_anchors": 0, "no_loss": "ok", "no_loss_warrants": 2, "note": null, "over_budget": false, "repo": "replicator", "seams": 0, "seams_acked": 24, "skill_commit": "1bf970b", "skill_version": "1.9", "tokens": 5890, "tokens_exact": true, "tokens_live": 78803, "top_section": "Bus Conventions", "top_section_share": 22, "ts": "2026-08-17"}

‎.skills/context-seams-ok‎

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
# Seams judged legitimate — check-seams.sh --ack-file default.
2+
#
3+
# A back-reference is wrong only if the content it points at moved. Judged
4+
# 2026-08-17 during the #47 curation, which moved exactly one block (the
5+
# replicate/gcs bullet, AGENTS.md -> docs/CONVENTIONS.md). Nothing below points
6+
# at that block: verified with a grep for AGENTS.md mentions co-occurring with
7+
# replicate/gcs/create-if-absent/alias, which returned only DEPLOYMENT.md's
8+
# incident narrative — prose about 2026-08-14, not a pointer.
9+
#
10+
# One entry per judged line.
11+
12+
# --- back-references describing the AGENTS.md <-> docs relationship itself.
13+
# Each states the split that this skill maintains, so each is made MORE true by
14+
# a demotion, not less. They point at the arrangement, never at a given block.
15+
docs/ARCHITECTURE.md :: keeps the rules needed on nearly every task; the reasoning that makes
16+
docs/ARCHITECTURE.md :: Reference docs; the Detail Docs index in AGENTS.md is the roster
17+
docs/ARCHITECTURE.md :: Each rule `AGENTS.md` states in one line, with the reasoning that makes it non-negotiable.
18+
docs/CONVENTIONS.md :: touches, with the reasoning that makes each one non-negotiable.
19+
docs/DEPLOYMENT.md :: keeps the two-env-file boundary and the restart
20+
docs/STYLE.md :: Code style beyond the conventions `AGENTS.md` keeps inline. Today that is the
21+
22+
# --- narrative, not a pointer: the 2026-08-14 branch-deploy incident that #37
23+
# closes. "AGENTS.md asserted" the main-is-deployed invariant, which it still
24+
# does, under ## Infrastructure.
25+
docs/DEPLOYMENT.md :: was an invariant AGENTS.md asserted and nothing enforced
26+
27+
# --- docs/SKILLS.md rows: an inventory of tooling that ACTS on AGENTS.md.
28+
# These name the file as a target, so they stay true wherever its content sits.
29+
docs/SKILLS.md :: Curates `AGENTS.md` and its live reference docs against a token budget
30+
docs/SKILLS.md :: write guard: warns when an edit pushes `AGENTS.md` over
31+
docs/SKILLS.md :: and every doc here are registered artifacts; nothing re-embeds them
32+
docs/SKILLS.md :: Repo-specific *prose* needs no re-applying — it lives in `AGENTS.md` under
33+
34+
# --- docs/SOCRATICODE.md: points at two AGENTS.md SECTION HEADINGS, both of
35+
# which this run left in place (## Code Exploration Policy, ## Code Exploration
36+
# Notes (repo-specific)). Re-checked against the current headings.
37+
docs/SOCRATICODE.md :: Detail doc for the `## Code Exploration Policy` block in `AGENTS.md`. That block
38+
docs/SOCRATICODE.md :: file wholesale. Repo-specific exploration notes belong in `AGENTS.md` under
39+
docs/SOCRATICODE.md :: `AGENTS.md` → `## Code Exploration Notes (repo-specific)`.
40+
docs/SOCRATICODE.md :: Two ways this check lies on this repo — both in `AGENTS.md` under
41+
42+
# --- provenance headings: `(#NN)` in a heading makes the anchor slug carry an
43+
# issue number. An established convention here rather than drift — 8 of the 9
44+
# predate this branch, across four docs and three contracts. Renaming them is a
45+
# repo-wide decision with its own dead-anchor risk, not a curation side effect.
46+
docs/COMMANDS.md :: Politeness — `content.fetch-policy` (#19)
47+
docs/DEPLOYMENT.md :: The checkout guard — the service refuses to start off `main` (#37)
48+
docs/DEPLOYMENT.md :: The co-core 0.8.0 cutover is a two-repo deploy, streams flushed between (#28)
49+
docs/DEPLOYMENT.md :: The dedupe keys gain a stream segment (#29) — no action, but know why
50+
docs/contracts/content-fetch-issuer-contract.md :: Request options: what Replicator will send, and what it refuses (#11)
51+
docs/contracts/content-fetch-issuer-reference.md :: Request options: what Replicator will send, and what it refuses (#11)
52+
docs/contracts/content-replicate-issuer-contract.md :: T3a — the *source* is a path too, and it is the sharper half (#29)
53+
docs/contracts/replicator-boundaries.md :: The policy stream (shipped, #19)
54+
docs/contracts/replicator-boundaries.md :: The fallback default (was the interim, #12 → #19)

‎.skills/context-token-counts‎

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -3,15 +3,15 @@
33
# The offline estimators prefer a file's own last exact measurement to the
44
# repo-wide figure in .skills/context-token-ratio, falling back to it for a
55
# file never counted exactly or since drifted far from the size recorded here.
6-
14483 5847 AGENTS.md
6+
14446 5890 AGENTS.md
77
26209 9510 docs/ARCHITECTURE.md
8-
9668 3826 docs/COMMANDS.md
9-
4753 1864 docs/CONVENTIONS.md
10-
16910 6670 docs/DEPLOYMENT.md
11-
11333 4297 docs/SKILLS.md
12-
5204 2023 docs/SOCRATICODE.md
8+
9766 3857 docs/COMMANDS.md
9+
5653 2203 docs/CONVENTIONS.md
10+
20574 8003 docs/DEPLOYMENT.md
11+
15846 6006 docs/SKILLS.md
12+
4796 1847 docs/SOCRATICODE.md
1313
5686 2097 docs/STORAGE.md
14-
2907 1034 docs/STYLE.md
14+
3325 1206 docs/STYLE.md
1515
2952 1088 docs/TESTING.md
1616
24690 9338 docs/contracts/content-fetch-issuer-contract.md
1717
24893 9234 docs/contracts/content-fetch-issuer-reference.md

‎AGENTS.md‎

Lines changed: 11 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,7 @@ Python ≥3.12, uv, pytest, ruff. `ty` is available as a **non-gating** type che
3131
uv run --no-project --with 'google-cloud-storage>=2,<4' python scripts/sync_wheelhouse.py
3232
```
3333

34-
Auth is ADC: on the VM the SA key at `GOOGLE_APPLICATION_CREDENTIALS` (`/etc/replicator/co-pypi-reader.json`), in CI a keyless WIF token. Pin the current minor — `>=0.9.4,<0.10`. The **patch** floor is load-bearing, not tidiness: the change-bus payloads are `extra="ignore"`, so on an older wheel a model constructed with fields it does not have yet succeeds and silently discards them. Raise the floor with every co-core feature the code starts depending on, or a version skew publishes facts that look right and carry nothing (#10). Both floors since fail *loudly* instead — a ValidationError at construction (0.8.0 requires `info_source_id` on all three fetch payloads, #19/#28) or an ImportError at load, never reaching a running worker (0.9.4 cuts the replicate contracts, #29).
34+
Auth is ADC: on the VM the SA key at `GOOGLE_APPLICATION_CREDENTIALS` (`/etc/replicator/co-pypi-reader.json`), in CI a keyless WIF token. Pin the current minor — `>=0.10,<0.11`. The **patch** floor is load-bearing, not tidiness: the change-bus payloads are `extra="ignore"`, so on an older wheel a model constructed with fields it does not have yet succeeds and silently discards them. Raise the floor with every co-core feature the code starts depending on, or a version skew publishes facts that look right and carry nothing (#10). Both floors since fail *loudly* instead — a ValidationError at construction (0.8.0 requires `info_source_id` on all three fetch payloads, #19/#28) or an ImportError at load, never reaching a running worker (0.9.4 cuts the replicate contracts, #29).
3535

3636
<!-- BEGIN socraticode-policy -->
3737
## Code Exploration Policy
@@ -91,8 +91,10 @@ test a branch with `uv run python -m src.worker.main` under a distinct
9191
`REPLICATOR_CONSUMER_NAME`. **After editing `deploy/replicator.service`, `cp` it to
9292
`/etc/systemd/system/`** — the installed unit is a copy, not a symlink, so
9393
`daemon-reload` alone silently re-reads the old file and the mismatch has no
94-
symptom until a directive matters. Full lifecycle table and the dev-server
95-
invocation: [docs/DEPLOYMENT.md](docs/DEPLOYMENT.md).
94+
symptom until a directive matters. **Refuses to start off `main`** (#37) —
95+
`scripts/check_main_checkout.sh`; `REPLICATOR_ALLOW_ANY_CHECKOUT=1` overrides.
96+
Full lifecycle table and the dev-server invocation:
97+
[docs/DEPLOYMENT.md](docs/DEPLOYMENT.md).
9698

9799
## Environment Variables
98100

@@ -143,21 +145,16 @@ Replicator is a **consumer** first. Follow the conventions co-core and the archi
143145
`content.blobs` and `content.artifacts` each carry both outcomes of their
144146
command; `content.fetch-policy` is read **groupless** — no group, no ack, no
145147
DLQ.
146-
- **The replicate loop writes for `gcs` (#29).** T4's create-if-absent, so a
147-
redelivery onto matching bytes re-emits the same `public_url` and differing bytes
148-
are a terminal conflict. `blob_uri` is **never resolved as a path** — fingerprint
149-
out, compared against `store.uri_for()`. Writers are keyed **by alias**, and every
150-
refusal happens before any credential is touched. Provider failures classify by
151-
HTTP status — 4xx closes the command, 5xx/408/429 and statusless errors leave it
152-
pending — because a transient failure is exempt from the delivery ceiling and
153-
publishes no fact at all, so misclassifying one strands the issuer forever
154-
(#29 CR #26, #27).
148+
- **The replicate loop writes for `gcs` (#29)** — create-if-absent, `blob_uri`
149+
never resolved as a path, writers keyed by alias, refusals before credentials,
150+
provider failures classified by HTTP status. Read
151+
[docs/CONVENTIONS.md](docs/CONVENTIONS.md) before touching that path.
155152
- **Nothing but the seed script writes to `content.fetch`.** `scripts/seed_fetch.py`
156153
requires `--production` for the one combination the live worker consumes — a
157154
frame there is fetched for real.
158155
- **Three normative contracts bound the wire and the roadmap** — four documents,
159156
all under `docs/contracts/`, linked from sibling repos and indexed below.
160-
`tests/test_boundaries.py` enforces eight charter invariants in CI; change a
157+
`tests/test_boundaries.py` enforces the charter in CI; change a
161158
charter and its tests together.
162159

163160
Where the reasoning lives:
@@ -219,7 +216,7 @@ logger = get_logger(__name__)
219216
```
220217
Entry points only: `configure_logging()` is called once inside the FastAPI `lifespan` or the worker's `run()`. Never in library modules.
221218

222-
The logging stack — one formatter, two installers, and the two journald lines that
219+
The logging stack — one formatter, two installers, and the journald lines that
223220
are deliberately not JSON: [docs/STYLE.md](docs/STYLE.md).
224221

225222
**Date & Time:**

‎deploy/replicator.service‎

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,30 @@ StateDirectory=replicator
2828
# Create runtime dir (runs as root before User= takes effect with + prefix)
2929
ExecStartPre=+/bin/bash -c 'mkdir -p /run/replicator && chown exedev:exedev /run/replicator'
3030

31+
# Hard guard: "Code committed to main is the deployed code" (AGENTS.md) was an
32+
# invariant nothing enforced until #37 — a restart while the checkout sat on a
33+
# feature branch deployed unmerged code. Refuses a non-main or detached HEAD;
34+
# warns only on a stale, unpushed, or dirty tree. Not '-' prefixed: a guard whose
35+
# refusal is logged and ignored is not a guard.
36+
#
37+
# Placed BEFORE the BUILD_ID stamp on purpose. /run/replicator/build-id outlives a
38+
# failed start, so stamping the branch SHA first would leave the journal
39+
# describing code that never ran — the same "looks correct, is not" failure the
40+
# guard exists to close.
41+
#
42+
# REPLICATOR_ALLOW_ANY_CHECKOUT=1 in /etc/replicator/.env bypasses it. That is not
43+
# the documented way to test a branch on this VM (run the worker directly under a
44+
# distinct REPLICATOR_CONSUMER_NAME) — it is for testing this unit itself. The
45+
# override still reaches this step even though the EnvironmentFile= lines appear
46+
# below it: EnvironmentFile= is a unit-level setting applied to every Exec*
47+
# process, not a sequential instruction, so its position in the file is not an
48+
# ordering.
49+
#
50+
# The checkout it inspects is WorkingDirectory= (systemd's cwd for every Exec*),
51+
# which is the same tree the stamp below reads and ExecStart runs from — so the
52+
# guard cannot end up verifying a different tree than the one that starts.
53+
ExecStartPre=/bin/bash /home/exedev/replicator/scripts/check_main_checkout.sh
54+
3155
# Write current git SHA to a runtime env file before starting
3256
ExecStartPre=/bin/bash -c 'echo BUILD_ID=$(git rev-parse --short HEAD) > /run/replicator/build-id'
3357

‎docs/COMMANDS.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -211,6 +211,8 @@ git submodule update --remote --merge # pull upstream skill changes
211211
## Deploy
212212

213213
```bash
214+
bash scripts/check_main_checkout.sh # what the unit asserts before it starts
215+
214216
sudo cp deploy/replicator.service /etc/systemd/system/replicator.service
215217
sudo systemctl daemon-reload
216218
sudo systemctl enable --now replicator

‎docs/CONVENTIONS.md‎

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,3 +20,15 @@ states them in one line each; what a *particular* stream carries, and why, is in
2020
- **`occurred_at` is enforced tz-aware UTC on every payload** since co-core v0.7.2 (cannobserv#273). Naive is rejected fail-loud rather than assumed UTC; aware non-UTC is normalized. Load-bearing beyond tidiness — `isoformat()` is half `fetch_failed`'s envelope key, and a naive value would serialize without an offset. Issuer-visible: a naive `occurred_at` now fails `from_wire` and dead-letters as an anomaly.
2121
- **`from_wire`'s topic and message_id are keyword-only** — `from_wire(fields, topic=..., message_id=...)`. The founding plan's API table showed them positionally.
2222
- `sha256` lives at `co_core.pure.util.hashing`, not `co_core.pure.extract` (which carries `simhash`, `Chunk`, and the parsers). Import parsers from submodules — they are not re-exported from `__init__`.
23+
- **The replicate loop writes for `gcs` (#29).** T4's create-if-absent, so a
24+
redelivery onto matching bytes re-emits the same `public_url` and differing bytes
25+
are a terminal conflict. `blob_uri` is **never resolved as a path** — fingerprint
26+
out, compared against `store.uri_for()`. Writers are keyed **by alias**, and every
27+
refusal happens before any credential is touched. Provider failures classify by
28+
HTTP status — 4xx closes the command, 5xx/408/429 and statusless errors leave it
29+
pending — because a transient failure is exempt from the delivery ceiling and
30+
publishes no fact at all, so misclassifying one strands the issuer forever
31+
(#29 CR #26, #27).
32+
The mechanism in full — the four outcomes, the guard order, why the writers are
33+
keyed by alias — is [ARCHITECTURE.md](ARCHITECTURE.md); this bullet is the rule
34+
an agent needs before touching the replicate path.

0 commit comments

Comments
 (0)