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
3 changes: 2 additions & 1 deletion .github/workflows/quality.yml
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
30 changes: 30 additions & 0 deletions .zenodo.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
{
"title": "Nirmata: Trajectory-Aware Evaluation and Delegated Authority for AI Agents",
"upload_type": "software",
"description": "<p>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.</p><p>No blind confirmatory result is claimed. See the evidence ledger in the repository for the current evidence boundary.</p>",
"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"
}
]
}
3 changes: 3 additions & 0 deletions CITATION.cff
Original file line number Diff line number Diff line change
Expand Up @@ -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: >-
Expand Down
4 changes: 4 additions & 0 deletions codemeta.json
Original file line number Diff line number Diff line change
Expand Up @@ -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",
Expand Down
77 changes: 77 additions & 0 deletions scripts/check_metadata_sync.py
Original file line number Diff line number Diff line change
@@ -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())
Loading