Skip to content

context.predicates reports unmapped properties under a rule about prefixes #120

Open

Description

@LukasOro

context.predicates reports two distinct findings but cites one rule, and the second finding has no rule behind it.

What it reports

Finding Message Matches the cited rule?
suspicious a term expands to a syntactically absolute IRI whose prefix is undefined yes
dropped property with no @context term: <name> no

The check cites OOLD-EXT-2b61, whose specification text is:

Compact form specifically - a "pattern" such as "^[A-Za-z_][\w.-]:(?!//)\S$", which accepts ex:alice and schema:Person while rejecting http://...; the prefix MUST be defined in the @context.

That is a rule about compact-IRI prefixes. It does not require every property to carry a term.

Why it matters

Severity is read from the cited rule's level. OOLD-EXT-2b61 is a MUST, so the dropped-property finding is reported as a failure on the authority of a rule that does not state it.

Searching the 66-rule catalogue in src/oold/validation/meta/1.0.0-rc.2/oold-rules.json finds no rule requiring complete context coverage. Raised in review as "complete context coverage is not mandatory".

For contrast

roundtrip.generated reports the same situation as "property lost through RDF" and cites no rule (rule=None). That is defensible on its own terms: round-trip fidelity is this package's own promise rather than a specification requirement, and it is reported without borrowing normative authority.

Work

  • Split the two findings so the prefix half keeps citing OOLD-EXT-2b61 and the coverage half does not
  • Decide what the coverage half is: a package-level lint like roundtrip.generated, a warning, or something that needs a rule upstream in oold-schema
  • If it stays, it must not claim MUST severity from an unrelated rule

Raised from review of #114 (#114 (comment)).

