diff --git a/.github/workflows/quality.yml b/.github/workflows/quality.yml index a87fc3a..e1e74ad 100644 --- a/.github/workflows/quality.yml +++ b/.github/workflows/quality.yml @@ -19,12 +19,13 @@ jobs: with: python-version: "3.13" - name: Install schema validator - run: python -m pip install jsonschema==4.25.1 + run: python -m pip install jsonschema==4.25.1 PyYAML==6.0.2 - name: Validate public artifacts run: | python -m json.tool codemeta.json >/dev/null python -m json.tool reproducibility/schema/trajectory.schema.json >/dev/null python scripts/check_local_links.py + python scripts/check_metadata_sync.py python scripts/audit_publication_safety.py --history python scripts/validate_experiment_002.py python reproducibility/scripts/validate.py diff --git a/.zenodo.json b/.zenodo.json new file mode 100644 index 0000000..57b3b1c --- /dev/null +++ b/.zenodo.json @@ -0,0 +1,30 @@ +{ + "title": "Nirmata: Trajectory-Aware Evaluation and Delegated Authority for AI Agents", + "upload_type": "software", + "description": "
Nirmata is an independent research program for evaluating AI agents from execution trajectories, delegated-authority events, tool calls, state transitions, and external effects in addition to final outputs.
No blind confirmatory result is claimed. See the evidence ledger in the repository for the current evidence boundary.
", + "creators": [ + { + "name": "Jahoda, Tabata", + "orcid": "0009-0007-1104-9204" + } + ], + "license": "cc-by-4.0", + "notes": "Dual-licensed. Source code and executable scripts: Apache License 2.0. Documentation, diagrams, research protocols, taxonomies, articles, and public datasets: Creative Commons Attribution 4.0 International. See LICENSE.md in the archive.", + "keywords": [ + "AI agents", + "agent evaluation", + "execution trajectories", + "delegated authority", + "agentic AI safety", + "human-in-the-loop", + "control integrity", + "reproducible evaluation" + ], + "related_identifiers": [ + { + "identifier": "https://github.com/thabatarj8-stack/nirmata", + "relation": "isSupplementTo", + "scheme": "url" + } + ] +} diff --git a/CITATION.cff b/CITATION.cff index ba2e9b8..df50929 100644 --- a/CITATION.cff +++ b/CITATION.cff @@ -9,6 +9,9 @@ authors: version: 0.1.0 date-released: 2026-08-27 doi: 10.5281/zenodo.22132451 +license: + - Apache-2.0 + - CC-BY-4.0 repository-code: "https://github.com/thabatarj8-stack/nirmata" url: "https://github.com/thabatarj8-stack/nirmata" abstract: >- diff --git a/codemeta.json b/codemeta.json index bcac717..fdef346 100644 --- a/codemeta.json +++ b/codemeta.json @@ -9,6 +9,10 @@ "dateModified": "2026-08-27", "codeRepository": "https://github.com/thabatarj8-stack/nirmata", "issueTracker": "https://github.com/thabatarj8-stack/nirmata/issues", + "license": [ + "https://spdx.org/licenses/Apache-2.0", + "https://spdx.org/licenses/CC-BY-4.0" + ], "programmingLanguage": ["Python", "Markdown", "JSON"], "keywords": [ "AI agents", diff --git a/scripts/check_metadata_sync.py b/scripts/check_metadata_sync.py new file mode 100755 index 0000000..47b1a3f --- /dev/null +++ b/scripts/check_metadata_sync.py @@ -0,0 +1,77 @@ +#!/usr/bin/env python3 +"""Fail when CITATION.cff, .zenodo.json, and codemeta.json disagree. + +Zenodo reads .zenodo.json instead of CITATION.cff when both exist, so a +field updated in only one file would be archived with stale metadata. +""" + +from __future__ import annotations + +import json +import sys +from pathlib import Path + +import yaml + + +ROOT = Path(__file__).resolve().parents[1] +ORCID_PREFIX = "https://orcid.org/" +SPDX_PREFIX = "https://spdx.org/licenses/" + + +def compare(failures: list[str], field: str, expected: object, actual: object, source: str) -> None: + if expected != actual: + failures.append(f"{field}: CITATION.cff has {expected!r}, {source} has {actual!r}") + + +def main() -> int: + citation = yaml.safe_load((ROOT / "CITATION.cff").read_text(encoding="utf-8")) + zenodo = json.loads((ROOT / ".zenodo.json").read_text(encoding="utf-8")) + codemeta = json.loads((ROOT / "codemeta.json").read_text(encoding="utf-8")) + + licenses = list(citation.get("license") or []) + authors = [ + (f"{a['family-names']}, {a['given-names']}", a.get("orcid", "").removeprefix(ORCID_PREFIX)) + for a in citation["authors"] + ] + failures: list[str] = [] + + compare(failures, "title", citation["title"], zenodo.get("title"), ".zenodo.json") + compare(failures, "keywords", citation.get("keywords"), zenodo.get("keywords"), ".zenodo.json") + compare( + failures, + "creators", + authors, + [(c.get("name"), c.get("orcid", "")) for c in zenodo.get("creators", [])], + ".zenodo.json", + ) + if zenodo.get("license", "").lower() not in {name.lower() for name in licenses}: + failures.append(f"license: .zenodo.json has {zenodo.get('license')!r}, not one of CITATION.cff {licenses!r}") + + compare(failures, "version", str(citation["version"]), codemeta.get("version"), "codemeta.json") + compare(failures, "doi", f"https://doi.org/{citation['doi']}", codemeta.get("identifier"), "codemeta.json") + compare( + failures, + "license", + licenses, + [url.removeprefix(SPDX_PREFIX) for url in codemeta.get("license", [])], + "codemeta.json", + ) + compare( + failures, + "authors", + [orcid for _, orcid in authors], + [p.get("@id", "").removeprefix(ORCID_PREFIX) for p in codemeta.get("author", [])], + "codemeta.json", + ) + + if failures: + print("Citation metadata out of sync:", file=sys.stderr) + print("\n".join(failures), file=sys.stderr) + return 1 + print("citation metadata sync: ok") + return 0 + + +if __name__ == "__main__": + raise SystemExit(main())