Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion .github/workflows/validate.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,19 @@ jobs:
- name: Install dependencies
run: |
pip install --upgrade pip
pip install pyyaml jsonschema requests
pip install pyyaml jsonschema requests pytest
pip install linkml linkml-runtime || echo "WARNING: linkml install failed"
pip install rocrate || echo "WARNING: rocrate install failed"
# indicium-adapters provides the verify-claims console script (Plan 1c)
pip install indicium-adapters || echo "WARNING: indicium-adapters not yet available; gate 9 will warn-only"

# -- Gate 0: Unit tests -------------------------------------------------
# Until now the suite ran only inside release.yml, so a test could sit broken
# on main for weeks and nothing would say so. The suite is hermetic (pyyaml
# + pytest, no network).
- name: Run test suite
run: python -m pytest tests/ -q

# -- Gate 10: Plugin manifest validates --------------------------------
- name: Validate .claude-plugin/marketplace.json
run: |
Expand Down
17 changes: 17 additions & 0 deletions .github/workflows/verify-paper.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,13 +18,17 @@ on:
- 'collections/**/v*/collection.yaml'
- 'staged-collections/**/corpus.yaml'
- 'staged-collections/**/v*/collection.yaml'
# Editing the gate must re-run the gate, or a change to the admitted tier set
# ships without ever being exercised.
- '.github/workflows/verify-paper.yml'
push:
branches: [main]
paths:
- 'collections/**/corpus.yaml'
- 'collections/**/v*/collection.yaml'
- 'staged-collections/**/corpus.yaml'
- 'staged-collections/**/v*/collection.yaml'
- '.github/workflows/verify-paper.yml'

permissions:
contents: read
Expand Down Expand Up @@ -76,6 +80,19 @@ jobs:
"diamond",
}

# Repository-OA tier (CONTENT_POLICY.md §3, "software / dataset / tutorial
# DOIs"): a public git repository cloned at build qualifies as `repo-oa`.
# This is the paper-ACCESS axis only; what a consumer may do with the tool is
# the separate `license_tier` axis. Must stay in sync with
# scripts/release_gate.py:_REPO_OA_TIERS -- tests/test_oa_tier_parity.py
# fails if the two ever drift apart.
REPO_OA_TYPES = {
"repo-oa",
"repo-permissive",
"repo-copyleft",
}
OPEN_ACCESS_TYPES |= REPO_OA_TYPES

