diff --git a/.ground-control.yaml b/.ground-control.yaml index 36c6585..9299d78 100644 --- a/.ground-control.yaml +++ b/.ground-control.yaml @@ -1,6 +1,6 @@ schema_version: 1 project: aces-adapters -github_repo: RAESystem/adapters +github_repo: OpenRAE/adapters workflow: test_command: make verify completion_command: make verify diff --git a/README.md b/README.md index 63a3561..17f0f09 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,8 @@ # raes-adapters [![Documentation](https://readthedocs.org/projects/raes-adapters/badge/?version=latest)](https://raes-adapters.readthedocs.io/en/latest/) -[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/RAESystem/adapters/badge)](https://scorecard.dev/viewer/?uri=github.com/RAESystem/adapters) -[![OpenSSF Best Practices](https://www.bestpractices.dev/projects?as=badge&url=https%3A%2F%2Fgithub.com%2FRAESystem%2Fadapters)](https://www.bestpractices.dev/projects?as=entry&url=https%3A%2F%2Fgithub.com%2FRAESystem%2Fadapters) +[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/OpenRAE/adapters/badge)](https://scorecard.dev/viewer/?uri=github.com/OpenRAE/adapters) +[![OpenSSF Best Practices](https://www.bestpractices.dev/projects?as=badge&url=https%3A%2F%2Fgithub.com%2FOpenRAE%2Fadapters)](https://www.bestpractices.dev/projects?as=entry&url=https%3A%2F%2Fgithub.com%2FOpenRAE%2Fadapters) A single distribution, **`raes-adapters`**, that qualifies and realizes [RAES](https://github.com/RAESystem/rae) scenarios against concrete simulator @@ -35,7 +35,7 @@ pip install raes-adapters # shared base plumbing + qualification evidence The selected CybORG backend is admitted and usable through its documented source installation. The `cyborg` extra key is dependency-light. Issue -[#12](https://github.com/RAESystem/adapters/issues/12) qualified the official +[#12](https://github.com/OpenRAE/adapters/issues/12) qualified the official CAGE Challenge 2 source and a packaging-only fix, but the upstream wheel omits the version and Scenario2 runtime data. The fixed wheel passed a clean Python 3.12 smoke locally, but it is not a governed public artifact and the @@ -57,6 +57,16 @@ profile. Because no official index/release artifact exists, users install the pinned simulator source separately. Unbound random streams and open benchmark findings remain explicit limits on deterministic-replay and outcome claims. +The `primaite` extra is dependency-light for the same reason. The +[qualification record](src/raes_adapters/primaite/qualification.json) binds +DSTL's MIT-licensed PrimAITE source (tag `v4.0.0`) and admits the selected +`data_manipulation` profile through the source-native `PrimaiteGymEnv`. PrimAITE +publishes no index/release wheel, so users install the pinned source separately +(the qualified route pins `setuptools==75.6.0` to supply `pkg_resources`). A +broken public seed seam, undeclared runtime dependencies, and an unpinned +dependency graph remain explicit limits on deterministic-replay and +reproducibility claims. + The `nasim` extra pins the published `nasim==0.12.0` distribution together with its qualified runtime (`gymnasium==0.26.3`, `numpy==1.26.4`), so installing `raes-adapters[nasim]` reproduces the admitted, runnable protocol. The @@ -127,6 +137,7 @@ raes-adapters/ cyborg/ # CybORG qualification, patch evidence, and future backend mapping/ # pinned CAGE-2 → RAES source ledger (REP-003) profiles/ # conformance profile overrides + primaite/ # immutable qualification + selected public protocol nasim/ # immutable NASim qualification + selected public protocol tests/ # pytest suite for the distribution release-please-config.json # Release Please: versioning + CHANGELOG from main @@ -150,7 +161,7 @@ backends land issue by issue: ## CyberBattleSim qualification -Issue [#25](https://github.com/RAESystem/adapters/issues/25) selects the +Issue [#25](https://github.com/OpenRAE/adapters/issues/25) selects the official Microsoft source at commit `854d6966607fb68645651f55b0f97221bd293e0d` and one public `CyberBattleChain-v0` protocol with the credential-cache baseline and basic @@ -161,7 +172,7 @@ semantics. The separate [architecture guardrails](docs/decisions/cyberbattlesim-qualification-guardrails.md) explain why this evidence is not an adapter manifest or RAES conformance claim. -Issue [#26](https://github.com/RAESystem/adapters/issues/26) authors the +Issue [#26](https://github.com/OpenRAE/adapters/issues/26) authors the portable evidence set for that case: an authored RAES SDL scenario (`scenario/cyberbattle-chain.sdl.yaml`) that validates and compiles against `raes==2.0.0`, companion published experiment contracts @@ -181,7 +192,7 @@ fix its boundaries. ## CyberBattleSim backend -Issue [#27](https://github.com/RAESystem/adapters/issues/27) implements a RAES +Issue [#27](https://github.com/OpenRAE/adapters/issues/27) implements a RAES runtime target for the admitted size-10 `CyberBattleChain-v0` profile: ```python @@ -233,7 +244,7 @@ The [backend architecture guardrails](docs/decisions/cyberbattlesim-backend-guar record the component ownership, failure hygiene, capability claims, and acceptance-test mapping. -Issue [#28](https://github.com/RAESystem/adapters/issues/28) composes that +Issue [#28](https://github.com/OpenRAE/adapters/issues/28) composes that runtime target with the published RAES conformance report and adapter-local source-protocol probes. The backend conformance result remains the exact `BackendConformanceReport` from RAES and is serialized only through the @@ -244,9 +255,35 @@ corpus, report schema, or research-validity claim. The [conformance-composition guardrails](docs/decisions/cyberbattlesim-conformance-guardrails.md) fix those boundaries. +## PrimAITE qualification + +Issue [#39](https://github.com/OpenRAE/adapters/issues/39) selects the ARCD +PrimAITE source at tag `v4.0.0` (commit +`98617981d7f6ae2c3ffd9a8cc39944e05c9a09ea`) and one public `data_manipulation` +protocol driven through the source-native Gymnasium entrypoint +`primaite.session.environment.PrimaiteGymEnv`. The shipped +[protocol](src/raes_adapters/primaite/public-protocol.md) fixes the exact +scenario, participants (BLUE `proxy-agent`, scripted RED, probabilistic GREEN), +`Discrete(78)` action space, flattened `Box(1652,)` observation, seed +obligations, metrics, and the fixed-horizon truncation semantics (`terminated` +is always false; the episode truncates at `max_episode_length=128`). + +The [qualification record](src/raes_adapters/primaite/qualification.json) binds +the source identity, the canonical import-root digest (which matches the built +wheel exactly), the MIT/Crown-copyright legal disposition, the clean-install and +bounded do-nothing smoke, the resolved dependency graph and its permissive +license summary, and the maintainer admission with graded claim strength. It +also records the source's honest limitations: no index/release wheel, an +undeclared `pkg_resources`/setuptools runtime dependency (the qualified route +pins `setuptools==75.6.0`), a public seed seam that raises without the `rl`/torch +stack, a `requires-python` vs classifier inconsistency, and an unpinned upstream +dependency graph. The +[qualification guardrails](docs/decisions/primaite-qualification-guardrails.md) +explain why this evidence is not an adapter manifest or RAES conformance claim. + ## CybORG/CAGE-2 runtime qualification -Issue [#12](https://github.com/RAESystem/adapters/issues/12) selects the +Issue [#12](https://github.com/OpenRAE/adapters/issues/12) selects the official CAGE Challenge 2 repository at commit `26ce1c1253fa9e2e73f25e6a7f2da32860c11257`, including its bundled CybORG 2.1, Scenario2, evaluator, wrappers, and baseline agents as one source closure. The @@ -256,7 +293,7 @@ and sanitized red/blue/green smoke result. The accompanying [packaging patch](src/raes_adapters/cyborg/cage2-wheel-package-data.patch) is qualification evidence only; it is not silently applied or published. -Issue [#15](https://github.com/RAESystem/adapters/issues/15) supplies the +Issue [#15](https://github.com/OpenRAE/adapters/issues/15) supplies the provisioning path for that backend. `create_cyborg_target()` accepts admitted RAES provisioning plans and deterministically generates the native CybORG scenario: RAES switches become subnets, VM multiplicity becomes hosts, @@ -266,7 +303,7 @@ configuration-bound realization-envelope identity remain in the RAES snapshot; native CybORG objects stay private. Unsupported or lossy node facts fail before construction. -Issue [#16](https://github.com/RAESystem/adapters/issues/16) adds aggregate +Issue [#16](https://github.com/OpenRAE/adapters/issues/16) adds aggregate logical-turn execution. A validated blue action is translated by exact contract address and drives one source-native turn; the resulting blue, green, and red occurrences are recorded in declared source order with shared-state, joint-action, @@ -317,7 +354,7 @@ deterministic replay or scientific equivalence. See the ## NASim qualification -Issue [#32](https://github.com/RAESystem/adapters/issues/32) selects Jonathon +Issue [#32](https://github.com/OpenRAE/adapters/issues/32) selects Jonathon Schwartz's official [NASim](https://github.com/Jjschwartz/NetworkAttackSimulator) source at tag `v0.12.0` (commit `7c732bc4620d20a25b221a782adee29c2a89d800`, published as diff --git a/docs/decisions/identity-register.yaml b/docs/decisions/identity-register.yaml index 96ec905..a2ce69b 100644 --- a/docs/decisions/identity-register.yaml +++ b/docs/decisions/identity-register.yaml @@ -26,7 +26,7 @@ # granting a new exception is a reviewed edit to this file. entries: - path: .ground-control.yaml - digest: de1c598e28e28cbc1f336cf23a4549eb7ed214812b1ad04f2b7323741abd8e33 + digest: 63d6cedefd87fe724b1d587bfe750d38f7d55610879124b587f1184544a77ee5 record_class: external-identity owner: ground-control rationale: >- diff --git a/docs/decisions/primaite-qualification-guardrails.md b/docs/decisions/primaite-qualification-guardrails.md new file mode 100644 index 0000000..daf91bc --- /dev/null +++ b/docs/decisions/primaite-qualification-guardrails.md @@ -0,0 +1,109 @@ +# PrimAITE qualification guardrails + +Issue #39 is the authority for the qualification outcome. This note fixes the +repository and contract boundaries that outcome must respect; it does not select +an upstream revision, define a profile schema, or describe an implementation +plan. + +Maintainer selection admits the PrimAITE source and public experiment profile. +Qualification records what RAES can attest or reproduce and explicitly grades +any limitation. It does not veto later adapter work or make RAES invent missing +simulator behavior. + +## Keep the artifacts and claims separate + +Place qualification evidence under `raes_adapters.primaite`. It is backend-local +package data, distinct from all of the following: + +- a public, source-native experiment protocol, which names the chosen use case, + YAML configuration, red/green/blue agents, traffic model, action and + observation spaces, evaluator, rewards, seeds, metrics, and termination; +- a future RAES SDL, published experiment contract, backend manifest, or + conformance profile, each of which is owned by published RAES contracts and + must be supported by later implementation evidence; and +- CybORG and CyberBattleSim qualification records and ledgers, which are + precedents for local ownership rather than schemas to copy or generalize. + +The record must bind the official repository, full commit and tree ids, archive +digest, installable distribution/wheel identity, selected files and symbols, +resolved dependency graph, Python and host identity, and every observed or +configured default that changes behavior. A tag, release label, use-case name, +notebook title, package version, or YAML filename alone is not immutable. + +Qualification must separately identify the unmodified source and every patch. +For a compatibility patch, record the patch and resulting tree/artifact +digests, purpose, license, semantic effect, and a bounded comparison with the +unmodified source. A dependency override, monkey patch, edited example, copied +file, or setup-time mutation that is not recorded is a prohibited silent patch. + +Record license, notices/attribution, redistribution and retained-output rights, +external downloads, datasets, model weights, cache behavior, archive/ +maintenance state, known upstream defects, and the evidence supporting each +conclusion. Refer to large upstream artifacts by immutable identity and digest; +do not vendor them unless necessary and explicitly permitted. + +## Reuse the canonical boundaries + +| Concern | Canonical incumbent | Required use | +| --- | --- | --- | +| Backend-local qualification evidence | `raes_adapters.cyborg.qualification` and `raes_adapters.cyberbattlesim` | Use package resources and small accessors; do not create a shared profile loader, DTO, registry, or schema. | +| Packaging and isolation | `pyproject.toml`, one `uv.lock`, ADR-003, `_extras()` and `_verification_envs()` in `noxfile.py` | Add only `primaite`; keep base and every extra independently resolvable. Add a uv `conflicts` declaration only for a demonstrated, recorded incompatibility. | +| Clean built-artifact proof | `_distributions()` and `tools/probe_installed_identity.py` | Extend the existing wheel/sdist, throwaway-venv proof rather than adding a second install workflow. The native smoke runs outside the checkout with `PYTHONPATH` cleared and safe-path/isolation enabled. | +| Portable experiment artifacts | `ExperimentTaskModel`, `ExperimentSpecModel`, `parse_experiment_spec`, `ContractModel` (`extra="forbid"`), and RAES cross-artifact validation | Use only if machine-readable RAES protocol/evidence is emitted; do not define local YAML/JSON schemas, permissive parsing, or duplicate validation. | +| Artifact/provenance identity | `ExperimentArtifactRefModel`, `ExperimentChecksumModel`, `ExperimentScenarioSnapshotReferenceModel`, and apparatus models | Use published meanings and checksum forms when source identity is referenced in RAES artifacts; keep legal and qualification metadata backend-local. | +| Stochastic control | `ExperimentStochasticControlModel`, `PublicSeedModel`, and `RandomStreamControlBindingModel` | Record simulator, use-case/topology, traffic, scheduler, action/observation-space, red/green/blue policy, evaluator, Python, NumPy and library RNGs separately. A top-level seed is not evidence of complete control. | +| Metrics and outcomes | `ExperimentEvaluationProtocolModel`, `ExperimentMetricDefinitionModel`, `ExperimentEvidenceRecordModel`, and `ExperimentDerivedMeasureModel` | Keep native reward, evaluator output, study metric, evidence, and derived measure distinct. | +| Failures and diagnostics | RAES `Diagnostic`, `DiagnosticModel`, `ApplyResult`, plus `raes_adapters.base.redaction` | Later portable errors use bounded, input-free diagnostics; never carry native exception text, rejected values, raw observations, reward vectors, action ids, object reprs, paths, env dumps, or tracebacks. | +| Durable state and workflow | Checked-in package resources, ephemeral temp directories, and the `policy`, `typecheck`, `tests`, `distributions`, `docs`, and `verify` nox sessions | Do not add a database, cache authority, evidence service, standalone validator, or CI workflow. | + +`raes_adapters.base` remains plumbing only. This issue does not justify a +generic simulator protocol, qualification exception hierarchy, backend catalog, +schema registry, or shared persistence layer. + +## Native smoke, security, and observability + +The source-native smoke precedes adapter normalization. From the documented +supported route it must construct the selected case, reset with the selected +seed, issue one representative valid action/step, and record bounded facts about +the returned observation and reward/result shapes. It must exercise a bounded +path to every selected termination behavior and record their identity, +precedence, and off-by-one semantics. It must also disclose undisclosed +defaults, incompatibilities, runtime downloads/writes, network access, +subprocesses, and stochastic sources. + +This is a local library execution: no HTTP, authentication, authorization, or +secret-binding surface is introduced. Public sources only; no credentials, +private downloads, token-bearing argv, environment dumps, or home-cache +evidence. Use argument vectors, explicit temporary/cache paths, isolated +working directories, frozen resolution, and bounded structural output. A later +runtime service must use RAES runtime strict defaults, verified identities, +target/role authorization, request-size guards, denial audit, and its redacted +exception handler; qualification creates no alternate path. + +Use ordinary module logging only for bounded local operational facts. Portable +observability belongs to RAES diagnostics and experiment-evidence contracts. +Native state, traffic contents, hidden truth, learned credentials, full action +or observation payloads, raw logs, tracebacks, and full reward vectors remain +source-private. A digest of a native-state dump does not make it portable. + +## Extension seam and boundaries + +The one extension seam is an explicit module-local immutable selection identity +passed to the source-native smoke runner. Source paths, use case, YAML files, +agents, traffic configuration, evaluator, seed, representative action, metric, +and termination bounds come from that selection, not repeated across tests, +scripts, notebooks, and prose. A second public PrimAITE profile must be +addable as another selection without editing a central registry or altering the +first profile. + +Non-goals: adapter semantics, RAES SDL, backend manifest, conformance profile, +participant implementation, persistence, environment-pack content, and any +claim of deterministic replay, scientific validity, or outcome equivalence. + +Do not treat a successful import, notebook, one episode, or green CI as proof +of reproducibility, legal usability, public installability, determinism, or +equivalence. Do not conflate source-native `done`/`terminated`/`truncated`, +scenario success/failure, reward thresholds, evaluator cutoffs, max steps, and +participant stop conditions. Do not lower the repository Python boundary, hide +an incompatibility in an environment marker, make PrimAITE a base dependency, +or use another simulator extra to satisfy it transitively. diff --git a/docs/index.md b/docs/index.md index 8aca477..7b7ec4f 100644 --- a/docs/index.md +++ b/docs/index.md @@ -17,9 +17,9 @@ semantic and protocol authority. ## Start here -- [Repository overview](https://github.com/RAESystem/adapters#readme) +- [Repository overview](https://github.com/OpenRAE/adapters#readme) - [Installed researcher command](researcher-command.md) -- [Contribution guide](https://github.com/RAESystem/adapters/blob/dev/CONTRIBUTING.md) +- [Contribution guide](https://github.com/OpenRAE/adapters/blob/dev/CONTRIBUTING.md) - [Architecture decisions](decisions/adrs/README.md) - [CybORG/CAGE-2 backend qualification guardrails](decisions/cyborg-cage2-runtime-qualification-guardrails.md) - [CybORG/CAGE-2 source-ledger guardrails](decisions/cyborg-cage2-source-ledger-guardrails.md) @@ -27,6 +27,7 @@ semantic and protocol authority. - [CybORG conformance-composition guardrails](decisions/cyborg-conformance-guardrails.md) - [CybORG researcher run-and-evidence command guardrails](decisions/cyborg-researcher-command-guardrails.md) - [CyberBattleSim qualification guardrails](decisions/cyberbattlesim-qualification-guardrails.md) +- [PrimAITE qualification guardrails](decisions/primaite-qualification-guardrails.md) - [CyberBattleSim scenario and source-ledger guardrails](decisions/cyberbattlesim-scenario-ledger-guardrails.md) - [CyberBattleSim backend architecture guardrails](decisions/cyberbattlesim-backend-guardrails.md) - [CyberBattleSim conformance-composition guardrails](decisions/cyberbattlesim-conformance-guardrails.md) diff --git a/docs/maintainers/project-services.md b/docs/maintainers/project-services.md index 0c23775..999bab4 100644 --- a/docs/maintainers/project-services.md +++ b/docs/maintainers/project-services.md @@ -3,7 +3,7 @@ The repository-owned configuration for each service lives in the default branch. Service-side settings must match these identifiers. -- GitHub repository: [`RAESystem/adapters`](https://github.com/RAESystem/adapters) +- GitHub repository: [`OpenRAE/adapters`](https://github.com/OpenRAE/adapters) - Read the Docs project: `raes-adapters` - PyPI distribution name: `raes-adapters` — the single published distribution (shared base plumbing plus optional per-simulator extras, e.g. @@ -12,12 +12,12 @@ branch. Service-side settings must match these identifiers. - PyPI Trusted Publisher workflow: `release-please.yml` - PyPI environment: `pypi` - SonarCloud project key: `RAESystem_adapters` -- OpenSSF Scorecard URI: `github.com/RAESystem/adapters` -- OpenSSF Best Practices lookup: `https://github.com/RAESystem/adapters` +- OpenSSF Scorecard URI: `github.com/OpenRAE/adapters` +- OpenSSF Best Practices lookup: `https://github.com/OpenRAE/adapters` Publication uses PyPI Trusted Publishing over GitHub OIDC (no stored API token): configure the `raes-adapters` Trusted Publisher for repository -`RAESystem/adapters`, workflow `release-please.yml`, and environment `pypi`. +`OpenRAE/adapters`, workflow `release-please.yml`, and environment `pypi`. Provision a `RELEASE_PLEASE_TOKEN` repository secret — a GitHub App installation token or a fine-grained PAT with `contents` + `pull-requests` write — so the diff --git a/mkdocs.yml b/mkdocs.yml index 23999cb..269dfcf 100644 --- a/mkdocs.yml +++ b/mkdocs.yml @@ -1,8 +1,8 @@ site_name: RAES Adapters site_description: Adapter implementations for reproducible agentic environments. site_url: !ENV [READTHEDOCS_CANONICAL_URL, "https://raes-adapters.readthedocs.io/"] -repo_url: https://github.com/RAESystem/adapters -repo_name: RAESystem/adapters +repo_url: https://github.com/OpenRAE/adapters +repo_name: OpenRAE/adapters edit_uri: edit/dev/docs/ docs_dir: docs @@ -47,6 +47,7 @@ nav: - CybORG conformance-composition guardrails: decisions/cyborg-conformance-guardrails.md - CybORG researcher run-and-evidence command guardrails: decisions/cyborg-researcher-command-guardrails.md - CyberBattleSim qualification guardrails: decisions/cyberbattlesim-qualification-guardrails.md + - PrimAITE qualification guardrails: decisions/primaite-qualification-guardrails.md - NASim qualification guardrails: decisions/nasim-qualification-guardrails.md - CyberBattleSim scenario and source-ledger guardrails: decisions/cyberbattlesim-scenario-ledger-guardrails.md - CyberBattleSim backend architecture guardrails: decisions/cyberbattlesim-backend-guardrails.md diff --git a/pyproject.toml b/pyproject.toml index 58f882e..dfe979a 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -33,7 +33,7 @@ classifiers = [ "Programming Language :: Python :: 3.12", "Typing :: Typed", ] -dependencies = ["raes==2.0.0"] +dependencies = ["raes==3.3.0"] [project.optional-dependencies] # Microsoft publishes the selected CyberBattleSim source but no index or @@ -41,11 +41,18 @@ dependencies = ["raes==2.0.0"] # install the pinned simulator source separately and target construction # reports its absence without weakening the base installation. cyberbattlesim = [] +# DSTL publishes the selected PrimAITE source (tag v4.0.0) but no index or +# release wheel. Its pins (gymnasium==0.28.1, numpy~=1.23, pydantic==2.7.0) are +# mutually incompatible with the resolved cyberbattlesim/base graph, so the +# adapter extra is dependency-light: users install the pinned source separately +# and keep those pins out of the single lock. No uv `conflicts` declaration is +# needed while the extra is empty. +primaite = [] # The environment-pack validator is installable. The native backend remains on # the documented source-install route because the official CybORG wheel still # lacks the qualified packaging-data fix; the command reports that limitation # instead of cloning source or advertising an ungoverned wheel. -cyborg = ["raes-env-packs==3.2.0"] +cyborg = ["raes-env-packs==3.6.2"] # NASim publishes an index distribution, so the extra pins it directly, together # with the qualified runtime (gymnasium 0.26.3 / numpy 1.26.4) so that installing # the extra reproduces the admitted, runnable protocol rather than a newer @@ -59,11 +66,11 @@ nasim = ["nasim==0.12.0", "gymnasium==0.26.3", "numpy==1.26.4"] raes-adapters = "raes_adapters.cli:main" [project.urls] -Homepage = "https://github.com/RAESystem/adapters" +Homepage = "https://github.com/OpenRAE/adapters" Documentation = "https://raes-adapters.readthedocs.io" -Repository = "https://github.com/RAESystem/adapters" -Issues = "https://github.com/RAESystem/adapters/issues" -Changelog = "https://github.com/RAESystem/adapters/blob/main/CHANGELOG.md" +Repository = "https://github.com/OpenRAE/adapters" +Issues = "https://github.com/OpenRAE/adapters/issues" +Changelog = "https://github.com/OpenRAE/adapters/blob/main/CHANGELOG.md" [build-system] requires = ["hatchling"] diff --git a/src/raes_adapters/cyborg/qualification.json b/src/raes_adapters/cyborg/qualification.json index 42c0810..94a826b 100644 --- a/src/raes_adapters/cyborg/qualification.json +++ b/src/raes_adapters/cyborg/qualification.json @@ -369,7 +369,7 @@ "python": "3.12.3", "requirements_sha256": "a930508faee1766fb685ffa2fdd924121fdd0c056c92144850d20383951ba301", "normalized_environment_sha256": "56e2c189d6fd4b195584f34d93f66500b30eba8eedff6fca869ed61a8ab4079a", - "uv_lock_sha256": "3ce9977df839c17bf6f817d40b89cd0bc874f03d1c91330e6909c5adcfba7dcf", + "uv_lock_sha256": "141df24b3118815584c1423f8bdd07b7997f7882cc30082efddde0bf982de9fe", "candidate_resolution": "passed", "canonical_lock_disposition": "The current single uv.lock intentionally excludes the unpublished candidate CybORG artifact; the resolved environment is qualification evidence, not a second lockfile." }, diff --git a/src/raes_adapters/primaite/__init__.py b/src/raes_adapters/primaite/__init__.py new file mode 100644 index 0000000..c8666cd --- /dev/null +++ b/src/raes_adapters/primaite/__init__.py @@ -0,0 +1,14 @@ +"""PrimAITE qualification evidence. + +This module carries the immutable source qualification and selected public +experiment protocol for issue #39. It does not implement an adapter, backend +manifest, conformance profile, or RAES semantic model. +""" + +from __future__ import annotations + +from raes_adapters._qualification import backend_evidence_loaders + +__all__ = ["load_qualification", "read_public_protocol"] + +load_qualification, read_public_protocol = backend_evidence_loaders(__name__) diff --git a/src/raes_adapters/primaite/public-protocol.md b/src/raes_adapters/primaite/public-protocol.md new file mode 100644 index 0000000..70a7748 --- /dev/null +++ b/src/raes_adapters/primaite/public-protocol.md @@ -0,0 +1,138 @@ +# PrimAITE public experiment protocol + +## Status and scope + +This is the one selected source-native protocol for the PrimAITE qualification +in RAESystem/adapters issue #39. It is backend-local evidence, not a +RAES-authored scenario, adapter manifest, conformance profile, or claim of +equivalence. The maintainer-selected profile is admitted for adapter +realization. The associated qualification record fixes the source identity and +bounds the attainable claims; its packaging, dependency, stochastic-control, and +upstream defect limitations do not veto execution. + +The source is the ARCD +[`Autonomous-Resilient-Cyber-Defence/PrimAITE`](https://github.com/Autonomous-Resilient-Cyber-Defence/PrimAITE) +repository at tag `v4.0.0`, commit +[`98617981d7f6ae2c3ffd9a8cc39944e05c9a09ea`](https://github.com/Autonomous-Resilient-Cyber-Defence/PrimAITE/tree/98617981d7f6ae2c3ffd9a8cc39944e05c9a09ea). +All paths and symbols below resolve at that commit. + +## Selected apparatus + +| Role | Exact selection | +| --- | --- | +| Distribution metadata | `primaite==4.0.0` from `pyproject.toml`; no public index distribution or upstream release wheel exists (the README documents wheel-from-GitHub install only) | +| Python | CPython 3.11 (upstream classifiers and README support `>=3.9,<3.12`; the `requires-python` metadata says `<3.13`) | +| Gymnasium entrypoint | `primaite.session.environment.PrimaiteGymEnv` | +| Use case / scenario | `primaite/config/_package_data/data_manipulation.yaml` (`metadata.version 3.0`, `game.max_episode_length 128`) | +| RL-controlled seat (BLUE) | agent `defender`, `type: proxy-agent` | +| RED participant | agent `data_manipulation_attacker`, `type: red-database-corrupting-agent` (scripted) | +| GREEN participants | agents `client_1_green_user`, `client_2_green_user`, `type: probabilistic-agent` | +| Baseline participant | a scriptable policy over the BLUE action space; the do-nothing baseline (`action 0`) is used for the smoke | + +The core install **without** the `rl` extra is selected. It requires no Ray +RLlib, TensorFlow, StableBaselines3, or deep-learning weights, and is the +lightest documented supported route. The RL training path behind the `rl` extra +is not part of this qualified protocol. + +## Exact configuration and spaces + +The scenario is loaded verbatim from the shipped +`data_manipulation.yaml`; its `sha256` is pinned in the qualification record. +The environment is constructed as: + +```text +PrimaiteGymEnv(env_config="/primaite/config/_package_data/data_manipulation.yaml") +``` + +At the selected commit the BLUE seat exposes: + +```text +action_space = Discrete(78) +observation_space = Box(shape=(1652,), dtype=int64, low=0, high=1) # flatten_obs: true +``` + +`reset()` returns `(observation, info)` (arity 2); `observation` is a NumPy +`ndarray` of shape `(1652,)` dtype `int64` and `info` is an empty dict. +`step(action)` returns `(observation, reward, terminated, truncated, info)` +(arity 5); `reward` is a Python `float`. Native observations, traffic contents, +learned state, action ids, full reward vectors, and complete `info` values +remain inside the source-native runner; portable evidence retains only bounded +types, shapes, dtypes, booleans, scalar measures, and stable digests. + +## Seeds and stochastic control + +The public seed seam is `PrimaiteGymEnv.reset(seed=)`, which calls +`primaite.session.environment.set_random_seed(seed, generate_seed_value)` and +seeds Python `random`, the global NumPy generator, and (when present) `torch`. +The selected public seed is decimal `20260802`. + +This seam is **not usable on the light route**. `set_random_seed` dereferences +`sys.modules["torch"]` unconditionally, so `reset(seed=)` raises +`KeyError('torch')` unless the `rl`/torch stack is installed. In addition, each +GREEN `ProbabilisticAgent` seeds its own generator from the uncontrolled global +NumPy RNG (`numpy.random.default_rng(numpy.random.randint(0, 65535))`), and the +scenario sets no `game.seed`. A conforming future runner must therefore treat +the following as separate, currently uncontrolled stochastic sources: + +1. the Gymnasium reset seam `reset(seed=...)` (broken without torch); +2. Python `random` (scripted RED timing); +3. the global NumPy generator (GREEN policy generators, and the env + construction seed path); +4. `torch` (only when the `rl` stack is installed). + +No result from this protocol may be called deterministic or replayable until the +torch seam is fixed or an explicit, identified compatibility patch is qualified. +The bounded smoke below was nonetheless observed identical across repeated +fresh-process runs; that is empirical stability of the do-nothing smoke, not a +seed-controlled determinism claim for RL or varied-action episodes. + +## Reset, action, result, and termination + +The representative smoke action is the do-nothing action (`action 0`). From an +unseeded reset it returns, at the first step, a scalar reward of `0.65`, +`terminated=false`, `truncated=false`, and `step_count=1`. This value proves the +pinned smoke only; it is not an experiment result or study metric. + +`step()` sets `terminated=False` unconditionally. The episode ends **only** by +truncation: `PrimaiteGame.calculate_truncated()` returns `True` when +`step_counter >= max_episode_length`. For this scenario the horizon is `128` +steps. A full do-nothing episode therefore ends after `128` steps with +`truncated=true`, `terminated=false`, a final step reward of `-0.8`, and a +cumulative reward of `-57.05`. + +There is no source-native success/failure `terminated` condition in this +scenario: goal satisfaction, reward thresholds, an evaluator cutoff, the fixed +horizon, and participant stop conditions are distinct and must not be conflated. +Only the fixed-horizon truncation is present here. + +## Measures and retained output + +The primary measures are the per-step BLUE reward and its per-episode cumulative +series over the fixed 128-step horizon, together with the terminal cause +(`fixed-horizon-truncation`). Reward, terminal result, and any derived measure +remain distinct; a cumulative reward is not automatically the study metric. No +native object representation, raw observation, traffic payload, hidden state, +action identifier, full reward vector, environment dump, argument dump, token, or +traceback is retained in a portable RAES artifact. + +PrimAITE writes session, agent-action, and system logs under a +platform-dependent home/app directory on import and during a run. These are +source-private operational outputs; the smoke isolates them under a temporary +`HOME` and does not retain them as RAES evidence. + +## Network, files, and patches + +After source acquisition and dependency installation, execution requires no +external datasets, downloads, caches, or model weights, and performs no network +access. The protocol uses a temporary working directory and isolated `HOME`, +clears `PYTHONPATH`, enables Python safe-path behavior, and performs no +checkout-relative import. + +No compatibility patch, monkey patch, dependency override, edited scenario, or +copied upstream source is part of this selection; the `patches` list in the +qualification record is empty. Pinning `setuptools==75.6.0` (the upstream `dev` +pin) to supply the `pkg_resources` module the runtime imports is an environment +provisioning choice, not a source modification. Any future patch must record the +base commit, patch digest, resulting-tree or artifact digest, purpose, license, +semantic effect, and side-by-side unmodified behavior before this protocol can +adopt it. diff --git a/src/raes_adapters/primaite/qualification.json b/src/raes_adapters/primaite/qualification.json new file mode 100644 index 0000000..4bc0795 --- /dev/null +++ b/src/raes_adapters/primaite/qualification.json @@ -0,0 +1,1096 @@ +{ + "record_version": "primaite-qualification/1", + "qualified_at": "2026-08-02", + "scope": "RAESystem/adapters#39", + "source": { + "repository": "https://github.com/Autonomous-Resilient-Cyber-Defence/PrimAITE", + "tag": "v4.0.0", + "tag_object": "f6513362e9882217fb90595de51e7ca659ee1416", + "commit": "98617981d7f6ae2c3ffd9a8cc39944e05c9a09ea", + "tree": "3928ab452c51206ba8706b3f575e31fe99eb842e", + "package": "primaite", + "version": "4.0.0", + "requires_python": ">=3.9,<3.13", + "public_index_distribution": null, + "index_note": "No PyPI distribution or upstream release wheel exists; the README documents wheel-from-GitHub install only." + }, + "source_files": [ + { + "path": "LICENSE", + "sha256": "669b434561703dbfa093fe2c02c2569bcfe3347caf339e5d42643e4c1b4b76e8" + }, + { + "path": "pyproject.toml", + "sha256": "395aac38b3c5ad3c9b753de7f393eb728ae61def16da29728d1850ce6f841a1d" + }, + { + "path": "setup.py", + "sha256": "44fd8461dc126f7afd361f79cc7691ac63bd1eae372d67f3d7f79e7b29a44904" + }, + { + "path": "setup.cfg", + "sha256": "e27820e2ecf571875a202008dd09fbe9d65106484994a5afe22db8e4822ef030" + }, + { + "path": "MANIFEST.in", + "sha256": "18c40c8b5802e5266f4c6529432369f48a0606db2daf92a7e3aad78c3083a63f" + }, + { + "path": "README.md", + "sha256": "2c5fb5e1378fe4ec3c64520aced07ba7c05b3bdbb7609c9dc5c55b9b40ab353c" + }, + { + "path": "CHANGELOG.md", + "sha256": "1aefc7638836252426d73aef97aa558693f307138c549bd18c574672a3b355cd" + }, + { + "path": "src/primaite/VERSION", + "sha256": "e1d49c569ae09b2ede16e2d83820a3809b35eb6c44c13ba9b5330598d6b8f43c" + }, + { + "path": "src/primaite/__init__.py", + "sha256": "4e387d180c99a456364afb0a61e49b937dfcc8352278b1929250f4d4b0def9ea" + }, + { + "path": "src/primaite/cli.py", + "sha256": "82b0ab66eedc99c08b8bb7157ecff323f583f42c4e2c2e6d18cb5a5762ecf378" + }, + { + "path": "src/primaite/session/environment.py", + "sha256": "2ccb132667e4b9de5bd90c2709aea24ae800748ccdc429f05476e47a4fc27773" + }, + { + "path": "src/primaite/game/game.py", + "sha256": "ccfbc17fdb903e9bc96211c8542572998deab6dc7d41a34306c8b18b22b35c09" + }, + { + "path": "src/primaite/game/agent/scripted_agents/probabilistic_agent.py", + "sha256": "16bbe2fa0dc5cf022caaaeb6ef0dc7bddb8b11f03a4a10d81ab3d7bbcb67999b" + }, + { + "path": "src/primaite/config/_package_data/data_manipulation.yaml", + "sha256": "73c93965a32783f72f272993df4d65ab9bfcf670ba761503985199ff7e6debd6" + } + ], + "runtime_source_tree": { + "root": "primaite", + "include": "all regular files under src/primaite; __pycache__/*.pyc excluded; symlinks and non-regular files rejected", + "canonicalization": "Sorted POSIX path, NUL, lowercase SHA-256 of raw file bytes, LF.", + "file_count": 234, + "sha256": "9fe7ba158a005ead6133769336ff66e95da38c1cfe5054954be38d16d7f54dcc", + "installed_wheel_tree_matches_source": true + }, + "runtime_artifacts": [ + { + "name": "primaite", + "version": "4.0.0", + "artifact": { + "filename": "primaite-4.0.0-py3-none-any.whl", + "sha256": "06df7275d2e5334a041e075695f9ce465789989d7bcf0a7696145fecc417c926", + "require_direct_archive_sha256": false, + "runtime_identity": "complete-root-tree" + }, + "roots": [ + { + "path": "primaite", + "file_count": 234, + "sha256": "9fe7ba158a005ead6133769336ff66e95da38c1cfe5054954be38d16d7f54dcc" + } + ], + "sdist": { + "filename": "primaite-4.0.0.tar.gz", + "sha256": "1f5cf05b831687012355436efe6172fbdf1565a69bddeba759d511429b4ae844" + }, + "build": "Built from the pinned source. Wheel/sdist bytes are not byte-reproducible, so runtime identity rests on commit + tree + the canonical import-root digest, not the archive bytes." + } + ], + "protocol": { + "resource": "public-protocol.md", + "sha256": "90b116ab86f3623fe5237b65500176cb4e46c71ee66ec6f849be0e02adee552e", + "selection": { + "scenario": { + "use_case": "data_manipulation", + "config_path": "primaite/config/_package_data/data_manipulation.yaml", + "config_sha256": "73c93965a32783f72f272993df4d65ab9bfcf670ba761503985199ff7e6debd6", + "config_metadata_version": "3.0", + "max_episode_length": 128 + }, + "entrypoint": "primaite.session.environment.PrimaiteGymEnv", + "participants": { + "blue": { + "ref": "defender", + "type": "proxy-agent", + "role": "rl-controlled" + }, + "red": { + "ref": "data_manipulation_attacker", + "type": "red-database-corrupting-agent", + "role": "scripted" + }, + "green": [ + { + "ref": "client_1_green_user", + "type": "probabilistic-agent" + }, + { + "ref": "client_2_green_user", + "type": "probabilistic-agent" + } + ] + }, + "baseline_participant": "do-nothing (action 0) over the BLUE action space", + "action_space": { + "type": "Discrete", + "n": 78 + }, + "observation_space": { + "type": "Box", + "shape": [ + 1652 + ], + "dtype": "int64", + "low": 0.0, + "high": 1.0, + "flattened": true + }, + "reward_type": "float", + "seed": 20260802, + "termination": { + "terminated": "always false (hardcoded in step())", + "truncated_rule": "step_counter >= max_episode_length", + "max_episode_length": 128, + "terminal_cause": "fixed-horizon-truncation" + }, + "metrics": [ + "per-step-blue-reward", + "cumulative-blue-reward-per-episode", + "steps-to-fixed-horizon-truncation", + "terminal-cause" + ] + } + }, + "runtime": { + "python": "3.11.15", + "platform": "Linux-6.8.0-117-generic-x86_64-with-glibc2.39", + "build": "unmodified official git checkout wheel; no-rl extra", + "install_route": "Build the wheel from the pinned source, install it (no rl extra) with setuptools==75.6.0 into a fresh venv, run under an isolated HOME; import creates the PrimAITE app directories.", + "wheel": "primaite-4.0.0-py3-none-any.whl", + "wheel_sha256": "06df7275d2e5334a041e075695f9ce465789989d7bcf0a7696145fecc417c926", + "setuptools": "75.6.0", + "numpy": "1.26.4", + "gymnasium": "0.28.1", + "clean_install": "passed", + "working_directory": "isolated-temporary-directory", + "home": "isolated-temporary-directory", + "pythonpath": "cleared", + "python_safe_path": true, + "network_after_install": "not-required", + "smoke": { + "env_class": "PrimaiteGymEnv", + "is_gymnasium_env": true, + "reset_arity": 2, + "step_arity": 5, + "reset_obs_type": "ndarray", + "reset_obs_shape": [ + 1652 + ], + "reset_obs_dtype": "int64", + "reset_info_empty": true, + "reward_type": "float", + "terminated_type": "bool", + "truncated_type": "bool", + "seeded_reset": { + "exception_type": "KeyError", + "missing_module": "torch", + "raised": true + }, + "representative_step": { + "action": 0, + "reward": 0.65, + "reward_type": "float", + "step_count": 1, + "terminated": false, + "truncated": false + }, + "episode_run": { + "action_policy": "do-nothing-every-step", + "cumulative_reward": -57.05, + "final_step_reward": -0.8, + "steps_to_end": 128, + "terminated": false, + "truncated": true, + "observed_stable_runs": 3 + } + } + }, + "dependency_resolution": { + "resolver": "uv", + "python": "3.11.15", + "resolved_at": "2026-08-02", + "route": "no-rl", + "no_rl_dependency_count": 133, + "normalized_environment_sha256": "b8f86ccf3989b0a9370590bfe31fd0a99c383aaeea72978af4384693297edbdf", + "normalization": "sha256 of the LF-joined, lexicographically (codepoint) sorted 'name==version' lines of the resolved graph, with a trailing LF.", + "upstream_lockfile": null, + "disposition": "Dated resolved snapshot. Upstream ships no lockfile and constrains many dependencies loosely (numpy~=1.23, matplotlib>=3.7.1, PyYAML>=6.0, typer>=0.9, ipywidgets, deepdiff), so those versions drift over time. Evidence only, not a runtime authenticity claim; no second repository lockfile is introduced." + }, + "dependencies_declared": { + "core": [ + "gymnasium==0.28.1", + "jupyterlab==3.6.1", + "kaleido==0.2.1", + "matplotlib>=3.7.1", + "networkx==3.1", + "numpy~=1.23", + "platformdirs==3.5.1", + "plotly==5.15.0", + "polars==0.20.30", + "prettytable==3.8.0", + "PyYAML>=6.0", + "typer[all]>=0.9", + "pydantic==2.7.0", + "ipywidgets", + "deepdiff" + ], + "extras": { + "rl": [ + "ray[rllib]>=2.20.0,<2.33", + "tensorflow~=2.12", + "stable-baselines3==2.1.0", + "sb3-contrib==2.1.0" + ], + "dev_setuptools": "setuptools==75.6.0" + } + }, + "dependency_license_summary": { + "distinct_licenses": { + "Apache 2.0": 1, + "Apache License, Version 2.0": 1, + "Apache Software License": 9, + "Apache Software License; BSD License": 1, + "Apache-2.0": 1, + "Apache-2.0 AND BSD-2-Clause": 1, + "Apache-2.0 OR BSD-2-Clause": 1, + "BSD 3-Clause License": 1, + "BSD License": 43, + "BSD-2-Clause": 2, + "BSD-3-Clause": 8, + "ISC License (ISCL)": 4, + "MIT": 23, + "MIT License": 28, + "MIT License License (see metadata)": 1, + "MIT-0": 1, + "MIT-CMU": 1, + "Mozilla Public License 2.0 (MPL 2.0)": 2, + "PSF-2.0": 1, + "Python Software Foundation License": 2, + "UNKNOWN": 1 + }, + "strong_copyleft": [], + "unknown_license": [ + "ypy-websocket" + ] + }, + "dependencies": [ + { + "name": "aiofiles", + "version": "22.1.0", + "license": "Apache Software License" + }, + { + "name": "aiosqlite", + "version": "0.22.1", + "license": "MIT License" + }, + { + "name": "annotated-doc", + "version": "0.0.5", + "license": "MIT" + }, + { + "name": "annotated-types", + "version": "0.8.0", + "license": "MIT" + }, + { + "name": "anyio", + "version": "4.14.2", + "license": "MIT" + }, + { + "name": "argon2-cffi", + "version": "25.1.0", + "license": "MIT" + }, + { + "name": "argon2-cffi-bindings", + "version": "25.1.0", + "license": "MIT" + }, + { + "name": "arrow", + "version": "1.4.0", + "license": "Apache Software License" + }, + { + "name": "asttokens", + "version": "3.0.2", + "license": "Apache 2.0" + }, + { + "name": "attrs", + "version": "26.1.0", + "license": "MIT" + }, + { + "name": "babel", + "version": "2.18.0", + "license": "BSD License" + }, + { + "name": "beautifulsoup4", + "version": "4.15.0", + "license": "MIT License" + }, + { + "name": "bleach", + "version": "6.4.0", + "license": "Apache Software License" + }, + { + "name": "cachebox", + "version": "5.2.3", + "license": "MIT License" + }, + { + "name": "certifi", + "version": "2026.7.22", + "license": "Mozilla Public License 2.0 (MPL 2.0)" + }, + { + "name": "cffi", + "version": "2.1.0", + "license": "MIT-0" + }, + { + "name": "charset-normalizer", + "version": "3.4.9", + "license": "MIT" + }, + { + "name": "cloudpickle", + "version": "3.1.2", + "license": "BSD License" + }, + { + "name": "comm", + "version": "0.2.3", + "license": "BSD License" + }, + { + "name": "contourpy", + "version": "1.3.3", + "license": "BSD License" + }, + { + "name": "cycler", + "version": "0.12.1", + "license": "BSD License" + }, + { + "name": "debugpy", + "version": "1.8.21", + "license": "MIT License" + }, + { + "name": "deepdiff", + "version": "9.1.0", + "license": "MIT License" + }, + { + "name": "defusedxml", + "version": "0.7.1", + "license": "Python Software Foundation License" + }, + { + "name": "entrypoints", + "version": "0.4", + "license": "MIT License" + }, + { + "name": "executing", + "version": "2.2.1", + "license": "MIT License" + }, + { + "name": "Farama-Notifications", + "version": "0.0.6", + "license": "MIT License" + }, + { + "name": "fastjsonschema", + "version": "2.22.1", + "license": "BSD License" + }, + { + "name": "fonttools", + "version": "4.63.0", + "license": "MIT" + }, + { + "name": "fqdn", + "version": "1.5.1", + "license": "Mozilla Public License 2.0 (MPL 2.0)" + }, + { + "name": "gymnasium", + "version": "0.28.1", + "license": "MIT License" + }, + { + "name": "idna", + "version": "3.18", + "license": "BSD-3-Clause" + }, + { + "name": "ipykernel", + "version": "6.29.5", + "license": "BSD License" + }, + { + "name": "ipython", + "version": "9.16.0", + "license": "BSD-3-Clause" + }, + { + "name": "ipython-genutils", + "version": "0.2.0", + "license": "BSD License" + }, + { + "name": "ipython_pygments_lexers", + "version": "1.1.1", + "license": "BSD License" + }, + { + "name": "ipywidgets", + "version": "8.1.8", + "license": "BSD License" + }, + { + "name": "isoduration", + "version": "20.11.0", + "license": "ISC License (ISCL)" + }, + { + "name": "jax-jumpy", + "version": "1.0.0", + "license": "Apache Software License" + }, + { + "name": "jedi", + "version": "0.20.0", + "license": "MIT License" + }, + { + "name": "Jinja2", + "version": "3.1.6", + "license": "BSD License" + }, + { + "name": "json5", + "version": "0.15.0", + "license": "Apache Software License" + }, + { + "name": "jsonpointer", + "version": "3.1.1", + "license": "BSD License" + }, + { + "name": "jsonschema", + "version": "4.26.0", + "license": "MIT" + }, + { + "name": "jsonschema-specifications", + "version": "2025.9.1", + "license": "MIT" + }, + { + "name": "jupyter-events", + "version": "0.12.1", + "license": "BSD License" + }, + { + "name": "jupyter-ydoc", + "version": "0.2.5", + "license": "BSD 3-Clause License" + }, + { + "name": "jupyter_client", + "version": "7.4.9", + "license": "BSD License" + }, + { + "name": "jupyter_core", + "version": "5.9.1", + "license": "BSD-3-Clause" + }, + { + "name": "jupyter_server", + "version": "2.20.0", + "license": "BSD License" + }, + { + "name": "jupyter_server_fileid", + "version": "0.9.3", + "license": "BSD License" + }, + { + "name": "jupyter_server_terminals", + "version": "0.5.4", + "license": "BSD License" + }, + { + "name": "jupyter_server_ydoc", + "version": "0.6.1", + "license": "BSD License" + }, + { + "name": "jupyterlab", + "version": "3.6.1", + "license": "BSD License" + }, + { + "name": "jupyterlab_pygments", + "version": "0.3.0", + "license": "BSD License" + }, + { + "name": "jupyterlab_server", + "version": "2.28.0", + "license": "BSD License" + }, + { + "name": "jupyterlab_widgets", + "version": "3.0.16", + "license": "BSD License" + }, + { + "name": "kaleido", + "version": "0.2.1", + "license": "MIT" + }, + { + "name": "kiwisolver", + "version": "1.5.0", + "license": "BSD License" + }, + { + "name": "lark", + "version": "1.3.1", + "license": "MIT License" + }, + { + "name": "markdown-it-py", + "version": "4.2.0", + "license": "MIT License" + }, + { + "name": "MarkupSafe", + "version": "3.0.3", + "license": "BSD-3-Clause" + }, + { + "name": "matplotlib", + "version": "3.11.1", + "license": "Python Software Foundation License" + }, + { + "name": "matplotlib-inline", + "version": "0.2.2", + "license": "BSD-3-Clause" + }, + { + "name": "mdurl", + "version": "0.1.2", + "license": "MIT License" + }, + { + "name": "mistune", + "version": "3.3.4", + "license": "BSD License" + }, + { + "name": "nbclassic", + "version": "1.3.3", + "license": "BSD License" + }, + { + "name": "nbclient", + "version": "0.11.0", + "license": "BSD License" + }, + { + "name": "nbconvert", + "version": "7.17.1", + "license": "BSD License" + }, + { + "name": "nbformat", + "version": "5.10.4", + "license": "BSD License" + }, + { + "name": "nest-asyncio", + "version": "1.6.0", + "license": "BSD License" + }, + { + "name": "networkx", + "version": "3.1", + "license": "BSD License" + }, + { + "name": "notebook", + "version": "6.5.7", + "license": "BSD License" + }, + { + "name": "notebook_shim", + "version": "0.2.4", + "license": "BSD License" + }, + { + "name": "numpy", + "version": "1.26.4", + "license": "BSD License" + }, + { + "name": "orderly-set", + "version": "5.5.0", + "license": "MIT License" + }, + { + "name": "overrides", + "version": "7.7.0", + "license": "Apache License, Version 2.0" + }, + { + "name": "packaging", + "version": "26.2", + "license": "Apache-2.0 OR BSD-2-Clause" + }, + { + "name": "pandocfilters", + "version": "1.5.1", + "license": "BSD License" + }, + { + "name": "parso", + "version": "0.8.7", + "license": "MIT License" + }, + { + "name": "pexpect", + "version": "4.9.0", + "license": "ISC License (ISCL)" + }, + { + "name": "pillow", + "version": "12.3.0", + "license": "MIT-CMU" + }, + { + "name": "pip", + "version": "26.2", + "license": "MIT" + }, + { + "name": "platformdirs", + "version": "3.5.1", + "license": "MIT" + }, + { + "name": "plotly", + "version": "5.15.0", + "license": "MIT License" + }, + { + "name": "polars", + "version": "0.20.30", + "license": "MIT License" + }, + { + "name": "prettytable", + "version": "3.8.0", + "license": "BSD License" + }, + { + "name": "primaite", + "version": "4.0.0", + "license": "MIT License License (see metadata)" + }, + { + "name": "prometheus_client", + "version": "0.26.0", + "license": "Apache-2.0 AND BSD-2-Clause" + }, + { + "name": "prompt_toolkit", + "version": "3.0.53", + "license": "BSD License" + }, + { + "name": "psutil", + "version": "7.2.2", + "license": "BSD-3-Clause" + }, + { + "name": "ptyprocess", + "version": "0.7.0", + "license": "ISC License (ISCL)" + }, + { + "name": "pure_eval", + "version": "0.2.3", + "license": "MIT License" + }, + { + "name": "pycparser", + "version": "3.0", + "license": "BSD-3-Clause" + }, + { + "name": "pydantic", + "version": "2.7.0", + "license": "MIT" + }, + { + "name": "pydantic_core", + "version": "2.18.1", + "license": "MIT License" + }, + { + "name": "Pygments", + "version": "2.20.0", + "license": "BSD-2-Clause" + }, + { + "name": "pyparsing", + "version": "3.3.2", + "license": "MIT" + }, + { + "name": "python-dateutil", + "version": "2.9.0.post0", + "license": "Apache Software License; BSD License" + }, + { + "name": "python-json-logger", + "version": "4.1.0", + "license": "BSD-2-Clause" + }, + { + "name": "PyYAML", + "version": "6.0.3", + "license": "MIT License" + }, + { + "name": "pyzmq", + "version": "27.1.0", + "license": "BSD License" + }, + { + "name": "referencing", + "version": "0.37.0", + "license": "MIT" + }, + { + "name": "requests", + "version": "2.34.2", + "license": "Apache Software License" + }, + { + "name": "rfc3339-validator", + "version": "0.1.4", + "license": "MIT License" + }, + { + "name": "rfc3986-validator", + "version": "0.1.1", + "license": "MIT License" + }, + { + "name": "rfc3987-syntax", + "version": "1.1.0", + "license": "MIT" + }, + { + "name": "rich", + "version": "15.0.0", + "license": "MIT License" + }, + { + "name": "rpds-py", + "version": "2026.6.3", + "license": "MIT" + }, + { + "name": "Send2Trash", + "version": "2.1.0", + "license": "BSD-3-Clause" + }, + { + "name": "setuptools", + "version": "75.6.0", + "license": "MIT License" + }, + { + "name": "shellingham", + "version": "1.5.4", + "license": "ISC License (ISCL)" + }, + { + "name": "six", + "version": "1.17.0", + "license": "MIT License" + }, + { + "name": "soupsieve", + "version": "2.9.1", + "license": "MIT" + }, + { + "name": "stack-data", + "version": "0.6.3", + "license": "MIT License" + }, + { + "name": "tenacity", + "version": "9.1.4", + "license": "Apache Software License" + }, + { + "name": "terminado", + "version": "0.18.1", + "license": "BSD License" + }, + { + "name": "tinycss2", + "version": "1.5.1", + "license": "BSD License" + }, + { + "name": "tornado", + "version": "6.5.7", + "license": "Apache Software License" + }, + { + "name": "traitlets", + "version": "5.16.0", + "license": "BSD License" + }, + { + "name": "typer", + "version": "0.27.0", + "license": "MIT" + }, + { + "name": "typing_extensions", + "version": "4.16.0", + "license": "PSF-2.0" + }, + { + "name": "tzdata", + "version": "2026.3", + "license": "Apache-2.0" + }, + { + "name": "uri-template", + "version": "1.3.0", + "license": "MIT License" + }, + { + "name": "urllib3", + "version": "2.7.0", + "license": "MIT" + }, + { + "name": "wcwidth", + "version": "0.8.2", + "license": "MIT" + }, + { + "name": "webcolors", + "version": "25.10.0", + "license": "BSD License" + }, + { + "name": "webencodings", + "version": "0.5.1", + "license": "BSD License" + }, + { + "name": "websocket-client", + "version": "1.9.0", + "license": "Apache Software License" + }, + { + "name": "wheel", + "version": "0.47.0", + "license": "MIT" + }, + { + "name": "widgetsnbextension", + "version": "4.0.15", + "license": "BSD License" + }, + { + "name": "y-py", + "version": "0.6.2", + "license": "MIT License" + }, + { + "name": "ypy-websocket", + "version": "0.8.4", + "license": "UNKNOWN" + } + ], + "stochastic_sources": [ + { + "owner": "gym-reset-seam", + "api": "PrimaiteGymEnv.reset(seed=20260802)", + "binding_status": "broken-without-torch-KeyError" + }, + { + "owner": "python-random", + "api": "random.seed(seed) via set_random_seed", + "binding_status": "reachable-only-through-broken-reset-seam" + }, + { + "owner": "numpy-global", + "api": "numpy.random.seed(seed) via set_random_seed; GREEN ProbabilisticAgent seeds default_rng from it", + "binding_status": "uncontrolled-for-this-scenario" + }, + { + "owner": "torch", + "api": "torch.manual_seed(seed) via set_random_seed", + "binding_status": "absent-on-no-rl-route" + } + ], + "legal": { + "upstream_license": "MIT", + "license_file": "LICENSE", + "license_sha256": "669b434561703dbfa093fe2c02c2569bcfe3347caf339e5d42643e4c1b4b76e8", + "copyright": "Crown-owned copyright 2023, Defence Science and Technology Laboratory UK", + "license_file_finding": "The LICENSE preamble is mangled ('MIT License License ...'); GitHub reports NOASSERTION and the string propagates into the wheel metadata. The grant and warranty clauses are standard MIT.", + "notice_required": true, + "attribution": "Retain the MIT permission notice and the DSTL Crown copyright when redistributing PrimAITE source.", + "redistribution": "permitted-with-notice", + "retained_output": "permitted", + "raes_redistributes_source": false, + "raes_redistributes_note": "The primaite extra is dependency-light; RAES does not vendor PrimAITE source or its dependencies, so no attribution obligation attaches to the raes-adapters wheel beyond referencing.", + "external_downloads": [], + "model_weights": [], + "privacy": "Simulated, fictitious network and traffic data; no customer data.", + "dependency_licenses": "All resolved no-rl dependencies are permissive or weak-copyleft (MIT/BSD/Apache/ISC/PSF/MPL-2.0); none are strong copyleft (no GPL/LGPL/AGPL). One transitive dependency (ypy-websocket 0.8.4) declares no license in its metadata." + }, + "patches": [], + "maintenance": { + "archived": false, + "default_branch": "dev", + "selected_tag": "v4.0.0", + "selected_commit": "98617981d7f6ae2c3ffd9a8cc39944e05c9a09ea", + "latest_release": "v4.0.0", + "latest_release_at": "2025-03-18", + "release_artifacts": [ + "Common.Action.Observation.Space.Definition.v0.10.3.xlsx" + ], + "public_index_distribution": null, + "tags": [ + "v4.0.0", + "v3.3.0", + "v3.2.0", + "v3.1.0", + "v3.0.0", + "v2.0.0", + "v.1.2.1", + "v1.2.0", + "v1.1.0", + "v1.0.0" + ] + }, + "known_defects": [ + { + "id": "undeclared-pkg-resources-setuptools-runtime-dep", + "status": "observed-in-selected-source", + "impact": "primaite imports pkg_resources at import time but does not declare setuptools as a runtime dependency. It breaks with setuptools>=81 (pkg_resources removed) and on default Python-3.12 venvs (no seeded setuptools). The qualified route pins setuptools==75.6.0." + }, + { + "id": "seeded-reset-requires-torch", + "status": "observed-in-selected-source", + "impact": "set_random_seed dereferences sys.modules['torch'] unconditionally, so PrimaiteGymEnv.reset(seed=) raises KeyError('torch') without the rl/torch stack. The public seed seam is unusable on the light route." + }, + { + "id": "requires-python-vs-classifiers-inconsistency", + "status": "observed-in-selected-source", + "impact": "pyproject requires-python is >=3.9,<3.13 but the classifiers and README document support only through 3.11 (<3.12). Qualified on 3.11." + }, + { + "id": "cli-requires-undeclared-click", + "status": "observed-in-selected-source", + "impact": "primaite.cli imports click (via utils/cli), which is not a declared dependency, so 'primaite setup' and the CLI fail on the no-rl route. The Gymnasium runtime does not require it (import creates app dirs)." + }, + { + "id": "typer-all-extra-removed", + "status": "observed-in-resolution", + "impact": "The declared typer[all] extra no longer exists in modern typer; resolution emits a benign warning." + }, + { + "id": "unpinned-dependency-graph", + "status": "observed-in-selected-source", + "impact": "Upstream ships no lockfile and constrains several dependencies loosely, so the resolved graph is a dated snapshot rather than a reproducible set." + }, + { + "id": "jupyterlab-core-dependency", + "status": "observed-in-selected-source", + "impact": "jupyterlab==3.6.1 is a core (non-optional) dependency, so the no-rl install still resolves 133 packages of mostly notebook tooling." + }, + { + "id": "green-rng-uncontrolled", + "status": "observed-in-selected-source", + "impact": "GREEN ProbabilisticAgent.rng seeds from the uncontrolled global NumPy generator and the scenario sets no game.seed, so green behaviour is not seed-controlled through the public API." + } + ], + "packaging": { + "repository_extra": "primaite", + "extra_dependencies": [], + "base_cyborg_cyberbattlesim_unchanged": true, + "uv_conflicts_declared": false, + "decision": "Publish a dependency-light adapter extra and keep simulator acquisition outside the wheel metadata.", + "reason": "PrimAITE publishes no index distribution or release wheel, and its pins (gymnasium==0.28.1, numpy~=1.23, pydantic==2.7.0) are mutually incompatible with the resolved cyberbattlesim and base graph. Keeping the extra empty keeps those pins out of the single uv.lock, so no uv conflicts declaration is needed yet; a future backend that resolves the PrimAITE dependencies into the lock would add one." + }, + "admission": { + "target": "RAESystem/research#12", + "decision": "admitted", + "authority": "maintainer-selection", + "decided_at": "2026-08-02", + "limitations": [ + "no-index-or-release-artifact", + "undeclared-setuptools-and-torch-runtime-deps", + "unpinned-dependency-graph", + "broken-public-seed-seam", + "python-support-inconsistency" + ], + "claim_strength": { + "source_identity": "attested", + "protocol_configuration": "attested", + "execution_controls": "partial", + "run_evidence": "attestable", + "outcome_reproduction": "stochastic-bounded" + }, + "interpretation": "The maintainer-selected profile is admitted for adapter realization. Packaging gaps, undeclared runtime dependencies, an unpinned dependency graph, a broken public seed seam, and the Python-support inconsistency bound the claims RAES may attest; they do not veto the simulator." + } +} diff --git a/tests/test_cyberbattlesim_qualification.py b/tests/test_cyberbattlesim_qualification.py index f27cef8..5dbb471 100644 --- a/tests/test_cyberbattlesim_qualification.py +++ b/tests/test_cyberbattlesim_qualification.py @@ -173,4 +173,4 @@ def test_qualification_admits_selected_backend_and_bounds_claim_strength() -> No "outcome_reproduction": "stochastic-bounded", } assert extras["cyberbattlesim"] == [] - assert extras["cyborg"] == ["raes-env-packs==3.2.0"] + assert extras["cyborg"] == ["raes-env-packs==3.6.2"] diff --git a/tests/test_cyborg_conformance.py b/tests/test_cyborg_conformance.py index 9531636..4cedd3b 100644 --- a/tests/test_cyborg_conformance.py +++ b/tests/test_cyborg_conformance.py @@ -367,7 +367,16 @@ def test_suite_refuses_an_unexpected_published_failure( report = run_cyborg_conformance(seed=3) passing = next(case for case in report.cases if case.passed) failed = replace(passing, passed=False, outcome="failed") - unexpected = replace(report, cases=(failed,)) + # raes>=3 validates the report before the suite inspects it: every cited case + # must be present and a passing report may not carry a failed case. Keep the + # full case set (complete evidence) and mark the report failed so the suite + # still refuses it for the genuinely-failed published case, not for an + # upstream-invalid payload. + unexpected = replace( + report, + passed=False, + cases=tuple(failed if case is passing else case for case in report.cases), + ) monkeypatch.setattr( conformance_module, "run_cyborg_conformance", diff --git a/tests/test_cyborg_qualification.py b/tests/test_cyborg_qualification.py index d4ee19c..4c55b21 100644 --- a/tests/test_cyborg_qualification.py +++ b/tests/test_cyborg_qualification.py @@ -242,7 +242,7 @@ def test_selection_dependencies_and_stochastic_sources_are_explicit() -> None: "56e2c189d6fd4b195584f34d93f66500b30eba8eedff6fca869ed61a8ab4079a" ) assert record["dependency_resolution"]["uv_lock_sha256"] == ( - "3ce9977df839c17bf6f817d40b89cd0bc874f03d1c91330e6909c5adcfba7dcf" + "141df24b3118815584c1423f8bdd07b7997f7882cc30082efddde0bf982de9fe" ) assert record["dependencies"] assert all( @@ -295,5 +295,5 @@ def test_legal_defect_and_claim_bounded_packaging_decisions_are_explicit() -> No "run_evidence": "attestable", "outcome_reproduction": "stochastic-bounded", } - assert extras["cyborg"] == ["raes-env-packs==3.2.0"] + assert extras["cyborg"] == ["raes-env-packs==3.6.2"] assert "CybORG" not in project["project"]["dependencies"] diff --git a/tests/test_nasim_qualification.py b/tests/test_nasim_qualification.py index ed94b8a..748d7a0 100644 --- a/tests/test_nasim_qualification.py +++ b/tests/test_nasim_qualification.py @@ -199,4 +199,4 @@ def test_qualification_admits_selected_backend_and_bounds_claim_strength() -> No assert f"gymnasium=={pins['gymnasium']}" in extras["nasim"] assert f"numpy=={pins['numpy']}" in extras["nasim"] assert extras["cyberbattlesim"] == [] - assert extras["cyborg"] == ["raes-env-packs==3.2.0"] + assert extras["cyborg"] == ["raes-env-packs==3.6.2"] diff --git a/tests/test_primaite_qualification.py b/tests/test_primaite_qualification.py new file mode 100644 index 0000000..0f79599 --- /dev/null +++ b/tests/test_primaite_qualification.py @@ -0,0 +1,215 @@ +"""Qualification evidence for the selected public PrimAITE case.""" + +from __future__ import annotations + +import hashlib +import tomllib +from pathlib import Path + +import raes_adapters.primaite as primaite + +REPO_ROOT = Path(__file__).resolve().parents[1] + + +def test_public_resources_select_one_immutable_source_and_protocol() -> None: + record = primaite.load_qualification() + protocol = primaite.read_public_protocol() + + assert primaite.__all__ == ["load_qualification", "read_public_protocol"] + assert record["source"] == { + "repository": "https://github.com/Autonomous-Resilient-Cyber-Defence/PrimAITE", + "tag": "v4.0.0", + "tag_object": "f6513362e9882217fb90595de51e7ca659ee1416", + "commit": "98617981d7f6ae2c3ffd9a8cc39944e05c9a09ea", + "tree": "3928ab452c51206ba8706b3f575e31fe99eb842e", + "package": "primaite", + "version": "4.0.0", + "requires_python": ">=3.9,<3.13", + "public_index_distribution": None, + "index_note": ( + "No PyPI distribution or upstream release wheel exists; the README " + "documents wheel-from-GitHub install only." + ), + } + assert "# PrimAITE public experiment protocol" in protocol + assert "`data_manipulation.yaml`" in protocol + assert "`PrimaiteGymEnv`" in protocol or "PrimaiteGymEnv" in protocol + assert hashlib.sha256(protocol.encode("utf-8")).hexdigest() == record["protocol"]["sha256"] + + +def test_clean_install_and_source_native_smoke_are_bounded_and_complete() -> None: + record = primaite.load_qualification() + runtime = record["runtime"] + smoke = runtime["smoke"] + + assert runtime["python"] == "3.11.15" + assert runtime["setuptools"] == "75.6.0" + assert runtime["clean_install"] == "passed" + assert runtime["network_after_install"] == "not-required" + assert runtime["wheel_sha256"] == ( + "06df7275d2e5334a041e075695f9ce465789989d7bcf0a7696145fecc417c926" + ) + assert smoke["reset_arity"] == 2 + assert smoke["step_arity"] == 5 + assert smoke["reset_obs_type"] == "ndarray" + assert smoke["reset_obs_shape"] == [1652] + assert smoke["reset_obs_dtype"] == "int64" + assert smoke["representative_step"] == { + "action": 0, + "reward_type": "float", + "reward": 0.65, + "terminated": False, + "truncated": False, + "step_count": 1, + } + assert smoke["episode_run"] == { + "action_policy": "do-nothing-every-step", + "steps_to_end": 128, + "terminated": False, + "truncated": True, + "final_step_reward": -0.8, + "cumulative_reward": -57.05, + "observed_stable_runs": 3, + } + # The public seed seam is broken without the rl/torch stack. + assert smoke["seeded_reset"]["raised"] is True + assert smoke["seeded_reset"]["exception_type"] == "KeyError" + assert smoke["seeded_reset"]["missing_module"] == "torch" + # Bounded structural summary only: no native payloads leak into the record. + forbidden = {"observation", "observation_values", "credentials", "traceback", "state"} + assert forbidden.isdisjoint(smoke) + + +def test_runtime_artifact_attestation_covers_complete_import_root() -> None: + record = primaite.load_qualification() + tree = record["runtime_source_tree"] + + assert tree["root"] == "primaite" + assert tree["file_count"] == 234 + assert tree["sha256"] == ("9fe7ba158a005ead6133769336ff66e95da38c1cfe5054954be38d16d7f54dcc") + assert tree["installed_wheel_tree_matches_source"] is True + + (artifact,) = record["runtime_artifacts"] + assert artifact["name"] == "primaite" + assert artifact["artifact"]["runtime_identity"] == "complete-root-tree" + assert artifact["artifact"]["require_direct_archive_sha256"] is False + assert artifact["roots"] == [ + { + "path": "primaite", + "file_count": 234, + "sha256": ("9fe7ba158a005ead6133769336ff66e95da38c1cfe5054954be38d16d7f54dcc"), + } + ] + + source_files = {entry["path"]: entry["sha256"] for entry in record["source_files"]} + assert source_files["LICENSE"] == ( + "669b434561703dbfa093fe2c02c2569bcfe3347caf339e5d42643e4c1b4b76e8" + ) + assert source_files["src/primaite/session/environment.py"] + + +def test_protocol_fixes_every_identity_and_discloses_random_streams() -> None: + record = primaite.load_qualification() + selection = record["protocol"]["selection"] + + assert selection["scenario"] == { + "use_case": "data_manipulation", + "config_path": "primaite/config/_package_data/data_manipulation.yaml", + "config_sha256": ("73c93965a32783f72f272993df4d65ab9bfcf670ba761503985199ff7e6debd6"), + "config_metadata_version": "3.0", + "max_episode_length": 128, + } + assert selection["entrypoint"] == "primaite.session.environment.PrimaiteGymEnv" + assert selection["action_space"] == {"type": "Discrete", "n": 78} + assert selection["observation_space"] == { + "type": "Box", + "shape": [1652], + "dtype": "int64", + "low": 0.0, + "high": 1.0, + "flattened": True, + } + assert selection["seed"] == 20260802 + assert selection["termination"] == { + "terminated": "always false (hardcoded in step())", + "truncated_rule": "step_counter >= max_episode_length", + "max_episode_length": 128, + "terminal_cause": "fixed-horizon-truncation", + } + assert selection["participants"]["blue"] == { + "ref": "defender", + "type": "proxy-agent", + "role": "rl-controlled", + } + assert selection["participants"]["red"]["type"] == "red-database-corrupting-agent" + assert {g["ref"] for g in selection["participants"]["green"]} == { + "client_1_green_user", + "client_2_green_user", + } + assert {stream["owner"] for stream in record["stochastic_sources"]} == { + "gym-reset-seam", + "python-random", + "numpy-global", + "torch", + } + + +def test_legal_maintenance_and_patch_dispositions_are_explicit() -> None: + record = primaite.load_qualification() + + assert record["legal"]["upstream_license"] == "MIT" + assert record["legal"]["notice_required"] is True + assert record["legal"]["redistribution"] == "permitted-with-notice" + assert record["legal"]["retained_output"] == "permitted" + assert record["legal"]["external_downloads"] == [] + assert record["legal"]["model_weights"] == [] + assert record["legal"]["raes_redistributes_source"] is False + assert record["patches"] == [] + assert record["maintenance"]["archived"] is False + assert record["maintenance"]["selected_commit"] == ("98617981d7f6ae2c3ffd9a8cc39944e05c9a09ea") + assert record["dependency_license_summary"]["strong_copyleft"] == [] + assert record["dependencies"] + assert all( + {"name", "version", "license"} <= dependency.keys() for dependency in record["dependencies"] + ) + + +def test_dependency_resolution_digest_is_reproducible_from_the_recorded_graph() -> None: + record = primaite.load_qualification() + resolution = record["dependency_resolution"] + + assert resolution["resolver"] == "uv" + assert resolution["upstream_lockfile"] is None + assert resolution["no_rl_dependency_count"] == len(record["dependencies"]) + + env_lines = sorted(f"{d['name']}=={d['version']}" for d in record["dependencies"]) + digest = hashlib.sha256(("\n".join(env_lines) + "\n").encode("utf-8")).hexdigest() + assert digest == resolution["normalized_environment_sha256"] + + +def test_qualification_admits_selected_backend_and_bounds_claim_strength() -> None: + record = primaite.load_qualification() + project = tomllib.loads((REPO_ROOT / "pyproject.toml").read_text(encoding="utf-8")) + extras = project["project"]["optional-dependencies"] + + assert record["admission"]["decision"] == "admitted" + assert record["admission"]["authority"] == "maintainer-selection" + assert set(record["admission"]["limitations"]) == { + "no-index-or-release-artifact", + "undeclared-setuptools-and-torch-runtime-deps", + "unpinned-dependency-graph", + "broken-public-seed-seam", + "python-support-inconsistency", + } + assert record["admission"]["claim_strength"] == { + "source_identity": "attested", + "protocol_configuration": "attested", + "execution_controls": "partial", + "run_evidence": "attestable", + "outcome_reproduction": "stochastic-bounded", + } + # The primaite extra is dependency-light; base and the other simulator + # extras remain independent and unchanged. + assert extras["primaite"] == [] + assert extras["cyberbattlesim"] == [] + assert extras["cyborg"] == ["raes-env-packs==3.6.2"] diff --git a/tools/check_project_services.py b/tools/check_project_services.py index 468236f..9d12067 100644 --- a/tools/check_project_services.py +++ b/tools/check_project_services.py @@ -83,7 +83,7 @@ def validate_repository(repo_root: Path) -> list[str]: errors: list[str] = [] ground_control = _read_required(repo_root, ".ground-control.yaml", errors) - _require(ground_control, "github_repo: RAESystem/adapters", ".ground-control.yaml", errors) + _require(ground_control, "github_repo: OpenRAE/adapters", ".ground-control.yaml", errors) _require(ground_control, "project_key: RAESystem_adapters", ".ground-control.yaml", errors) _require(ground_control, "test_command: make verify", ".ground-control.yaml", errors) _require(ground_control, "completion_command: make verify", ".ground-control.yaml", errors) @@ -193,8 +193,8 @@ def validate_repository(repo_root: Path) -> list[str]: readme = _read_required(repo_root, "README.md", errors) for expected in ( "readthedocs.org/projects/raes-adapters/badge/", - "api.scorecard.dev/projects/github.com/RAESystem/adapters/badge", - "bestpractices.dev/projects?as=badge&url=https%3A%2F%2Fgithub.com%2FRAESystem%2Fadapters", + "api.scorecard.dev/projects/github.com/OpenRAE/adapters/badge", + "bestpractices.dev/projects?as=badge&url=https%3A%2F%2Fgithub.com%2FOpenRAE%2Fadapters", ): _require(readme, expected, "README.md", errors) @@ -205,8 +205,8 @@ def validate_repository(repo_root: Path) -> list[str]: "PyPI Trusted Publisher workflow: `release-please.yml`", "PyPI environment: `pypi`", "SonarCloud project key: `RAESystem_adapters`", - "OpenSSF Scorecard URI: `github.com/RAESystem/adapters`", - "OpenSSF Best Practices lookup: `https://github.com/RAESystem/adapters`", + "OpenSSF Scorecard URI: `github.com/OpenRAE/adapters`", + "OpenSSF Best Practices lookup: `https://github.com/OpenRAE/adapters`", ): _require(services, expected, "docs/maintainers/project-services.md", errors) diff --git a/tools/tests/test_identity_policy.py b/tools/tests/test_identity_policy.py index ec19a1a..1ad198e 100644 --- a/tools/tests/test_identity_policy.py +++ b/tools/tests/test_identity_policy.py @@ -89,7 +89,7 @@ def test_legitimate_current_identifiers_are_not_flagged(self) -> None: "import raes_backend_protocols", "RAES is the authority", # Percent-encoded URL: the "F" of "%2F" precedes the stem. - "bestpractices.dev/projects?as=badge&url=https%3A%2F%2Fgithub.com%2FRAESystem%2Fadapters", + "bestpractices.dev/projects?as=badge&url=https%3A%2F%2Fgithub.com%2FOpenRAE%2Fadapters", ): with self.subTest(sample=sample): self.assertEqual([], scan_malformed(sample.encode("utf-8")), sample) diff --git a/uv.lock b/uv.lock index e1928be..7d50fc0 100644 --- a/uv.lock +++ b/uv.lock @@ -1795,7 +1795,7 @@ wheels = [ [[package]] name = "raes" -version = "2.0.0" +version = "3.3.0" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "asyncssh" }, @@ -1814,9 +1814,9 @@ dependencies = [ { name = "uvicorn", extra = ["standard"] }, { name = "z3-solver" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/00/d5/2444e66bd45ffdd8ffdffc2588c853bdb1025bfd3ce951448107d6275ac7/raes-2.0.0.tar.gz", hash = "sha256:12b7e338350ad11cb65a32216a4a6ea3fd3f165e7f97207e27d1bb88ddd0976d", size = 2392051, upload-time = "2026-07-27T06:59:00.395Z" } +sdist = { url = "https://files.pythonhosted.org/packages/1d/ad/2c9a895516bb8a6c5cc72e13d3203ac6ab5f5a79b2c6e438fb7046b9acb8/raes-3.3.0.tar.gz", hash = "sha256:2a99a48a22a1c800f91d003d4089d7d4b73b1e0d32e57715ceb610f908274c74", size = 2928408, upload-time = "2026-08-02T03:01:03.02Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/d3/30/29169747ee4629d733794c780f2684494a74cf2df5b7346a947ff485ec56/raes-2.0.0-py3-none-any.whl", hash = "sha256:8205f90631009a6867c861914fb0e6fa33b32f65faa15873f3dabed02c9e02c2", size = 2250915, upload-time = "2026-07-27T06:58:58.44Z" }, + { url = "https://files.pythonhosted.org/packages/f3/ab/c743ead99fbf68f1bbcfc0a588bebb8cfbee2b79e6431ebead6333dac4dc/raes-3.3.0-py3-none-any.whl", hash = "sha256:037cee182983121f330a2812a07a6f905bbf54d5d0be2c8799ecaa5535844e65", size = 2831463, upload-time = "2026-08-02T03:01:01.244Z" }, ] [[package]] @@ -1857,10 +1857,10 @@ requires-dist = [ { name = "gymnasium", marker = "extra == 'nasim'", specifier = "==0.26.3" }, { name = "nasim", marker = "extra == 'nasim'", specifier = "==0.12.0" }, { name = "numpy", marker = "extra == 'nasim'", specifier = "==1.26.4" }, - { name = "raes", specifier = "==2.0.0" }, - { name = "raes-env-packs", marker = "extra == 'cyborg'", specifier = "==3.2.0" }, + { name = "raes", specifier = "==3.3.0" }, + { name = "raes-env-packs", marker = "extra == 'cyborg'", specifier = "==3.6.2" }, ] -provides-extras = ["cyberbattlesim", "cyborg", "nasim"] +provides-extras = ["cyberbattlesim", "primaite", "cyborg", "nasim"] [package.metadata.requires-dev] dev = [ @@ -1879,15 +1879,15 @@ docs = [ [[package]] name = "raes-env-packs" -version = "3.2.0" +version = "3.6.2" source = { registry = "https://pypi.org/simple" } dependencies = [ { name = "pyyaml" }, { name = "raes" }, ] -sdist = { url = "https://files.pythonhosted.org/packages/8a/b9/c2486bfe77346715c93afe3c2e9272304e46c7213a760d3326af35d8eed7/raes_env_packs-3.2.0.tar.gz", hash = "sha256:d8020fb2dbc280d9889e443fa9bcaf5976086af0cfdf6b4cb0f54fc2942dc3bc", size = 342751, upload-time = "2026-07-31T23:49:04.435Z" } +sdist = { url = "https://files.pythonhosted.org/packages/63/2f/886159d97500fe27ae045612c64a3f9a1f07f0cfb8295503a531095e86e8/raes_env_packs-3.6.2.tar.gz", hash = "sha256:bff9844745cf175ca747500bfdf55efb84a4da658485a71cb21d0c21a94a7d5b", size = 1215203, upload-time = "2026-08-02T04:49:33.561Z" } wheels = [ - { url = "https://files.pythonhosted.org/packages/a8/af/b54e1114ba53e74c86609e3789ca6ebb52cdc07a694cf7e6ce856c3d9f56/raes_env_packs-3.2.0-py3-none-any.whl", hash = "sha256:8fba95f896e06b8abde685553457ed927d35c355e806b76f57d82313644e6992", size = 158145, upload-time = "2026-07-31T23:49:03.184Z" }, + { url = "https://files.pythonhosted.org/packages/c6/2b/556551e194ec577f5185feee6c9dfdd5b0c6ca970e8a1439c5afd7a40a84/raes_env_packs-3.6.2-py3-none-any.whl", hash = "sha256:7fefc42e60a4dcd6dae20d935199be6d2214f19a4c2d74a05ef3a185daaf9172", size = 1026186, upload-time = "2026-08-02T04:49:32.029Z" }, ] [[package]]