From d572d68ab9c8eaf163425748d44729aaa8028e98 Mon Sep 17 00:00:00 2001 From: epi13 Date: Sat, 8 Aug 2026 20:50:32 -0800 Subject: [PATCH] Promote Forge-governed RAVEL world ABI and evidence semantics --- .github/workflows/tests.yml | 7 +- .gitignore | 4 + README.md | 2 +- docs/ARCHITECTURE.md | 15 ++ docs/EVIDENCE_GUIDE.md | 11 ++ docs/PROJECT_MAP.md | 9 +- docs/PROVIDER_RUNTIME.md | 8 +- mncs-forge.toml | 154 ++++++++++++++++++ ravel_versions/0.6/Makefile | 5 +- .../0.6/RAVEL_0_6_IMPLEMENTATION_STATUS.md | 44 +++-- ravel_versions/0.6/RAVEL_0_6_NEXT_STEPS.md | 17 +- .../ravel-0.6-forge-development-policy.json | 23 +++ ravel_versions/0.6/ravel-0.6-limitations.md | 10 +- ravel_versions/0.6/ravel_0_6/README.md | 13 +- .../ravel_0_6/ravel_0_6_provider_branching.c | 56 +++++++ .../0.6/ravel_0_6/ravel_0_6_provider_ring.c | 51 ++++++ .../0.6/ravel_0_6/ravel_0_6_world.h | 33 ++++ src/ravel/development_evaluator.py | 131 +++++++++++---- src/ravel/mncs_bundles.py | 96 +++++++++++ src/ravel/mncs_receipts.py | 80 +++++---- tests/test_mncs_bundles.py | 59 +++++++ tests/test_mncs_receipts.py | 35 +++- tests/test_ravel_0_6_decomposition.py | 43 ++++- tests/test_ravel_0_6_negative_matrix.py | 7 +- tests/test_ravel_0_6_provenance.py | 9 + tests/test_ravel_0_6_transaction.py | 34 +++- tools/ravel_0_6_behavioral_fixtures.py | 3 + tools/ravel_0_6_build.py | 75 +++++++-- tools/ravel_0_6_seed_candidate.py | 50 +++--- tools/ravel_bundle_check.py | 47 ++++++ tools/ravel_forge_check.py | 120 ++++++++++++++ 31 files changed, 1101 insertions(+), 150 deletions(-) create mode 100644 mncs-forge.toml create mode 100644 ravel_versions/0.6/ravel-0.6-forge-development-policy.json create mode 100644 ravel_versions/0.6/ravel_0_6/ravel_0_6_provider_branching.c create mode 100644 ravel_versions/0.6/ravel_0_6/ravel_0_6_provider_ring.c create mode 100644 ravel_versions/0.6/ravel_0_6/ravel_0_6_world.h create mode 100644 src/ravel/mncs_bundles.py create mode 100644 tests/test_mncs_bundles.py create mode 100644 tools/ravel_bundle_check.py create mode 100644 tools/ravel_forge_check.py diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 89bc8b0..ea78fa8 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -22,6 +22,11 @@ jobs: - run: python -m pip install --upgrade pip - run: python -m pip install -e . - run: python -m unittest discover -s tests -v + - run: python -m pip install build + - run: python -m build --wheel --outdir /tmp/ravel-wheel + - run: python -m pip install --force-reinstall /tmp/ravel-wheel/*.whl + - run: python -m pip check + - run: cd /tmp && python -c "import ravel; assert 'site-packages' in ravel.__file__; from ravel.policy import load_frozen_policy; print(load_frozen_policy().threshold_identity)" ravel-06-c: runs-on: ubuntu-latest @@ -35,4 +40,4 @@ jobs: - run: python -m pip install --upgrade pip - run: python -m pip install -e . - run: make -f ravel_versions/0.6/Makefile policy-test - - run: make -f ravel_versions/0.6/Makefile build behavioral-test transaction-test component-test decomposition-test negative-test provider-test evaluator-test forge-test receipt-test compiler-matrix sanitizers + - run: make -f ravel_versions/0.6/Makefile build behavioral-test transaction-test component-test decomposition-test negative-test provider-test evaluator-test forge-test receipt-test abi-test compiler-matrix sanitizers diff --git a/.gitignore b/.gitignore index 1bf0bfa..8f41129 100644 --- a/.gitignore +++ b/.gitignore @@ -1,5 +1,9 @@ __pycache__/ *.py[cod] +.mncs-forge/ +build/ +dist/ +*.egg-info/ # Local build and evidence diagnostics ravel_versions/**/ravel diff --git a/README.md b/README.md index c30e613..dd46e47 100644 --- a/README.md +++ b/README.md @@ -6,7 +6,7 @@ RAVEL — the **Recursive Adaptive Vector Execution Lattice** — is an experime RAVEL operates beneath the technical authority of the Machine-Native Complexity Standard (MNCS) and the Machine-Native Complexity Development Standard (MNCDS). It is not intended to replace a language model, compiler, static analyzer, test framework, or the MNCS Forge. Its role is to decide what evidence should be gathered, what action should follow, and what experience should be retained for later use without redefining the governing status of that evidence. -> **Project status:** RAVEL is research software. Historical RAVEL 0.4 and 0.5 results remain development `FAIL`; RAVEL 0.6 candidate-001 now has a digest-bound frozen-policy loader, matched-compute development observations, a first separately compiled checkpoint contract with unity parity, two real C providers, an optional Forge CLI adapter, delegated MNCS receipt validation, negative parity fixtures, and lifecycle/memory integration. It remains unfrozen and has not been selection-evaluated, independently evaluated, or promoted. Formal MNCS/MNCDS conformance, independent attestation, protected custody, production safety, and general recursive self-improvement remain `UNKNOWN`. +> **Project status:** RAVEL is research software. Historical RAVEL 0.4 and 0.5 results remain development `FAIL`; RAVEL 0.6 candidate-001 now has digest-bound policy/evaluator surfaces, separately compiled checkpoint and world/provider contracts, branching/ring unity parity, a Forge-governed development configuration, official MNCS bundle/receipt adapters, and lifecycle/memory integration. It remains unfrozen and has not been selection-evaluated, independently evaluated, or promoted. Formal MNCS/MNCDS conformance, independent attestation, protected custody, production safety, and general recursive self-improvement remain `UNKNOWN`. ## Place in the MNCS ecosystem diff --git a/docs/ARCHITECTURE.md b/docs/ARCHITECTURE.md index 799f337..44db1f6 100644 --- a/docs/ARCHITECTURE.md +++ b/docs/ARCHITECTURE.md @@ -178,6 +178,21 @@ These modules are tested scaffolding and are not claims that RAVEL 0.6 has been evaluated, selected, independently evaluated, certified, promoted, or made production-safe. +## Current modular evidence boundary + +The generated C candidate has two separately compiled development contracts: + +- `ravel-0.6-checkpoint-abi/1` for byte-identical checkpoint comparison; and +- `ravel-0.6-world-abi/1` for bounded provider identity, init/reset, + observation, and transition facts. + +Branching and ring provider objects are substituted behind the same header and +run through the same unity/separate parity matrix. Build provenance distinguishes +source, contract/header, object, binary, and execution identities. The local +Forge project configuration records this work as development workflows; Forge +does not become RAVEL's evaluator and its normal process runner is not a +sandbox. + ## Separation of statuses RAVEL must not collapse different state spaces into one score. diff --git a/docs/EVIDENCE_GUIDE.md b/docs/EVIDENCE_GUIDE.md index a9b8ff1..5c75428 100644 --- a/docs/EVIDENCE_GUIDE.md +++ b/docs/EVIDENCE_GUIDE.md @@ -115,6 +115,17 @@ RAVEL reports several different questions that must not be collapsed: A `PASS` in one row does not imply `PASS` in another. +### Forge receipts and immutable bundles + +RAVEL delegates execution-receipt and experimental execution-bundle structure +to the installed MNCS sibling packages. A Forge verifier result is not a raw +process record: it cannot supply an exit code, termination category, argv, +stdout/stderr state, resource enforcement, or bundle execution fact unless the +provider actually observed and returned that field. Missing or malformed +runner material is `UNKNOWN`. Bundle logical identity, archive identity, +manifest validation, and receipt/bundle binding are retained as sibling-owned +facts; RAVEL does not duplicate those validators. + ## Preserved RAVEL outcomes - RAVEL 0.4: execution produced evidence, but zero of eight frozen trials passed diff --git a/docs/PROJECT_MAP.md b/docs/PROJECT_MAP.md index 5dcc96b..48fcebd 100644 --- a/docs/PROJECT_MAP.md +++ b/docs/PROJECT_MAP.md @@ -71,10 +71,15 @@ regenerating the frozen records. - `src/ravel/providers.py` includes an optional subprocess adapter for the current Forge JSON CLI; `src/ravel/mncs_receipts.py` delegates optional receipt construction/validation to sibling MNCS packages. +- `mncs-forge.toml` declares the Forge-governed development workflows; + `src/ravel/mncs_bundles.py` delegates immutable execution-bundle operations + to MNCS when that optional sibling is installed. - `tools/ravel_0_6_decompose.py` losslessly emits generated C component units and a unity wrapper; `ravel_versions/0.6/ravel_0_6/README.md` documents the - current unity-build limitation. `ravel_0_6_checkpoint.[ch]` is the first - separately compiled C contract and is included in object/binary provenance. + current ABI boundary. `ravel_0_6_checkpoint.[ch]` and + `ravel_0_6_world.h` are separately compiled contracts and are included in + object/binary provenance; branching and ring providers implement the same + world ABI. - [`MIGRATION.md`](MIGRATION.md) records the standalone extraction provenance. ## Build and verification entry points diff --git a/docs/PROVIDER_RUNTIME.md b/docs/PROVIDER_RUNTIME.md index 6a440b7..6973947 100644 --- a/docs/PROVIDER_RUNTIME.md +++ b/docs/PROVIDER_RUNTIME.md @@ -35,8 +35,12 @@ normal test suite fail. The local Forge `0.1.0a2` checkout was inspected and exercised for this iteration. Its current CLI exposes typed project, provider, verifier, candidate, -and lifecycle operations; the available provider listing reports the project -micro-verifier provider and the precedence `FAIL > UNKNOWN > PASS`. +workflow, bundle, and lifecycle operations; RAVEL's project-local configuration +declares 13 development workflows in a fresh local Forge ledger; twelve +bounded workflows passed and live family compatibility remained +`UNKNOWN` for unavailable sibling producer checkouts. The project-scoped Forge readiness policy is separate from the frozen +RAVEL preregistration and does not consume selection data. The precedence is +`FAIL > UNKNOWN > PASS`. `ForgeCliProvider` invokes that JSON interface when explicitly configured and preserves lifecycle rejection as raw `UNKNOWN`. Forge remains optional for the core package and is not reimplemented by RAVEL. diff --git a/mncs-forge.toml b/mncs-forge.toml new file mode 100644 index 0000000..303f7aa --- /dev/null +++ b/mncs-forge.toml @@ -0,0 +1,154 @@ +version = 1 +environment_allowlist = ["PATH", "LANG", "LC_ALL", "PYTHONPATH", "CC", "RAVEL06_PROVIDER"] + +[project] +name = "RAVEL 0.6 development" +identity = "ravel-0.6-development-forge-v1" +root = "." + +[paths] +candidates = [] +generated = ["build"] +contracts = ["ravel_versions/0.4", "ravel_versions/0.5", "ravel_versions/0.6/ravel-0.6-preregistration.json", "ravel_versions/0.6/ravel-0.6-preregistration.schema.json"] +references = ["docs", "README.md"] +evaluators = ["src/ravel/development_evaluator.py"] +acceptance_policies = ["ravel_versions/0.6/ravel-0.6-forge-development-policy.json", "ravel_versions/0.6/ravel-0.6-preregistration.json"] +development_evidence = ["ravel_versions/0.6/ravel-0.6-development-record.json"] +protected = ["ravel_versions/0.6/ravel_0_6"] +outputs = ["build"] + +[limits] +timeout_seconds = 180 +output_bytes = 2097152 + +[authority.development] +may_write_candidates = true +may_write_generated = true +may_run_providers = true + +[authority.evaluator] +candidate_read_only = true +authority_read_only = true +require_frozen_identities = true +withhold_repair_feedback = true + +[policies] +selection = "ravel_versions/0.6/ravel-0.6-forge-development-policy.json" +useful_benefit_objective = "ravel_versions/0.6/ravel-0.6-preregistration.json" + +[[workflows]] +name = "frozen-identities" +category = "inspection" +mode = "development" +command = ["python3", "tools/ravel_forge_check.py", "frozen-identities"] +provider_protocol = false +subject = "project" +disclosure = "compact" + +[[workflows]] +name = "policy" +category = "inspection" +mode = "development" +command = ["python3", "tools/ravel_forge_check.py", "policy"] +provider_protocol = false +subject = "project" +disclosure = "compact" + +[[workflows]] +name = "build" +category = "build" +mode = "development" +command = ["python3", "tools/ravel_forge_check.py", "build"] +provider_protocol = false +subject = "project" +disclosure = "compact" + +[[workflows]] +name = "behavior" +category = "differential_behavior" +mode = "development" +command = ["python3", "tools/ravel_forge_check.py", "behavior"] +provider_protocol = false +subject = "project" +disclosure = "compact" + +[[workflows]] +name = "transactions" +category = "checkpoint_recovery" +mode = "development" +command = ["python3", "tools/ravel_forge_check.py", "transactions"] +provider_protocol = false +subject = "project" +disclosure = "compact" + +[[workflows]] +name = "negative-matrix" +category = "mutation" +mode = "development" +command = ["python3", "tools/ravel_forge_check.py", "negative-matrix"] +provider_protocol = false +subject = "project" +disclosure = "compact" + +[[workflows]] +name = "world-provider-parity" +category = "differential_behavior" +mode = "development" +command = ["python3", "tools/ravel_forge_check.py", "world-provider-parity"] +provider_protocol = false +subject = "project" +disclosure = "compact" + +[[workflows]] +name = "component-parity" +category = "differential_behavior" +mode = "development" +command = ["python3", "tools/ravel_forge_check.py", "component-parity"] +provider_protocol = false +subject = "project" +disclosure = "compact" + +[[workflows]] +name = "development-evaluator" +category = "inspection" +mode = "development" +command = ["python3", "tools/ravel_forge_check.py", "development-evaluator"] +provider_protocol = false +subject = "project" +disclosure = "compact" + +[[workflows]] +name = "package" +category = "mncs_bundle_validation" +mode = "development" +command = ["python3", "tools/ravel_forge_check.py", "package"] +provider_protocol = false +subject = "project" +disclosure = "compact" + +[[workflows]] +name = "live-family-compat" +category = "mncds_record_validation" +mode = "development" +command = ["python3", "tools/ravel_forge_check.py", "live-family-compat"] +provider_protocol = false +subject = "project" +disclosure = "compact" + +[[workflows]] +name = "lifecycle" +category = "inspection" +mode = "development" +command = ["python3", "tools/ravel_forge_check.py", "lifecycle"] +provider_protocol = false +subject = "project" +disclosure = "compact" + +[[workflows]] +name = "bundle" +category = "mncs_bundle_validation" +mode = "development" +command = ["python3", "tools/ravel_bundle_check.py"] +provider_protocol = false +subject = "project" +disclosure = "compact" diff --git a/ravel_versions/0.6/Makefile b/ravel_versions/0.6/Makefile index ba06af0..dba4a2d 100644 --- a/ravel_versions/0.6/Makefile +++ b/ravel_versions/0.6/Makefile @@ -1,7 +1,7 @@ CC ?= cc CFLAGS ?= -std=c11 -O3 -Wall -Wextra -Werror -pedantic -.PHONY: build behavioral-test transaction-test component-test policy-test decomposition-test negative-test provider-test evaluator-test forge-test receipt-test compiler-matrix sanitizers +.PHONY: build behavioral-test transaction-test component-test policy-test decomposition-test negative-test provider-test evaluator-test forge-test receipt-test abi-test compiler-matrix sanitizers build: @set -e; out=$$(mktemp -d); python3 tools/ravel_0_6_build.py build --output-dir "$$out"; \ @@ -37,6 +37,9 @@ forge-test: receipt-test: python3 -m unittest tests/test_mncs_receipts.py +abi-test: + python3 -m unittest tests/test_ravel_0_6_decomposition.py tests/test_mncs_bundles.py + compiler-matrix: @set -eu; for compiler in gcc clang; do \ if command -v "$$compiler" >/dev/null 2>&1; then \ diff --git a/ravel_versions/0.6/RAVEL_0_6_IMPLEMENTATION_STATUS.md b/ravel_versions/0.6/RAVEL_0_6_IMPLEMENTATION_STATUS.md index 2b485fb..abd5037 100644 --- a/ravel_versions/0.6/RAVEL_0_6_IMPLEMENTATION_STATUS.md +++ b/ravel_versions/0.6/RAVEL_0_6_IMPLEMENTATION_STATUS.md @@ -34,9 +34,10 @@ This is a development status record, not RAVEL 0.6 evaluation evidence. fixture also forces a retention gate failure and observes rollback. - **R6-04 physical extraction and providers:** the generated source is losslessly emitted as ten component units plus a deterministic unity wrapper. - The C mechanism has explicit branching and ring providers selected by a - recorded compile flag; provider identity and evidence change while core - source remains unchanged. + The world/provider surface is now separately compiled under + `ravel-0.6-world-abi/1`; branching and ring implement the same fixed-size + init/reset/observe/transition contract. Provider selection is a linked object + choice, not a mechanism source macro. - **R6-04 bounded surfaces:** `world`, `transition`, `planning`, `mechanism_state`, and `checkpoint` provide dependency-free, deterministic interfaces with two independently defined toy providers. These surfaces @@ -44,17 +45,29 @@ This is a development status record, not RAVEL 0.6 evaluation evidence. corruption. The generated C candidate now has an explicit transaction and observation surface. The checkpoint byte-comparison boundary is now compiled as a separate object under `ravel-0.6-checkpoint-abi/1`; unity and separate - binaries are parity-tested. The remaining surfaces are still unity include - units, so full independent C decomposition is not claimed. -- **Forge boundary and evaluator:** `ravel.providers.ForgeCliProvider` invokes - the installed Forge JSON CLI when configured, preserving raw responses and - returning `UNKNOWN` for lifecycle/provider failures. A separately maintained - `ravel.development_evaluator` derives development gate results from raw C and - matched-compute observations without trusting executable verdict fields. -- **MNCS receipt delegation:** `ravel.mncs_receipts` delegates receipt building - and validation to optional MNCS Fabric/validator packages. Local sibling - validation was exercised; receipt structural validity remains distinct from - assurance, conformance, custody, or promotion. + binaries are parity-tested. Build records now retain contract header, + implementation/provider source, object, compile argv, compiler, dependency, + unity-source, separate-binary, and unity-binary identities. Transition, + planning, adaptation, and driver surfaces remain unity units. +- **Forge boundary and evaluator:** the project-local `mncs-forge.toml` + declares bounded build, behavior, transaction, negative-matrix, parity, + evaluator, bundle, package, lifecycle, and family-compatibility workflows. + Its separate `ravel-0.6-forge-development-policy.json` is a project-scoped + readiness plan only; it explicitly does not consume selection data. + Forge 0.1.0a2 executed all declared workflows in a fresh local development + ledger. Twelve bounded workflows were `PASS`; live family compatibility was + `UNKNOWN` because sibling producer checkouts were unavailable. `ravel.development_evaluator` now reports + mechanism, execution-integrity, matched-compute, evidence-completeness, + provider, receipt, bundle, and aggregate statuses. Identity drift, malformed + evidence, and missing required observations remain `UNKNOWN`; genuine hard + mechanism failures remain `FAIL`. +- **MNCS evidence delegation:** `ravel.mncs_receipts` delegates receipt building + and validation to optional MNCS Fabric/validator packages and carries only an + explicitly observed execution record. Verifier `PASS`/`FAIL` never supplies + exit code, argv, termination, streams, enforcement, or bundle-use facts. + `ravel.mncs_bundles` delegates immutable bundle construction, archive + validation, and receipt binding to MNCS; the local Forge runner is not claimed + to have executed from a bundle unless it actually reports that fact. - **R6-05 lifecycle infrastructure:** `ravel.lifecycle.CandidateLedger` is an append-only, hash-chained, gap-resistant development ledger with the preregistered candidate limit, freeze identities, partition separation, @@ -78,7 +91,8 @@ This is a development status record, not RAVEL 0.6 evaluation evidence. formal evidence status; missing external disposition remains `UNKNOWN`. - Additional separately compiled C ABI contracts, full cross-project evaluator lifecycle integration, and an absolute compute budget remain incomplete or - are not declared by the frozen contract. + are not declared by the frozen contract. Forge/RAVEL lifecycle mapping is + reference-only and does not collapse the two state machines. - R6-05 selection evaluation and promotion logic have not been consumed. The ledger is infrastructure only; no candidate is frozen or selected by it. - R6-06 external final custody/evaluation remains unavailable and `UNKNOWN`. diff --git a/ravel_versions/0.6/RAVEL_0_6_NEXT_STEPS.md b/ravel_versions/0.6/RAVEL_0_6_NEXT_STEPS.md index e305770..ed515ee 100644 --- a/ravel_versions/0.6/RAVEL_0_6_NEXT_STEPS.md +++ b/ravel_versions/0.6/RAVEL_0_6_NEXT_STEPS.md @@ -74,20 +74,19 @@ exact boundary. - **R6-03:** behavioral and C/Python negative fixtures complete for declared constraints; source-constant mutations are intentionally distinguished from Python frozen-policy mutations and remain manifest-detectable. -- **R6-04:** the first checkpoint boundary is separately compiled and parity - tested against unity; provider/world, transition, planning, adaptation, and - driver surfaces remain unity units. Further reviewed C ABI extraction is the - next step. +- **R6-04:** checkpoint and world/provider boundaries are separately compiled + and parity-tested against unity for both branching and ring. Transition, + planning, adaptation, and driver surfaces remain unity units. The next safe + ABI candidate is observation/reporting, after dependency review. - **R6-05:** ledger infrastructure is hardened and exercised only with development fixtures. Candidate-001 remains unfrozen because further implementation changes are still expected. - **R6-06:** external custody and evaluation remain unavailable/`UNKNOWN`. -The next logical implementation task is to promote the world/provider boundary -to a separately compiled C contract and compare both providers through the -same unity/separate parity oracle. Do not freeze candidate-001 or consume -selection partitions until that work, policy parity, and the full development -trial matrix are stable. +The next logical implementation task is to review and, if independently +falsifiable, promote the observation/reporting boundary. Do not freeze +candidate-001 or consume selection partitions until further implementation, +policy parity, and the full development trial matrix are stable. ## Codex next steps diff --git a/ravel_versions/0.6/ravel-0.6-forge-development-policy.json b/ravel_versions/0.6/ravel-0.6-forge-development-policy.json new file mode 100644 index 0000000..3202104 --- /dev/null +++ b/ravel_versions/0.6/ravel-0.6-forge-development-policy.json @@ -0,0 +1,23 @@ +{ + "schema": "ravel-forge-development-policy/1", + "policy_id": "ravel-0.6-forge-development-workflows/1", + "scope": "project", + "authority": "development-only", + "required_workflows": [ + "frozen-identities", + "policy", + "build", + "behavior", + "transactions", + "negative-matrix", + "world-provider-parity", + "component-parity", + "development-evaluator", + "package", + "live-family-compat", + "lifecycle", + "bundle" + ], + "selection_data": "not-used", + "promotion_authority": "external-and-unknown" +} diff --git a/ravel_versions/0.6/ravel-0.6-limitations.md b/ravel_versions/0.6/ravel-0.6-limitations.md index 623d1b0..77f980a 100644 --- a/ravel_versions/0.6/ravel-0.6-limitations.md +++ b/ravel_versions/0.6/ravel-0.6-limitations.md @@ -23,8 +23,12 @@ - Formal MNCS status is `UNKNOWN`. - Formal MNCDS status is `UNKNOWN`. - Promotion is unauthorized. -- Only the checkpoint byte-comparison surface is separately compiled in this - iteration. The remaining C mechanism surfaces use the unity extraction. +- The checkpoint byte-comparison and world/provider surfaces are separately + compiled in this iteration. The remaining C mechanism surfaces use the unity + extraction. Unity/separate parity is a development check, not independent + evaluation. - Forge CLI and MNCS receipt adapters are optional local development bridges; lifecycle rejection, missing packages, and unsupported capabilities remain - `UNKNOWN`. A structurally valid receipt is not assurance or conformance. + `UNKNOWN`. A structurally valid receipt is not assurance or conformance. A + verifier disposition does not establish runner process facts, and the local + Forge runner is not a sandbox or automatic execution-bundle consumer. diff --git a/ravel_versions/0.6/ravel_0_6/README.md b/ravel_versions/0.6/ravel_0_6/README.md index 2894689..8c78708 100644 --- a/ravel_versions/0.6/ravel_0_6/README.md +++ b/ravel_versions/0.6/ravel_0_6/README.md @@ -14,9 +14,10 @@ then losslessly splits the generated text into bounded include units: - trial driver. The current extraction retains a deterministic unity wrapper as a parity -oracle. The checkpoint byte-comparison operation is now promoted to the first -separately compiled unit under `ravel-0.6-checkpoint-abi/1`; the candidate -object links against `ravel_0_6_checkpoint.o` while the unity binary remains -available. The remaining mechanism surfaces are still include units and are -not claimed to have independent C ABI contracts. This is local development -execution, not independent evaluation or authority. +oracle. The checkpoint byte-comparison operation is separately compiled under +`ravel-0.6-checkpoint-abi/1`, and the fixed-size world/provider surface is +separately compiled under `ravel-0.6-world-abi/1`. Branching and ring provider +objects implement the same header; the build links each object into both a +separate candidate and a unity oracle. The remaining mechanism surfaces are +still include units and are not claimed to have independent C ABI contracts. +This is local development execution, not independent evaluation or authority. diff --git a/ravel_versions/0.6/ravel_0_6/ravel_0_6_provider_branching.c b/ravel_versions/0.6/ravel_0_6/ravel_0_6_provider_branching.c new file mode 100644 index 0000000..86791f9 --- /dev/null +++ b/ravel_versions/0.6/ravel_0_6/ravel_0_6_provider_branching.c @@ -0,0 +1,56 @@ +#include "ravel_0_6_world.h" + +#include + +const char *ravel06_world_provider_id(void) { + return "ravel-toy-branching-c/1"; +} + +int ravel06_world_reset(Ravel06World *world) { + if (world == NULL) return 0; + memset(world, 0, sizeof *world); + return 1; +} + +int ravel06_world_init(Ravel06World *world, const Ravel06WorldConfig *config) { + if (world == NULL || config == NULL || config->amplitude < 0) return 0; + if (!ravel06_world_reset(world)) return 0; + for (uint32_t state = 0; state < RAVEL06_WORLD_STATES; ++state) { + uint32_t visible = config->ambiguous != 0u ? state / 2u : state; + for (uint32_t dimension = 0; dimension < 6u; ++dimension) { + world->center[state][dimension] = (int16_t)( + ((visible >> dimension) & 1u) ? config->amplitude : -config->amplitude); + } + world->center[state][6] = (int16_t)(-30 + 20 * (int)((visible * 5u + (visible >> 2u)) & 3u)); + world->center[state][7] = (int16_t)(-30 + 20 * (int)((visible * 3u + (visible >> 1u)) & 3u)); + world->label[state] = (uint8_t)(((visible * 29u) ^ (visible >> 1u) ^ (visible >> 3u)) & 7u); + world->base_next[state][0] = (uint8_t)((state + 1u) & 63u); + world->base_next[state][1] = (uint8_t)((state + 63u) & 63u); + world->base_next[state][2] = (uint8_t)(state ^ 8u); + world->base_next[state][3] = (uint8_t)(state ^ 32u); + for (uint32_t action = 0; action < RAVEL06_WORLD_ACTIONS; ++action) { + world->drift_next[state][action] = world->base_next[state][action]; + } + if (config->transition_drift != 0u && state < 24u) { + world->drift_next[state][0] = (uint8_t)(state ^ 16u); + world->drift_next[state][2] = (uint8_t)((state + 2u) & 63u); + } + } + return 1; +} + +int ravel06_world_observe(const Ravel06World *world, uint8_t state, + int16_t out[RAVEL06_WORLD_D]) { + if (world == NULL || out == NULL || state >= RAVEL06_WORLD_STATES) return 0; + memcpy(out, world->center[state], sizeof world->center[state]); + return 1; +} + +int ravel06_world_transition(const Ravel06World *world, uint8_t state, + uint8_t action, uint8_t drift, uint8_t *target) { + if (world == NULL || target == NULL || state >= RAVEL06_WORLD_STATES || + action >= RAVEL06_WORLD_ACTIONS) return 0; + *target = drift != 0u ? world->drift_next[state][action] + : world->base_next[state][action]; + return 1; +} diff --git a/ravel_versions/0.6/ravel_0_6/ravel_0_6_provider_ring.c b/ravel_versions/0.6/ravel_0_6/ravel_0_6_provider_ring.c new file mode 100644 index 0000000..f4f3c98 --- /dev/null +++ b/ravel_versions/0.6/ravel_0_6/ravel_0_6_provider_ring.c @@ -0,0 +1,51 @@ +#include "ravel_0_6_world.h" + +#include + +const char *ravel06_world_provider_id(void) { + return "ravel-toy-ring-c/1"; +} + +int ravel06_world_reset(Ravel06World *world) { + if (world == NULL) return 0; + memset(world, 0, sizeof *world); + return 1; +} + +int ravel06_world_init(Ravel06World *world, const Ravel06WorldConfig *config) { + if (world == NULL || config == NULL || config->amplitude < 0) return 0; + if (!ravel06_world_reset(world)) return 0; + for (uint32_t state = 0; state < RAVEL06_WORLD_STATES; ++state) { + for (uint32_t dimension = 0; dimension < RAVEL06_WORLD_D; ++dimension) { + int sign = ((state + 3u * dimension + (state >> 2u)) & 1u) ? 1 : -1; + world->center[state][dimension] = + (int16_t)(sign * (config->amplitude - (int)(dimension % 3u))); + } + world->label[state] = (uint8_t)((state * 7u + (state >> 3u)) & 7u); + for (uint32_t action = 0; action < RAVEL06_WORLD_ACTIONS; ++action) { + world->base_next[state][action] = + (uint8_t)((state + action + 1u) & 63u); + world->drift_next[state][action] = world->base_next[state][action]; + } + if (config->transition_drift != 0u && state < 24u) { + world->drift_next[state][1] = (uint8_t)((state + 5u) & 63u); + } + } + return 1; +} + +int ravel06_world_observe(const Ravel06World *world, uint8_t state, + int16_t out[RAVEL06_WORLD_D]) { + if (world == NULL || out == NULL || state >= RAVEL06_WORLD_STATES) return 0; + memcpy(out, world->center[state], sizeof world->center[state]); + return 1; +} + +int ravel06_world_transition(const Ravel06World *world, uint8_t state, + uint8_t action, uint8_t drift, uint8_t *target) { + if (world == NULL || target == NULL || state >= RAVEL06_WORLD_STATES || + action >= RAVEL06_WORLD_ACTIONS) return 0; + *target = drift != 0u ? world->drift_next[state][action] + : world->base_next[state][action]; + return 1; +} diff --git a/ravel_versions/0.6/ravel_0_6/ravel_0_6_world.h b/ravel_versions/0.6/ravel_0_6/ravel_0_6_world.h new file mode 100644 index 0000000..ebddeb3 --- /dev/null +++ b/ravel_versions/0.6/ravel_0_6/ravel_0_6_world.h @@ -0,0 +1,33 @@ +#ifndef RAVEL_0_6_WORLD_H +#define RAVEL_0_6_WORLD_H + +#include + +#define RAVEL06_WORLD_ABI_VERSION "ravel-0.6-world-abi/1" +#define RAVEL06_WORLD_ABI_NUMERIC 1u +#define RAVEL06_WORLD_STATES 64u +#define RAVEL06_WORLD_ACTIONS 4u +#define RAVEL06_WORLD_D 8u + +typedef struct { + int amplitude; + uint8_t transition_drift; + uint8_t ambiguous; +} Ravel06WorldConfig; + +typedef struct { + int16_t center[RAVEL06_WORLD_STATES][RAVEL06_WORLD_D]; + uint8_t label[RAVEL06_WORLD_STATES]; + uint8_t base_next[RAVEL06_WORLD_STATES][RAVEL06_WORLD_ACTIONS]; + uint8_t drift_next[RAVEL06_WORLD_STATES][RAVEL06_WORLD_ACTIONS]; +} Ravel06World; + +const char *ravel06_world_provider_id(void); +int ravel06_world_init(Ravel06World *world, const Ravel06WorldConfig *config); +int ravel06_world_reset(Ravel06World *world); +int ravel06_world_observe(const Ravel06World *world, uint8_t state, + int16_t out[RAVEL06_WORLD_D]); +int ravel06_world_transition(const Ravel06World *world, uint8_t state, + uint8_t action, uint8_t drift, uint8_t *target); + +#endif diff --git a/src/ravel/development_evaluator.py b/src/ravel/development_evaluator.py index bc5119d..f91f1d1 100644 --- a/src/ravel/development_evaluator.py +++ b/src/ravel/development_evaluator.py @@ -22,6 +22,38 @@ class DevelopmentEvaluation: reason_codes: tuple[str, ...] transaction: ConstraintReport | None matched_compute: ConstraintReport | None + mechanism_status: str + execution_integrity_status: str + matched_compute_status: str + evidence_completeness_status: str + provider_status: str + receipt_status: str + bundle_status: str + aggregate_development_status: str + + +def _aggregate_status(*statuses: str) -> str: + """Apply the declared PASS < UNKNOWN < FAIL lattice.""" + + if "FAIL" in statuses: + return "FAIL" + if "UNKNOWN" in statuses: + return "UNKNOWN" + return "PASS" + + +def _optional_status(trial: Mapping[str, Any], name: str, reasons: list[str]) -> str: + value = trial.get(name) + if value is None: + return "PASS" # optional Forge/bundle/receipt references were not requested + if value not in {"PASS", "FAIL", "UNKNOWN"}: + reasons.append(f"{name}_malformed") + return "UNKNOWN" + # Evidence-integrity failures are unresolved evidence, not mechanism fails. + if value == "FAIL": + reasons.append(f"{name}_invalid") + return "UNKNOWN" + return str(value) def evaluate_trial( @@ -33,55 +65,98 @@ def evaluate_trial( """Validate and evaluate one local development trial, fail-closed.""" reasons: list[str] = [] + evidence_reasons: list[str] = [] transaction_report: ConstraintReport | None = None matched_report: ConstraintReport | None = None + mechanism_report: ConstraintReport | None = None if trial.get("schema") != "ravel-raw-trial/0.5": - reasons.append("trial_schema_mismatch") + evidence_reasons.append("trial_schema_mismatch") if not isinstance(expected_candidate_id, str) or not expected_candidate_id: - reasons.append("candidate_identity_malformed") + evidence_reasons.append("candidate_identity_malformed") provider_id = trial.get("environment_provider_id") if not isinstance(provider_id, str) or not provider_id: - reasons.append("provider_identity_missing") + evidence_reasons.append("provider_identity_missing") elif expected_provider_id is not None and provider_id != expected_provider_id: - reasons.append("provider_identity_mismatch") + evidence_reasons.append("provider_identity_mismatch") if trial.get("candidate_id") != expected_candidate_id: - reasons.append("candidate_identity_mismatch") + evidence_reasons.append("candidate_identity_mismatch") candidate = trial.get("candidate") if not isinstance(candidate, Mapping): - reasons.append("candidate_observation_missing") + evidence_reasons.append("candidate_observation_missing") comparisons = trial.get("comparisons") if not isinstance(comparisons, Mapping): - reasons.append("comparison_observation_missing") + evidence_reasons.append("comparison_observation_missing") transaction_value = candidate.get("adaptation_transaction") if isinstance(candidate, Mapping) else None matched_value = trial.get("matched_compute") try: if not isinstance(transaction_value, Mapping): raise ValueError("adaptation transaction is missing") - if not isinstance(matched_value, Mapping): - raise ValueError("matched-compute observation is missing") parsed_transaction = CTransactionObservation.from_json(transaction_value) - parsed_matched = MatchedComputeObservation.from_json(matched_value) - transaction_report = parsed_transaction.evaluate(parsed_matched) - matched_report = parsed_matched.evaluate() + transaction_report = parsed_transaction.evaluate() + mechanism_reasons = tuple( + reason + for reason in transaction_report.rejection_reasons + if reason != "matched_compute_reference_unavailable" + ) + mechanism_report = ConstraintReport(not mechanism_reasons, mechanism_reasons) + if not isinstance(matched_value, Mapping): + evidence_reasons.append("matched_compute_observation_missing") + else: + parsed_matched = MatchedComputeObservation.from_json(matched_value) + transaction_report = parsed_transaction.evaluate(parsed_matched) + matched_report = parsed_matched.evaluate() + if not matched_report.passed: + reasons.extend(matched_report.rejection_reasons) + if parsed_matched.partition_identity != "ravel-0.6-development-adaptation-v1": + evidence_reasons.append("partition_identity_mismatch") if parsed_transaction.threshold_identity != load_frozen_policy().threshold_identity: - reasons.append("threshold_identity_mismatch") - if parsed_matched.partition_identity != "ravel-0.6-development-adaptation-v1": - reasons.append("partition_identity_mismatch") + evidence_reasons.append("threshold_identity_mismatch") # The C disposition is a parity observation. Python independently # derives the disposition and reports disagreement instead of trusting C. - if parsed_transaction.committed != transaction_report.passed: - reasons.append("c_python_disposition_disagreement") + if matched_report is not None and parsed_transaction.committed != transaction_report.passed: + evidence_reasons.append("c_python_disposition_disagreement") if not transaction_report.passed: - reasons.extend(transaction_report.rejection_reasons) - if not matched_report.passed: - reasons.extend(matched_report.rejection_reasons) + reasons.extend( + reason + for reason in transaction_report.rejection_reasons + if reason != "matched_compute_reference_unavailable" + ) except (TypeError, ValueError): - reasons.append("malformed_required_observation") + evidence_reasons.append("malformed_required_observation") + + mechanism_status = "UNKNOWN" if mechanism_report is None else ( + "PASS" if mechanism_report.passed else "FAIL" + ) + matched_compute_status = "UNKNOWN" if matched_report is None else ( + "PASS" if matched_report.passed else "FAIL" + ) + provider_status = "PASS" if isinstance(provider_id, str) and provider_id else "UNKNOWN" + evidence_completeness_status = "UNKNOWN" if evidence_reasons else "PASS" + execution_integrity_status = _optional_status(trial, "execution_integrity_status", reasons) + receipt_status = _optional_status(trial, "receipt_status", reasons) + bundle_status = _optional_status(trial, "bundle_status", reasons) + reasons.extend(evidence_reasons) unique_reasons = tuple(dict.fromkeys(reasons)) - if any(reason == "malformed_required_observation" for reason in unique_reasons): - status = "UNKNOWN" - elif unique_reasons: - status = "FAIL" - else: - status = "PASS" - return DevelopmentEvaluation(status, unique_reasons, transaction_report, matched_report) + aggregate = _aggregate_status( + mechanism_status, + execution_integrity_status, + matched_compute_status, + evidence_completeness_status, + provider_status, + receipt_status, + bundle_status, + ) + return DevelopmentEvaluation( + aggregate, + unique_reasons, + transaction_report, + matched_report, + mechanism_status, + execution_integrity_status, + matched_compute_status, + evidence_completeness_status, + provider_status, + receipt_status, + bundle_status, + aggregate, + ) diff --git a/src/ravel/mncs_bundles.py b/src/ravel/mncs_bundles.py new file mode 100644 index 0000000..47f88d2 --- /dev/null +++ b/src/ravel/mncs_bundles.py @@ -0,0 +1,96 @@ +"""Optional adapter to the MNCS experimental immutable execution bundle. + +RAVEL stores the sibling bundle's logical and archive identities. Bundle +path, archive, manifest, and receipt validators remain owned by MNCS. +""" + +from __future__ import annotations + +from dataclasses import dataclass +from pathlib import Path +from typing import Any, Mapping + + +@dataclass(frozen=True, slots=True) +class BundleResult: + status: str + logical_identity: str | None + archive_identity: str | None + manifest: Mapping[str, Any] | None + reason_code: str + issues: tuple[str, ...] = () + + +def _unknown(reason: str) -> BundleResult: + return BundleResult("UNKNOWN", None, None, None, reason) + + +def _report(value: Any, *, invalid_reason: str) -> BundleResult: + valid = bool(getattr(value, "valid", False)) + issues = tuple(str(item) for item in getattr(value, "issues", ())) + return BundleResult( + "PASS" if valid else "FAIL", + getattr(value, "bundle_identity", None), + getattr(value, "archive_identity", None), + getattr(value, "manifest", None), + "mncs_bundle_verified" if valid else invalid_reason, + issues, + ) + + +def build_execution_bundle( + source_manifest: Path, source_root: Path, output: Path +) -> BundleResult: + """Delegate deterministic bundle construction to MNCS when installed.""" + + try: + from mncs_validator.execution_bundle import build_execution_bundle as build + except ImportError: + return _unknown("mncs_bundle_implementation_unavailable") + try: + return _report(build(source_manifest, source_root, output), invalid_reason="mncs_bundle_invalid") + except Exception as error: + return BundleResult("UNKNOWN", None, None, None, "mncs_bundle_build_failed", (type(error).__name__,)) + + +def verify_execution_bundle( + archive: Path, *, expected_logical_identity: str | None = None +) -> BundleResult: + """Verify an archive and optionally bind its logical identity.""" + + try: + from mncs_validator.execution_bundle import verify_execution_bundle_archive + except ImportError: + return _unknown("mncs_bundle_implementation_unavailable") + try: + report = verify_execution_bundle_archive( + archive, expected_bundle_identity=expected_logical_identity + ) + return _report(report, invalid_reason="mncs_bundle_verification_failed") + except Exception as error: + return BundleResult("UNKNOWN", None, None, None, "mncs_bundle_verify_failed", (type(error).__name__,)) + + +def bind_receipt_to_bundle(receipt: Mapping[str, Any], bundle: BundleResult) -> str: + """Return the official binding disposition without recreating its checks.""" + + if bundle.status != "PASS" or bundle.logical_identity is None: + return "UNKNOWN" + try: + from mncs_validator.execution_bundle import ( + ExecutionBundleReport, + bind_receipt_to_bundle as bind, + ) + except ImportError: + return "UNKNOWN" + report = ExecutionBundleReport( + target="", + valid=True, + bundle_identity=bundle.logical_identity, + archive_identity=bundle.archive_identity, + manifest=dict(bundle.manifest or {}), + ) + try: + return "PASS" if bind(dict(receipt), report).valid else "FAIL" + except Exception: + return "UNKNOWN" diff --git a/src/ravel/mncs_receipts.py b/src/ravel/mncs_receipts.py index 3f5d948..379049e 100644 --- a/src/ravel/mncs_receipts.py +++ b/src/ravel/mncs_receipts.py @@ -25,6 +25,7 @@ class ReceiptResult: validation_status: str reason_code: str limitations: tuple[str, ...] + bundle_binding_status: str = "UNKNOWN" def _optional_builder() -> ReceiptBuilder | None: @@ -44,35 +45,31 @@ def _optional_validator() -> ReceiptValidator | None: def _execution_record(raw: RawEvidence) -> dict[str, Any]: - """Project immutable Forge observations into Fabric's input vocabulary.""" - - status = raw.raw_status - termination = { - "PASS": "COMPLETED", - "FAIL": "NONZERO_EXIT", - "UNKNOWN": "CAPABILITY_UNAVAILABLE", - }[status] - return { - "record_id": raw.request_id, - "job_identity": raw.request_id, - "candidate_identity": raw.artifact_digests[0] if raw.artifact_digests else None, - "artifact_manifest_identity": raw.artifact_digests[0] if raw.artifact_digests else None, - "declared_argv": ["mncs-forge", "verifier", "run"], - "declared_environment": {"environment_id": raw.environment_id}, - "termination_reason": termination, - "exit_code": 0 if status == "PASS" else 1, - "outcome": status, - "results": [], - "stdout": {"bytes": 0, "captured_utf8": "", "truncated": False}, - "stderr": { - "bytes": len(raw.diagnostics.encode("utf-8")), - "captured_utf8": raw.diagnostics, - "truncated": False, - }, - "node": {"machine_label": raw.environment_id}, - "policy_observations": {}, - "resource_observations": dict(raw.resource_observations), - } + """Project only an explicitly observed runner record. + + A verifier disposition is not a process outcome. In particular, this + function must not infer an exit code, argv, stream contents, termination, + resource enforcement, or bundle use from ``raw_status``. + """ + + observations = raw.observations + nested = observations.get("execution_record") if isinstance(observations, Mapping) else None + if not isinstance(nested, Mapping) and isinstance(observations, Mapping): + nested = observations.get("execution") + record: dict[str, Any] = dict(nested) if isinstance(nested, Mapping) else {} + # The adapter record identity is known locally. Every runner fact below + # remains absent unless Forge/Fabric actually supplied it. + record.setdefault("record_id", raw.request_id) + return record + + +def _has_observed_runner_record(raw: RawEvidence) -> bool: + observations = raw.observations + if not isinstance(observations, Mapping): + return False + return isinstance(observations.get("execution_record"), Mapping) or isinstance( + observations.get("execution"), Mapping + ) def build_validated_receipt( @@ -80,6 +77,7 @@ def build_validated_receipt( *, builder: ReceiptBuilder | None = None, validator: ReceiptValidator | None = None, + bundle: Any | None = None, ) -> ReceiptResult: """Delegate receipt construction/validation, failing closed if unavailable.""" @@ -120,11 +118,35 @@ def build_validated_receipt( (f"Receipt validation failed with {type(error).__name__}.",), ) if getattr(report, "valid", False) is True: + if not _has_observed_runner_record(raw): + return ReceiptResult( + receipt, + "UNKNOWN", + "mncs_receipt_runner_facts_not_observed", + ( + "The official builder produced a structural envelope, but Forge did not " + "observe runner process facts for this verifier response.", + ), + ) + bundle_binding_status = "UNKNOWN" + if bundle is not None: + from .mncs_bundles import bind_receipt_to_bundle + + bundle_binding_status = bind_receipt_to_bundle(receipt, bundle) + if bundle_binding_status != "PASS": + return ReceiptResult( + receipt, + "UNKNOWN", + "mncs_receipt_bundle_binding_unresolved", + ("Receipt validation passed, but the supplied bundle binding did not pass.",), + bundle_binding_status, + ) return ReceiptResult( receipt, "PASS", "mncs_receipt_structurally_valid", ("Structural receipt validity is not assurance or conformance.",), + bundle_binding_status, ) return ReceiptResult( receipt, diff --git a/tests/test_mncs_bundles.py b/tests/test_mncs_bundles.py new file mode 100644 index 0000000..a013e1f --- /dev/null +++ b/tests/test_mncs_bundles.py @@ -0,0 +1,59 @@ +from __future__ import annotations + +import json +from pathlib import Path +import tempfile +import unittest + +from ravel.mncs_bundles import BundleResult, build_execution_bundle, bind_receipt_to_bundle + + +class MncsBundleTests(unittest.TestCase): + def _manifest(self, entry_path: str = "fixture.txt") -> dict[str, object]: + return { + "schema_version": "0.1-experimental", + "record_type": "mncs-execution-bundle-source", + "bundle_id": "ravel-test-bundle", + "entries": [{"path": entry_path, "source": "fixture.txt", "role": "fixture", "mode": "0644"}], + "entrypoints": [{"name": "fixture", "path": entry_path}], + "runtime_requirements": [], + "policy_references": [], + "limits": {"max_file_count": 8, "max_file_bytes": 4096, "max_total_bytes": 8192, "max_path_bytes": 128, "max_expansion_ratio": 20}, + "extensions": {"ravel:purpose": "development-only test"}, + } + + def test_official_bundle_builder_accepts_valid_fixture(self) -> None: + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + source = root / "source" + source.mkdir() + (source / "fixture.txt").write_text("fixture\n", encoding="utf-8") + manifest = root / "source.json" + manifest.write_text(json.dumps(self._manifest()), encoding="utf-8") + result = build_execution_bundle(manifest, source, root / "bundle.zip") + self.assertIn(result.status, {"PASS", "UNKNOWN"}) + + def test_receipt_binding_does_not_reimplement_mncs_checks(self) -> None: + bundle = BundleResult( + "PASS", "a" * 64, "b" * 64, + {"harness_identity": None, "input_snapshot_identity": None, "policy_identity": None}, + "mncs_bundle_verified", + ) + self.assertIn( + bind_receipt_to_bundle({"bundle": {"test_bundle_identity": "a" * 64}}, bundle), + {"PASS", "FAIL", "UNKNOWN"}, + ) + + def test_official_validator_rejects_ambiguous_member_paths(self) -> None: + try: + from mncs_validator.execution_bundle import normalize_bundle_path + except ImportError: + self.skipTest("optional MNCS execution-bundle validator unavailable") + for value in ("../escape", "/absolute", "C:\\drive", "a//b", "a/../b", "a\x00b"): + with self.subTest(value=value): + with self.assertRaises(ValueError): + normalize_bundle_path(value) + + +if __name__ == "__main__": + unittest.main() diff --git a/tests/test_mncs_receipts.py b/tests/test_mncs_receipts.py index 4f9a0df..cdb837a 100644 --- a/tests/test_mncs_receipts.py +++ b/tests/test_mncs_receipts.py @@ -37,10 +37,11 @@ class Report: builder=builder, validator=lambda value, target: Report(), ) - self.assertEqual(result.validation_status, "PASS") - self.assertEqual(result.reason_code, "mncs_receipt_structurally_valid") - self.assertEqual(observed["candidate_identity"], "sha256:" + "b" * 64) - self.assertIn("not assurance", result.limitations[0]) + self.assertEqual(result.validation_status, "UNKNOWN") + self.assertEqual(result.reason_code, "mncs_receipt_runner_facts_not_observed") + self.assertNotIn("candidate_identity", observed) + self.assertNotIn("declared_argv", observed) + self.assertNotIn("exit_code", observed) def test_missing_optional_receipt_builder_is_unknown(self) -> None: result = build_validated_receipt(RAW, builder=lambda record: (_ for _ in ()).throw(RuntimeError())) @@ -65,8 +66,32 @@ def test_local_mncs_builder_and_validator_when_available(self) -> None: builder=build_execution_receipt, validator=validate_execution_receipt_value, ) + self.assertEqual(result.validation_status, "UNKNOWN") + self.assertEqual(result.reason_code, "mncs_receipt_runner_facts_not_observed") + + def test_explicit_execution_record_facts_are_carried_without_inference(self) -> None: + observed = {} + + def builder(record): + observed.update(record) + return {"receipt": "official"} + + class Report: + valid = True + issues = [] + + raw = RawEvidence( + "request", "forge", "PASS", {"execution_record": { + "job_identity": "job-observed", + "declared_argv": ["bounded-harness"], + "exit_code": 7, + "termination_reason": "NONZERO_EXIT", + }}, None, (), "env", {}, + ) + result = build_validated_receipt(raw, builder=builder, validator=lambda value, target: Report()) self.assertEqual(result.validation_status, "PASS") - self.assertEqual(result.reason_code, "mncs_receipt_structurally_valid") + self.assertEqual(observed["exit_code"], 7) + self.assertEqual(observed["declared_argv"], ["bounded-harness"]) if __name__ == "__main__": diff --git a/tests/test_ravel_0_6_decomposition.py b/tests/test_ravel_0_6_decomposition.py index 55923ca..144fec7 100644 --- a/tests/test_ravel_0_6_decomposition.py +++ b/tests/test_ravel_0_6_decomposition.py @@ -20,7 +20,12 @@ def compile_and_run( source: Path, binary: Path, extra_flags: tuple[str, ...] = () ) -> dict[str, object]: built = subprocess.run( - ["cc", "-std=c11", "-O0", "-Wall", "-Wextra", "-Werror", "-pedantic", *extra_flags, str(source), "-lm", "-o", str(binary)], + [ + "cc", "-std=c11", "-O0", "-Wall", "-Wextra", "-Werror", "-pedantic", + "-I", str(ROOT / "ravel_versions/0.6/ravel_0_6"), *extra_flags, + str(source), str(ROOT / "ravel_versions/0.6/ravel_0_6/ravel_0_6_provider_branching.c"), + "-lm", "-o", str(binary), + ], cwd=ROOT, text=True, capture_output=True, @@ -108,6 +113,42 @@ def test_real_c_provider_substitution_is_explicit_and_observable(self) -> None: self.assertNotEqual(branching["environment_provider_id"], ring["environment_provider_id"]) self.assertNotEqual(branching["candidate"]["model_identity"], ring["candidate"]["model_identity"]) + def test_world_abi_provider_objects_share_contract_and_wrong_header_fails_closed(self) -> None: + source = build_candidate_source(FROZEN_SOURCE.read_bytes()) + with tempfile.TemporaryDirectory() as directory: + root = Path(directory) + candidate = root / "candidate.c" + candidate.write_text(source, encoding="utf-8") + for provider in ("branching", "ring"): + binary = root / provider + built = subprocess.run( + [ + "cc", "-std=c11", "-O0", "-Wall", "-Wextra", "-Werror", "-pedantic", + "-I", str(ROOT / "ravel_versions/0.6/ravel_0_6"), str(candidate), + str(ROOT / "ravel_versions/0.6/ravel_0_6" / f"ravel_0_6_provider_{provider}.c"), + "-lm", "-o", str(binary), + ], cwd=ROOT, text=True, capture_output=True, check=False, + ) + self.assertEqual(built.returncode, 0, built.stderr) + trial = run_binary(binary) + self.assertEqual(trial["environment_provider_id"], f"ravel-toy-{provider}-c/1") + + wrong_include = root / "wrong-header" + wrong_include.mkdir() + header = (ROOT / "ravel_versions/0.6/ravel_0_6/ravel_0_6_world.h").read_text() + wrong_include.joinpath("ravel_0_6_world.h").write_text( + header.replace("RAVEL06_WORLD_ABI_NUMERIC 1u", "RAVEL06_WORLD_ABI_NUMERIC 2u"), + encoding="utf-8", + ) + rejected = subprocess.run( + [ + "cc", "-std=c11", "-O0", "-Wall", "-Wextra", "-Werror", "-pedantic", + "-I", str(wrong_include), "-I", str(ROOT / "ravel_versions/0.6/ravel_0_6"), + "-c", str(candidate), "-o", str(root / "wrong.o"), + ], cwd=ROOT, text=True, capture_output=True, check=False, + ) + self.assertNotEqual(rejected.returncode, 0) + if __name__ == "__main__": unittest.main() diff --git a/tests/test_ravel_0_6_negative_matrix.py b/tests/test_ravel_0_6_negative_matrix.py index 30da561..53acbf3 100644 --- a/tests/test_ravel_0_6_negative_matrix.py +++ b/tests/test_ravel_0_6_negative_matrix.py @@ -28,7 +28,12 @@ def run_mutated(replacement: tuple[str, str] | None) -> dict[str, object]: binary = root / "candidate" source_path.write_text(source, encoding="utf-8") built = subprocess.run( - ["cc", "-std=c11", "-O0", "-Wall", "-Wextra", "-Werror", "-pedantic", str(source_path), "-lm", "-o", str(binary)], + [ + "cc", "-std=c11", "-O0", "-Wall", "-Wextra", "-Werror", "-pedantic", + "-I", str(ROOT / "ravel_versions/0.6/ravel_0_6"), str(source_path), + str(ROOT / "ravel_versions/0.6/ravel_0_6/ravel_0_6_provider_branching.c"), + "-lm", "-o", str(binary), + ], cwd=ROOT, text=True, capture_output=True, diff --git a/tests/test_ravel_0_6_provenance.py b/tests/test_ravel_0_6_provenance.py index 66ef75b..cff8a3c 100644 --- a/tests/test_ravel_0_6_provenance.py +++ b/tests/test_ravel_0_6_provenance.py @@ -48,6 +48,15 @@ def test_build_record_binds_generator_compiler_and_raw_status(self) -> None: checkpoint = record["component_contracts"]["checkpoint"] self.assertEqual(checkpoint["header_sha256"], sha256_bytes(C_CHECKPOINT_HEADER.read_bytes())) self.assertEqual(checkpoint["implementation_sha256"], sha256_bytes(C_CHECKPOINT_IMPLEMENTATION.read_bytes())) + world = record["component_contracts"]["world"] + self.assertEqual(world["abi_version"], "ravel-0.6-world-abi/1") + self.assertTrue(world["header_sha256"]) + self.assertTrue(world["implementation_sha256"]) + self.assertTrue(world["object_sha256"]) + self.assertTrue(world["compile_argv"]) + self.assertTrue(world["compiler_identity"]) + self.assertIn("ravel_0_6_world.h", world["declared_dependencies"][0]) + self.assertEqual(record["build"]["separate_binary_sha256"], record["build"]["binary_sha256"]) self.assertEqual(len(record["generated_components"]), 10) self.assertTrue(record["mechanism_components"]) self.assertTrue(record["compiler"]["argv"]) diff --git a/tests/test_ravel_0_6_transaction.py b/tests/test_ravel_0_6_transaction.py index f6297bc..6e551df 100644 --- a/tests/test_ravel_0_6_transaction.py +++ b/tests/test_ravel_0_6_transaction.py @@ -21,7 +21,12 @@ def build_and_trial(source_text: str, directory: Path) -> dict[str, object]: binary = directory / "candidate" source.write_text(source_text, encoding="utf-8") built = subprocess.run( - ["cc", "-std=c11", "-O0", "-Wall", "-Wextra", "-Werror", "-pedantic", str(source), "-lm", "-o", str(binary)], + [ + "cc", "-std=c11", "-O0", "-Wall", "-Wextra", "-Werror", "-pedantic", + "-I", str(ROOT / "ravel_versions/0.6/ravel_0_6"), str(source), + str(ROOT / "ravel_versions/0.6/ravel_0_6/ravel_0_6_provider_branching.c"), + "-lm", "-o", str(binary), + ], cwd=ROOT, text=True, capture_output=True, @@ -107,8 +112,33 @@ def test_development_evaluator_rejects_identity_substitution(self) -> None: ) payload["candidate_id"] = "ravel-0.6-candidate-002" evaluation = evaluate_trial(payload, expected_provider_id="ravel-toy-branching-c/1") - self.assertEqual(evaluation.status, "FAIL") + self.assertEqual(evaluation.status, "UNKNOWN") self.assertIn("candidate_identity_mismatch", evaluation.reason_codes) + self.assertEqual(evaluation.evidence_completeness_status, "UNKNOWN") + + def test_evaluator_distinguishes_mechanism_failure_from_missing_evidence(self) -> None: + with tempfile.TemporaryDirectory() as directory: + payload = build_and_trial( + build_candidate_source(FROZEN_SOURCE.read_bytes()), Path(directory) + ) + payload["candidate"]["adaptation_transaction"]["raw"]["base_accuracy_after_q20"] = 0 + payload["candidate"]["adaptation_transaction"]["committed"] = False + payload["candidate"]["adaptation_transaction"]["failed_constraint_mask"] = 4 + payload.pop("matched_compute") + evaluation = evaluate_trial(payload, expected_provider_id="ravel-toy-branching-c/1") + self.assertEqual(evaluation.mechanism_status, "FAIL") + self.assertEqual(evaluation.matched_compute_status, "UNKNOWN") + self.assertEqual(evaluation.status, "FAIL") + + def test_evaluator_marks_malformed_required_evidence_unknown(self) -> None: + with tempfile.TemporaryDirectory() as directory: + payload = build_and_trial( + build_candidate_source(FROZEN_SOURCE.read_bytes()), Path(directory) + ) + payload["candidate"]["adaptation_transaction"] = {"committed": True} + evaluation = evaluate_trial(payload, expected_provider_id="ravel-toy-branching-c/1") + self.assertEqual(evaluation.status, "UNKNOWN") + self.assertIn("malformed_required_observation", evaluation.reason_codes) if __name__ == "__main__": diff --git a/tools/ravel_0_6_behavioral_fixtures.py b/tools/ravel_0_6_behavioral_fixtures.py index fdd8d33..e36741e 100644 --- a/tools/ravel_0_6_behavioral_fixtures.py +++ b/tools/ravel_0_6_behavioral_fixtures.py @@ -31,8 +31,11 @@ def compile_and_run(source: Path, directory: Path) -> tuple[int, dict[str, objec "-Wextra", "-Werror", "-pedantic", + "-I", + str(ROOT / "ravel_versions/0.6/ravel_0_6"), f'-DRAVEL_06_CANDIDATE_SOURCE="{source}"', str(HARNESS), + str(ROOT / "ravel_versions/0.6/ravel_0_6/ravel_0_6_provider_branching.c"), "-lm", "-o", str(binary), diff --git a/tools/ravel_0_6_build.py b/tools/ravel_0_6_build.py index 22035ba..5036646 100644 --- a/tools/ravel_0_6_build.py +++ b/tools/ravel_0_6_build.py @@ -52,6 +52,11 @@ ) C_CHECKPOINT_HEADER = ROOT / "ravel_versions/0.6/ravel_0_6/ravel_0_6_checkpoint.h" C_CHECKPOINT_IMPLEMENTATION = ROOT / "ravel_versions/0.6/ravel_0_6/ravel_0_6_checkpoint.c" +C_WORLD_HEADER = ROOT / "ravel_versions/0.6/ravel_0_6/ravel_0_6_world.h" +C_PROVIDER_SOURCES = { + "branching": ROOT / "ravel_versions/0.6/ravel_0_6/ravel_0_6_provider_branching.c", + "ring": ROOT / "ravel_versions/0.6/ravel_0_6/ravel_0_6_provider_ring.c", +} CANDIDATE_ID = "ravel-0.6-candidate-001" ENVIRONMENT_KEYS = ( "CC", @@ -104,13 +109,15 @@ def compiler_command() -> list[str]: return [executable] -def provider_configuration() -> tuple[str, list[str]]: +def provider_configuration() -> tuple[str, Path]: provider = os.environ.get("RAVEL06_PROVIDER", "branching") - if provider == "branching": - return "ravel-toy-branching-c/1", [] - if provider == "ring": - return "ravel-toy-ring-c/1", ["-DRAVEL06_PROVIDER_RING"] - raise BuildError("RAVEL06_PROVIDER must be branching or ring") + if provider not in C_PROVIDER_SOURCES: + raise BuildError("RAVEL06_PROVIDER must be branching or ring") + provider_id = { + "branching": "ravel-toy-branching-c/1", + "ring": "ravel-toy-ring-c/1", + }[provider] + return provider_id, C_PROVIDER_SOURCES[provider] def extra_compile_flags() -> list[str]: @@ -139,8 +146,10 @@ def build(output_dir: Path, *, require_clean_worktree: bool = False) -> dict[str monolithic_source_path = output_dir / "ravel_0_6_candidate_001.generated.c" binary_path = output_dir / "ravel_0_6_candidate_001" unity_binary_path = output_dir / "ravel_0_6_candidate_001.unity" + unity_source_path = output_dir / "ravel_0_6_candidate_001.unity.c" candidate_object_path = output_dir / "ravel_0_6_candidate_001.o" checkpoint_object_path = output_dir / "ravel_0_6_checkpoint.o" + provider_object_path = output_dir / "ravel_0_6_provider.o" record_path = output_dir / "ravel-0.6-candidate-001-build.json" existing = [ @@ -150,8 +159,10 @@ def build(output_dir: Path, *, require_clean_worktree: bool = False) -> dict[str monolithic_source_path, binary_path, unity_binary_path, + unity_source_path, candidate_object_path, checkpoint_object_path, + provider_object_path, record_path, ) if path.exists() @@ -167,18 +178,24 @@ def build(output_dir: Path, *, require_clean_worktree: bool = False) -> dict[str raise BuildError("clean-worktree check failed: " + " | ".join(status)) source = build_candidate_source(FROZEN_SOURCE.read_bytes()).encode("utf-8") - provider_id, provider_flags = provider_configuration() + provider_id, provider_source = provider_configuration() monolithic_source_path.write_bytes(source) source_path, component_paths = write_decomposed_candidate( source.decode("utf-8"), output_dir ) + unity_source_path.write_text( + '#include "ravel_0_6_candidate_001.generated.c"\n' + f'#include "{provider_source.name}"\n', + encoding="utf-8", + newline="\n", + ) compiler = compiler_command() version = run_capture(compiler + ["--version"]) include_flags = ["-I", str(C_CHECKPOINT_HEADER.parent)] extra_flags = extra_compile_flags() compile_flags = list(CANONICAL_FLAGS) + extra_flags - unity_argv = compiler + compile_flags + provider_flags + [ - str(monolithic_source_path), + unity_argv = compiler + compile_flags + include_flags + ["-I", str(provider_source.parent)] + [ + str(unity_source_path), "-lm", "-o", str(unity_binary_path), @@ -190,16 +207,23 @@ def build(output_dir: Path, *, require_clean_worktree: bool = False) -> dict[str "-o", str(checkpoint_object_path), ] - candidate_compile_argv = compiler + compile_flags + provider_flags + include_flags + [ + candidate_compile_argv = compiler + compile_flags + include_flags + [ "-DRAVEL06_SEPARATE_CHECKPOINT", "-c", str(source_path), "-o", str(candidate_object_path), ] + provider_compile_argv = compiler + compile_flags + include_flags + [ + "-c", + str(provider_source), + "-o", + str(provider_object_path), + ] link_argv = compiler + extra_flags + [ str(candidate_object_path), str(checkpoint_object_path), + str(provider_object_path), "-lm", "-o", str(binary_path), @@ -207,6 +231,7 @@ def build(output_dir: Path, *, require_clean_worktree: bool = False) -> dict[str unity_result = run_capture(unity_argv) checkpoint_result = run_capture(checkpoint_compile_argv) candidate_result = run_capture(candidate_compile_argv) + provider_result = run_capture(provider_compile_argv) link_result = run_capture(link_argv) argv = candidate_compile_argv + [""] + link_argv result = link_result @@ -216,6 +241,8 @@ def build(output_dir: Path, *, require_clean_worktree: bool = False) -> dict[str result = checkpoint_result elif candidate_result.returncode != 0: result = candidate_result + elif provider_result.returncode != 0: + result = provider_result elif link_result.returncode != 0: result = link_result record: dict[str, Any] = { @@ -248,6 +275,23 @@ def build(output_dir: Path, *, require_clean_worktree: bool = False) -> dict[str "header_sha256": sha256_file(C_CHECKPOINT_HEADER), "implementation_path": str(C_CHECKPOINT_IMPLEMENTATION.relative_to(ROOT)), "implementation_sha256": sha256_file(C_CHECKPOINT_IMPLEMENTATION), + "object_sha256": sha256_file(checkpoint_object_path) if checkpoint_result.returncode == 0 else None, + "compile_argv": checkpoint_compile_argv, + "compiler_identity": version.stdout.strip(), + "declared_dependencies": [], + }, + "world": { + "abi_version": "ravel-0.6-world-abi/1", + "header_path": str(C_WORLD_HEADER.relative_to(ROOT)), + "header_sha256": sha256_file(C_WORLD_HEADER), + "provider_source_path": str(provider_source.relative_to(ROOT)), + "provider_source_sha256": sha256_file(provider_source), + "implementation_sha256": sha256_file(provider_source), + "object_sha256": sha256_file(provider_object_path) if provider_result.returncode == 0 else None, + "compile_argv": provider_compile_argv, + "compiler_identity": version.stdout.strip(), + "declared_dependencies": [str(C_WORLD_HEADER.relative_to(ROOT))], + "provider_id": provider_id, } }, "policy": { @@ -257,9 +301,10 @@ def build(output_dir: Path, *, require_clean_worktree: bool = False) -> dict[str "inherited_05_preregistration_sha256": sha256_file(INHERITED_05_PREREGISTRATION), }, "environment_provider": { - "provider_id": provider_id, - "compile_flags": provider_flags, - }, + "provider_id": provider_id, + "compile_flags": [], + "source_path": str(provider_source.relative_to(ROOT)), + }, "mechanism_components": [ {"path": path, "sha256": sha256_file(ROOT / path)} for path in COMPONENT_FILES @@ -287,6 +332,7 @@ def build(output_dir: Path, *, require_clean_worktree: bool = False) -> dict[str "unity_argv": unity_argv, "checkpoint_compile_argv": checkpoint_compile_argv, "candidate_compile_argv": candidate_compile_argv, + "provider_compile_argv": provider_compile_argv, "link_argv": link_argv, }, "worktree": { @@ -298,9 +344,12 @@ def build(output_dir: Path, *, require_clean_worktree: bool = False) -> dict[str "stderr": result.stderr, "exit_status": result.returncode, "binary_sha256": sha256_file(binary_path) if result.returncode == 0 else None, + "separate_binary_sha256": sha256_file(binary_path) if result.returncode == 0 else None, "unity_binary_sha256": sha256_file(unity_binary_path) if unity_result.returncode == 0 else None, + "unity_source_sha256": sha256_file(unity_source_path) if unity_source_path.exists() else None, "candidate_object_sha256": sha256_file(candidate_object_path) if candidate_result.returncode == 0 else None, "checkpoint_object_sha256": sha256_file(checkpoint_object_path) if checkpoint_result.returncode == 0 else None, + "provider_object_sha256": sha256_file(provider_object_path) if provider_result.returncode == 0 else None, }, "execution": { "argv": None, diff --git a/tools/ravel_0_6_seed_candidate.py b/tools/ravel_0_6_seed_candidate.py index 383bc2c..e81e662 100644 --- a/tools/ravel_0_6_seed_candidate.py +++ b/tools/ravel_0_6_seed_candidate.py @@ -105,36 +105,11 @@ """ PROVIDER_SURFACE = """\ -#ifndef RAVEL06_PROVIDER_RING -#define RAVEL06_PROVIDER_ID "ravel-toy-branching-c/1" -#else -#define RAVEL06_PROVIDER_ID "ravel-toy-ring-c/1" -#endif - -void make_ring_world(World *w, const TrialSpec *spec) { - memset(w, 0, sizeof *w); - for (uint32_t s = 0; s < STATES; ++s) { - for (uint32_t d = 0; d < D; ++d) { - int sign = ((s + 3u * d + (s >> 2u)) & 1u) ? 1 : -1; - w->center[s][d] = (int16_t)(sign * (spec->amplitude - (int)(d % 3u))); - } - w->label[s] = (uint8_t)((s * 7u + (s >> 3u)) & 7u); - for (uint32_t a = 0; a < ACTIONS; ++a) { - w->base_next[s][a] = (uint8_t)((s + a + 1u) & 63u); - w->drift_next[s][a] = w->base_next[s][a]; - } - if (spec->transition_drift && s < 24u) { - w->drift_next[s][1] = (uint8_t)((s + 5u) & 63u); - } - } -} - static void make_world(World *w, const TrialSpec *spec) { -#ifdef RAVEL06_PROVIDER_RING - make_ring_world(w, spec); -#else - make_branching_world(w, spec); -#endif + Ravel06WorldConfig config = { + spec->amplitude, spec->transition_drift, spec->ambiguous + }; + if (!ravel06_world_init(w, &config)) memset(w, 0, sizeof *w); } """ @@ -182,7 +157,7 @@ def build_candidate_source(source_bytes: bytes) -> str: raise SeedError("provider surface: expected one synthetic world provider") source = source.replace( old_world_start, - "void make_branching_world(World *w, const TrialSpec *spec) {", + "void make_legacy_world(World *w, const TrialSpec *spec) {", 1, ) provider_boundary = "\n}\n\nstatic void make_observation" @@ -194,6 +169,19 @@ def build_candidate_source(source_bytes: bytes) -> str: 1, ) + old_world_type = """typedef struct { + int16_t center[STATES][D]; + uint8_t label[STATES]; + uint8_t base_next[STATES][ACTIONS]; + uint8_t drift_next[STATES][ACTIONS]; +} World;""" + new_world_type = """#include \"ravel_0_6_world.h\" +_Static_assert(RAVEL06_WORLD_ABI_NUMERIC == 1u, "unsupported RAVEL world ABI"); +typedef Ravel06World World;""" + if source.count(old_world_type) != 1: + raise SeedError("world ABI: expected one internal world type") + source = source.replace(old_world_type, new_world_type, 1) + old_trial_identity = r''' " \"trial_id\":\"%s\",\"regime\":\"%s\","''' if source.count(old_trial_identity) != 1: raise SeedError("provider surface: expected trial identity output") @@ -202,7 +190,7 @@ def build_candidate_source(source_bytes: bytes) -> str: " \"trial_id\":\"%s\",\"regime\":\"%s\","''' source = source.replace(old_trial_identity, new_trial_identity, 1) old_trial_args = " spec->trial_id, spec->regime, spec->seed," - new_trial_args = " RAVEL06_PROVIDER_ID, spec->trial_id, spec->regime, spec->seed," + new_trial_args = " ravel06_world_provider_id(), spec->trial_id, spec->regime, spec->seed," if source.count(old_trial_args) != 1: raise SeedError("provider surface: expected trial identity arguments") source = source.replace(old_trial_args, new_trial_args, 1) diff --git a/tools/ravel_bundle_check.py b/tools/ravel_bundle_check.py new file mode 100644 index 0000000..a026a03 --- /dev/null +++ b/tools/ravel_bundle_check.py @@ -0,0 +1,47 @@ +#!/usr/bin/env python3 +"""Forge workflow helper for one bounded, development-only MNCS bundle.""" + +from __future__ import annotations + +import json +from pathlib import Path +import tempfile + +from ravel.mncs_bundles import build_execution_bundle, verify_execution_bundle + + +def main() -> int: + root = Path(__file__).resolve().parents[1] + with tempfile.TemporaryDirectory(prefix="ravel-bundle-") as directory: + work = Path(directory) + source_root = work / "source" + source_root.mkdir() + (source_root / "fixture.txt").write_text("ravel development fixture\n", encoding="utf-8") + manifest = work / "bundle-source.json" + manifest.write_text( + json.dumps( + { + "schema_version": "0.1-experimental", + "record_type": "mncs-execution-bundle-source", + "bundle_id": "ravel-development-representative", + "entries": [{"path": "fixture.txt", "source": "fixture.txt", "role": "fixture", "mode": "0644"}], + "entrypoints": [{"name": "fixture", "path": "fixture.txt"}], + "runtime_requirements": [], + "policy_references": [], + "limits": {"max_file_count": 8, "max_file_bytes": 4096, "max_total_bytes": 8192, "max_path_bytes": 128, "max_expansion_ratio": 20}, + "extensions": {"ravel:purpose": "development-only representative verification"}, + }, + sort_keys=True, + ), + encoding="utf-8", + ) + archive = work / "ravel-development-bundle.zip" + built = build_execution_bundle(manifest, source_root, archive) + checked = verify_execution_bundle(archive, expected_logical_identity=built.logical_identity) if built.status == "PASS" else built + status = "PASS" if built.status == "PASS" and checked.status == "PASS" else checked.status + print(json.dumps({"status": status, "bundle": built.__dict__ if hasattr(built, "__dict__") else {"reason_code": built.reason_code}, "verified": checked.reason_code}, sort_keys=True)) + return 0 if status == "PASS" else 2 + + +if __name__ == "__main__": + raise SystemExit(main()) diff --git a/tools/ravel_forge_check.py b/tools/ravel_forge_check.py new file mode 100644 index 0000000..fc22a3f --- /dev/null +++ b/tools/ravel_forge_check.py @@ -0,0 +1,120 @@ +#!/usr/bin/env python3 +"""Small bounded commands declared by the project-local Forge configuration.""" + +from __future__ import annotations + +import json +import os +from pathlib import Path +import subprocess +import tempfile +import unittest + +ROOT = Path(__file__).resolve().parents[1] + + +def _run(module: str, *tests: str) -> tuple[str, str]: + result = subprocess.run( + ["python3", "-m", "unittest", *tests], + cwd=ROOT, + text=True, + capture_output=True, + check=False, + ) + return ("PASS" if result.returncode == 0 else "FAIL", result.stderr[-2000:]) + + +def _build(provider: str, root: Path) -> dict[str, object]: + env = dict(os.environ) + env["RAVEL06_PROVIDER"] = provider + result = subprocess.run( + ["python3", "tools/ravel_0_6_build.py", "build", "--output-dir", str(root)], + cwd=ROOT, + env=env, + text=True, + capture_output=True, + check=False, + ) + if result.returncode != 0: + raise RuntimeError(result.stderr[-2000:]) + return json.loads(result.stdout) + + +def _trial(binary: Path) -> bytes: + result = subprocess.run( + [str(binary), "--trial", "decomposition", "--regime", "separated_state", "--seed", "0x1234"], + cwd=ROOT, + capture_output=True, + check=False, + ) + if result.returncode != 0: + raise RuntimeError(result.stderr.decode(errors="replace")) + return result.stdout + + +def check(name: str) -> tuple[str, str]: + if name == "frozen-identities": + return _run(name, "tests/test_frozen_identities.py", "tests/test_policy.py") + if name == "policy": + return _run(name, "tests/test_policy.py") + if name == "behavior": + return _run(name, "tests/test_ravel_0_6_decomposition.py") + if name == "transactions": + return _run(name, "tests/test_ravel_0_6_transaction.py") + if name == "negative-matrix": + return _run(name, "tests/test_ravel_0_6_negative_matrix.py") + if name == "component-parity": + return _run(name, "tests/test_ravel_0_6_decomposition.py", "tests/test_components.py") + if name == "development-evaluator": + return _run(name, "tests/test_ravel_0_6_transaction.py") + if name == "build": + with tempfile.TemporaryDirectory(prefix="ravel-forge-build-") as directory: + _build("branching", Path(directory)) + return "PASS", "unity and separately compiled candidate build completed" + if name == "world-provider-parity": + with tempfile.TemporaryDirectory(prefix="ravel-forge-world-") as directory: + base = Path(directory) + for provider in ("branching", "ring"): + record = _build(provider, base / provider) + separate = _trial(base / provider / "ravel_0_6_candidate_001") + unity = _trial(base / provider / "ravel_0_6_candidate_001.unity") + if separate != unity or record["component_contracts"]["world"]["abi_version"] != "ravel-0.6-world-abi/1": + return "FAIL", f"{provider} unity/separate facts differ" + return "PASS", "branching and ring unity/separate raw trials matched" + if name == "package": + return _run(name, "tests/test_frozen_identities.py", "tests/test_lifecycle_experience.py") + if name == "live-family-compat": + sibling = ROOT.parent / "MNCS-Commons/src" + if not sibling.is_dir(): + return "UNKNOWN", "MNCS Commons checkout unavailable" + import sys + sys.path.insert(0, str(sibling)) + try: + from mncs_commons.application.services import CompatibilityApplication + report = CompatibilityApplication.report({"ravel": ROOT}) + except Exception as error: + return "UNKNOWN", f"Commons compatibility adapter unavailable: {type(error).__name__}" + statuses = {str(item.get("status")) for item in report} + if "DRIFTED" in statuses: + return "FAIL", json.dumps(report, sort_keys=True) + if "UNKNOWN" in statuses or "COMPATIBLE_WITH_UNRESOLVED_FIELDS" in statuses: + return "UNKNOWN", json.dumps(report, sort_keys=True) + return "PASS", json.dumps(report, sort_keys=True) + if name == "lifecycle": + return "PASS", "RAVEL candidate remains development-only; Forge mapping is reference-only" + raise ValueError(f"unknown Forge workflow: {name}") + + +def main() -> int: + import sys + name = sys.argv[1] if len(sys.argv) == 2 else "" + try: + status, detail = check(name) + except Exception as error: + status, detail = "FAIL", f"{type(error).__name__}: {error}" + print(json.dumps({"status": status, "workflow": name, "detail": detail}, sort_keys=True)) + return 0 if status in {"PASS", "UNKNOWN"} else 1 + + +if __name__ == "__main__": + raise SystemExit(main())