Skip to content

fix: validation - RegistryValidator ignores the loaded registry schema #3522

Closed

Description

@eleshar

RegistryValidator.validateObject (scripts/validation/lib/registry-validator.js L58–L93) loads registry-schema.json into this.schema but never evaluates it. It passes any object with a truthy agents, skills or entries field, so {"skills":[{}]} is reported valid although it matches none of the schema's oneOf branches.

Raised by CodeRabbit on #3500 (thread on contracts/registry-schema.json L5–L8). #3500 only moves specs, so the code fix is tracked here.

Fix

  • Validate with ajv (already a dependency, ^8.17.1) against this.schema, keeping the oneOf branches. The schema uses $defs, so compile with the 2019-09 draft or rename to definitions.
  • Keep the entries format only if a producer still emits it; otherwise drop it.
  • Add tests: {"skills":[{}]} fails; a valid consolidated registry passes.
  • audit:registry is still a stub and does not call the validator; out of scope.

Verified 2026-09-23: with the #3500 schema compiled in ajv, {"skills":[{}]} fails and a well-formed consolidated registry passes.

Activity

  1. linear-code commented on Sep 23, 2026

    @linear-code
  2. eleshar commented on Sep 26, 2026

    @eleshar
    ContributorAuthor

    Fixed on #3550 (e4d38861e2), with review feedback resolved.

    Changes

    • RegistryValidator compiles the loaded schema with ajv (strict: false, allErrors: true) plus ajv-formats, and evaluates it in validateObject. The schema uses $defs, so no rename to definitions was needed. When no schema compiles, it falls back to the previous structural check.
    • entries and generatedAt checks are kept and run on both the schema and fallback paths, since the schema does not declare them.
    • Tests: {skills:[{}]} fails; a well-formed consolidated registry passes; a non-object is rejected; a missing schema file falls back.

    Acceptance criteria verified

    {skills:[{}]}          valid = false   <- correctly rejected
    well-formed registry    valid = true
    

    Both check out against the current merged schema (the oneOf/$defs form from #3500), not the older shape the branch was written against.

    Mutation-checked: disabling the schema evaluation makes 2 of the 8 tests fail, so the coverage genuinely detects the original bug rather than passing incidentally.

    Follow-on work

    • fix: registry schema has no agents-only branch, so an agents registry cannot validate #3585 — the schema has no agents-only oneOf branch, so a standalone agents registry (required by spec 014 FR-004) cannot validate. validateAllRegistries() has no caller and audit:registry is still a stub, so this is not yet breaking anything, but it will be as soon as either is wired up.
    • Qodo's claim that split registries are rejected was checked against each shape: consolidatedSkillsRegistry and categorySkillsRegistry are separate oneOf branches, so skills-only registries validate correctly. Tests now pin that, so a future change cannot silently break it.

    Also in #3550

    Merged develop into the branch — the base-branch update check had been failing — and resolved the CHANGELOG.md union-merge conflict by keeping both sides.

  3. eleshar commented on Sep 26, 2026

    @eleshar
    ContributorAuthor

    Update: a third defect was found during Linear code review and fixed at 95d380740f.

    Broken schemas silently disabled validation

    compileSchema() caught every ajv compilation error and returned null, which validateObject() could not distinguish from a missing schema file. The result was a silent fall-back to the permissive structural check — the exact behaviour #3522 exists to remove.

    Reproduced with a schema containing an unresolvable $ref:

    BROKEN schema  -> {skills:[{}]} valid = true   <-- malformed registry accepted
    

    Reachable in practice through a typo in a $ref, or a JSON Schema draft the bundled ajv cannot handle. Qodo raised it and rated the relevance Weak; that rating is wrong, because this path defeats the PR's entire purpose while still reporting success.

    Fix

    compileSchema() records this.schemaError; validateObject() returns invalid for every registry until the schema is corrected. An absent schema keeps its documented structural fallback, so the two failure modes are no longer indistinguishable to callers:

    BROKEN schema  -> {skills:[{}]} valid = false   (fails closed)
    ABSENT schema  -> {skills:[{}]} valid = true    (documented fallback preserved)
    

    Regression test added using a schema ajv cannot compile; mutation-checked — removing the fail-closed branch fails it.

    Current state

    • 9/9 in registry-validator.test.js; full suite 290/290 suites, 5698 passed
    • ESLint clean; 18/18 checks pass; 0 behind develop
    • Review posted via Linear Diffs. GitHub rejected the formal approval because the authenticated account is the PR author, so a human approval is still required.

    Leaving this issue open until #3550 merges.

  4. eleshar commented on Sep 26, 2026

    @eleshar
    ContributorAuthor

    Merged to develop as fc3cb0a8f6 (squash, #3550) and verified on a clean checkout of the merge commit.

    Behavioural verification on merged develop

    Every case run against RegistryValidator as it now exists on develop:

    Case Result Expected
    {skills:[{}]} — the original #3522 bug invalid invalid
    well-formed combined registry valid valid
    consolidated skills-only registry valid valid
    per-category skills-only registry valid valid
    agents-only, no skills key invalid invalid
    skill id Bad_ID (pattern violation) invalid invalid
    skill type: 'javascript' (not in enum) invalid invalid

    7/7 as expected. The malformed registry that previously validated as true is now correctly rejected.

    Fail-closed path, added during review:

    Schema state {skills:[{}]} Expected
    Present but uncompilable invalid invalid
    Absent valid valid (documented fallback preserved)

    Regression status

    Landed

    3 files, +241/−2: CHANGELOG.md (entry present exactly once), registry-validator.js, registry-validator.test.js.

    The 2 duplicate CHANGELOG entries in the file are the pre-existing ones under [1.0.0], tracked by #3451 — not introduced here.

    Follow-ups raised during review

    Note: Qodo's subscription lapsed during this work, so parts of the automated review coverage were unavailable.

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

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Fields

    Priority

    None yet

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions