You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
Catch up co-core 0.37.0 → 0.39.1 ahead of the capture consumer #153
Prerequisite for #138, filed by the backlog epic's coordinator (#154).
Why
#138 consumes ContentCaptureCommand and publishes CaptureStoredEmit / CaptureFailedEmit. Those shipped in co-core 0.37.1 (CannObserv/cannobserv#571). Production runs 0.37.0, which has no capture types [measured 2026-10-24: none of co-core's 241 modules defines ContentCaptureCommand in the service venv].
The wheelhouse now carries 0.39.1, so the pin is two minors behind the current minor that AGENTS.md says to track (>=0.37.0,<0.38). The #114 rule applies: a multi-minor pin move lands alone, in an isolated venv, before any behaviour that needs it. #140 was the last one.
What moved, from cannobserv's CHANGELOG [traced, not yet checked against the wheels]
0.39.0 (MINOR): co/v1 lookup refusals become UnfindableLookupError. Replicator doesn't use the co/v1 adapters.
0.39.1 (PATCH): two model datetimes serialize in JSON mode only. Replicator doesn't dump those models.
So the expected diff is the pin and the lock. Confirm it from the wheels, not from the changelog: run scripts/co_core_closure_diff.py after the wheel sync (#145) and paste its report into the pin comment.
Scope
co-core, co-core-aio[bus,gcs] and co-core-sync[gcs] to >=0.39.1,<0.40, all three together. Check that each co-core-aio / co-core-sync 0.39.x requires co-core>=0.39,<0.40.
Rewrite the pyproject.toml pin comment, and the AGENTS.md / DEPLOYMENT.md lines that name the current minor.
Work in an isolated venv (the shared-venv .pth flip is the hazard).
Deployed: replicator.service restarted after the merge, /run/replicator/build-id is the merge SHA, uv pip show reports 0.39.1 for all three, and the boot journal shows worker ready and the fetch-policy replay with no decode warnings.
An outcome after worker ready (a stored blob) discharges "no traffic yet".
Authorities: this issue's body and this comment. Where they disagree with the code, stop and post a correction before building.
Pre-flight against origin/main42b728c and .wheelhouse
The wheels exist and move together [measured]. co_core, co_core_aio and co_core_sync 0.39.1 are synced. Both co-core-aio and co-core-sync 0.39.1 declare Requires-Dist: co-core<0.40,>=0.39.
docs/DEPLOYMENT.md:75-79. Re-read it: it may name the minor only by example.
Leave the historical 0.36.0/0.37.0 mentions alone: scripts/co_core_closure_diff.py's usage line, tests/test_co_core_closure_diff.py's real-wheel fixture, and tests/worker/test_policy*.py's "pre-0.37 producer". They describe history, not the pin.
AGENTS.md is close to its 6000-token ratchet (5991 on 2026-10-20 [stated]). The edit there should be a version swap of the same length. Measure exact after.
Template
#140 (PR #141): the last multi-minor catch-up. It used an isolated venv, the three pins together, a pin comment rewrite and a closure diff. #145's script now does the diff: uv run --no-project python scripts/co_core_closure_diff.py 0.37.0 0.39.1, after the wheel sync. Paste its report into the pin comment.
Hazards, to weigh
The changelog says no surface Replicator uses moved [traced, from cannobserv's CHANGELOG]. 0.38's breaks are [extract]'s, which Drop the unused co-core [extract] extra #142 dropped. 0.39's are the co/v1 adapters'. 0.39.1 changes Python-mode dumps of two CRM models. Upgrade that to [measured] with the closure diff before relying on it. Pay particular attention to anything under co_core.pure.models.changes, co_core_aio.bus and co_core_aio.fetch, the three surfaces the worker imports most.
The shared venv. Work in an isolated venv (memory: develop-in-worktrees; the .pth flip). The service runs the main checkout's .venv, so the main checkout's uv sync --frozen at deploy is what moves production.
[Operator decision: floor >=0.39.0 (AGENTS.md's rule: the floor moves only for a feature the code depends on, and nothing here needs 0.39.1) or >=0.39.1 (as this issue's body says) — recommend >=0.39.0,<0.40, with the lock resolving 0.39.1. #138 raises nothing further: its types exist from 0.37.1.]
Expected side effects
One worker restart at deploy, and a short consumer gap that the bus absorbs.
Nothing wire-visible: no new stream or field is consumed yet.
Brief review (implementer, against origin/main42b728c and .wheelhouse)
Pre-flight confirmed: wheels synced; co-core-aio / co-core-sync at 0.39.0 and 0.39.1 both require co-core<0.40,>=0.39 [measured, METADATA]; the capture types are in 0.39.1's changes.py [measured]; production venv is 0.37.0 ×3 [measured].
Corrections
docs/DEPLOYMENT.md:75 names the minor outright, not by example ("Pin the current minor — >=0.37.0,<0.38, on all three"). It's a pin site; it gets the same swap as AGENTS.md. :79 also carries a version-anchored fact ("At 0.37.0 that's co-core-sync's two lazy __getattr__ sites"). Re-measured at 0.39.1: still exactly those two (drivers/__init__.py:89, drivers/blobstore/__init__.py:32), so the line moves to 0.39.1.
"0.38's breaks are [extract]'s, which Drop the unused co-core [extract] extra #142 dropped" is half right [measured, co_core_closure_diff.py 0.37.0 0.39.1 --diff]. Drop the unused co-core [extract] extra #142 dropped the extra (the parsers), not the package: pure/extract/* is in the import closure through changes.py's import of extract.canonical, and seven of its files changed — JsonExtractConfig, ignore_selectors, refuse_unhonoured's signature (two new required kwargs), the jsonpath algorithm, .json in the extension map, spec_fingerprint routed through a new _canonical_json.py. None is reached from here: src/ and scripts/ import nothing from co_core.pure.extract and call none of spec_fingerprint, canonical_text, processor_version or extraction_config_from_spec. The only new Requires-Dist (python-jsonpath, soupsieve) is gated on extra == 'extract', which isn't installed. Conclusion stands; the reason in the pin comment changes.
envelope.py moved two things the brief didn't list. (a) from_wire's global table gains content_capture / capture_stored / capture_failed. On 0.37.0 such a frame on a stream we read raised BusMessageUnknownEventTypeError; on 0.39 it decodes and falls to the isinstance guards, as the Watcher types did at Apply FetchPolicyState.generation only if newer #136. (b) idempotency_key for InfoItemPrimaryChangedEvent gains occurred_at. Replicator neither publishes that type nor calls idempotency_key; its own facts' keys are unchanged.
The three surfaces the brief flags are clean: co_core_aio.bus and co_core_aio.fetch are byte-identical bar __version__ (all of co_core_aio is). In changes.py every model Replicator consumes or emits (ContentFetchCommand, BlobAvailableEvent, FetchFailedEvent, ReplicationFailedEvent, PersistFailedEvent, FetchPolicyState) has the same field list at both ends [measured, AST]. The --diff output appears to give PersistFailedEvent a new detail field. That's difflib alignment: the field was already there at 0.37.0.
Operator decision, evidence for it:co_core_closure_diff.py 0.39.0 0.39.1 → three version-only__init__s, Requires-Dist unchanged [measured]. 0.39.1 brings nothing the closure sees, so I'm building the recommended >=0.39.0,<0.40, with the lock resolving 0.39.1. If you'd rather have >=0.39.1, it's a three-character swap before merge.
Deployed build is cb0d7b9, not main's 42b728c [measured, /run/replicator/build-id]. The nine commits between them touch only docs, tests and skills-vendor, so nothing at runtime. This deploy moves the build-id across both.
No real break → no fallback to >=0.37.1,<0.38. Proceeding.
AGENTS.md:37 (same-length swap, 14090 bytes), DEPLOYMENT.md:75 and :79, manifest description
b4e515f
CR 2: tests/test_co_core_pin.py. Asserts the three specifiers are identical and one minor wide, and that AGENTS.md, DEPLOYMENT.md and the manifest each name the pin. Red against 5ed0219 (3 failed) and against one skewed specifier
6ac5841
CR 1: the pin comment states the measured fact. Every class that already existed in changes.py at 0.37.0 is AST-identical at 0.39.1 apart from docstrings
2790a53
CR 3: the pin comment names what a misrouted capture frame now costs (wrong-type DLQ or skip instead of a poison frame)
Floor >=0.39.0, as the brief recommended. Operator: say if you want >=0.39.1 before merge. It's three characters, and the new test keeps the four doc sites in step.
Verification
Isolated venv: the worktree's own .venv, created fresh, not linked to the deploy checkout's. uv pip show → 0.39.1 ×3.
uv run pytest, final gate after the CR fixes: 1463 passed, 6 skipped, coverage 99.89%.
-m integration tests/worker/test_oom_integration.py (the CI step): 16 passed.
The CI import smoke, run locally, plus ContentCaptureCommand / CaptureStoredEmit / CaptureFailedEmit / CONTENT_CAPTURE: OK.
ruff check / ruff format --check: clean.
Skipped verification
AGENTS.md exact token count. No ANTHROPIC_API_KEY on the host, so measure-context.sh --exact fell back to the estimate: 5991/6000. The swap is byte-for-byte the same length (0.37.0,<0.38 → 0.39.0,<0.40).
CI. Not run yet, because nothing is pushed.
Other integration tests (-m integration apart from the OOM file). They need a scratch broker; CI doesn't run them either.
The baseline suite at 0.37.0 is contaminated. A uv run ruff re-synced the venv to the new lock mid-run, so the 7 failures in that run aren't attributable to 0.37.0. The three its tail names were two submodule-dependent tests (submodules weren't initialised in the fresh worktree) and test_main during the swap. After git submodule update --init, the full suite on 0.39.1 is green.
A misrouted capture frame now dead-letters, or skips, under a different reason (wrong type rather than poison). It's still dead-lettered or skipped, never processed.
The pasted report puts lines over 100 characters into a TOML comment. No linter reads it.
CONTENT_CAPTURE is a command kind in _STREAM_KINDS, so it gets one group with competing consumers, like fetch, replicate and persist.
idempotency_key for capture: the command keys on command_id, and CaptureStoredEvent / CaptureFailedEvent on command_id:occurred_at, the same pattern as persist.
ContentCaptureCommandEmit re-validates viewport / wait instances via model_dump(). Consume with the canonical ContentCaptureCommand (extra="ignore"), per AGENTS.md.
CI's import smoke doesn't name the capture types yet. Add them with the consumer.
Deploy from the main checkout: git pull --ff-only && uv sync --frozen && sudo systemctl restart replicator. The main checkout's sync is what moves production; the worktree venv is separate.
Read back:
cat /run/replicator/build-id = the merge SHA (it's cb0d7b9 today; the commits between that and 42b728c are docs/tests only);
uv pip show co-core co-core-aio co-core-sync → 0.39.1 for all three;
journalctl -u replicator -b (since restart): worker ready and the fetch-policy replay, with no decode / poison warnings;
one stored blob (blob_available) after worker ready, which discharges "no traffic yet".
CI on the merge SHA, by job: push run 40139262350: lint ✅, test ✅, gcs (T4) ✅. The PR run 40139003308 on the same SHA: ✅ ×3.
Deployed:uv sync --frozen in the main checkout moved co-core ×3 from 0.37.0 to 0.39.1. replicator.service restarted at 22:36:14Z. /run/replicator/build-id is BUILD_ID=2790a53. uv pip show reports 0.39.1 for co-core, co-core-aio and co-core-sync.
Boot journal:check_main_checkout: on main at 2790a53, the Redis floor check passed, wheelhouse in sync, the fetch-policy replay completed (508 messages), then worker ready. Zero WARNING or ERROR lines. NRestarts=0.
Traffic:stored a blob and published blob_available at 23:09:01Z (command 01M5R2SD9JH0H4MDZFRDPR38QH), after worker ready. That discharges "no traffic yet".
codebase_update, run from the main checkout under the COMMANDS.md cap: worker untouched, available memory flat. codebase_context shows pyproject.toml, AGENTS.md and docs/DEPLOYMENT.md re-indexed at 22:37Z, and the manifest description reads >=0.39.0,<0.40 since #153. Note: the vendored mcp-driver.mjs has no update subcommand, so I called the tool through a throwaway shim over the driver's withClient. Same pinned launch, same cap.
Floor:>=0.39.0,<0.40, as recommended, accepted by the operator at ship. The lock resolves 0.39.1.
Steps 1–3 of the post-merge list are done. Step 4: worktree and branch removed.
Activity
gregoryfoster commentedon Oct 24, 2026
Brief (coordinator, epic #154, 2026-10-24)
Authorities: this issue's body and this comment. Where they disagree with the code, stop and post a correction before building.
Pre-flight against
origin/main42b728cand.wheelhouseco_core,co_core_aioandco_core_sync0.39.1 are synced. Bothco-core-aioandco-core-sync0.39.1 declareRequires-Dist: co-core<0.40,>=0.39.co_core/pure/models/changes.pyin both 0.37.1 and 0.39.1:ContentCaptureCommand,CaptureStoredEvent/CaptureFailedEventandCaptureStoredEmit/CaptureFailedEmit[measured, from the wheels]. Not in 0.37.0.git grep 0.37 origin/main):pyproject.toml:10-18(the floor's rationale comment) and:189-191(the three specifiers).AGENTS.md:37("Pin the current minor —>=0.37.0,<0.38")..socraticodecontextartifacts.json:156(the manifestdescription). This meanscodebase_updateafter the merge (AGENTS.md, Consume content.fetch-policy — replace the single env default with Watcher's per-host numbers (cannobserv#285) #19).docs/DEPLOYMENT.md:75-79. Re-read it: it may name the minor only by example.0.36.0/0.37.0mentions alone:scripts/co_core_closure_diff.py's usage line,tests/test_co_core_closure_diff.py's real-wheel fixture, andtests/worker/test_policy*.py's "pre-0.37 producer". They describe history, not the pin.Template
#140 (PR #141): the last multi-minor catch-up. It used an isolated venv, the three pins together, a pin comment rewrite and a closure diff. #145's script now does the diff:
uv run --no-project python scripts/co_core_closure_diff.py 0.37.0 0.39.1, after the wheel sync. Paste its report into the pin comment.Hazards, to weigh
[extract]'s, which Drop the unused co-core [extract] extra #142 dropped. 0.39's are the co/v1 adapters'. 0.39.1 changes Python-mode dumps of two CRM models. Upgrade that to [measured] with the closure diff before relying on it. Pay particular attention to anything underco_core.pure.models.changes,co_core_aio.busandco_core_aio.fetch, the three surfaces the worker imports most..pthflip). The service runs the main checkout's.venv, so the main checkout'suv sync --frozenat deploy is what moves production.>=0.37.1,<0.38. It's an in-minor patch that still unblocks Viewport capture: consume the capture command, render headless, store, publish the stored-capture fact #138, and the minor move becomes its own child. Post the evidence and stop for the operator.Operator decision
[Operator decision: floor
>=0.39.0(AGENTS.md's rule: the floor moves only for a feature the code depends on, and nothing here needs 0.39.1) or>=0.39.1(as this issue's body says) — recommend>=0.39.0,<0.40, with the lock resolving 0.39.1. #138 raises nothing further: its types exist from 0.37.1.]Expected side effects
Out of scope
Post-merge host steps (for the issue, when shipping)
git pull --ff-only && uv sync --frozen && sudo systemctl restart replicator./run/replicator/build-idis the merge SHA;uv pip show co-core co-core-aio co-core-syncshows 0.39.1 for all three;worker readyand the fetch-policy replay with no decode warnings;worker ready.codebase_updatefrom the main checkout, because the manifest description changed.gregoryfoster commentedon Oct 24, 2026
Brief review (implementer, against
origin/main42b728cand.wheelhouse)Pre-flight confirmed: wheels synced;
co-core-aio/co-core-syncat 0.39.0 and 0.39.1 both requireco-core<0.40,>=0.39[measured, METADATA]; the capture types are in 0.39.1'schanges.py[measured]; production venv is 0.37.0 ×3 [measured].Corrections
docs/DEPLOYMENT.md:75names the minor outright, not by example ("Pin the current minor —>=0.37.0,<0.38, on all three"). It's a pin site; it gets the same swap as AGENTS.md.:79also carries a version-anchored fact ("At 0.37.0 that's co-core-sync's two lazy__getattr__sites"). Re-measured at 0.39.1: still exactly those two (drivers/__init__.py:89,drivers/blobstore/__init__.py:32), so the line moves to 0.39.1.[extract]'s, which Drop the unused co-core [extract] extra #142 dropped" is half right [measured,co_core_closure_diff.py 0.37.0 0.39.1 --diff]. Drop the unused co-core [extract] extra #142 dropped the extra (the parsers), not the package:pure/extract/*is in the import closure throughchanges.py's import ofextract.canonical, and seven of its files changed —JsonExtractConfig,ignore_selectors,refuse_unhonoured's signature (two new required kwargs), thejsonpathalgorithm,.jsonin the extension map,spec_fingerprintrouted through a new_canonical_json.py. None is reached from here:src/andscripts/import nothing fromco_core.pure.extractand call none ofspec_fingerprint,canonical_text,processor_versionorextraction_config_from_spec. The only new Requires-Dist (python-jsonpath,soupsieve) is gated onextra == 'extract', which isn't installed. Conclusion stands; the reason in the pin comment changes.envelope.pymoved two things the brief didn't list. (a)from_wire's global table gainscontent_capture/capture_stored/capture_failed. On 0.37.0 such a frame on a stream we read raisedBusMessageUnknownEventTypeError; on 0.39 it decodes and falls to theisinstanceguards, as the Watcher types did at Apply FetchPolicyState.generation only if newer #136. (b)idempotency_keyforInfoItemPrimaryChangedEventgainsoccurred_at. Replicator neither publishes that type nor callsidempotency_key; its own facts' keys are unchanged.co_core_aio.busandco_core_aio.fetchare byte-identical bar__version__(all ofco_core_aiois). Inchanges.pyevery model Replicator consumes or emits (ContentFetchCommand,BlobAvailableEvent,FetchFailedEvent,ReplicationFailedEvent,PersistFailedEvent,FetchPolicyState) has the same field list at both ends [measured, AST]. The--diffoutput appears to givePersistFailedEventa newdetailfield. That's difflib alignment: the field was already there at 0.37.0.co_core_closure_diff.py 0.39.0 0.39.1→ threeversion-only__init__s, Requires-Dist unchanged [measured]. 0.39.1 brings nothing the closure sees, so I'm building the recommended>=0.39.0,<0.40, with the lock resolving 0.39.1. If you'd rather have>=0.39.1, it's a three-character swap before merge.cb0d7b9, not main's42b728c[measured,/run/replicator/build-id]. The nine commits between them touch only docs, tests andskills-vendor, so nothing at runtime. This deploy moves the build-id across both.No real break → no fallback to
>=0.37.1,<0.38. Proceeding.gregoryfoster commentedon Oct 24, 2026
Implementation report (branch
153-co-core-0.39, not yet pushed)5ed0219co-core,co-core-aio[bus,gcs],co-core-sync[gcs]→>=0.39.0,<0.40. Lock moves co-core ×3 0.37.0 → 0.39.1, nothing else. Closure diff pasted verbatim into the pin comment774a761b4e515ftests/test_co_core_pin.py. Asserts the three specifiers are identical and one minor wide, and that AGENTS.md, DEPLOYMENT.md and the manifest each name the pin. Red against5ed0219(3 failed) and against one skewed specifier6ac5841changes.pyat 0.37.0 is AST-identical at 0.39.1 apart from docstrings2790a53Floor
>=0.39.0, as the brief recommended. Operator: say if you want>=0.39.1before merge. It's three characters, and the new test keeps the four doc sites in step.Verification
.venv, created fresh, not linked to the deploy checkout's.uv pip show→ 0.39.1 ×3.uv run pytest, final gate after the CR fixes: 1463 passed, 6 skipped, coverage 99.89%.-m integration tests/worker/test_oom_integration.py(the CI step): 16 passed.ContentCaptureCommand/CaptureStoredEmit/CaptureFailedEmit/CONTENT_CAPTURE: OK.ruff check/ruff format --check: clean.Skipped verification
ANTHROPIC_API_KEYon the host, someasure-context.sh --exactfell back to the estimate: 5991/6000. The swap is byte-for-byte the same length (0.37.0,<0.38→0.39.0,<0.40).-m integrationapart from the OOM file). They need a scratch broker; CI doesn't run them either.uv run ruffre-synced the venv to the new lock mid-run, so the 7 failures in that run aren't attributable to 0.37.0. The three its tail names were two submodule-dependent tests (submodules weren't initialised in the fresh worktree) andtest_mainduring the swap. Aftergit submodule update --init, the full suite on 0.39.1 is green.Accepted limits
For #138:
CONTENT_CAPTUREare present from 0.37.1 [measured, wheel], so Viewport capture: consume the capture command, render headless, store, publish the stored-capture fact #138 needs no floor raise inside 0.39.CONTENT_CAPTUREis acommandkind in_STREAM_KINDS, so it gets one group with competing consumers, like fetch, replicate and persist.idempotency_keyfor capture: the command keys oncommand_id, andCaptureStoredEvent/CaptureFailedEventoncommand_id:occurred_at, the same pattern as persist.ContentCaptureCommandEmitre-validatesviewport/waitinstances viamodel_dump(). Consume with the canonicalContentCaptureCommand(extra="ignore"), per AGENTS.md.tests/test_co_core_pin.pynow guards the four pin sites. If Viewport capture: consume the capture command, render headless, store, publish the stored-capture fact #138 raises the floor, it fails until AGENTS.md, DEPLOYMENT.md and the manifest move too.Post-merge host steps
git pull --ff-only && uv sync --frozen && sudo systemctl restart replicator. The main checkout's sync is what moves production; the worktree venv is separate.cat /run/replicator/build-id= the merge SHA (it'scb0d7b9today; the commits between that and42b728care docs/tests only);uv pip show co-core co-core-aio co-core-sync→ 0.39.1 for all three;journalctl -u replicator -b(since restart):worker readyand the fetch-policy replay, with no decode / poison warnings;blob_available) afterworker ready, which discharges "no traffic yet".codebase_updatefrom the main checkout (manifest description changed; AGENTS.md Consume content.fetch-policy — replace the single env default with Watcher's per-host numbers (cannobserv#285) #19). Cap the server launch per docs/COMMANDS.md (Replicator did not reconnect after the broker's 2026-09-16 outage and restart - restart it, then validate reconnection as a property #94).git worktree remove .worktrees/153-co-core-0.39, thengit branch -d 153-co-core-0.39.gregoryfoster commentedon Oct 24, 2026
Shipped:
2790a53(PR #155, fast-forward merged)Done when, verified:
uv sync --frozenin the main checkout moved co-core ×3 from 0.37.0 to 0.39.1.replicator.servicerestarted at 22:36:14Z./run/replicator/build-idisBUILD_ID=2790a53.uv pip showreports 0.39.1 forco-core,co-core-aioandco-core-sync.check_main_checkout: on main at 2790a53, the Redis floor check passed, wheelhouse in sync, the fetch-policy replay completed (508 messages), thenworker ready. Zero WARNING or ERROR lines.NRestarts=0.stored a blob and published blob_availableat 23:09:01Z (command01M5R2SD9JH0H4MDZFRDPR38QH), afterworker ready. That discharges "no traffic yet".codebase_update, run from the main checkout under the COMMANDS.md cap: worker untouched, available memory flat.codebase_contextshowspyproject.toml,AGENTS.mdanddocs/DEPLOYMENT.mdre-indexed at 22:37Z, and the manifest description reads>=0.39.0,<0.40 since #153. Note: the vendoredmcp-driver.mjshas noupdatesubcommand, so I called the tool through a throwaway shim over the driver'swithClient. Same pinned launch, same cap.Floor:
>=0.39.0,<0.40, as recommended, accepted by the operator at ship. The lock resolves 0.39.1.Steps 1–3 of the post-merge list are done. Step 4: worktree and branch removed.