Skip to main content
Status: implemented (designed 2026-07-18) · Skill: dataset

Context

The dataset skill keeps a single, git-tracked, public registry (datasets.yml) as the source of truth for every dataset a project depends on. The schema is defined in the skill (../../../skills/dataset/SKILL.md, “Manifest”) and grounded in ../../../resources/references/dataset-management-standards.md (§2 schema, §3 tiers) and the shared base-asset record in ../../../resources/substrate/asset-registry.md. Today the manifest has no tooling. Interim (until the module is implemented): the skill orchestrates the step manually — loading with yaml.safe_load, eyeballing the required fields against the table, and hand-mapping Croissant on register/export. That is error-prone and does not scale to audit across a whole manifest, nor does it give the venue-mandated Croissant round-trip (NeurIPS D&B now requires a Croissant file) any machine backing. Once honest-scholar is installed (via ensure-tooling) the skill calls honest-scholar dataset … instead. This proposal covers only the manifest tooling: parsing, schema validation, and Croissant/DataCite interop. Retrieval, the rclone mirror, fixity re-hashing, and the resolution chain are a separate proposal — this tooling reads and writes metadata and never touches bytes or the network.

Goal

A light-dependency Python module the dataset skill’s register and audit verbs call, that:
  1. Loads datasets.yml into typed records and reports precise, located errors.
  2. Validates the base asset record plus tier-conditional required fields, with clear messages (which entry, which field, why).
  3. Ingests a published Croissant JSON-LD to bootstrap a draft entry.
  4. Emits a Croissant JSON-LD file per entry (or for the whole registry).
  5. Round-trips DataCite citation fields for citability / DOI minting.
Non-goals: fetching bytes, computing SHA-256 (retrieval tooling owns hashing; this layer validates the shape sha256:<64-hex>), rclone, tier decisions (human-confirmed), or choosing a dataset for a claim (agency principle).

Design sketch

Single module, stdlib + pyyaml only. No new heavy deps (no jsonschema, no pydantic, no datasets); validation is hand-rolled against the documented field table, Croissant I/O uses stdlib json.

Data model

@dataclass records mirroring the schema — Manifest(mirror, datasets[]), DatasetEntry(id, version, tier, license, redistributable, access, files[], …), FileRef(path, sha256, size), Retrieval(kind, url), Citation(...). Dataclasses keep the surface stdlib-only and JSON/YAML round-trippable.

Validation rules

  • Core (all tiers): id (unique slug), version, tier ∈ {A,B,C}, license (SPDX id or explicit terms + URL), redistributable: bool, access ∈ {open,gated}, files[] each with path + sha256 matching ^sha256:?[0-9a-f]{64}$, datasheet (path/URL required; a "N/A + <reason>" placeholder is accepted structurally by validate but flagged by audit as a datasheet-completeness deficiency, never a clean state).
  • Tier-conditional: source/retrieval required for Tier B (and retrieval.kind ∈ {http,doi,openml,git-lfs,manual}); acquisition instructions required for Tier C; sensitivity required if PII/confidential.
  • Cross-entry: duplicate id detection; a tier: A entry with redistributable: false or access: gated is a hard error (tier follows the license/access, not convenience — matches the skill’s tier-consistency guardrail).
  • Errors accumulate (report all, not first-fail) and are formatted as entry '<id>': <field>: <reason>; validation returns a structured result the audit verb renders as a gap report.

Croissant mapping (registry ⇄ JSON-LD, schema.org/Dataset)

Registry is the superset source of truth; Croissant is an export/interop format. Ingest fills what it can and leaves tier / retrieval / datasheet / sensitivity as TODO for the human on register. Emit produces a valid @context/@type: Dataset document; fields with no registry value are omitted rather than emitted empty.

DataCite

citation is the DataCite 6-mandatory tuple (Identifier/DOI, Creator, Title, Publisher, PublicationYear, ResourceType=Dataset). Validation warns (not errors) when incomplete, since a citable record may not yet exist; emit maps it to citeAs; the tuple is the minimum to mint a DOI at a public archive.

API / CLI the skill verbs call

Importable from honest_scholar.dataset.manifest:
Exposed under the honest-scholar dataset command group:
  • register calls ingest (if a Croissant is supplied) then validate on the new entry before writing; the human still confirms tier + license.
  • audit calls validate across every entry and folds the report into its fixity/presence/completeness output.

Dependencies & posture

  • Deps: pyyaml + stdlib json only. No jsonschema, no pydantic, no network, no crypto. Fits the plugin’s light posture (asset-registry.md, “Plugin vs. consumer”: the plugin owns the schema + loader/validator).
  • Read/write scope: metadata only. Never opens data files, never hashes, never reaches the network — that boundary is what keeps this proposal separable from retrieval/mirror tooling.
  • Placement: ships in the plugin (generic engine); consumer projects own the datasets.yml entries and blobs.

Open questions

  • Package vs. single file: start as one manifest.py, or split croissant.py / schema.py now?
  • Should base-record validation be shared with literature (common substrate spine) or kept dataset-local until a second consumer exists?
  • Croissant recordSet / RAI metadata: emit a minimal stub, or leave to the human?
  • SPDX id validation — ship a small allowlist, or accept any non-empty string plus the explicit-terms+URL alternative?
  • DataCite: v4.6 (anchored in the standards digest) vs. v4.7 — does emit need to declare a schema version?

Acceptance criteria

  • load parses a valid datasets.yml into typed records and raises with the offending line/entry on malformed YAML.
  • validate flags every missing/invalid core field and every tier-conditional omission (B: source/retrieval; C: instructions; PII: sensitivity), accumulating all violations with entry '<id>': <field>: <reason> messages; exits non-zero from the CLI on any error.
  • Duplicate id, and a tier: A entry with redistributable: false or access: gated, are hard errors.
  • emit produces schema.org/Dataset Croissant JSON-LD whose distribution[] carries contentUrl + sha256, validated by a round-trip entry → croissant → entry that preserves core + citation fields.
  • ingest bootstraps a draft entry from a published Croissant and marks tier/retrieval/datasheet/sensitivity as human-TODO.
  • No dependency beyond pyyaml + stdlib; no network or filesystem access to data files during validate/emit/ingest.
  • register and audit invoke honest-scholar dataset …; the skill’s § Tooling note already documents this CLI (the interim manual workaround stays until the module lands).
  • Skill: ../../../skills/dataset/SKILL.md (Manifest, Tiers; § Tooling)
  • Standards digest: ../../../resources/references/dataset-management-standards.md (§2, §3)
  • Substrate base record: ../../../resources/substrate/asset-registry.md
  • Design: ../03-dataset.md, ../04-substrate-and-contract.md
  • ADRs: ../../../decisions/0009-dataset-thin-manifest.md, 0012-shared-substrate.md
  • Separate proposal (not this one): retrieval / rclone mirror / fixity tooling