Skip to content

Co-core bump rule: resolve_dispatch_essence is notice, not coordination #57

Closed

Description

@gregoryfoster

Problem

The co-core bump rule names resolve_dispatch_essence as part of the contract. A change to the contract needs coordination with Watcher first, not just notice. But Processor never calls resolve_dispatch_essence: grep -rn resolve_dispatch_essence src/ returns nothing on 540975e. Watcher resolves the essence on its own co-core pin and puts it on the command. Processor dispatches whatever essence arrives and never re-resolves it (spec §3, cannobserv#486 D1).

So a change to resolve_dispatch_essence can't change Processor's behaviour on its own, and the rule asks for coordination it doesn't need. It has happened twice:

The argument both times:

  • An old Watcher keeps sending the ambiguous essence, which reaches the same extractor as before.
  • A new Watcher's resolved essence reaches the old extractor on an old Processor, and the new one on a new Processor.
  • The moved-digest list in the notice already covers what Watcher sees when the dispatch map changes for an essence Watcher sends today.

Where the rule is stated

  • AGENTS.md:21: "coordinated with Watcher only if it changes the contract: … or resolve_dispatch_essence"
  • docs/DEPLOYMENT.md:461, "Bumping co-core" step 4
  • the spec: D5 (:134) and §5 (:507-509)

Proposal

  1. Drop resolve_dispatch_essence from the contract list in all four places. The contract becomes the content.process / content.derived models and canonical_text semantics.
  2. Say why, once, in §5. Essence resolution is the issuer's, on its own pin, and Processor never re-resolves. So a change to it is notice, carrying the either-order reasoning and the moved digests. It is never coordination.
  3. Guard the premise with a test, so the rule can't silently go stale. Assert that nothing under src/processor/ imports or names resolve_dispatch_essence / extension_media_type, the same way gen_parity_goldens.py's import test works. If Processor ever starts resolving essences itself, the test fails, and the rule has to come back with it.
  4. Amend the spec (an amendment entry), noting that Take co-core's XML and feed extractors in step with Watcher, and re-run parity with a feed fixture #29's coordination was more than the rule needed. The history stays as written.

Out of scope

  • Watcher's own pin and its handling of the tiebreak.
  • Changes to EXTRACTOR_BY_ESSENCE. Those already reach Watcher as moved digests in the notice, which stays required on every bump.

Found while shipping #37.

Activity

  1. gregoryfoster commented on Oct 21, 2026

    @gregoryfoster
    ContributorAuthor

    Hand-off from the burn-down epic #24, 2026-10-21. This is next, and it is the last open item before the epic returns to a parking lot (#54). Size S: docs and one test, so no deploy. The pre-flight is against origin/main (a1a9e91, #58's head).

    The premise holds. grep -rn 'resolve_dispatch_essence\|extension_media_type' src/ tests/ scripts/ returns nothing. Processor dispatches the essence Watcher puts on the command and never resolves one itself.

    Where the rule is stated (current line numbers; #58 shifted DEPLOYMENT.md by one):

    The change (the issue's proposal, which the coordinator agrees with):

    1. Drop resolve_dispatch_essence from the contract list in the four places above. The contract becomes the content.process / content.derived models and canonical_text semantics.
    2. Explain why once, in §5:
      • essence resolution is the issuer's job, on the issuer's own pin, and Processor never re-resolves;
      • so a change to it is notice, carrying the either-order reasoning and the moved digests;
      • and it becomes coordination again only if Processor starts resolving essences itself.
    3. The guard test. Model it on tests/test_gen_parity_goldens.py:29-40, which walks the AST. Walk every src/processor/**/*.py and fail on any import of, or Name / Attribute use of, resolve_dispatch_essence or extension_media_type.
      • Check imports and code references, not text. A docstring that mentions the function is allowed, and the spec quotes it.
      • The test's message points back to §5's rule. If Processor ever resolves essences itself, the test fails, and the coordination rule has to come back with it.
    4. A spec amendment entry dated at the merge. It notes that Take co-core's XML and feed extractors in step with Watcher, and re-run parity with a feed fixture #29's coordination on Take cannobserv v0.32.0 in step with Processor: resolve_dispatch_essence maps .xml/.rss/.atom to the XML essences watcher#366 was more than the rule needed, and that JSON: take co-core's JsonExtractor (cannobserv#354) and add a jsonpath parity fixture #37 used notice. History isn't rewritten.

    Gate (operator): tell Watcher? D5 is a promise Processor made to Watcher: #47 posted the restated D5 on CannObserv/watcher#325, and Watcher agreed to the original there. This change narrows when Watcher is consulted, so the coordinator recommends a short notice after the merge. CannObserv/watcher#325 is closed now, so post it on CannObserv/watcher#368, where #37's notice went. It should say:

    • a dispatch-essence change is now notice, with the either-order reasoning;
    • every bump still gives notice with the moved digests;
    • and the guard test keeps the premise true.

    Draft it in the PR. It needs the operator's approval: the earlier approval covered #37's two posts only. If the operator declines, say so in the closing comment.

    Traps:

    1. EXTRACTOR_BY_ESSENCE stays where it is. A change to the dispatch map is still Processor's behaviour, and it reaches Watcher as moved digests in the notice, which stays required on every bump. Don't widen the change to it (the issue's out of scope).
    2. Don't touch D5's other clause: notice on every processor_version move stays.
    3. AGENTS.md line 21 is one long line, and it also carries the pin and the bump procedure link. Change only the contract list.

    Done when:

    • the four rule sites drop resolve_dispatch_essence, and §5 says why once;
    • the guard test is in and passes, and it fails on a deliberate from co_core.pure.extract import resolve_dispatch_essence in a scratch copy (show the red in the PR);
    • the amendment entry is in, and the history is unchanged;
    • the Watcher notice is posted with approval, or declined and said so;
    • CI is green on the PR and main push runs. No deploy: nothing under src/, deploy/, scripts/deploy.sh, pyproject.toml or uv.lock changes.

    Out of scope:

    Ship: issue → branch in a worktree → plan (short) → TDD (the guard test first) → PR (Closes #57 if the notice is declined or already posted, otherwise Refs #57) → FF merge → git pull --ff-only → the notice. The closing comment gives each box with its evidence, the known limits, and the follow-ups (or "none").

    Kickoff prompt:

    Pick up processor#57. Read its hand-off comment from epic #24 first.

  2. gregoryfoster commented on Oct 21, 2026

    @gregoryfoster
    ContributorAuthor

    Shipped in #60, FF-merged to main as 359e102. No deploy: the merge changes only AGENTS.md, docs/ and tests/ (and nothing under src/, deploy/, scripts/deploy.sh, pyproject.toml or uv.lock).

    Done when:

    • The four rule sites drop resolve_dispatch_essence, and §5 says why once:
      • AGENTS.md:21: the contract list only.
      • docs/DEPLOYMENT.md "Bumping co-core" step 4: the list, plus a one-line pointer to §5.
      • Spec D5: the list, plus a dated note in its italic history.
      • Spec §5: the list, plus a new bullet, "Essence resolution is not in the contract". It gives the issuer-resolves premise, the either-order reasoning, notice with moved digests, and when the rule would revert.
    • The guard test is in and passes. It is tests/test_pin.py::test_processor_never_resolves_a_dispatch_essence, which walks the AST, plus test_the_guard_catches_code_not_text (7 cases). The PR shows the red: the guard fails on a scratch copy with from co_core.pure.extract import resolve_dispatch_essence planted in processors/extract.py, naming line 16: resolve_dispatch_essence.
    • The amendment entry is in, and the history is unchanged. The spec has "Amended 2026-10-21 (the bump rule, processor#57)". The diff touches no line of the 2026-10-12 or 2026-10-20 entries, or of §5's per-bump notes.
    • The Watcher notice is posted with the operator's approval: [Epic] Headless Watcher and cohort boundaries watcher#368 (comment)
    • CI is green. On the PR: run 39658479046, lint and test. On the main push at 359e102: run 39659160499, lint and test. Locally: 696 passed, 14 skipped. Every skip is environmental: no .env in the worktree, and /etc/processor is unreadable as exedev.

    The premise, checked on co-core 0.38.0: Processor calls extractor_for_essence and extraction_overrides_for_essence, and neither of them calls resolve_dispatch_essence or extension_media_type.

    Known limits:

    • The guard catches imports, names and attributes. It doesn't catch a string-keyed lookup (getattr(m, "resolve_dispatch_essence")), which would be deliberate evasion rather than drift.
    • It covers src/processor/ only. That is everything that runs; scripts/ isn't runtime.

    Follow-ups: none.

  3. gregoryfoster commented on Oct 21, 2026

    @gregoryfoster
    ContributorAuthor

    Review round 1 shipped in #61, FF-merged to main as 18deb3c. CI is green on the PR (run 39661447910) and on the main push (run 39683627450). No deploy: only docs/ and tests/ changed.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions