Skip to content

Choose the collector origin in one place, from a fixed set of trusted origins - #29

Merged
anth-volk merged 10 commits into
mainfrom
claude/configurable-collector-origin
Oct 28, 2026
Merged

anth-volk merged 10 commits into
mainfrom
claude/configurable-collector-origin

Conversation

@anth-volk

@anth-volk anth-volk commented Oct 20, 2026 •

Copy link
Copy Markdown
Contributor

Fixes #28

Summary

The emitter now chooses the collector origin (the address that receives the build's Hugging Face token) in one place, core/collector_origin.py. A new collector_origin module setting is accepted only when it names an origin in a fixed set, TRUSTED_COLLECTOR_ORIGINS.

It changes no behaviour. The set holds only PRODUCTION_COLLECTOR_URL, the current default, so every build sends to the same collector as before. It adds no environment variable.

Plan item C3 moves the emitter to the collector's own hostname. The decisions it waited on are made:

  • D2: the collector gets a dedicated Google Cloud project.
  • D3: a load balancer serves it at a hostname of our own. The hostname is live, and the dashboard already uses it.

This PR doesn't add that hostname. The follow-up (tracker task 5.1 · 2) adds it to TRUSTED_COLLECTOR_ORIGINS and makes it the default.

What changes

  • One resolver, core/collector_origin.py, with two functions:

    • resolve_collector_origin(development_collector_url=..., collector_origin=...);
    • configured_collector_origin(configuration).

    With neither setting, it returns PRODUCTION_COLLECTOR_URL. Every module that sends requests to the collector now calls it:

    • TelemetryModule (telemetry/service/module.py), for the shared collector session;
    • CollectorDelivery (telemetry/service/collector.py), for run registrations and event batches;
    • OrreryModule (orrery/service/module.py), for the shared session;
    • new in this update: HuggingFaceModule (huggingface/service/module.py), for Submit staged bundles' calibration diagnostics and Orrery inputs to the collector #27's collector submissions. That covers the artifacts:submit token exchange and the requests that create, complete and check submissions.
  • A fixed set of trusted origins, TRUSTED_COLLECTOR_ORIGINS in core/constants.py. It holds only PRODUCTION_COLLECTOR_URL.

  • A collector_origin module setting. It's accepted only when it is an HTTPS origin in the trusted set. Setting it together with development_collector_url is also refused, and so is a value of either setting that isn't a string (such as true, 1 or a list), which the resolver refuses with ValueError. When a setting is refused:

  • OrreryModule.initialize checks the origin before it opens its queue, so a refused origin no longer leaves the queue open.

  • An empty setting counts as unset in every module, for both development_collector_url and collector_origin.

What stays the same:

  • No environment variable selects the destination. test_production_collector_cannot_be_replaced_by_environment still passes.
  • The development URL must be on loopback.
  • Requests that carry a token don't follow redirects.
  • A setting can choose only among origins the installed release already trusts, so it can't send a builder's Hugging Face token anywhere new.

Collector submissions ignored the setting before this update

After main (with #27, released as 0.8.0) was merged in, the Hugging Face module still chose its origin with its own expression: _development_collector_url(...) if ... else _collector_origin(PRODUCTION_COLLECTOR_URL).

This description earlier predicted the shared session would then raise "Provider modules must use the same collector origin." It wouldn't have: the Hugging Face module opens its own artifacts:submit session, not the shared one. Instead, the module ignored collector_origin:

  • with the setting on, telemetry and Orrery graphs would have gone to the selected origin, but submissions to PRODUCTION_COLLECTOR_URL;
  • an untrusted value wouldn't have turned submissions off.

Nothing sets collector_origin or turns submissions on today, so no build was affected.

I searched src/ for PRODUCTION_COLLECTOR_URL, _collector_origin( and _development_collector_url(. Outside the resolver, they now appear only where they're defined, in core/constants.py and core/auth.py.

Two other kinds of request use credentials the collector issues, but they aren't sent to the collector, so they don't use the resolver:

The interface package

On main, microcosm-emitter-interface still has no collector_origin field:

  • TelemetrySettings and OrrerySettings each have one collector setting, development_collector_url. OrrerySettings also has publication_origin, the run viewer's address for graph publications, which isn't a collector setting.
  • HuggingFaceSettings carries no collector setting at all, not even collector_submissions.

Nothing that exists today needs the field, so this PR doesn't add it. Microcosm would need it only to select a trusted origin other than the default.

Changing the default (tracker task 5.1 · 2)

Both edits go in one emitter PR:

  1. Add the hostname's origin to TRUSTED_COLLECTOR_ORIGINS.
  2. Point PRODUCTION_COLLECTOR_URL at it.

Keep the old origin in the set while the old address still serves. Then, if the new address has to be rolled back, a build can still select the old one with collector_origin. test_every_trusted_origin_is_a_bare_https_origin checks the new entry.

Which builds pick that release up:

  • Builds that start the separately installed emitter use the newest installed emitter at their next start. They need no Microcosm change.
  • Builds pinned to microcosm-emitter==0.2.1 keep the old address until Microcosm moves off that pin. Microcosm's packages/microcosm-build/pyproject.toml on main still has that pin. The old address has to keep answering until the pin moves. If it doesn't, those builds' telemetry stays on their build machines, where the emitter keeps retrying and keeps events for seven days.

Release

The only changelog fragment is changelog.d/trusted-collector-origins.added.md. Its type is added, so tools/bump-version.py makes the next release a minor version. Merging now releases 0.9.0 (after 0.8.0) of both microcosm-emitter and microcosm-emitter-interface to PyPI. tools/check-changelog.sh prints 0.8.0 -> 0.9.0.

Other open PRs

Commits and tests

Commit What it does Red (before the code) Green (after)
9b31f08 Choose the collector origin in one place, from a fixed set of trusted origins tests/test_collector_origin.py (new, 14 tests): ImportError for microcosm_emitter.core.collector_origin, so 0 of 14 ran 14 passed; tests/telemetry/test_delivery.py: 29 passed
30046a1 Test that the telemetry module treats an empty development URL as unset test_modules_treat_an_empty_development_url_as_unset: 2 failed
13c5235 Treat an empty development URL as unset in the telemetry module too 2 passed
498427f Merge main (46 commits, up to 0.8.0) into this branch, with no conflicts tests/test_collector_origin.py and tests/submissions/test_delivery.py: 62 passed
12f7260 Test that collector submissions use the configured collector origin 5 new tests: 4 failed. The 5th (no setting, so the production collector) passes both before and after the fix.
1a3b7cb Send collector submissions to the configured collector origin 5 passed; all of tests/test_collector_origin.py: 21 passed
a84d457 Test that a collector setting that isn't a string turns submissions off 3 new cases in test_collector_submissions_refuse_an_unusable_origin_at_start: 3 failed, 21 passed. The Hugging Face module's initialize raised AttributeError (a boolean or a number) or TypeError (a list), which would stop the emitter host.
c2ba970 Refuse a collector setting that isn't a string with ValueError tests/test_collector_origin.py: 24 passed
f0d3037 Keep only the collector origin tests that check distinct behaviour: remove 6, merge the 3 "without a setting" tests into one that starts all three modules, and drop a redundant https:// check tests/test_collector_origin.py: 24 passed before, 16 passed after
3261983 Say which modules already treated an empty development URL as unset (a docstring)

The results for the first three commits were recorded when they were written. The first commit then had the hash 5c4c86f; the branch has been rebased since.

After the last commit, I ran these locally:

  • The tests for the changed modules, with -m "not collector" as CI runs them: 448 passed (453 before the test clean-up in f0d3037). The files were tests/test_collector_origin.py, tests/submissions/, tests/huggingface/, tests/telemetry/test_delivery.py, tests/telemetry/test_module.py, tests/orrery/test_client.py, tests/orrery/test_publication.py and tests/interface/test_protocol_fixtures.py.
  • tests/test_boundaries.py and tests/test_collector_contract.py: 11 passed. Their 9 collector-marked tests need PostgreSQL, and CI's collector job runs them.
  • tools/check-quality.sh (Ruff check and format) and tools/check-changelog.sh: both pass.

CI on 3261983 passed all 10 checks:

  • the tests on Ubuntu and macOS, with Python 3.13 and 3.14;
  • the collector tests against PostgreSQL;
  • quality, package builds, the changelog fragment check and the workflow lint;
  • "Required checks".

This PR changes no workflow, deployment code or setting, and adds no environment variable.

🤖 Generated with Claude Code

anth-volk and others added 3 commits October 22, 2026 22:07
… origins

Every service module now resolves its collector origin through
core/collector_origin.py. A new `collector_origin` module setting may name
any origin in TRUSTED_COLLECTOR_ORIGINS and is refused otherwise; the set
holds only the current production collector, so nothing changes until a
release adds the collector's next origin. No environment variable chooses
the destination, and the development URL still must be on loopback.

The Orrery module now refuses a bad origin before it opens its queue.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`configured_collector_origin` treats an empty `development_collector_url` as
unset, and the shared collector session and the Orrery module follow it. The
telemetry module passes the raw setting to `CollectorDelivery`, which treats
an empty string as a development URL: on its own it is refused as not
loopback, and with `collector_origin` it is refused as setting both.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The telemetry module now passes `development_collector_url` to
`CollectorDelivery` with the same empty-means-unset rule as
`configured_collector_origin`, so its delivery URL and the shared session's
origin come from one rule, as the Orrery module's already do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk force-pushed the claude/configurable-collector-origin branch from 40e0073 to 13c5235 Compare October 22, 2026 18:07
anth-volk and others added 7 commits October 28, 2026 01:00
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Hugging Face module's collector submissions should resolve their origin
like the telemetry and Orrery modules: a trusted `collector_origin` is used
for the `artifacts:submit` session and the submission client, the default is
the production collector, and an untrusted origin, or one set together with a
development URL, turns submissions off at start.

Fails before the fix: 4 failed, 1 passed (the default case).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The Hugging Face module chose its collector origin with its own expression,
so it ignored `collector_origin` and could disagree with the telemetry and
Orrery modules. It now calls `configured_collector_origin`, like they do: a
trusted origin is used for the `artifacts:submit` session and the
submission client, and an untrusted one turns submissions off at start with
the existing warning. With no setting, submissions still go to the
production collector, so behaviour is unchanged.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A `collector_origin` or `development_collector_url` that isn't a string, such
as `true`, `1` or a list, should be refused with `ValueError` like any other
unusable collector setting, so the Hugging Face module turns collector
submissions off at start instead of stopping the emitter host.

Fails before the fix: 3 failed, 21 passed. Each new case raises from the
module's `initialize` (`AttributeError` for a boolean or a number,
`TypeError` for a list), because `urlsplit` receives a value that isn't text.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
`resolve_collector_origin` passed any given setting to `urlsplit`, so a
`collector_origin` or `development_collector_url` such as `true`, `1` or a
list raised `AttributeError` or `TypeError`. The Hugging Face module catches
only `ValueError`, so such a value stopped the whole emitter host instead of
turning collector submissions off. The resolver now raises `ValueError` for
any given setting that isn't a string, so every module applies its existing
rule for a refused setting: telemetry and Orrery fail to start, and the
Hugging Face module turns submissions off with its one warning.

After the fix: `tests/test_collector_origin.py` 24 passed.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Remove six tests that repeat others or check nothing new: a constant
compared with itself, two module tests that the every-module test covers,
an environment test that only set invented variable names (the existing
`test_production_collector_cannot_be_replaced_by_environment` covers that),
a loopback test that repeats `test_development_collector_must_be_on_loopback`,
and a delivery test that only read attributes. Merge the three "without a
setting" tests into one that starts the telemetry, Orrery and Hugging Face
modules together and checks that each uses the production collector. Drop
the `https://` prefix check, since resolving the origin already requires
HTTPS.

`tests/test_collector_origin.py`: 24 passed before, 16 passed after.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The telemetry module's `CollectorDelivery` refused an empty
`development_collector_url` until this branch, so "as modules have always
treated" was inaccurate. Only the Orrery and Hugging Face modules did.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@anth-volk
anth-volk merged commit d3e466a into main Oct 28, 2026
10 checks passed
@anth-volk
anth-volk deleted the claude/configurable-collector-origin branch October 28, 2026 10:25
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Choose the collector origin in one place, from a fixed set of trusted origins

1 participant