What needs to be done
Two related pieces of work on the provider adapter, done together because the second is what makes the first legible.
Enforce the capability schema packs on resourceAttributes. Base validation checks the Beckn v2 envelope and treats resourceAttributes as a free-form object, so nothing checks a capability's own attributes at all — a wrong unit, a missing required attribute or an undefined field all pass, and become a consumer's problem to discover later. The schemav2validator plugin already has an extended layer for exactly this, and it was configured but switched off.
Name the capability plugins after their capabilities. weather and mandi name a domain; the payloads they serve name a capability. Renaming them to WeatherObservation and MandiPrice puts the binding key, the schema pack and the plugin that answers for it into one vocabulary.
Why extended validation could not simply be switched on
Two separate blockers, both needing a change rather than a config flag.
The validator removed the key the packs require. validateReferencedObject stripped both @context and @type from every object before validating it. That suited a schema which closes itself with additionalProperties: false and never mentions either key — which is what the plugin's existing tests use, so the behaviour could not just be reversed. The OAN packs do the opposite: they declare @type and list it in allOf[].required. So a conforming payload was rejected for a missing @type that it had in fact sent and the validator had just taken out. No configuration could bridge that.
The fix is to decide per key, by asking the resolved schema whether it declares that key — as a property or in required, anywhere in its composition tree, since the packs declare @type one level down inside allOf. Both schema styles then validate, with no config switch and without either style having to know about the other. not and if are deliberately not walked: naming a property under not forbids it and under if only selects a branch, so neither means the key is permitted.
Keeping @type also means its const is now checked, so a resource claiming a @type the capability does not declare no longer passes silently.
The @context on the wire did not resolve. The payloads declared https://schemas.openagrinet.global/..., which does not resolve, and a failed schema fetch rejects the payload — so enabling the layer would have refused every request. A local mounted directory of schema files was tried first and then removed: the published packs do serve context.jsonld, so pointing @context at them makes the fetch the validator already knows how to do work, and the mount, the fetch script and the startup dependency all become unnecessary.
The validator swaps context.jsonld for attributes.yaml to reach the schema beside it, so the revision a payload is judged against comes from the payload rather than from adapter config. Nothing in this repo can go stale against what the network publishes. extendedSchema_allowedDomains is narrowed to the host @context actually resolves to and is now load-bearing: an @context on any other host is refused before a fetch is attempted.
What extended validation does and does not enforce
Worth recording, because a green result is otherwise misleading. kin-openapi v0.144.0 has If, Then and Else struct fields, so the keywords look supported, but visitJSON never dispatches them — verified empirically rather than inferred from the struct.
Enforced: types, string formats, enum, const, required, minItems, additionalProperties, not, and allOf/anyOf/oneOf.
Not enforced: if/then/else, which in the OAN packs is every rule predicated on informationMode.
This matters beyond a footnote. The packs require informationMode and allow only OnDemand or Direct. OnDemand forbids exactly the fields a select must carry to be a question at all — location for weather, market and validity for mandi — while Direct requires answer data a consumer does not have. The packs have no query mode, so our select requests are not pack-conformant on paper and pass only because the forbidding lives under then. That is a gap to raise on the schema packs, not something to work around in the payloads.
Renaming
The .so basename is the plugin id the adapter config refers to, so the package directories, the package clauses, install/build-plugins.sh and the providerSteps and steps entries in config/oan-provider-adapter.yaml all move together. Both package docs claimed "one package per schema pack family", which the new names contradict, so they now say what is true: one package per capability, named for the capability, with the binding keys it answers to still configuration.
This is a breaking change for any deployment: a plugin id that is not one of the built-in step names is looked up among the loaded plugins, so an adapter running a config that names WeatherObservation against an image built before the rename exits with unrecognized step: WeatherObservation. Config and image have to be rolled forward and back together.
Verification
The full suite passes — 63 packages — with four new tests covering the per-key strip, including a pack-shaped schema that requires @type and one that rejects a wrong @type.
End to end against a running stack, with the renamed plugins loading from a freshly built image: publish and select pass for both capabilities, the first payload logs fetching from network and later ones LRU cache hit, and the local collection is 51 of 51.
Negative controls, so a pass is not just an absence of checking: a value outside an enum, a bare date in a date-time field, and a field undefined in a closed sub-object are each rejected with SCH_SCHEMA_VALIDATION_FAILED and the JSON path that failed — the last of those resolving through a schema.beckn.io $ref — and an @context on a host outside the allowlist is rejected with SCH_INVALID_JSONLD_CONTEXT before any fetch.
Known and not addressed here
The published weather mapping emits parameters[].aggregation, which the pack forbids: its parameter item is {parameter, value, unit} with additionalProperties: false, and the parameter enum has no minimum or maximum variants, so a daily minimum and maximum temperature cannot be told apart at all. Nothing validates responses today, so it does not bite until a consumer checks. Fixing it needs a change to the packs rather than to the mapping.
Where
Branch feat/8-capability-schema-conformance, four commits:
3853264 fix(schemav2validator): keep the JSON-LD keys a schema declares
a1db4e7 refactor(plugins): name the capability plugins after their capabilities
16a63e5 feat(config): validate resource attributes against their schema packs
0769807 refactor(config): resolve capability schemas from the payload's @context
What needs to be done
Two related pieces of work on the provider adapter, done together because the second is what makes the first legible.
Enforce the capability schema packs on
resourceAttributes. Base validation checks the Beckn v2 envelope and treatsresourceAttributesas a free-form object, so nothing checks a capability's own attributes at all — a wrong unit, a missing required attribute or an undefined field all pass, and become a consumer's problem to discover later. Theschemav2validatorplugin already has an extended layer for exactly this, and it was configured but switched off.Name the capability plugins after their capabilities.
weatherandmandiname a domain; the payloads they serve name a capability. Renaming them toWeatherObservationandMandiPriceputs the binding key, the schema pack and the plugin that answers for it into one vocabulary.Why extended validation could not simply be switched on
Two separate blockers, both needing a change rather than a config flag.
The validator removed the key the packs require.
validateReferencedObjectstripped both@contextand@typefrom every object before validating it. That suited a schema which closes itself withadditionalProperties: falseand never mentions either key — which is what the plugin's existing tests use, so the behaviour could not just be reversed. The OAN packs do the opposite: they declare@typeand list it inallOf[].required. So a conforming payload was rejected for a missing@typethat it had in fact sent and the validator had just taken out. No configuration could bridge that.The fix is to decide per key, by asking the resolved schema whether it declares that key — as a property or in
required, anywhere in its composition tree, since the packs declare@typeone level down insideallOf. Both schema styles then validate, with no config switch and without either style having to know about the other.notandifare deliberately not walked: naming a property undernotforbids it and underifonly selects a branch, so neither means the key is permitted.Keeping
@typealso means itsconstis now checked, so a resource claiming a@typethe capability does not declare no longer passes silently.The
@contexton the wire did not resolve. The payloads declaredhttps://schemas.openagrinet.global/..., which does not resolve, and a failed schema fetch rejects the payload — so enabling the layer would have refused every request. A local mounted directory of schema files was tried first and then removed: the published packs do servecontext.jsonld, so pointing@contextat them makes the fetch the validator already knows how to do work, and the mount, the fetch script and the startup dependency all become unnecessary.The validator swaps
context.jsonldforattributes.yamlto reach the schema beside it, so the revision a payload is judged against comes from the payload rather than from adapter config. Nothing in this repo can go stale against what the network publishes.extendedSchema_allowedDomainsis narrowed to the host@contextactually resolves to and is now load-bearing: an@contexton any other host is refused before a fetch is attempted.What extended validation does and does not enforce
Worth recording, because a green result is otherwise misleading.
kin-openapiv0.144.0 hasIf,ThenandElsestruct fields, so the keywords look supported, butvisitJSONnever dispatches them — verified empirically rather than inferred from the struct.Enforced: types, string formats,
enum,const,required,minItems,additionalProperties,not, andallOf/anyOf/oneOf.Not enforced:
if/then/else, which in the OAN packs is every rule predicated oninformationMode.This matters beyond a footnote. The packs require
informationModeand allow onlyOnDemandorDirect.OnDemandforbids exactly the fields aselectmust carry to be a question at all —locationfor weather,marketandvalidityfor mandi — whileDirectrequires answer data a consumer does not have. The packs have no query mode, so ourselectrequests are not pack-conformant on paper and pass only because the forbidding lives underthen. That is a gap to raise on the schema packs, not something to work around in the payloads.Renaming
The
.sobasename is the plugin id the adapter config refers to, so the package directories, thepackageclauses,install/build-plugins.shand theproviderStepsandstepsentries inconfig/oan-provider-adapter.yamlall move together. Both package docs claimed "one package per schema pack family", which the new names contradict, so they now say what is true: one package per capability, named for the capability, with the binding keys it answers to still configuration.This is a breaking change for any deployment: a plugin id that is not one of the built-in step names is looked up among the loaded plugins, so an adapter running a config that names
WeatherObservationagainst an image built before the rename exits withunrecognized step: WeatherObservation. Config and image have to be rolled forward and back together.Verification
The full suite passes — 63 packages — with four new tests covering the per-key strip, including a pack-shaped schema that requires
@typeand one that rejects a wrong@type.End to end against a running stack, with the renamed plugins loading from a freshly built image: publish and select pass for both capabilities, the first payload logs
fetching from networkand later onesLRU cache hit, and the local collection is 51 of 51.Negative controls, so a pass is not just an absence of checking: a value outside an
enum, a bare date in adate-timefield, and a field undefined in a closed sub-object are each rejected withSCH_SCHEMA_VALIDATION_FAILEDand the JSON path that failed — the last of those resolving through aschema.beckn.io$ref— and an@contexton a host outside the allowlist is rejected withSCH_INVALID_JSONLD_CONTEXTbefore any fetch.Known and not addressed here
The published weather mapping emits
parameters[].aggregation, which the pack forbids: its parameter item is{parameter, value, unit}withadditionalProperties: false, and theparameterenum has no minimum or maximum variants, so a daily minimum and maximum temperature cannot be told apart at all. Nothing validates responses today, so it does not bite until a consumer checks. Fixing it needs a change to the packs rather than to the mapping.Where
Branch
feat/8-capability-schema-conformance, four commits:3853264fix(schemav2validator): keep the JSON-LD keys a schema declaresa1db4e7refactor(plugins): name the capability plugins after their capabilities16a63e5feat(config): validate resource attributes against their schema packs0769807refactor(config): resolve capability schemas from the payload's @context