You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
context.predicates reports unmapped properties under a rule about prefixes #120
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
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.
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
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.
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.
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:
FAILifrun.options.strictelseWARN
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.
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.
@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.
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.
Activity
simontaurus commentedon Aug 19, 2026
Stronger than "no rule requires coverage": the spec explicitly permits the opposite.
spec/sections/02-introduction.md:48: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:
OOLD-EXT-2b61. A warning citing no rule, likeroundtrip.generated, matches what the specification actually says.{#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 -MAYis not in the extractor's level vocabulary, since the catalogue tracks obligations - but normative is where a load-bearing permission belongs.Worth checking
@vocabinteracts as that paragraph says before settling the warning text: with@vocabdeclared, an otherwise-unmapped term does get an IRI, so the finding should not fire at all there.simontaurus commentedon Aug 19, 2026
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 ofOOLD-EXT-2b61.Work here:
OOLD-EXT-2b61OOLD-SCH-2d05once rc.3 is vendored--strictto promote it back to a failure@vocab, or add the mapping tox-oold-context@vocabis declared, since the term is then mappedLukasOro commentedon Aug 20, 2026
Resolution accepted, on all five points. One of them turns out to be already satisfied, so it can come off the list.
@vocabalready suppresses the finding. Checked rather than assumed, with a two-case probe against--meta latest, one property and no term for it:@contextcontext.predicates{"@version": 1.1, "@vocab": "https://example.org/vocab/"}mapped: 1, aliased: 0{"@version": 1.1}dropped: ['unmapped_term']So with
@vocabdeclared the term is counted as mapped and nothing fires, which is what that paragraph of the specification says should happen. The@vocabwork item is a no-op, and the second row is exactly the behaviour this issue exists to change: a FAIL, citingOOLD-EXT-2b61, on a document the specification explicitly permits.Remaining work, updated:
OOLD-EXT-2b61OOLD-SCH-2d05once rc.3 is vendored--strictpromotes it back to a failure@vocab, or add the mapping tox-oold-contextmust not fire whenalready holds; worth a regression test so it keeps holding through the split@vocabis declaredOne interaction to keep in view: #127.
predicates.py:112returnsDROPPEDwhenjsonld.expandraises, 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.errorsexists for that and is never written.Upstream OO-LD/oold-schema#137 is green and mergeable.
simontaurus commentedon Aug 23, 2026
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-2d05rather than qualifying it: an unmapped term is permitted, but leaving one is a deferral rather than a destination.Consequence here:
context.coveragecitesOOLD-SCH-21d7once rc.3 is vendored, and its severity then comes from the catalogue like every other finding - SHOULD makes it a warning, with--strictas the opt-in override rather than a level chosen in code.LukasGold commentedon Aug 28, 2026
OOLD-SCH-21d7accepted, and it is a better anchor than "cites no rule". The pairing is what the split was missing:OOLD-SCH-2d05says the validator must not fail on an unmapped term,OOLD-SCH-21d7says 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.coveragecitesOOLD-SCH-21d7andcontext.predicateskeepsOOLD-EXT-2b61,--strictpromotes, 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.
@vocabmust 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@vocabat all, and the only@vocabin the suite asserted that the message text mentions it, which is a different claim. Addedbroken/vocab_covers_the_remainder.schema.json, which ismissing_context_term.schema.jsonwith@vocabadded 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 assertsWARNon 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"incheck_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:503reads:severity()has exactly one call site,check_registry.py:1325, and it serves only therule.*checks. All 59run.addsites in the pipeline pass a literal status, so every pipeline check hardcodes its level,context.predicatesincluded.default_status=WARNon theCheckInfois the right fallback, but it is only read on the path this check does not take.The values agree today -
SHOULDgivesWARNeither way - so nothing is currently misreported. What is missing is the property the design rests on: relax or tightenOOLD-SCH-21d7upstream and the validator would not follow.Two ways to read that:
--strictoverriding afterwards, and this issue is not done until it does.I lean to the second. Fixing only
context.coverageleaves the pipeline inconsistent in a way that is harder to notice than the current uniform behaviour, andcontext.predicateshas exactly the same gap againstOOLD-EXT-2b61. Either way #120 should not close until we have picked one, so I have left it open, and #137 saysRefsrather thanCloses.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-mainstill declaringx-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 is6 passed. That the stale cache fails silently is real and is now #138.LukasGold commentedon Aug 31, 2026
@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.pyreadsFAIL if run.options.strict else WARN, andseverity()has a single call site,check_registry.py:1325, serving only therule.*checks. Every one of the pipeline'srun.addsites passes a literal status,context.predicatesincluded.The values agree today, since
SHOULDgivesWARNeither way, so nothing is misreported. What is missing is the property the design rests on: changeOOLD-SCH-21d7's level upstream and the validator would not follow.Two readings, and I have not acted on either:
--strictoverriding afterwards.I lean to the second, because
context.predicateshas the identical gap againstOOLD-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.