# API stability (roadmap item 338, docs/gate-dependency-contract.md): the # primary promised dependency surface for `pip install revl` is `revl.gate` # (its `__all__`, pinned exactly by tests/test_gate_compat.py). The narrow # `revl.fs_workspace` Python root-binding and committed-preimage host APIs have # separate capability versions and contracts in docs/witnessed-fs.md. Every other # `revl.*` module is importable — a wheel cannot hide its modules — but # UNVERSIONED, and may change on any release including a patch. Within # `revl.gate`, `gate_version().api` (see src/revl/gate.py) is the semver of # that surface itself, independent of this package's own `version` below, # which tracks the revl LANGUAGE. See docs/gate-dependency-contract.md for # the full consumer-facing security contract this classifier implies: a # refusal is authoritative and fail-closed; an admission is a compile-time # judgment scoped to `gate_version().frontier`, never a runtime sandbox. # # `revl.fs` is a second such narrow, separately versioned host surface # (`WRITE_RECEIPT_API_VERSION`): a guarded native write returning a receipt with # an expected-before check (issue #523), documented in docs/witnessed-fs.md. [project] name = "revl" version = "2.0.0" description = "A research language for spatiotemporal composability (Cordis paradigm)" readme = "README.md" requires-python = ">=3.11" license = { text = "AGPL-3.0-only AND MIT" } license-files = ["LICENSE", "LICENSES/*", "LICENSING.md", "TRADEMARK.md"] classifiers = [ "Development Status :: 4 - Beta", "Intended Audience :: Developers", "Programming Language :: Python :: 3", "Programming Language :: Python :: 3 :: Only", "Topic :: Software Development :: Compilers", ] [project.optional-dependencies] # `coverage` is a TEST dependency, not a runtime one: tests/test_selfhost_coverage.py # measures which statements of the mirrored reference emitters the self-host corpus # never executes (roadmap item 429) and gates on it, which needs a real tracer. # `pyyaml`: tests/test_env_gated_skips_run_somewhere.py reads .github/workflows/ # to pair each CI-gated test against the job that actually runs it. It used to # reach for PyYAML through `importorskip`, so on an install without it the # strongest anti-silent-skip checks in the repo silently skipped (issue #266). # It is a test dependency now, so they cannot. # `cryptography`: a TEST dependency for the same reason `pyyaml` is one. # src/revl/tee_quote.py implements ECDSA over P-256 and P-384 in pure Python # integers, and that code decides whether a TEE attestation quote is genuine. # tests/test_ecdsa_differential.py checks it against OpenSSL's implementation on # random keys and messages in both directions, which is the only check that does # not rely on the module agreeing with itself. It imports `cryptography` HARD: # behind an `importorskip` the one independent check on the quote verifier's # crypto would report SKIPPED, which is the same colour as a pass, on every # machine that happens not to have the library (issue #266). The one module in # `src/revl` that imports it is the optional signing backend below, and # tests/test_1460_signing_backend.py asserts that; the differentials pin that # backend off so they still compare two implementations. # The test extra pins the exact release the python backend's hashed lock pins # (backends/python/requirements.txt, installed with `--require-hashes` by # backends/python/setup.sh), so a repo-level `pip install -e ".[test]"` gets the # same artifact version the backend verifies its signature vectors against. # The consumer-facing `crypto` extra below stays a range: it is the optional # signing surface, not a test toolchain. # `llguidance`: a TEST dependency too, so the grammar-engine checks in # tests/test_constrained_decoding_1462.py run in CI instead of skipping, and # tests/test_source_grammar_1661.py runs the grammar `revl grammar --format # lark` exports over every parseable `.rvl` in the tree with it (imported hard, # so that check cannot skip). At run time it is the optional `constrain` extra # below and never required; `src/revl` never imports it for the grammar export. test = ["pytest>=8.0", "pytest-asyncio", "coverage>=7.0", "pyyaml", "cryptography==50.0.2", "llguidance>=1.0"] # `crypto`: constant-time ECDSA SIGNING (issue #1460). The pure-Python signer in # src/revl/tee_quote.py takes time that depends on the secret nonce, so with this # extra installed `ecdsa_sign` goes through OpenSSL instead (src/revl/ # _ecdsa_backend.py), byte-for-byte the same RFC 6979 signatures. A signer that # is exposed to a network refuses without it. Verification never needs it. 44 is # the first release with deterministic ECDSA. revl still has no REQUIRED runtime # dependency. crypto = ["cryptography>=44.0"] # Constrained decoding's local grammar engine (issue #1462). When installed, # the provider adapters compile a derived GBNF grammar before claiming it and # check a completion made under a `gbnf` claim byte for byte. Absent, both # checks are skipped and nothing else changes (revl.providers.structured). constrain = ["llguidance>=1.0"] # OpenTelemetry export for causal lifecycle traces (revl.otel, item 120). Kept # out of the base install so it stays dependency-light; absent, revl.otel # degrades to a no-op / span-mapping dump rather than a hard import error. otel = ["opentelemetry-sdk>=1.20", "opentelemetry-api>=1.20"] [project.scripts] revl = "revl.__main__:main" truc = "revl.truc:main" [build-system] requires = ["hatchling==1.27.0"] build-backend = "hatchling.build" # WHAT THE DISTRIBUTIONS SHIP, and why it is computed rather than copied. # # The per-tier emitters, runtimes and their golden data live in `backends/`, a # sibling of `src/revl` in a checkout. They must ride *inside* the wheel or every # backend-touching command (`revl run`, cross-tier `revl test`, placement, fault # tests) breaks once installed — the checkout path does not exist under # site-packages. They ship as `revl/backends`; `revl._backends_root()` prefers # the checkout location and falls back to that packaged copy. `stdlib/` ships # the same way (roadmap 319), for `revl._paths.stdlib_root()`. # # Both used to arrive through a plain `force-include` of the two directories. # hatchling applies `force-include` AFTER file selection and exempts it from # every exclude rule and from the ignore files, so it copied the two trees # VERBATIM off the builder's disk. The wheel therefore contained whatever # happened to be lying around rather than what the commit says # (GHSA-gj88-cx6q-38r2), which is the wrong property for a published artifact # in two ways: # # * size and correctness — a developer build shipped `node_modules`, cargo # `target/debug`, a cloned `.cordis-py`, compiled runner binaries and stray # `*.log` scratch files: 2933 members / 250 MB unpacked against the 523 the # commit actually describes; # * non-determinism — two builds of the SAME commit on two machines produced # different wheels, so nothing about the artifact could be derived from the # revision. Anything sitting under `backends/` at build time rode along, # `.gitignore`d or not, and `.gitignore` routes machine-generated secrets # there on purpose (`backends/typescript/test-secret-store/`). # # `hatch_build.py` replaces the directory-level force-include with a PER-FILE # one it computes: from `git ls-files` in a checkout, and from the deny-list # below when there is no git (a wheel built out of an unpacked sdist). The # wheel target keeps `packages = ["src/revl"]` — remapping `backends` with a # `sources` entry would have been the tidier spelling, but a `sources` rewrite # that CHANGES a prefix rather than removing one makes editable installs # impossible, and `pip install -e ".[test]"` is how eleven CI jobs and every # contributor set up (README.md, CONTRIBUTING.md). # # `tools/check_wheel_manifest.py` is the gate: it asserts the built wheel's # member list EQUALS `git ls-files` of those trees plus the `.dist-info`, and # that the sdist carries no untracked file. It runs in `lint`, in the release # dry run, and immediately before the PyPI upload. [tool.hatch.build.targets.wheel] packages = ["src/revl"] [tool.hatch.build.targets.wheel.hooks.custom] path = "hatch_build.py" # The sdist has the same shape of problem, one step milder: hatchling reads the # ROOT `.gitignore` and none of the sixteen nested ones, so a plain build swept # in `backends/rust/*/target/` (909 files) and the compiled # `backends/go/placement_runner/revl_placement_runner`. These excludes state the # artifact classes in the packaging file itself, root-anchored, mirroring the # nested ignore files. They also feed `hatch_build.py`'s no-git fallback. # # Redundancy with `.gitignore` is the point: this is packaging's own statement # of what may never ship, and it does not move when an ignore rule is relaxed # somewhere else for an unrelated reason. [tool.hatch.build] exclude = [ # dependency, build and vendored-clone trees (npm, cargo, go, java, python) "**/node_modules", "**/target", "**/.cordis-py", "**/.cordis4j*", "**/.venv", "**/__pycache__", "**/*.py[cod]", "**/*.class", "**/Cargo.lock", # generated and scratch output written by the harnesses and by test runs "**/.pytest_cache", "**/.conformance-*", "**/*.log", "**/*-note.txt", "backends/typescript/_gen", "backends/go/placement_runner/emitted/*.go", "backends/go/placement_runner/revl_placement_runner", "backends/java/placement/out", "backends/java/placement/gen", "backends/java/placement/real_out", "backends/java/placement/real_gen", "backends/java/placement/adt_out", "backends/java/placement/adt_gen", "backends/java/placement/*spec.json", "backends/java/placement/adt_prov.json", "backends/java/placement/adt_cons.json", # machine-generated secrets: `.gitignore` routes them here on purpose, and a # distribution is the last place on earth they may appear "backends/typescript/test-secret-store", # credential-shaped files, by name. Nothing tracked matches any of these, so # they cost nothing; they exist for the one path that cannot consult git -- # `hatch_build.py`'s fallback, building a wheel out of an unpacked sdist -- # where the rule is necessarily a deny-list rather than an allow-list. "**/.env", "**/.env.*", "**/.npmrc", "**/.netrc", "**/credentials", "**/*.pem", "**/*.key", "**/*.p12", "**/*.pfx", "**/id_rsa*", "**/id_ed25519*", ] # The one place tracked content sits UNDER an ignore rule. `.gitignore:36` is # `backends/typescript/tests/generated/` — the TS harness writes throwaway # modules there — but seventeen fixtures in that directory were force-added and # ARE tracked. Without this, hatchling's ignore handling drops them from the # sdist, and a wheel built out of that sdist (`pip install revl-2.0.0.tar.gz`) # comes out seventeen files short of one built in a checkout. # # `artifacts` is hatchling's "include even though VCS ignores it" hook. It lists # the seventeen by exact path ON PURPOSE rather than as `*.ts`: a glob would # also sweep up whatever the harness last wrote there, which is the # non-determinism this whole section exists to remove. Add a tracked fixture to # that directory and `tools/check_wheel_manifest.py` reds until the path is # added here too — loud rot beats a silently short distribution. artifacts = [ "backends/typescript/tests/generated/conformance.ts", "backends/typescript/tests/generated/dynamic_reserved_key.ts", "backends/typescript/tests/generated/empty_list_types.ts", "backends/typescript/tests/generated/fr1_loop.ts", "backends/typescript/tests/generated/fr3_json_int.ts", "backends/typescript/tests/generated/instance_get.ts", "backends/typescript/tests/generated/r3_migrator.ts", "backends/typescript/tests/generated/reserved_words.ts", "backends/typescript/tests/generated/spawn.ts", "backends/typescript/tests/generated/str_scan.ts", "backends/typescript/tests/generated/tenants.ts", "backends/typescript/tests/generated/unique_writes.ts", "backends/typescript/tests/generated/user_cache.ts", "backends/typescript/tests/generated/v3_map.ts", "backends/typescript/tests/generated/v3_stdlib.ts", "backends/typescript/tests/generated/v3_tests.test.ts", "backends/typescript/tests/generated/v3_types_functions.ts", "backends/typescript/tests/generated/witnessed_teardown.ts", ] # Lint gate (roadmap 110d). The repo already carries a `# noqa` discipline whose # codes are overwhelmingly Pyflakes (F): unused imports/locals, undefined names, # placeholder-less f-strings. Pin ruff to that same F-level surface, not ruff's # broader default set (E4/E7/E9 + F), so the gate enforces the discipline that # exists without surfacing hundreds of pre-existing E/PL findings this sweep is # not meant to litigate. Widen deliberately (its own change), never by drift. [tool.ruff] target-version = "py311" [tool.ruff.lint] select = ["F"] # roadmap item 419a: `backends/python/pytest.ini` (asyncio_mode = auto) is # only picked up when pytest's rootdir search lands INSIDE backends/python — # e.g. `cd backends/python && pytest`, or `pytest backends/python/tests/` # given alone. pytest resolves exactly one inifile per session from the # common ancestor of the given args; combine backends/python/tests/ with a # sibling like backends/wasm/ (`pytest backends/wasm/ backends/python/tests/`) # and the common ancestor climbs to the repo root, where this file — until # now with no [tool.pytest.ini_options] — won, silently discarding # backends/python/pytest.ini's asyncio_mode and reddening every unmarked # `async def` test with "async def functions are not natively supported". # Mirror the same setting here so a combined run stays covered regardless of # which inifile pytest happens to pick. This is a no-op for every OTHER # pytest invocation in the repo (`pytest tests/ -q`, # `pytest backends/wasm/... -q`, ...): none of those trees define any # `async def test_*` for asyncio_mode to touch. [tool.pytest.ini_options] asyncio_mode = "auto" asyncio_default_fixture_loop_scope = "function" markers = [ # Issue #2216: a test that is slow by NATURE rather than by repeating work # a session could share. The fast lanes (the pre-commit hook, # `pre-merge --affected`, CI's pull-request `root-suite-affected` job) set # REVL_SKIP_SLOW=1 and tests/conftest.py deselects these; the full gate and # the merge-queue matrix keep running them. "slow: slow-by-nature test the affected/fast lanes deselect (issue #2216)", ]