Skip to content

feat: registry manifest, and refuse unregistered process addresses at submission - #184

Merged
jcschaff merged 2 commits into
mainfrom
feat/registry-manifest-enforcement
Oct 6, 2026
Merged

jcschaff merged 2 commits into
mainfrom
feat/registry-manifest-enforcement

Conversation

@jcschaff

Copy link
Copy Markdown
Contributor

Strategy decision 5, phase A (manifest and enforcement). This closes goal G6's done-test: the service rejects a composite naming an unregistered process address, or a component below tested*, with a clear message.* Catalog ingest, the rest of phase A, follows in a separate PR.

What was true before this

Nothing was checked:

  • /simulation/run wrote the upload to a temp file and handed it to the cluster byte for byte.
  • PBAllowList was built from a hardcoded package list and ignored by run_simulation.
  • pbest's determine_dependencies was never called, and it lets every local: address through in any case.
  • Inside the container, allocate_core() registers every installed process under its full and short name. It honours local:!module.Class, which imports any module, and the rest: and parallel: protocols.

What this adds

compose_api/registry/manifest.yaml is the registry of record. Each entry is pinned (by version or commit) and carries module_roots, a curation level on the five-rung ladder, bundles, and the evidence for its level. The pydantic schema in manifest.py requires a pin and unique ids, and matches modules by longest root.

It is seeded with the two packages behind today's registry_env:

entry pin level evidence
pbsim-common 0.1.2 tested our cluster tests for COPASI and Tellurium
pb-multiscale-actin 1.3.1 tested our cluster test for ReaDDy

Both are grandfathered at tested. The evidence text says plainly that the gate is our cluster tests, not the packages' own suites, until phase B computes levels.

compose_api/registry/validation.py opens every composite document in the upload, including archives nested in a batch bundle. It checks all of them, because the container runs whichever one it finds first, and collects every address in string or {protocol, data} form. It refuses:

address reason under warn
a protocol other than local (rest:, parallel:) runs remote or out-of-process code still refused
local:!… imports an arbitrary module still refused
a short name (local:CopasiUTCStep) ambiguous across packages; the message gives the qualified form logged, runs
a module under no entry not registered logged, runs
an entry below tested, or outside registry_env not runnable here logged, runs

A quoted type default under an address key, as in the SED-ML template, is a type declaration rather than an address, and is not collected.

Router.

  • Validation runs before any service is consulted or anything is recorded.
  • A refusal is a 400 whose detail is {message, violations: [{document, address, reason, always_rejected}]}. It is declared in the OpenAPI spec (+4 lines), and the rejected upload's temp file is deleted.
  • An unsupported file suffix is now a 400; it was an unhandled 500.

Setting address_policy: enforce | warn, default enforce. warn is an escape hatch if enforcement blocks a real user.

Removed: PBAllowList, the hardcoded allow_list, the pb_allow_list parameter, and the commented-out allow-list test.

Compatibility

Every address today's clients send passes: phase_cycle.omex, readdy.omex, and the rendered COPASI and Tellurium curated templates are all tested. pbest submits fully qualified local: names, so it is unaffected. The one known short-name use is in pbest's own tests, which call the engine locally rather than this service.

Tests

  • tests/registry/test_validation.py (22) and tests/registry/test_manifest.py (6) need no services. They cover every refusal row above, nested bundles, a bad document hiding behind a good one, unreadable archives, both policies, and SBML.
  • tests/api/test_submission_rejection.py (4) checks the 400 end to end through the app.
  • Full local suite: 74 passed, 7 skipped. make check clean.

Docs

  • CLAUDE.md describes the check and notes that a new simulator needs both a manifest entry and a library in simulator_registry.json.
  • The strategy's phase A row records what landed.

🤖 Generated with Claude Code

… submission

Strategy decision 5, phase A. compose_api/registry/manifest.yaml is the
registry of record: pinned entries with module roots, a curation level
and the evidence for it. /simulation/run now reads every composite
document in the upload, nested bundle archives included, and refuses any
address that is not local:<module>.<Class> under an entry at level
tested or higher in the registry_env bundle, with a 400 naming each one.

Until now nothing was checked: the upload went to the cluster byte for
byte, the allow-list was built and ignored, and the container honours
local:!module (arbitrary import) and the rest/parallel protocols. Those
unsafe forms are refused even under address_policy=warn.

Also: an unsupported file suffix is a 400, not a 500; the unused
PBAllowList and hardcoded allow_list are removed.

Co-Authored-By: Claude Opus 5.5 (1M context) <noreply@anthropic.com>
A prebuilt simulator's image brings its own processes, which the registry
manifest does not describe, so /simulation/run checks a `simulator=<name>`
submission's name against settings.prebuilt_simulators and skips the address
check (the deployment vouches for the image by listing it). Unlisted names are
refused before the registry is consulted.

The #190 test drops the pip allow list this branch removes.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@jcschaff
jcschaff merged commit cd8f004 into main Oct 6, 2026
6 checks passed
jcschaff added a commit that referenced this pull request Oct 6, 2026
…log ingest

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
jcschaff added a commit that referenced this pull request Oct 6, 2026
….6.0

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.

1 participant