Skip to content

capability publish doesn't require/validate the contract's artifact field, so a real, human-mergeable PR can produce an unusable index entry #859

Description

@enricopiovesan

What's wrong

A capability's contract.json needs a top-level artifact: {digest, url} field for the capability to be usable at all -- traverse-framework/registry's own scripts/ci/build_index.py reads contract.get("artifact") or {} and copies digest/url straight into the published index record (verified directly: capabilities/<ns>/<id>/<ver>/contract.json -> index.json's digest/artifact_url fields). If a contract has no artifact field, the index entry silently gets digest: null, artifact_url: null -- a capability that appears in registry sync's output but has no way for any consumer to actually fetch its WASM binary.

traverse-cli capability publish doesn't require this field at all: parse_contract/validate_contract accept a contract with no artifact key (verified -- a real contract missing it passed --dry-run and a real publish with validation_status: "passed", traverse-framework/registry#89's first attempt). Registry's own scripts/ci/capability_validation.py also treats it as fully optional (artifact = contract.get("artifact"); if artifact is not None: ... -- the whole check is skipped when the field is absent). Nothing in the real, automated path stops a syntactically-valid, CI-green, human-mergeable PR from publishing a capability that's structurally unusable.

This isn't hypothetical -- it was hands-on reproduced this session authoring validation.validate-email: the contract validated and dry-ran cleanly with no artifact field, and the gap was only caught by separately reasoning through build_index.py's source (not by anything in the actual publish pipeline surfacing it).

Definition of Done

  • capability publish requires an artifact.digest/artifact.url in the contract before allowing a non-dry-run publish (or, if there's a reason artifact-less contracts are meant to be valid for some other reason, that needs to be an explicit, documented decision -- not silence).
  • If a real reason exists for the field staying schema-optional, capability_validation.py (registry-side) should fail loudly, not skip silently, when a contract lacks it -- matching the precedent already set for corrupt/unreadable contracts in build_index.py (registry decision-log entry 27: "a silently-dropped record is the same failure class... as an unverifiable record reaching a consumer").
  • Whatever the fix, verify it actually blocks an artifact-less contract from reaching a green, mergeable PR -- not just that a unit test asserts the right error type.

Where this was found

traverse-framework/registry#89, first publish attempt of validation.validate-email -- the initial dry-run/publish succeeded with "validation_status": "passed" despite the contract having no artifact field. Fixed for that specific PR by manually adding the field before the real (non-dry-run) publish, but nothing in the tooling would have caught it if the author hadn't independently checked build_index.py's source.

Metadata

Metadata

Assignees

No one assigned

    Labels

    agent:claudeClaimed by Claude CodebugSomething isn't working

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions