Design Note: Artifact Claims And Facets¶
This note records the first claim and facet schema slice from ADR 0002. It is limited to JSON-compatible persistence, validation, and documentation. It does not implement reader dispatch, writer registration, converters, or optional scientific readers.
Chosen Shape¶
Artifact claims use one open envelope with a kind field:
{
"kind": "interface",
"name": "bytes",
"namespace": "ogcat.core",
"version": "1",
"evidence": "validated",
"confidence": "validated",
"metadata": {}
}
Core recognizes three standard claim kinds from ADR 0002:
data_typefor source or external forms such as NetCDF, CSV, service, or collection pattern.representationfor storage or encoding shapes such as file, directory, archive, text, or Zarr store.interfacefor access contracts such as bytes, text, directory listing, archive members, table, xarray dataset, manifest, or collection.
The kind vocabulary is intentionally open. Plugins may persist their own claim
kinds, names, and metadata without changing core schema. The Python API exposes
ArtifactClaim plus small convenience constructors: DataTypeClaim,
RepresentationClaim/Representation, and InterfaceClaim.
Facets use the same envelope, but they describe structured facts rather than type or interface claims:
{
"kind": "stat",
"name": "size",
"namespace": "ogcat.core",
"version": "1",
"evidence": "probed",
"confidence": "validated",
"metadata": {"bytes": 12345}
}
The Python API exposes ArtifactFacet and the alias Facet.
Required Fields¶
Normalized claims and facets always contain:
kind: non-empty category string.name: non-empty namespace-local name.namespace: non-empty owner namespace, defaulting toogcat.core.version: non-empty schema version string, defaulting to1.evidence: one supported vocabulary term.confidence: one supported vocabulary term, defaulting toevidence.metadata: JSON-compatible dictionary for structured details.
Existing artifact descriptors remain readable. Raw dict claims with missing
namespace, version, evidence, confidence, or metadata are
normalized with defaults. Raw dict facets may also omit name; in that case
name defaults to kind so earlier payloads such as
{"kind": "image", "format": "png"} remain readable. Unknown top-level
fields are folded into metadata during normalization.
Claims still require both kind and name. Facets still require kind.
Required identifier fields must be strings; None, non-string values, and
empty strings are rejected. Invalid evidence, confidence, metadata, list, or
item shapes raise ValueError or TypeError during ArtifactDescriptor
and CatalogRecord construction, including repository load.
Evidence And Confidence¶
The first vocabulary is:
declared: supplied by caller, writer, plugin, or source metadata.inferred: cheap inference such as locator text, suffix, or directory shape.probed: inspected cheaply without proving domain validity.validated: checked by a validator or reader-specific capability.stale: previously true or declared evidence that may no longer describe the artifact.failed: attempted evidence collection or validation failed.
Suffix-only detection should use evidence="inferred" and usually
confidence="inferred". Details such as source and suffix list belong in
metadata:
{
"kind": "representation",
"name": "netcdf",
"namespace": "ogcat.core",
"version": "1",
"evidence": "inferred",
"confidence": "inferred",
"metadata": {"source": "suffix", "suffixes": [".nc"]}
}
This makes the difference between “looks like NetCDF” and “validated as NetCDF” explicit.
Namespaces¶
ogcat.core is reserved for core claim and facet names. Plugin-owned claims
and facets should use a stable namespace such as a package name, plugin id, or
reverse-DNS string, for example pydata.xarray or org.unidata. The
version field is scoped to that namespace and name. Core validates only the
envelope and JSON compatibility, not plugin-specific schemas.
Cheap Core Examples¶
Core can represent common cheap facts without importing optional reader libraries:
interfaces:
bytes,text,directory-listing,archive-members,manifest.representations:
path,file,directory,archive,compressed-file,text,manifest.facets:
stat/size,stat/mtime,checksum/sha256,suffix/suffixes,archive/member-summary,manifest/entries.
This slice provides the schema and constructors. It does not automatically generate these claims and facets during ingest, and it does not use them for reader dispatch yet.
Small query helpers in ogcat.artifact_claims support the next registry
slice without duplicating normalization logic: iter_claims(),
has_claim(), claim_key(), iter_facets(), has_facet(), and
facet_key().
Relationship To Capability Selection¶
The #119 capability registry uses these claims and facets as dispatch facts.
It should inspect the ArtifactDescriptor attached to the artifact being
read, written, or converted; it should not use CatalogRecord.record_type as
an I/O dispatch key.
Selection is explicit. A single artifact may legitimately advertise several
interfaces at the same time, for example bytes, text, table, and JSON. A caller
that asks only to “read” such an artifact has not supplied enough information.
The registry should raise an ambiguity error until the caller requests a
specific interface or output claim such as interface=text or
interface=table.
Facets make otherwise similar claims precise. Text readers can use encoding facets, delimited-table readers can use dialect facets, archive readers can use member facets, and collection readers can use member-pattern facets. These facts are plugin-readable metadata; they do not require core to import optional scientific libraries.
Capability selection treats claim metadata as descriptive. Claims match by their namespace/kind/name/version envelope. Facets match by the same envelope plus required metadata as a subset, so dispatch-significant details such as encoding values, delimiters, archive members, or local path requirements should be modeled as facets.
Worked Examples¶
These examples are design targets for writers, inspectors, and future readers. They are not automatic output in this schema slice.
Zarr Directory Store¶
Issue #121 and PR #122 made add_file() intentionally support managed
directory artifacts such as example.zarr. A Zarr store is a directory-shaped
artifact, but it is one dataset artifact, not a collection. The useful claims
separate storage shape, data type, and optional interfaces:
{
"claims": [
{"kind": "representation", "name": "directory", "namespace": "ogcat.core"},
{"kind": "data_type", "name": "zarr", "namespace": "zarr.dev"},
{"kind": "interface", "name": "directory-listing", "namespace": "ogcat.core"},
{"kind": "interface", "name": "zarr-group", "namespace": "zarr.dev"},
{"kind": "interface", "name": "xarray-dataset", "namespace": "pydata.xarray"}
],
"facets": [
{"kind": "suffix", "name": "suffixes", "metadata": {"suffixes": [".zarr"]}}
]
}
The Zarr and xarray claims do not mean core imports those libraries. They are capability claims that a plugin or optional reader can satisfy later.
NetCDF Member Collection¶
A directory of NetCDF files opened with a glob is different from a Zarr store. It is a directory representation with explicit collection semantics:
{
"claims": [
{"kind": "representation", "name": "directory", "namespace": "ogcat.core"},
{"kind": "interface", "name": "collection", "namespace": "ogcat.core"},
{"kind": "interface", "name": "xarray-dataset", "namespace": "pydata.xarray"}
],
"facets": [
{
"kind": "collection",
"name": "members",
"metadata": {
"pattern": "*.nc",
"member_format": "netcdf",
"member_suffixes": [".nc"],
"reader_hint": "xarray.open_mfdataset"
}
}
]
}
This is the claim/facet shape that issue #109 can use for managed collection
writes. A reader can dispatch on the collection or xarray-dataset
interface and then use the member facet to decide how to open the directory.
CSV-Like Table¶
CSV-like data can stay dependency-light in core by recording text and table interfaces without importing pandas:
{
"claims": [
{"kind": "representation", "name": "text", "namespace": "ogcat.core"},
{"kind": "data_type", "name": "csv", "namespace": "iana.media-types"},
{"kind": "interface", "name": "text", "namespace": "ogcat.core"},
{"kind": "interface", "name": "table", "namespace": "ogcat.core"}
]
}
Single NetCDF File¶
A single NetCDF-like artifact has file representation and can advertise both cheap byte access and optional scientific interfaces:
{
"claims": [
{"kind": "representation", "name": "file", "namespace": "ogcat.core"},
{"kind": "data_type", "name": "netcdf", "namespace": "org.unidata"},
{"kind": "interface", "name": "bytes", "namespace": "ogcat.core"},
{"kind": "interface", "name": "xarray-dataset", "namespace": "pydata.xarray"}
]
}
Grouped NetCDF/HDF5 File¶
Groups are structured facts or interfaces, not a reason for a core NetCDF dependency:
{
"claims": [
{"kind": "data_type", "name": "netcdf", "namespace": "org.unidata"},
{"kind": "interface", "name": "netcdf-groups", "namespace": "org.unidata"}
],
"facets": [
{
"kind": "netcdf",
"name": "groups",
"namespace": "org.unidata",
"metadata": {"groups": ["/", "/observations", "/metadata"]}
}
]
}
Directory Stores Versus Collections¶
PR #122 clarified an important ADR distinction. Directory-backed artifacts are
not automatically collections. A managed .zarr directory added with
add_file() is one dataset artifact whose representation is directory.
A managed directory of monthly .nc files is a collection only when a caller,
writer, or plugin explicitly adds collection claims/facets such as member
pattern, member format, and member suffixes.
This keeps storage shape separate from logical dataset shape:
representation=directorysays how bytes are stored.data_type=zarrordata_type=netcdfsays what external format or data type is claimed.interface=collectionsays member traversal is part of the artifact contract.collection facets say which members participate and how a reader should combine them.
Writer Result Open Loop¶
Writers are the natural source of produced artifact claims/facets because they
know what they materialized. This branch defines the schema, but it does not
define the merge path from writer output into descriptors. Issue #117 should
define that path, preserve existing None-returning writers, and decide how
writer results interact with OperationContext, rollback, storage plans,
audit metadata, and derived classification metadata.
Relationship To Classification Metadata¶
derived_metadata.classification remains the current home for cheap inferred
facts such as artifact_kind, format, archive_format,
inner_format, collection_pattern, member_format,
member_suffixes, and reader_hint. This slice does not replace that
namespace and does not automatically mirror classification into facets.
Future migration can mirror selected classification facts into inferred claims/facets while preserving the existing derived metadata search behavior. For now, the bridge is documentation and compatibility: classification facts remain readable and searchable, and new descriptors may add explicit inferred claims/facets when a writer or plugin has enough context.