def normalize_access_type(raw: str) -> str:
"""Normalize an access.type token to a canonical OA tier.

Expand Down
80 changes: 76 additions & 4 deletions collections/metabolomics/proposals/wave-2026-06-community.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -13,13 +13,13 @@ schema: asb-corpus/1.0
collection: metabolomics
version: 2
generated_on: '2026-06-23'
source: community proposals (GitHub issues #11–#14), curator-normalized
source: community proposals (GitHub issues #11–#16), curator-normalized
policy: OPEN_ACCESS_POLICY.md (OA verified per entry; unresolved tiers flagged in triage)
wave: community-2026-06
summary:
total: 4
total: 6
included: 0
hold: 4
hold: 6

papers:
- name: RforMassSpectrometry
Expand All @@ -32,7 +32,7 @@ papers:
license_tier: open
wave: community-2026-06
resource_type: software-ecosystem
source_issue: 14 # filed alongside #13
source_issue: 13 # co-staged with #14 (Metabonaut), same authors
access:
type: gold-oa
license: cc-by-4.0
Expand Down Expand Up @@ -147,3 +147,75 @@ papers:
prose-only SKILL.md with link-only grounding and a runtime use-acknowledgment
gate; do NOT embed the repo. access.type stays "restricted" (OA axis unchanged) —
the license_tier is what makes it includable. Still status: hold pending merge.

- name: OmniPath Metabo
# DOI prefix 10.64898 is openRxiv (bioRxiv/medRxiv's registrant since the Cold Spring
# Harbor handover). It is NOT a typo for the legacy 10.1101 prefix. Do not "correct" it.
doi: 10.64898/2026.06.18.733117
title: 'OmniPath Metabo: chemical structures, interactions and mechanisms to study the metabolome'
year: 2026
category: Databases
repo_url: https://github.com/saezlab/omnipath-metabo
status: hold
license_tier: open
wave: community-2026-06
resource_type: database-knowledge-base
source_issue: 15
access:
type: open-access
license: cc-by-4.0
is_oa: true
provenance: preprint # bioRxiv; update to the canonical DOI once peer-reviewed
verified_via: biorxiv_api_license # api.biorxiv.org reported license=cc_by
verified_on: '2026-07-10'
code_license: BSD-3-Clause # saezlab/omnipath-metabo LICENSE
triage:
proposer: lfnothias
rationale: >-
A metabolome-oriented extension of the OmniPath knowledge base (Saez-Rodriguez
lab) linking chemical structures to interactions and mechanisms. Useful for
grounding agent tasks that connect annotated metabolites to biological context.
decision: accept
notes: >-
The preprint's licence was read from the bioRxiv API (cc_by), NOT inferred from
OPEN_ACCESS_POLICY.md's blanket "preprints are always CC-BY" rule — bioRxiv lets
authors choose CC-BY-NC-ND or no-reuse, so that rule can admit non-reusable text.
Repository is BSD-3-Clause. Re-point the entry at the peer-reviewed DOI on
publication, keeping this preprint as source_history.

- name: Workflow4Metabolomics
doi: 10.1093/bioinformatics/btu813
title: 'Workflow4Metabolomics: a collaborative research infrastructure for computational metabolomics'
year: 2015
category: Multifunctional
repo_url: https://github.com/workflow4metabolomics/tools-metabolomics
status: hold
license_tier: open
wave: community-2026-06
resource_type: workflow-platform
source_issue: 16
links:
- https://workflow4metabolomics.usegalaxy.fr/
- https://doi.org/10.1002/cpz1.70095 # Delport 2025 protocol; green OA, NOT reusable
access:
type: open-access
license: cc-by
is_oa: true
oa_status: hybrid
verified_via: unpaywall_best_oa_location # publisher-hosted cc-by
verified_on: '2026-07-10'
code_license: GPL-3.0-only # tools-metabolomics LICENSE.txt
triage:
proposer: yguitton # first external community proposal
rationale: >-
The Galaxy Workflow4Metabolomics suite is a long-standing, widely adopted public
infrastructure for reproducible LC-MS/GC-MS/NMR metabolomics. It covers the
preprocessing-to-statistics span with tools our corpus otherwise reaches only
through individual R/Python packages.
decision: accept
notes: >-
Anchored on Giacomoni 2015 (Bioinformatics), verified CC-BY at the publisher.
The Delport 2025 Current Protocols paper is green OA with no reuse licence, so it
is linked for context only and must NOT be used as the grounding source. The
repository is GPL-3, which clears the repository-OA tier independently.
Proposed by an external contributor — credit yguitton on promotion.
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
---
name: asb-metabolomics
description: "Use FIRST when working with the ASB Metabolomics skill collection. The meta-skill: it explains good practice (search -> apply -> ground), enforces the license-tier acknowledgment for non-open tools, then hands off to the _router skill for actual skill selection."
description: "Use when starting any task with the ASB Metabolomics skill collection — read this meta-skill first. It explains good practice (search -> apply -> ground), enforces the license-tier acknowledgment for non-open tools, then hands off to the _router skill for actual skill selection."
license: CC-BY-4.0
metadata:
role: meta
Expand Down
2 changes: 1 addition & 1 deletion governance/CONTENT_POLICY.md
Original file line number Diff line number Diff line change
Expand Up @@ -86,7 +86,7 @@ Papers with the following status are **rejected at curation**:

1. **Automated check:** OpenAlex API lookup; flag `is_oa: true|false`
2. **Manual spot-check:** Sample papers in the final corpus (≥10% per domain) to confirm OA license accuracy
3. **Red-flag gate:** CI verify-paper.yml (stage §7.5) enforces `access.type ∈ {open-access, open_access, oa, gold-oa, gold_oa, green-oa, green, diamond}` (after hyphen/underscore + `green`→`green-oa` normalization) for all `status:included` papers; unknown/non-OA triggers a hard fail. `preprint` is rejected as an `access.type` value because it is a provenance value, not an OA tier.
3. **Red-flag gate:** CI verify-paper.yml (stage §7.5) enforces `access.type ∈ {open-access, open_access, oa, gold-oa, gold_oa, green-oa, green, diamond} ∪ {repo-oa, repo-permissive, repo-copyleft}` (after hyphen/underscore + `green`→`green-oa` normalization) for all `status:included` papers; unknown/non-OA triggers a hard fail. `preprint` is rejected as an `access.type` value because it is a provenance value, not an OA tier. The three `repo-*` values are the repository-access tiers from the software/dataset note above; `tests/test_oa_tier_parity.py` keeps this set identical to `scripts/release_gate.py:_REPO_OA_TIERS`, so the push gate and the release gate cannot drift apart.

### Exception Procedure (Post-v0)

Expand Down
14 changes: 13 additions & 1 deletion governance/OPEN_ACCESS_POLICY.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,7 +146,19 @@ The static page at <https://holobiomicslab.github.io/asb-skill-collections/paper

### Pre-prints

Pre-prints (bioRxiv, medRxiv, ChemRxiv, arXiv) are treated as **open-access** (always CC-BY by repository policy) and may be included. When the peer-reviewed version is later published with a different DOI, the corpus entry is updated to point at the canonical DOI (linking back to the preprint as `source_history`).
Pre-prints (bioRxiv, medRxiv, ChemRxiv, arXiv) may be included, but **their posting licence must be verified per pre-print — it is not always CC-BY.** bioRxiv and medRxiv let the author choose CC-BY, CC-BY-NC, CC-BY-ND, CC-BY-NC-ND, CC0 or "no reuse allowed"; arXiv's default licence-to-distribute grants no reuse rights at all. Treating every pre-print as CC-BY would admit text this project has no right to redistribute.

Resolve the actual licence before admitting an entry, and record how it was resolved:

| Server | Where the licence lives | Record as |
|---|---|---|
| bioRxiv / medRxiv | `https://api.biorxiv.org/details/<server>/<doi>` → `license` (`cc_by`, `cc_by_nc_nd`, `cc_no`, …) | `verified_via: biorxiv_api_license` |
| arXiv | Atom API entry → `<license>` | `verified_via: arxiv_api_license` |
| ChemRxiv | item metadata → `license` | `verified_via: chemrxiv_api_license` |

Only a licence permitting the intended reuse admits the pre-print at an open `access.type`. A non-reuse licence makes the entry link-only, exactly as for a closed paper. Note that bioRxiv/medRxiv DOIs now carry the **openRxiv prefix `10.64898`** alongside the legacy Cold Spring Harbor prefix `10.1101`; both are genuine.

Per [`CONTENT_POLICY.md`](CONTENT_POLICY.md) §3, `preprint` is a **provenance** value and must never be used as an `access.type`. When the peer-reviewed version is later published with a different DOI, the corpus entry is updated to point at the canonical DOI (linking back to the pre-print as `source_history`).

### Books and book chapters

Expand Down
18 changes: 18 additions & 0 deletions governance/license_clarification/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -81,8 +81,26 @@ deployments:
status: open # open | responded | license-added | closed | wontfix
license_after: null # SPDX id if the repo later adds a license
last_checked: null # ISO date of last verify run
note: ... # optional prose; why an entry is settled. Preserved by verify.
```

### Before filing a wave: check for licences outside `LICENSE`

`verify` (and the original wave scan) resolve a licence through GitHub's SPDX
detection, which **only reads a `LICENSE`/`COPYING` file**. A project can be
properly licensed without one:

| Where the licence hides | Real example |
|---|---|
| Full licence text pasted into the `README` | `matteogiulietti/LipidOne` — BSD-2-Clause |
| `License:` field of an R package `DESCRIPTION` | `mariallr/amanida` — GPL-3 |
| `LICENSE.txt` (detected by GitHub, but not by a bare `LICENSE` glob) | `workflow4metabolomics/tools-metabolomics` — GPL-3 |

Filing against such a repo asks an author for something they already published.
This happened once (LipidOne, issue closed with an apology). Run
`scripts/readme_license_precheck.py` **before** `--create`, and treat a null
`licenseInfo` as *unknown*, never as *unlicensed*.

### Append-on-create

When `--create` is used, the script automatically appends one record per newly
Expand Down
25 changes: 16 additions & 9 deletions governance/license_clarification/deployments.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,20 @@ deployments:
wave: trial-2026-06-24
status: open
license_after: null
last_checked: '2026-06-24'
last_checked: '2026-07-10'
- repo: matteogiulietti/LipidOne
tool_names:
- LipidOne 2.0
doi: 10.1002/cpz1.70009
issue_url: https://github.com/matteogiulietti/LipidOne/issues/1
filed_on: '2026-06-24'
wave: trial-2026-06-24
status: open
license_after: null
last_checked: '2026-06-24'
status: closed
license_after: BSD-2-Clause
last_checked: '2026-07-10'
note: Already licensed when we filed. The two-clause FreeBSD text is embedded in
the README rather than a LICENSE file, so GitHub does not surface it and our LICENSE-file-only
scan missed it. Issue closed with an apology. Do not re-file.
- repo: Ansanqi/GutUDB
tool_names:
- GutUDB
Expand All @@ -29,24 +32,28 @@ deployments:
wave: trial-2026-06-24
status: open
license_after: null
last_checked: '2026-06-24'
last_checked: '2026-07-10'
- repo: MassBank/MassBank-data
tool_names:
- MassBank
doi: 10.1002/jms.1777
issue_url: https://github.com/MassBank/MassBank-data/issues/394
filed_on: '2026-06-24'
wave: trial-2026-06-24
status: open
status: closed
license_after: null
last_checked: '2026-06-24'
last_checked: '2026-07-10'
note: 'Answered by the maintainer: GPL-3 covers the MassBank software, but this
repository is a data collection whose records carry per-record licences (CC0 or
another Creative Commons variant). No single SPDX id applies, so license_after
stays null deliberately. Do not re-file.'
- repo: mariallr/easy-amanida
tool_names:
- Easy-Amanida
doi: 10.1002/jrsm.1713
issue_url: https://github.com/mariallr/easy-amanida/issues/2
filed_on: '2026-06-24'
wave: trial-2026-06-24
status: open
status: responded
license_after: null
last_checked: '2026-06-24'
last_checked: '2026-07-10'
101 changes: 101 additions & 0 deletions tests/test_oa_tier_parity.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,101 @@
"""The two open-access gates must admit exactly the same tiers.