Activity

  1. simontaurus commented on Aug 19, 2026

    @simontaurus
    Contributor

    Stronger than "no rule requires coverage": the spec explicitly permits the opposite.

    spec/sections/02-introduction.md:48:

    a term exists as a plain structural property with no semantics attached; an author MAY leave it unmapped, map it to a single IRI, or map it to several concurrent IRIs. An unmapped term produces no triples under JSON-LD expansion; to prevent such terms from being dropped silently, an author MAY declare @vocab [...]

    Deferring semantics is stated there as the differentiator from RDF/SHACL: "they cannot withhold semantic commitment, OO-LD can." So failing on an unmapped property does not merely cite the wrong rule, it inverts a design goal.

    Two fixes, in two repos:

    • Here: the dropped-property half must not fail and must not cite OOLD-EXT-2b61. A warning citing no rule, like roundtrip.generated, matches what the specification actually says.
    • In oold-schema: that permission sits in {#introduction .informative}, so it carries no normative force and nothing can cite it. It should be stated in normative prose. It will not get a rule id either way - MAY is not in the extractor's level vocabulary, since the catalogue tracks obligations - but normative is where a load-bearing permission belongs.

    Worth checking @vocab interacts as that paragraph says before settling the warning text: with @vocab declared, an otherwise-unmapped term does get an IRI, so the finding should not fire at all there.

  2. simontaurus commented on Aug 19, 2026

    @simontaurus
    Contributor

    Resolution agreed: warn, do not fail, and keep the strict behaviour behind an opt-in flag.

    The strict full-round-trip expectation was designed around the specification's own reference examples, where every term is mapped by construction; generalised to arbitrary schemas it rejects documents the specification permits.

    Upstream half is open as OO-LD/oold-schema#137, which states the permission normatively and adds OOLD-SCH-2d05 (MUST NOT, applies=implementation): an implementation must not treat an unmapped term as a conformance failure, but may report one, or reject it under a strict mode the user opts into. That gives this check something to cite instead of OOLD-EXT-2b61.

    Work here:

    • split the two findings; the prefix half keeps OOLD-EXT-2b61
    • the coverage half warns, and cites OOLD-SCH-2d05 once rc.3 is vendored
    • add --strict to promote it back to a failure
    • the message should name the two fixes: declare @vocab, or add the mapping to x-oold-context
    • it must not fire at all when @vocab is declared, since the term is then mapped
  3. LukasOro commented on Aug 20, 2026

    @LukasOro
    ContributorAuthor

    Resolution accepted, on all five points. One of them turns out to be already satisfied, so it can come off the list.

    @vocab already suppresses the finding. Checked rather than assumed, with a two-case probe against --meta latest, one property and no term for it:

    @context context.predicates detail
    {"@version": 1.1, "@vocab": "https://example.org/vocab/"} ok mapped: 1, aliased: 0
    {"@version": 1.1} fail dropped: ['unmapped_term']

    So with @vocab declared the term is counted as mapped and nothing fires, which is what that paragraph of the specification says should happen. The @vocab work item is a no-op, and the second row is exactly the behaviour this issue exists to change: a FAIL, citing OOLD-EXT-2b61, on a document the specification explicitly permits.

    Remaining work, updated:

    • split the two findings; the prefix half keeps OOLD-EXT-2b61
    • the coverage half warns and cites OOLD-SCH-2d05 once rc.3 is vendored
    • --strict promotes it back to a failure
    • the message names both fixes: declare @vocab, or add the mapping to x-oold-context
    • must not fire when @vocab is declared already holds; worth a regression test so it keeps holding through the split

    One interaction to keep in view: #127. predicates.py:112 returns DROPPED when jsonld.expand raises, the same status as a genuinely unmapped term, differing only in a detail string. Once the unmapped case becomes a warning, a pyld failure would silently be downgraded with it unless the two are separated first. PredicateResult.errors exists for that and is never written.

    Upstream OO-LD/oold-schema#137 is green and mergeable.

  4. simontaurus commented on Aug 23, 2026

    @simontaurus
    Contributor

    Better anchor for the coverage half than "cites no rule": OO-LD/oold-schema#142 adds OOLD-SCH-21d7 (SHOULD, applies=document) - a schema should offer at least one complete mapping, a target profile under which every declared property carries a term, since a property with no term produces no triple and a lossless RDF round-trip is only possible under a mapping that covers all of them.

    That pairs with OOLD-SCH-2d05 rather than qualifying it: an unmapped term is permitted, but leaving one is a deferral rather than a destination.

    Consequence here: context.coverage cites OOLD-SCH-21d7 once rc.3 is vendored, and its severity then comes from the catalogue like every other finding - SHOULD makes it a warning, with --strict as the opt-in override rather than a level chosen in code.

  5. LukasGold commented on Aug 28, 2026

    @LukasGold
    Contributor

    OOLD-SCH-21d7 accepted, and it is a better anchor than "cites no rule". The pairing is what the split was missing: OOLD-SCH-2d05 says the validator must not fail on an unmapped term, OOLD-SCH-21d7 says a schema should still aim to map them all. One constrains the implementation, the other the document, and only the second is a finding to report.

    Verified against main: context.coverage cites OOLD-SCH-21d7 and context.predicates keeps OOLD-EXT-2b61, --strict promotes, the message names both fixes, and the processor-failure case is separated with its own test.

    One demand from your second comment had no test and now does. @vocab must suppress the finding entirely. I confirmed that with an ad-hoc probe before the split and then never protected it: no fixture in the corpus declared a @vocab at all, and the only @vocab in the suite asserted that the message text mentions it, which is a different claim. Added broken/vocab_covers_the_remainder.schema.json, which is missing_context_term.schema.json with @vocab added and nothing else changed, so a regression there is the split losing the permitted case rather than a difference between two schemas. Up as #137: 560 passed, and the sibling test still asserts WARN on the same schema without the @vocab, so the pair is the mutation test.

    Also corrected two comments that still said the coverage half cites no rule, one of them three lines above rule="OOLD-SCH-21d7" in check_registry.py.

    One thing I did not change, because it reaches past this check

    You wrote that the severity then "comes from the catalogue like every other finding ... rather than a level chosen in code". That is not what happens yet. pipeline.py:503 reads:

    FAIL if run.options.strict else WARN

    severity() has exactly one call site, check_registry.py:1325, and it serves only the rule.* checks. All 59 run.add sites in the pipeline pass a literal status, so every pipeline check hardcodes its level, context.predicates included. default_status=WARN on the CheckInfo is the right fallback, but it is only read on the path this check does not take.

    The values agree today - SHOULD gives WARN either way - so nothing is currently misreported. What is missing is the property the design rests on: relax or tighten OOLD-SCH-21d7 upstream and the validator would not follow.

    Two ways to read that:

    1. A context.predicates reports unmapped properties under a rule about prefixes #120 defect. The coverage emission should read the level from the catalogue, with --strict overriding afterwards, and this issue is not done until it does.
    2. The wider pipeline pattern, older than this issue and inherited by it, worth its own issue covering every rule-citing pipeline check rather than fixing one site.

    I lean to the second. Fixing only context.coverage leaves the pipeline inconsistent in a way that is harder to notice than the current uniform behaviour, and context.predicates has exactly the same gap against OOLD-EXT-2b61. Either way #120 should not close until we have picked one, so I have left it open, and #137 says Refs rather than Closes.

    Which would you prefer?

    Unrelated, and a correction to what I first wrote here: I reported parity as red on main. It is not. Three parity tests did fail locally, but the cause was a 15-day-old ~/.cache/oold/meta/remote-main still declaring x-oold-reverse-default-properties, removed upstream in OO-LD/oold-schema#152. A forced refetch turned all three green with no change to the repository. Parity on #137 is 6 passed. That the stale cache fails silently is real and is now #138.

  6. LukasGold commented on Aug 31, 2026

    @LukasGold
    Contributor

    @simontaurus this is the one open decision left on this issue, and it is the only thing keeping it open.

    To restate it without the surrounding detail: you wrote that context.coverage's severity would then come "from the catalogue like every other finding ... rather than a level chosen in code". It does not yet. pipeline.py reads FAIL if run.options.strict else WARN, and severity() has a single call site, check_registry.py:1325, serving only the rule.* checks. Every one of the pipeline's run.add sites passes a literal status, context.predicates included.

    The values agree today, since SHOULD gives WARN either way, so nothing is misreported. What is missing is the property the design rests on: change OOLD-SCH-21d7's level upstream and the validator would not follow.

    Two readings, and I have not acted on either:

    1. A context.predicates reports unmapped properties under a rule about prefixes #120 defect, fixed by having the coverage emission read the level from the catalogue with --strict overriding afterwards.
    2. The wider pipeline pattern, inherited rather than introduced here, worth its own issue covering every rule-citing pipeline check.

    I lean to the second, because context.predicates has the identical gap against OOLD-EXT-2b61, and fixing one site leaves the invariant half-true in a way that is harder to notice than the current uniform behaviour. No issue exists for it yet; I would rather you pick the framing than have me open one that presumes the answer.

    Not urgent for the pull requests in flight. #137 does not depend on it, and #147 is about exception breadth, not severity. It only blocks closing this issue.

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

Metadata

Metadata

Labels

bugSomething isn't working

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions