Claude Skill

model-sourcing

Vet the concrete third-party model a study will be built on — this repository, this revision, this checkpoint — not the architecture family. Records a model dossier (source and version pin, licence and the file it was read from, intended use, pretrained-weight provenance, model t

LLM Mart · 0 points · 4 views 0 listing impressions 0 install-command copies
Virus-scanned Reviewed automatically before listing.

Full trust report

Download aperivue-medsci-skills-skills_model-sourcing-815765c.zip · 19 KB
Part of aperivue/medsci-skills — 47 skills

Install

skills CLI npx skills add https://github.com/Aperivue/medsci-skills/tree/main/skills/model-sourcing
Claude Code claude plugin marketplace add https://llmmart.ai/marketplace.json && claude plugin install aperivue-medsci-skills@llmmart
Git git clone https://github.com/Aperivue/medsci-skills.git

The skills CLI installs just this skill, for any of its supported agents. Claude Code installs the whole aperivue/medsci-skills collection as a plugin from our marketplace. Git is the plain clone.

Skill manifest

Model-Sourcing Skill

Purpose

/architecture-zoo answers a literature question — which family of model suits this task. That question has a stable answer. The next question does not: which concrete artifact do I run? A repository, a revision, a checkpoint. That is a provenance question, and the two facts a careful researcher usually checks are the two that cannot answer it.

The licence tells you whether you may use it. The citation count tells you whether others did. Neither tells you whether the number you are about to report means what you will say it means.

The failure this skill exists for is the quietest one in the lane. A method developed and tuned against a benchmark family gets evaluated by the next person on that same family, and the resulting figure reads like validation while sitting much closer to a training-set score. Nothing in the repository says so. The licence is clean, the paper is peer-reviewed and highly cited, the task matches, the code runs on your GPU. The conflict lives in the relationship between two facts that are documented in different places — what the model was developed on, and what you are about to evaluate it on — and it becomes visible only when they are written down side by side.

Writing them down side by side is what the dossier is for.

When to use

  • You have a concrete candidate (a GitHub repo, a Hugging Face checkpoint, a paper's released weights) and are about to build a study on it.
  • You are writing the Methods paragraph that says which model you used, and it has to survive a reviewer asking what it was trained on.
  • You inherited a pipeline whose model came from somewhere nobody recorded.

When NOT to use

  • Choosing an architecture family → /architecture-zoo (archetypes and the task-to-architecture logic; deliberately not a live leaderboard).
  • Building the training repo → /model-scaffold. Designing the validation study → /model-validation. Computing held-out metrics → /model-evaluation.
  • Documenting a model you built → /model-card (Model Card + Datasheet).
  • Auditing your own dataset before modelling → /profile-imaging.
  • Evaluating an LLM/multimodal system on a clinical task → /mllm-eval (which owns pretraining-contamination of public benchmarks for that setting).

Workflow

Step 1 — write the dossier

One JSON file recording what is known, with unknowns left unstated rather than guessed:

{
  "model": "OrganSeg-3D v2.5.1",
  "source": {"kind": "github", "url": "...", "version": "v2.5.1", "commit": "abc1234"},
  "licence": {"spdx": "Apache-2.0", "verified_from": "LICENSE at commit abc1234"},
  "intended_use": "research",
  "weights": {"pretrained": false},
  "task": {"model": "3d_ct_organ_segmentation", "study": "3d_ct_organ_segmentation"},
  "reported_validation": [{"dataset": "ExampleBench", "metric": "Dice", "source": "J Ex 2021"}],
  "developed_on": ["ExampleBench"],
  "evaluation_arms": [{"name": "external", "dataset": "OtherCohort-2026"}],
  "hardware": {"claimed": "any CUDA GPU", "verified_on": "GTX 1080 Ti", "verified": true}
}

Each field is read from the artifact, not from memory: the licence from the LICENSE file at the pinned commit (a README badge is not the licence), developed_on from the paper's own account of where the method was built and tuned, hardware.verified only after it has actually run.

developed_on is the field people skip, and it is the one the gate needs. A method that won a challenge was tuned on that challenge.

Step 2 — gate it

python3 scripts/check_model_provenance.py --dossier model_dossier.json \
    --out qc/model_provenance.json --strict

Stdlib-only, network-free — no repository is fetched and no licence resolved online, so the audit re-runs anywhere the JSON travels. Verdicts:

Verdict Severity Fires when
BENCHMARK_PROVENANCE_CONFLICT Major an evaluation arm uses a dataset the model was developed or tuned on
EVAL_DATA_IN_TRAINING Major an evaluation arm's dataset is inside the pretraining corpus
LICENCE_UNSTATED Major no licence recorded — which is not the same as a permissive one
LICENCE_INCOMPATIBLE Major a non-commercial / research-only licence under commercial or deployment intent
WEIGHTS_PROVENANCE_UNKNOWN Major pretrained weights whose training corpus is not stated
TASK_MISMATCH Minor the model's task is not the study's task
NO_VERSION_PIN Minor no commit, tag or revision
VALIDATION_UNREPORTED Minor no reported validation (dataset + metric + source)
HARDWARE_UNVERIFIED Minor hardware support claimed but never executed
LICENCE_UNVERIFIED Minor a licence is named but the file it was read from is not

The gate flags a relationship, not a reputation. A dossier that declares developed_on: ExampleBench passes cleanly as long as no evaluation arm uses ExampleBench. Being developed on a benchmark is not a defect; evaluating on it and calling that independent is. The clean fixture exists to prove exactly that distinction.

Dataset names are matched as token sequences with a small family-alias table, so MSD Task09 Spleen matches MSD and MS Cohort 2026 does not. Matching never falls back to substring search.

Step 3 — turn a Major into a study decision

A BENCHMARK_PROVENANCE_CONFLICT is rarely a reason to abandon the model — it is usually the best-engineered option precisely because it was tuned hard. It is a reason to change what the arm is claimed to establish:

  1. Report that arm as a demonstration that the pipeline runs end to end, not as evidence the method works.
  2. Put the evidential weight on an arm whose data post-dates the model, and say so with dates.
  3. State the conflict in Methods and Limitations rather than leaving a reviewer to find it.

An EVAL_DATA_IN_TRAINING is different in kind: that arm produces a training-set score and cannot be reported as validation at all.

Carry the dossier forward — /model-validation (arm design), /model-evaluation (what each arm may claim), /model-card (provenance section), /write-paper (Methods + Limitations).

Outputs

  • model_dossier.json — the provenance record downstream skills and the Methods section read.
  • qc/model_provenance.json — deterministic audit with verdicts.
  • The arm-by-arm decision from Step 3, written into the study record.

Anti-Hallucination

  • Never infer a fact the dossier does not state. An unstated licence is LICENCE_UNSTATED, never "probably MIT"; an unstated pretraining corpus is a Major finding, never an assumption.
  • Never record a licence from a badge, a model card summary, or memory — only from the licence file at the pinned revision, and record which file that was.
  • Never mark hardware verified without executing it. A support matrix and what the stack actually runs is a different claim; a CUDA capability the compiler accepts may still be refused by a compiler in the same stack.
  • Never report an arm as independent validation when the gate flags a provenance conflict.
  • If a provenance fact cannot be established from the artifact, leave it unstated and let the gate say so.

Deterministic gate

scripts/check_model_provenance.py — 10 verdicts by set arithmetic over the dossier, stdlib-only and network-free. Reproducible challenge: bash ${CLAUDE_SKILL_DIR}/scripts/check_model_provenance_challenge/verify.sh. Regression suite: bash ${CLAUDE_SKILL_DIR}/tests/test_model_provenance.sh.

Boundaries

architecture-zoo (which family?) -> model-sourcing (this skill: which artifact, and what may its
  numbers claim?) -> profile-imaging / preprocess-imaging -> model-scaffold -> model-validation
  -> model-evaluation -> model-card -> write-paper
Files (medsci-skills)
  • scripts
    • check_model_provenance_challenge
      • expected
        • clean.txt 186 B
          Model provenance — OrganSeg-3D v2.5.1 (synthetic example)  (2 evaluation arm(s))
          
            no findings — every provenance fact the gate checks is stated and consistent
          
            Major 0   Minor 0
          
        • defect.txt 1.8 KB
          Model provenance — SynthSeg-3D (synthetic example)  (2 evaluation arm(s))
          
            [Major] BENCHMARK_PROVENANCE_CONFLICT
                     an evaluation arm uses a dataset the model was developed or tuned on; that arm does not independently test the model, no matter how held-out the split is for you. Report it as a demonstration of the pipeline and put the evidence on an arm whose data post-dates the model
                     - internal (ExampleBench Task03 Liver) <- developed on ExampleBench
            [Major] EVAL_DATA_IN_TRAINING
                     an evaluation arm's dataset appears in the corpus the weights were trained on; the resulting number is a training-set score
                     - internal (ExampleBench Task03 Liver) in the training corpus (ExampleBench)
            [Minor] LICENCE_UNVERIFIED
                     licence 'cc-by-nc-4.0' is named but the file it was read from is not recorded; a badge in a README is not the licence
            [Major] LICENCE_INCOMPATIBLE
                     licence 'cc-by-nc-4.0' restricts use to non-commercial or research purposes while the declared intended use is 'commercial'
            [Minor] TASK_MISMATCH
                     the model's task ('2d_xray_classification') is not the study's task ('3d_ct_organ_segmentation'); a transfer step and its own validation are required, not just a checkpoint
            [Minor] NO_VERSION_PIN
                     no commit, tag or revision recorded; 'we used <model>' does not identify what ran and cannot be reproduced once the default branch moves
            [Minor] VALIDATION_UNREPORTED
                     no reported validation (dataset + metric + source) recorded for the model as published; there is then no prior expectation to compare your own numbers against
            [Minor] HARDWARE_UNVERIFIED
                     hardware support is claimed ('any CUDA GPU') but never executed; support matrices and what the stack actually runs on are different statements
          
            Major 3   Minor 5
          
        • unstated.txt 652 B
          Model provenance — AnonNet (synthetic example)  (1 evaluation arm(s))
          
            [Major] WEIGHTS_PROVENANCE_UNKNOWN
                     pretrained weights are used and the corpus behind them is not stated, so it cannot be established whether your evaluation data is inside them
            [Major] LICENCE_UNSTATED
                     no licence recorded. An unstated licence is not a permissive one — it is an unanswered question about whether the work may be used or redistributed at all
            [Minor] NO_VERSION_PIN
                     no commit, tag or revision recorded; 'we used <model>' does not identify what ran and cannot be reproduced once the default branch moves
          
            Major 2   Minor 1
          
      • fixture
        • dossier_clean.json 839 B
          {
            "model": "OrganSeg-3D v2.5.1 (synthetic example)",
            "source": {"kind": "github", "url": "https://example.invalid/org/organseg3d",
                       "version": "v2.5.1", "commit": "abc1234def5678"},
            "licence": {"spdx": "Apache-2.0", "verified_from": "LICENSE at commit abc1234def5678"},
            "intended_use": "research",
            "weights": {"pretrained": false},
            "task": {"model": "3d_ct_organ_segmentation", "study": "3d_ct_organ_segmentation"},
            "reported_validation": [
              {"dataset": "ExampleBench", "metric": "Dice", "source": "Journal of Examples 2021"}
            ],
            "developed_on": ["ExampleBench"],
            "evaluation_arms": [
              {"name": "external", "dataset": "OtherCohort-2026"},
              {"name": "modality_shift", "dataset": "OtherCohort-2026 MRI"}
            ],
            "hardware": {"claimed": "any CUDA GPU", "verified_on": "GTX 1080 Ti", "verified": true}
          }
          
        • dossier_defect.json 626 B
          {
            "model": "SynthSeg-3D (synthetic example)",
            "source": {"kind": "github", "url": "https://example.invalid/org/synthseg3d"},
            "licence": {"spdx": "CC-BY-NC-4.0"},
            "intended_use": "commercial",
            "weights": {"pretrained": true, "trained_on": ["ExampleBench", "OpenOrgan-1k"]},
            "task": {"model": "2d_xray_classification", "study": "3d_ct_organ_segmentation"},
            "reported_validation": [],
            "developed_on": ["ExampleBench"],
            "evaluation_arms": [
              {"name": "internal", "dataset": "ExampleBench Task03 Liver"},
              {"name": "external", "dataset": "OtherCohort-2026"}
            ],
            "hardware": {"claimed": "any CUDA GPU"}
          }
          
        • dossier_unstated.json 556 B
          {
            "model": "AnonNet (synthetic example)",
            "source": {"kind": "huggingface", "url": "https://example.invalid/models/anonnet"},
            "intended_use": "research",
            "weights": {"pretrained": true},
            "task": {"model": "3d_ct_organ_segmentation", "study": "3d_ct_organ_segmentation"},
            "reported_validation": [{"dataset": "OtherCohort-2026", "metric": "Dice", "source": "preprint"}],
            "developed_on": [],
            "evaluation_arms": [{"name": "external", "dataset": "OtherCohort-2026"}],
            "hardware": {"claimed": "A100", "verified_on": "A100", "verified": true}
          }
          
      • problem.md 2.4 KB
        # Challenge — the provenance conflict a licence check cannot see
        
        You have chosen an architecture and found a concrete implementation: a public repository, well
        cited, permissively licensed, whose task matches yours and whose code runs on your hardware. You
        plan to report an internal validation on a public benchmark and an external validation on your
        own cohort.
        
        Every check a careful person performs by hand passes. The licence is real and permissive. The
        paper is peer-reviewed and highly cited. The task matches. The code executes.
        
        **The one fact that decides how your internal number should be read is not in any of those
        places.** If the method was developed, tuned, or competed against the benchmark family you are
        about to evaluate it on, that arm is not an independent test of it — however scrupulously you
        held out your own split. It reads like validation and is closer to a training-set score, and
        nothing in the repository will tell you, because the conflict lives in the relationship between
        two facts that sit in different documents.
        
        ## Task
        
        Write a **model dossier** for the artifact — source and version pin, licence and the file it was
        read from, intended use, whether weights are pretrained and on what, the model's task versus the
        study's, its reported validation, what it was developed on, and your evaluation arms — then run
        the gate over it:
        
        ```bash
        python3 ../check_model_provenance.py --dossier fixture/dossier_defect.json --strict
        ```
        
        ## What the fixtures show
        
        - `dossier_defect.json` — facts that **contradict each other**: an arm on the benchmark the model
          was developed on, that same arm inside the pretraining corpus, a non-commercial licence under
          commercial intent, a mismatched task, no pin, no reported validation, unverified hardware.
        - `dossier_unstated.json` — facts that are **absent**: no licence, pretrained weights of unknown
          provenance, no pin. A different failure and the more common one; an unstated licence is not a
          permissive licence.
        - `dossier_clean.json` — everything stated and consistent. It still records
          `developed_on: ExampleBench`, and **nothing fires**, because no evaluation arm uses
          ExampleBench. Being developed on a benchmark is not a defect; evaluating on it is.
        
        ## Verify
        
        ```bash
        bash verify.sh
        ```
        
        Deterministic and network-free: no repository is fetched and no licence is resolved online. An
        unstated fact is a finding, never a value the gate guesses at.
        
      • verify.sh 2.5 KB
        #!/usr/bin/env bash
        # Deterministic verifier for the model-provenance challenge card.
        # Runs check_model_provenance.py on three synthetic dossiers and diffs stdout against
        # expected/. No network, no repository fetch, no licence resolution — every finding is
        # decided by set arithmetic over the dossier JSON. Exit 0 = all match and exit codes correct.
        #
        # Fixtures (synthetic only — invented model and dataset names, no real artifacts):
        #   dossier_defect.json   — facts that CONTRADICT each other: an evaluation arm on the very
        #                           benchmark the model was developed on, that same arm inside the
        #                           pretraining corpus, a non-commercial licence under commercial
        #                           intent, a task that is not the study's task, no version pin, no
        #                           reported validation, hardware claimed but never run.
        #   dossier_unstated.json — facts that are ABSENT: no licence at all, pretrained weights whose
        #                           corpus is not stated, no version pin. A different failure mode from
        #                           the first, and the more common one.
        #   dossier_clean.json    — the same kind of artifact with every fact stated and consistent.
        #                           Note it still declares `developed_on: ExampleBench`; nothing fires,
        #                           because no evaluation arm uses ExampleBench. The gate flags the
        #                           RELATIONSHIP between provenance and evaluation, not the presence of
        #                           a benchmark in a model's history.
        set -euo pipefail
        HERE="$(cd "$(dirname "$0")" && pwd)"
        DET="$HERE/../check_model_provenance.py"
        
        ok=1
        for f in defect unstated clean; do
          got="$(python3 "$DET" --dossier "$HERE/fixture/dossier_$f.json")"
          if ! diff -u "$HERE/expected/$f.txt" <(printf '%s\n' "$got"); then
            echo "FAIL: $f-fixture output drifted from expected/$f.txt" >&2; ok=0
          fi
        done
        
        for f in defect unstated; do
          python3 "$DET" --dossier "$HERE/fixture/dossier_$f.json" --strict --quiet >/dev/null 2>&1 && rc=0 || rc=$?
          [ "${rc:-0}" -eq 1 ] || { echo "FAIL: $f fixture should exit 1 under --strict (got ${rc:-0})" >&2; ok=0; }
        done
        python3 "$DET" --dossier "$HERE/fixture/dossier_clean.json" --strict --quiet >/dev/null 2>&1 && rc=0 || rc=$?
        [ "$rc" -eq 0 ] || { echo "FAIL: clean fixture should exit 0 under --strict (got $rc)" >&2; ok=0; }
        
        if [ "$ok" -eq 1 ]; then
          echo "PASS: model-provenance gate separates contradictory facts from absent ones and clears a fully stated dossier."
        fi
        [ "$ok" -eq 1 ] || exit 1
        
    • check_model_provenance.py 12.4 KB
      #!/usr/bin/env python3
      """Model-provenance gate for a third-party model you are about to build a study on.
      
      Choosing an architecture is a literature question and `/architecture-zoo` answers it. Choosing a
      *concrete artifact* — this repository, this checkpoint, this revision — is a provenance question,
      and the two facts a researcher usually checks (the licence, the citation count) are the two that
      cannot answer it.
      
      The failure this gate exists for is the quietest one. A method developed and tuned against a
      benchmark family will be evaluated by the next person *on that same benchmark*, and the resulting
      number will read like validation while being closer to a training-set score. Nothing in the
      repository says so: the licence is clean, the paper is highly cited, the task matches, the code
      runs. The conflict lives in the relationship between two facts that sit in different documents —
      what the model was developed on, and what you are about to evaluate it on — and it is visible
      only when they are written down side by side. That is what the dossier is for.
      
      The gate reads a declarative **model dossier** and decides each finding by set arithmetic over
      it. It never fetches a repository, never resolves a licence from the network, and never infers a
      fact the dossier does not state: an unstated fact is a finding, not something to guess at.
      
      CHECKS (verdicts):
        MAJOR
        1. BENCHMARK_PROVENANCE_CONFLICT  a dataset the model was developed/tuned/competed on also
                                          appears among your evaluation arms. That arm does not
                                          independently test the model, however held-out it is for you.
        2. EVAL_DATA_IN_TRAINING          an evaluation arm's dataset appears in the model's own
                                          training corpus — the strong form of the same problem.
        3. LICENCE_UNSTATED               no licence recorded. Not "probably permissive": unstated.
        4. LICENCE_INCOMPATIBLE           the licence forbids the declared intended use (a
                                          non-commercial or research-only licence under commercial or
                                          deployment intent).
        5. WEIGHTS_PROVENANCE_UNKNOWN     pretrained weights are used and the corpus they were trained
                                          on is not stated — so what is in them cannot be reasoned
                                          about, including whether your evaluation set is.
      
        MINOR (each is a fact to record, not necessarily a defect)
        6. TASK_MISMATCH                  the model's declared task differs from the study's.
        7. NO_VERSION_PIN                 no commit, tag or revision — "we used nnU-Net" is not a
                                          reproducible statement.
        8. VALIDATION_UNREPORTED          no reported validation (dataset + metric) recorded.
        9. HARDWARE_UNVERIFIED            hardware compatibility claimed but never executed.
       10. LICENCE_UNVERIFIED             a licence is named but its source file is not, so it came
                                          from a badge or a memory rather than from the artifact.
      
      DOSSIER (JSON)
        {
          "model": "nnU-Net v2",
          "source": {"kind": "github", "url": "...", "version": "v2.5.1", "commit": "abc1234"},
          "licence": {"spdx": "Apache-2.0", "verified_from": "LICENSE at the pinned commit"},
          "intended_use": "research",              // research | commercial | clinical_deployment
          "weights": {"pretrained": false},        // if true, add "trained_on": [...]
          "task": {"model": "3d_ct_organ_segmentation", "study": "3d_ct_organ_segmentation"},
          "reported_validation": [{"dataset": "MSD", "metric": "Dice", "source": "Nat Methods 2021"}],
          "developed_on": ["Medical Segmentation Decathlon"],
          "evaluation_arms": [{"name": "internal", "dataset": "MSD Task09 Spleen"},
                              {"name": "external", "dataset": "AMOS22"}],
          "hardware": {"claimed": "any CUDA GPU", "verified_on": "GTX 1080 Ti", "verified": true}
        }
      
        Dataset names are matched as **token sequences**, so "MSD Task09 Spleen" matches "MSD" while
        "MS" does not match "MSD", plus a small alias table for families that are routinely written
        two ways (MSD / Decathlon). Matching never falls back to substring search.
      
      INPUTS
        --dossier   model dossier JSON (required).
      
      OUTPUT
        A findings table (stdout) and, with --out, a JSON artifact.
        Exit 1 under --strict when any Major finding exists. Stdlib-only.
      """
      from __future__ import annotations
      
      import argparse
      import json
      import re
      import sys
      from pathlib import Path
      
      # Families routinely written more than one way. Values are canonical token sequences.
      DATASET_ALIASES = {
          "decathlon": ["msd"],
          "medicalsegmentationdecathlon": ["msd"],
          "medical": ["msd"],  # only via the multi-token form below
      }
      MULTI_TOKEN_ALIASES = {
          ("medical", "segmentation", "decathlon"): ["msd"],
          ("the", "medical", "segmentation", "decathlon"): ["msd"],
      }
      NONCOMMERCIAL_MARKERS = ("-nc-", "-nc", "noncommercial", "non-commercial",
                               "research-only", "researchonly", "cc-by-nc")
      RESTRICTED_USES = {"commercial", "clinical_deployment", "clinical-deployment", "deployment",
                         "product"}
      
      
      def _tokens(name: str) -> tuple[str, ...]:
          toks = tuple(t for t in re.split(r"[^a-z0-9]+", str(name).strip().lower()) if t)
          if toks in MULTI_TOKEN_ALIASES:
              return tuple(MULTI_TOKEN_ALIASES[toks])
          if len(toks) == 1 and toks[0] in DATASET_ALIASES and toks[0] != "medical":
              return tuple(DATASET_ALIASES[toks[0]])
          return toks
      
      
      def datasets_match(a: str, b: str) -> bool:
          """True when two dataset names denote the same corpus (or one is a family of the other).
      
          Token-sequence prefix, never substring: ('msd','task09','spleen') matches ('msd',), and
          ('ms',) does not match ('msd',).
          """
          ta, tb = _tokens(a), _tokens(b)
          if not ta or not tb:
              return False
          short, long_ = (ta, tb) if len(ta) <= len(tb) else (tb, ta)
          return long_[:len(short)] == short
      
      
      def _norm(s) -> str:
          return str(s).strip().lower() if s is not None else ""
      
      
      def analyze(dossier_path: str) -> dict:
          d = json.loads(Path(dossier_path).read_text(encoding="utf-8"))
          claims: list[dict] = []
      
          def claim(verdict: str, severity: str, detail: str, where: list[str] | None = None) -> None:
              claims.append({"verdict": verdict, "severity": severity, "detail": detail,
                             "where": sorted(where or [])[:12], "n_where": len(where or [])})
      
          arms = d.get("evaluation_arms") or []
          arm_datasets = [(a.get("name") or "?", a.get("dataset") or "") for a in arms]
      
          # ---- the conflict neither the licence nor the citation count reveals ------
          developed_on = d.get("developed_on") or []
          hits = [f"{nm} ({ds}) <- developed on {dev}"
                  for nm, ds in arm_datasets for dev in developed_on if datasets_match(ds, dev)]
          if hits:
              claim("BENCHMARK_PROVENANCE_CONFLICT", "Major",
                    "an evaluation arm uses a dataset the model was developed or tuned on; that arm "
                    "does not independently test the model, no matter how held-out the split is for "
                    "you. Report it as a demonstration of the pipeline and put the evidence on an arm "
                    "whose data post-dates the model", hits)
      
          # ---- the strong form: your eval set is in the weights ---------------------
          weights = d.get("weights") or {}
          pretrained = bool(weights.get("pretrained"))
          trained_on = weights.get("trained_on") or []
          leaks = [f"{nm} ({ds}) in the training corpus ({tr})"
                   for nm, ds in arm_datasets for tr in trained_on if datasets_match(ds, tr)]
          if leaks:
              claim("EVAL_DATA_IN_TRAINING", "Major",
                    "an evaluation arm's dataset appears in the corpus the weights were trained on; "
                    "the resulting number is a training-set score", leaks)
          if pretrained and not trained_on:
              claim("WEIGHTS_PROVENANCE_UNKNOWN", "Major",
                    "pretrained weights are used and the corpus behind them is not stated, so it "
                    "cannot be established whether your evaluation data is inside them")
      
          # ---- licence -------------------------------------------------------------
          lic = d.get("licence") or d.get("license") or {}
          spdx = _norm(lic.get("spdx") or lic.get("name"))
          use = _norm(d.get("intended_use"))
          if not spdx:
              claim("LICENCE_UNSTATED", "Major",
                    "no licence recorded. An unstated licence is not a permissive one — it is an "
                    "unanswered question about whether the work may be used or redistributed at all")
          else:
              if not lic.get("verified_from"):
                  claim("LICENCE_UNVERIFIED", "Minor",
                        f"licence '{spdx}' is named but the file it was read from is not recorded; "
                        "a badge in a README is not the licence")
              if use in RESTRICTED_USES and any(m in spdx for m in NONCOMMERCIAL_MARKERS):
                  claim("LICENCE_INCOMPATIBLE", "Major",
                        f"licence '{spdx}' restricts use to non-commercial or research purposes while "
                        f"the declared intended use is '{use}'")
      
          # ---- task, pin, validation, hardware -------------------------------------
          task = d.get("task") or {}
          tm, ts = _norm(task.get("model")), _norm(task.get("study"))
          if tm and ts and tm != ts:
              claim("TASK_MISMATCH", "Minor",
                    f"the model's task ('{tm}') is not the study's task ('{ts}'); a transfer step and "
                    "its own validation are required, not just a checkpoint")
      
          src = d.get("source") or {}
          if not (src.get("commit") or src.get("version") or src.get("revision") or src.get("tag")):
              claim("NO_VERSION_PIN", "Minor",
                    "no commit, tag or revision recorded; 'we used <model>' does not identify what ran "
                    "and cannot be reproduced once the default branch moves")
      
          if not (d.get("reported_validation") or []):
              claim("VALIDATION_UNREPORTED", "Minor",
                    "no reported validation (dataset + metric + source) recorded for the model as "
                    "published; there is then no prior expectation to compare your own numbers against")
      
          hw = d.get("hardware") or {}
          if hw.get("claimed") and not hw.get("verified"):
              claim("HARDWARE_UNVERIFIED", "Minor",
                    f"hardware support is claimed ('{hw.get('claimed')}') but never executed; support "
                    "matrices and what the stack actually runs on are different statements")
      
          majors = [c for c in claims if c["severity"] == "Major"]
          return {
              # the artifact names its own author: a qc filename is chosen by the caller and
              # cannot be trusted to identify what wrote it
              "detector": "check_model_provenance",
              "dossier": dossier_path,
              "model": d.get("model", ""),
              "n_arms": len(arms),
              "claims": claims,
              "summary": {"major": len(majors), "minor": len(claims) - len(majors)},
          }
      
      
      def render(result: dict) -> str:
          lines = [f"Model provenance — {result['model'] or '(unnamed)'}  "
                   f"({result['n_arms']} evaluation arm(s))", ""]
          if not result["claims"]:
              lines.append("  no findings — every provenance fact the gate checks is stated and consistent")
          for c in result["claims"]:
              lines.append(f"  [{c['severity']:<5}] {c['verdict']}")
              lines.append(f"           {c['detail']}")
              for w in c["where"]:
                  lines.append(f"           - {w}")
          lines.append("")
          lines.append(f"  Major {result['summary']['major']}   Minor {result['summary']['minor']}")
          return "\n".join(lines)
      
      
      def main() -> int:
          ap = argparse.ArgumentParser(description="Audit a third-party model's provenance dossier.")
          ap.add_argument("--dossier", required=True)
          ap.add_argument("--out")
          ap.add_argument("--strict", action="store_true", help="exit 1 when a Major finding exists")
          ap.add_argument("--quiet", action="store_true")
          a = ap.parse_args()
      
          if not Path(a.dossier).exists():
              print(f"input error: dossier not found: {a.dossier}", file=sys.stderr)
              return 2
          try:
              result = analyze(a.dossier)
          except json.JSONDecodeError as exc:
              print(f"input error: dossier is not valid JSON: {exc}", file=sys.stderr)
              return 2
      
          if not a.quiet:
              print(render(result))
          if a.out:
              Path(a.out).parent.mkdir(parents=True, exist_ok=True)
              Path(a.out).write_text(json.dumps(result, indent=1), encoding="utf-8")
          return 1 if (a.strict and result["summary"]["major"]) else 0
      
      
      if __name__ == "__main__":
          sys.exit(main())
      
  • tests
    • test_model_provenance.sh 4.8 KB
      #!/usr/bin/env bash
      # Regression test for the model-provenance gate (model-sourcing).
      # Synthetic, PII-free JSON dossiers reproduce each verdict class. Stdlib-only (python3).
      set -u
      
      HERE="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
      SCRIPT="$HERE/../scripts/check_model_provenance.py"
      CH="$HERE/../scripts/check_model_provenance_challenge"
      TMP="$(mktemp -d -t modprov_XXXX)"
      OUT="$TMP/out.json"
      trap 'rm -rf "$TMP"' EXIT
      
      fail=0
      check() { local label="$1"; shift
          if "$@" >/dev/null 2>&1; then printf '  PASS  %s\n' "$label"
          else printf '  FAIL  %s\n' "$label"; fail=$((fail+1)); fi
      }
      has_verdict() { python3 -c "
      import json
      d=json.load(open('$OUT'))
      assert any(c['verdict']=='$1' for c in d['claims']), '$1 not found'
      "; }
      no_verdict() { python3 -c "
      import json
      d=json.load(open('$OUT'))
      assert not any(c['verdict']=='$1' for c in d['claims']), '$1 unexpectedly present'
      "; }
      
      [[ -f "$SCRIPT" ]] || { echo "ENV-ERR: script missing" >&2; exit 2; }
      
      # (1) contradictory facts
      python3 "$SCRIPT" --dossier "$CH/fixture/dossier_defect.json" --out "$OUT" --strict --quiet >/dev/null 2>&1
      check "exit 1 (contradictory dossier)" test "$?" -eq 1
      for v in BENCHMARK_PROVENANCE_CONFLICT EVAL_DATA_IN_TRAINING LICENCE_INCOMPATIBLE \
               LICENCE_UNVERIFIED TASK_MISMATCH NO_VERSION_PIN VALIDATION_UNREPORTED HARDWARE_UNVERIFIED; do
        check "$v detected" has_verdict "$v"
      done
      
      # (2) absent facts -- a different failure mode
      python3 "$SCRIPT" --dossier "$CH/fixture/dossier_unstated.json" --out "$OUT" --strict --quiet >/dev/null 2>&1
      check "exit 1 (unstated dossier)" test "$?" -eq 1
      check "LICENCE_UNSTATED detected" has_verdict LICENCE_UNSTATED
      check "WEIGHTS_PROVENANCE_UNKNOWN detected" has_verdict WEIGHTS_PROVENANCE_UNKNOWN
      check "an unstated licence does not also read as incompatible" no_verdict LICENCE_INCOMPATIBLE
      
      # (3) fully stated dossier -> silent
      python3 "$SCRIPT" --dossier "$CH/fixture/dossier_clean.json" --out "$OUT" --strict --quiet >/dev/null 2>&1
      check "exit 0 (clean dossier)" test "$?" -eq 0
      check "being developed on a benchmark you do NOT evaluate on is not a finding" \
            no_verdict BENCHMARK_PROVENANCE_CONFLICT
      check "no LICENCE_UNVERIFIED once the licence file is recorded" no_verdict LICENCE_UNVERIFIED
      check "no HARDWARE_UNVERIFIED once the claim has been executed" no_verdict HARDWARE_UNVERIFIED
      
      # (4) dataset names are matched as token sequences, never as substrings
      mk() { python3 - "$1" "$2" "$3" <<'PY'
      import json, sys
      out, dev, arm = sys.argv[1], sys.argv[2], sys.argv[3]
      json.dump({"model": "m", "source": {"commit": "c"},
                 "licence": {"spdx": "Apache-2.0", "verified_from": "LICENSE"},
                 "intended_use": "research", "weights": {"pretrained": False},
                 "task": {"model": "t", "study": "t"},
                 "reported_validation": [{"dataset": "x", "metric": "Dice", "source": "s"}],
                 "developed_on": [dev],
                 "evaluation_arms": [{"name": "a", "dataset": arm}],
                 "hardware": {}}, open(out, "w"))
      PY
      }
      mk "$TMP/d.json" "MSD" "MSD Task09 Spleen"
      python3 "$SCRIPT" --dossier "$TMP/d.json" --out "$OUT" --quiet >/dev/null 2>&1
      check "'MSD Task09 Spleen' matches the family 'MSD'" has_verdict BENCHMARK_PROVENANCE_CONFLICT
      
      mk "$TMP/d.json" "Medical Segmentation Decathlon" "MSD Task09 Spleen"
      python3 "$SCRIPT" --dossier "$TMP/d.json" --out "$OUT" --quiet >/dev/null 2>&1
      check "the spelled-out family name resolves to the same corpus" has_verdict BENCHMARK_PROVENANCE_CONFLICT
      
      mk "$TMP/d.json" "MSD" "MS Cohort 2026"
      python3 "$SCRIPT" --dossier "$TMP/d.json" --out "$OUT" --quiet >/dev/null 2>&1
      check "'MS Cohort 2026' does NOT match 'MSD' (no substring matching)" \
            no_verdict BENCHMARK_PROVENANCE_CONFLICT
      
      mk "$TMP/d.json" "MSD" "AMOS22"
      python3 "$SCRIPT" --dossier "$TMP/d.json" --out "$OUT" --quiet >/dev/null 2>&1
      check "an unrelated external cohort does not match" no_verdict BENCHMARK_PROVENANCE_CONFLICT
      
      # (5) a non-commercial licence is only a conflict under a restricted intended use
      python3 - "$TMP/nc.json" <<'PY'
      import json, sys
      json.dump({"model": "m", "source": {"commit": "c"},
                 "licence": {"spdx": "CC-BY-NC-4.0", "verified_from": "LICENSE"},
                 "intended_use": "research", "weights": {"pretrained": False},
                 "task": {"model": "t", "study": "t"},
                 "reported_validation": [{"dataset": "x", "metric": "Dice", "source": "s"}],
                 "developed_on": [], "evaluation_arms": [{"name": "a", "dataset": "z"}],
                 "hardware": {}}, open(sys.argv[1], "w"))
      PY
      python3 "$SCRIPT" --dossier "$TMP/nc.json" --out "$OUT" --quiet >/dev/null 2>&1
      check "a non-commercial licence under research use is not a conflict" no_verdict LICENCE_INCOMPATIBLE
      
      # (6) the shipped challenge card reproduces
      check "challenge verify.sh passes" bash "$CH/verify.sh"
      
      echo
      if [[ "$fail" -eq 0 ]]; then echo "ALL PASS (model-provenance gate)"; else echo "$fail FAILURE(S)"; exit 1; fi
      
  • SKILL.md 9 KB
    ---
    name: model-sourcing
    description: >
      Vet the concrete third-party model a study will be built on — this repository, this revision,
      this checkpoint — not the architecture family. Records a model dossier (source and version pin,
      licence and the file it was read from, intended use, pretrained-weight provenance, model task
      vs study task, reported validation, what the model was developed on, your evaluation arms) and
      gates it deterministically. Catches what a licence check and a citation count cannot: an
      evaluation arm sitting on the benchmark the model was developed or tuned on, so the arm reads
      like validation while being closer to a training-set score. Also an evaluation set inside a
      pretraining corpus, an unstated or use-incompatible licence, an unpinned revision, and a
      hardware claim never executed. It vets an artifact; it never downloads or runs one.
    triggers: source a model, vet a model, pick a model, model provenance, model dossier, pretrained weights, checkpoint, HuggingFace model, GitHub model, model licence, weight provenance, is this model independent, benchmark overlap, trained on my test set, data contamination, model version pin, third-party model, can I use this model
    tools: Read, Write, Edit, Bash, Grep, Glob
    model: inherit
    ---
    
    # Model-Sourcing Skill
    
    ## Purpose
    
    `/architecture-zoo` answers a literature question — which family of model suits this task. That
    question has a stable answer. The next question does not: *which concrete artifact do I run?*
    A repository, a revision, a checkpoint. That is a provenance question, and the two facts a
    careful researcher usually checks are the two that cannot answer it.
    
    The licence tells you whether you may use it. The citation count tells you whether others did.
    Neither tells you **whether the number you are about to report means what you will say it means.**
    
    The failure this skill exists for is the quietest one in the lane. A method developed and tuned
    against a benchmark family gets evaluated by the next person *on that same family*, and the
    resulting figure reads like validation while sitting much closer to a training-set score. Nothing
    in the repository says so. The licence is clean, the paper is peer-reviewed and highly cited, the
    task matches, the code runs on your GPU. The conflict lives in the **relationship** between two
    facts that are documented in different places — what the model was developed on, and what you are
    about to evaluate it on — and it becomes visible only when they are written down side by side.
    
    Writing them down side by side is what the dossier is for.
    
    ## When to use
    - You have a concrete candidate (a GitHub repo, a Hugging Face checkpoint, a paper's released
      weights) and are about to build a study on it.
    - You are writing the Methods paragraph that says which model you used, and it has to survive a
      reviewer asking what it was trained on.
    - You inherited a pipeline whose model came from somewhere nobody recorded.
    
    ## When NOT to use
    - Choosing an architecture *family* → `/architecture-zoo` (archetypes and the task-to-architecture
      logic; deliberately not a live leaderboard).
    - Building the training repo → `/model-scaffold`. Designing the validation study →
      `/model-validation`. Computing held-out metrics → `/model-evaluation`.
    - Documenting a model **you** built → `/model-card` (Model Card + Datasheet).
    - Auditing your own dataset before modelling → `/profile-imaging`.
    - Evaluating an LLM/multimodal system on a clinical task → `/mllm-eval` (which owns
      pretraining-contamination of public benchmarks for that setting).
    
    ## Workflow
    
    ### Step 1 — write the dossier
    
    One JSON file recording what is *known*, with unknowns left unstated rather than guessed:
    
    ```json
    {
      "model": "OrganSeg-3D v2.5.1",
      "source": {"kind": "github", "url": "...", "version": "v2.5.1", "commit": "abc1234"},
      "licence": {"spdx": "Apache-2.0", "verified_from": "LICENSE at commit abc1234"},
      "intended_use": "research",
      "weights": {"pretrained": false},
      "task": {"model": "3d_ct_organ_segmentation", "study": "3d_ct_organ_segmentation"},
      "reported_validation": [{"dataset": "ExampleBench", "metric": "Dice", "source": "J Ex 2021"}],
      "developed_on": ["ExampleBench"],
      "evaluation_arms": [{"name": "external", "dataset": "OtherCohort-2026"}],
      "hardware": {"claimed": "any CUDA GPU", "verified_on": "GTX 1080 Ti", "verified": true}
    }
    ```
    
    Each field is read from the artifact, not from memory: the licence from the `LICENSE` file at the
    pinned commit (a README badge is not the licence), `developed_on` from the paper's own account of
    where the method was built and tuned, `hardware.verified` only after it has actually run.
    
    `developed_on` is the field people skip, and it is the one the gate needs. A method that won a
    challenge was tuned on that challenge.
    
    ### Step 2 — gate it
    
    ```bash
    python3 scripts/check_model_provenance.py --dossier model_dossier.json \
        --out qc/model_provenance.json --strict
    ```
    
    Stdlib-only, network-free — no repository is fetched and no licence resolved online, so the audit
    re-runs anywhere the JSON travels. Verdicts:
    
    | Verdict | Severity | Fires when |
    |---|---|---|
    | `BENCHMARK_PROVENANCE_CONFLICT` | Major | an evaluation arm uses a dataset the model was developed or tuned on |
    | `EVAL_DATA_IN_TRAINING` | Major | an evaluation arm's dataset is inside the pretraining corpus |
    | `LICENCE_UNSTATED` | Major | no licence recorded — which is not the same as a permissive one |
    | `LICENCE_INCOMPATIBLE` | Major | a non-commercial / research-only licence under commercial or deployment intent |
    | `WEIGHTS_PROVENANCE_UNKNOWN` | Major | pretrained weights whose training corpus is not stated |
    | `TASK_MISMATCH` | Minor | the model's task is not the study's task |
    | `NO_VERSION_PIN` | Minor | no commit, tag or revision |
    | `VALIDATION_UNREPORTED` | Minor | no reported validation (dataset + metric + source) |
    | `HARDWARE_UNVERIFIED` | Minor | hardware support claimed but never executed |
    | `LICENCE_UNVERIFIED` | Minor | a licence is named but the file it was read from is not |
    
    **The gate flags a relationship, not a reputation.** A dossier that declares
    `developed_on: ExampleBench` passes cleanly as long as no evaluation arm uses ExampleBench.
    Being developed on a benchmark is not a defect; evaluating on it and calling that independent is.
    The clean fixture exists to prove exactly that distinction.
    
    Dataset names are matched as **token sequences** with a small family-alias table, so
    `MSD Task09 Spleen` matches `MSD` and `MS Cohort 2026` does not. Matching never falls back to
    substring search.
    
    ### Step 3 — turn a Major into a study decision
    
    A `BENCHMARK_PROVENANCE_CONFLICT` is rarely a reason to abandon the model — it is usually the
    best-engineered option precisely because it was tuned hard. It is a reason to change **what the
    arm is claimed to establish**:
    
    1. Report that arm as a demonstration that the pipeline runs end to end, not as evidence the
       method works.
    2. Put the evidential weight on an arm whose data **post-dates** the model, and say so with dates.
    3. State the conflict in Methods and Limitations rather than leaving a reviewer to find it.
    
    An `EVAL_DATA_IN_TRAINING` is different in kind: that arm produces a training-set score and cannot
    be reported as validation at all.
    
    Carry the dossier forward — `/model-validation` (arm design), `/model-evaluation` (what each arm
    may claim), `/model-card` (provenance section), `/write-paper` (Methods + Limitations).
    
    ## Outputs
    
    - `model_dossier.json` — the provenance record downstream skills and the Methods section read.
    - `qc/model_provenance.json` — deterministic audit with verdicts.
    - The arm-by-arm decision from Step 3, written into the study record.
    
    ## Anti-Hallucination
    
    - **Never infer a fact the dossier does not state.** An unstated licence is `LICENCE_UNSTATED`,
      never "probably MIT"; an unstated pretraining corpus is a Major finding, never an assumption.
    - **Never record a licence from a badge, a model card summary, or memory** — only from the licence
      file at the pinned revision, and record which file that was.
    - **Never mark hardware verified without executing it.** A support matrix and what the stack
      actually runs is a different claim; a CUDA capability the compiler accepts may still be refused
      by a compiler in the same stack.
    - **Never report an arm as independent validation when the gate flags a provenance conflict.**
    - If a provenance fact cannot be established from the artifact, leave it unstated and let the
      gate say so.
    
    ## Deterministic gate
    
    `scripts/check_model_provenance.py` — 10 verdicts by set arithmetic over the dossier, stdlib-only
    and network-free. Reproducible challenge:
    `bash ${CLAUDE_SKILL_DIR}/scripts/check_model_provenance_challenge/verify.sh`.
    Regression suite: `bash ${CLAUDE_SKILL_DIR}/tests/test_model_provenance.sh`.
    
    ## Boundaries
    
    ```
    architecture-zoo (which family?) -> model-sourcing (this skill: which artifact, and what may its
      numbers claim?) -> profile-imaging / preprocess-imaging -> model-scaffold -> model-validation
      -> model-evaluation -> model-card -> write-paper
    ```
    
  • skill.yml 4.1 KB
    schema_version: 2
    name: model-sourcing
    layer: D
    owner_domain: model_validation
    maturity: official
    
    when_to_use: "Vet the CONCRETE third-party model a study will be built on — this repository, this revision, this checkpoint — before scaffolding or evaluation. Records a model dossier (source and version pin, licence and the file it was read from, intended use, pretrained-weight provenance, model task vs study task, reported validation, what the model was developed on, your evaluation arms) and gates it. Catches what a licence check and a citation count cannot: an evaluation arm that uses the benchmark the model was developed or tuned on, so the arm reads like validation while sitting closer to a training-set score; an evaluation set inside a pretraining corpus; an unstated or use-incompatible licence; an unpinned revision; a hardware claim never executed."
    when_NOT_to_use: "Choosing an architecture family (use architecture-zoo). Building the training repo (use model-scaffold). Designing the validation study or auditing split leakage (use model-validation). Computing held-out metrics (use model-evaluation). Documenting a model you built yourself (use model-card). Profiling your dataset before modelling (use profile-imaging). Evaluating an LLM/multimodal system on a clinical task, including benchmark contamination in that setting (use mllm-eval)."
    
    inputs:
      - "the concrete artifact under consideration: repository or checkpoint URL, revision, licence file"
      - "the model's published account of what it was developed, tuned, or competed on"
      - "the study's evaluation arms (arm name -> dataset) and the study's task"
      - "the declared intended use (research / commercial / clinical deployment)"
    outputs:
      - "model dossier JSON (the provenance record downstream skills and the Methods section read)"
      - "model-provenance audit JSON (deterministic verdicts)"
      - "arm-by-arm decision: which arms may be claimed as independent validation and which may not"
    deterministic_scripts:
      - scripts/check_model_provenance.py
    side_effects:
      - writes_decision_notes
    downstream_consumers:
      - model-validation
      - model-evaluation
      - model-card
      - write-paper
      - check-reporting
    forbidden_actions:
      - infer_a_provenance_fact_the_dossier_does_not_state
      - record_a_licence_from_a_badge_or_summary_instead_of_the_licence_file
      - mark_hardware_verified_without_executing_it
      - report_an_arm_as_independent_validation_when_a_provenance_conflict_is_flagged
      - download_or_execute_a_third_party_model_as_part_of_the_audit
    
    # v2.1 quality card
    purpose: "Establish what a third-party model's numbers are allowed to claim, before a study is built on it — by writing the provenance facts that live in different documents into one record and auditing the relationships between them, chiefly whether an evaluation arm sits on the benchmark the model was developed against."
    safety_boundaries:
      - "Audit only: never downloads, executes, fine-tunes, or benchmarks a model, and never fetches a repository or resolves a licence over the network."
      - "Every verdict is decided by set arithmetic over the dossier JSON; an unstated fact yields a finding rather than an inferred value."
      - "Stdlib-only, so the audit reproduces anywhere the dossier travels."
    known_limitations:
      - "The dossier is taken at face value: the gate cannot tell that a stated licence is wrong or that `developed_on` is incomplete, so the reading of the artifact is the researcher's responsibility and the gate audits the consequences."
      - "Dataset matching is token-sequence based with a small family-alias table; two names for the same corpus that share no leading token (a private cohort renamed between papers) will not be matched."
      - "A clean dossier is necessary, not sufficient: split disjointness (model-validation), preprocessing leakage (preprocess-imaging) and metric choice (model-evaluation) are separate gates."
    validation_commands:
      - "python3 scripts/check_model_provenance.py --dossier <dossier.json> --strict"
      - "bash scripts/check_model_provenance_challenge/verify.sh  # deterministic, network-free"
      - "bash tests/test_model_provenance.sh"
    evidence_surface: ci_validator
    

Comments (0)

Sign in to join the conversation.

No comments yet.

Reviews (0)

No reviews yet.

Related