`scripts/release_gate.py` is the release authority; `.github/workflows/verify-paper.yml`
gates pushes and PRs. When they disagree, one of them silently rejects (or admits) a
whole tier -- which is how every `repo-oa` paper came to fail CI on main while the
release gate passed them.

Both directions matter. Missing a tier the release gate admits turns CI red on valid
content; admitting a tier the release gate rejects lets non-open content reach the
public branch, and only fails later at release time.
"""

import ast
import pathlib
import re
import sys

import yaml

sys.path.insert(0, str(pathlib.Path(__file__).parent.parent))
from scripts import release_gate

REPO_ROOT = pathlib.Path(__file__).parent.parent
VERIFY_PAPER_WORKFLOW = REPO_ROOT / ".github" / "workflows" / "verify-paper.yml"
TIER_NAMES = {"OPEN_ACCESS_TYPES", "REPO_OA_TYPES"}


def _inline_gate_source() -> str:
"""Return the python heredoc embedded in the verify-paper access-tier step."""
workflow = yaml.safe_load(VERIFY_PAPER_WORKFLOW.read_text())
for job in workflow["jobs"].values():
for step in job.get("steps", []):
run = step.get("run") or ""
if "OPEN_ACCESS_TYPES" in run:
match = re.search(r"<<'([A-Z]+)'\n(.*?)\n\s*\1\s*$", run, re.S | re.M)
if match:
return match.group(2)
raise AssertionError("no verify-paper step defines OPEN_ACCESS_TYPES")


def _assignments_to(tree: ast.Module, name: str) -> list[ast.stmt]:
"""Every Assign/AugAssign binding `name`, at any nesting depth."""
found = []
for node in ast.walk(tree):
if isinstance(node, ast.Assign):
targets = node.targets
elif isinstance(node, ast.AugAssign):
targets = [node.target]
else:
continue
if any(isinstance(t, ast.Name) and t.id == name for t in targets):
found.append(node)
return found


def _workflow_open_access_types() -> set[str]:
"""Evaluate the workflow's OPEN_ACCESS_TYPES without executing the whole gate."""
tree = ast.parse(_inline_gate_source())
namespace: dict[str, object] = {}
for node in tree.body:
is_binding = isinstance(node, (ast.Assign, ast.AugAssign))
if is_binding and _binds_a_tier_name(node):
exec(compile(ast.Module([node], []), "<workflow>", "exec"), namespace)
return set(namespace["OPEN_ACCESS_TYPES"])


def _binds_a_tier_name(node: ast.stmt) -> bool:
targets = node.targets if isinstance(node, ast.Assign) else [node.target]
return any(isinstance(t, ast.Name) and t.id in TIER_NAMES for t in targets)


def _normalized_workflow_tiers() -> set[str]:
return {release_gate._normalize_access_type(t) for t in _workflow_open_access_types()}


def test_tier_sets_are_bound_only_at_module_level():
"""A nested rebind would diverge from what this test statically evaluates."""
tree = ast.parse(_inline_gate_source())
for name in TIER_NAMES:
for node in _assignments_to(tree, name):
assert node in tree.body, f"{name} rebound outside module level; parity test would go stale"


def test_workflow_admits_every_repo_oa_tier():
assert release_gate._REPO_OA_TIERS <= _workflow_open_access_types()


def test_workflow_admits_every_release_gate_tier():
assert release_gate._NORMALIZED_OA_TIERS <= _normalized_workflow_tiers()


def test_workflow_admits_nothing_the_release_gate_rejects():
assert _normalized_workflow_tiers() <= release_gate._NORMALIZED_OA_TIERS


if __name__ == "__main__":
test_tier_sets_are_bound_only_at_module_level()
test_workflow_admits_every_repo_oa_tier()
test_workflow_admits_every_release_gate_tier()
test_workflow_admits_nothing_the_release_gate_rejects()
print(f"PASS: gates agree on {len(_workflow_open_access_types())} access tiers")
Loading