Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
28 changes: 19 additions & 9 deletions .github/workflows/test.yml
Original file line number Diff line number Diff line change
Expand Up @@ -7,17 +7,15 @@ on:
branches: [main]

jobs:
# Unit tests: the implementation's own suite — hermetic, offline, fast.
# This is the blocking gate (`test (ubuntu-24.04)`).
test:
runs-on: ${{ matrix.os }}
strategy:
fail-fast: false
matrix:
os: [ubuntu-24.04]
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: "3.13"
cache: pip
Expand All @@ -30,18 +28,30 @@ jobs:
- name: Unit tests
run: pytest -q

# Conformance: the language-agnostic suite from fruwehq/determa-state-conformance, run
# black-box against this implementation. Separate job: it downloads an external
# repo, so a failure here means "diverges from the spec suite", not "our code broke".
conformance:
runs-on: ubuntu-24.04
steps:
- uses: actions/checkout@v4
- uses: actions/setup-python@v5
- uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
- name: Check out pinned conformance suite
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
repository: fruwehq/determa-state-conformance
ref: fc4842010ab8d83bf4c5c6280a5627ca86829f7f
path: .pinned/determa-state-conformance
- name: Check out pinned specification
uses: actions/checkout@d23441a48e516b6c34aea4fa41551a30e30af803 # v6.1.0
with:
repository: fruwehq/determa-state-spec
ref: 4bd4d9588d11b75d376380b6120676a056a4bc45
path: .pinned/determa-state-spec
- uses: actions/setup-python@ece7cb06caefa5fff74198d8649806c4678c61a1 # v6.3.0
with:
python-version: "3.13"
cache: pip
- name: Install
run: pip install -e '.[dev]'
- name: Conformance suite
env:
DETERMA_CONFORMANCE_DIR: ${{ github.workspace }}/.pinned/determa-state-conformance
DETERMA_SPEC_DIR: ${{ github.workspace }}/.pinned/determa-state-spec
run: pytest conformance -q
112 changes: 60 additions & 52 deletions AGENTS.md
Original file line number Diff line number Diff line change
@@ -1,63 +1,71 @@
# AGENTS.md — determa-state-python

Guidance for AI/coding agents working in this repository. (Tool-agnostic; not specific to any one assistant.)
Guidance for coding agents working in this repository.

## What this repo is
The **Python reference implementation** of Determa State. Distribution name
**`determa-state`**; import name **`determa.state`** (a PEP 420 namespace package — there is
**no** `src/determa/__init__.py`, so it coexists with the `determa` launcher package). It is
correct **iff** it passes the conformance suite.
## Repository

This is the Python implementation of Determa State. The distribution is
`determa-state`; the import is `determa.state`. `src/determa` is a PEP 420 namespace
package so it can coexist with the umbrella `determa` launcher.

The implementation is conformant only when it passes the language-neutral suite.
Format-1 work currently uses these immutable pre-release inputs:

- specification: `4bd4d9588d11b75d376380b6120676a056a4bc45`;
- conformance: `fc4842010ab8d83bf4c5c6280a5627ca86829f7f` (75 core cases).

The package version is still `0.0.6`; the specification, conformance suite, Python
engine, and Rust engine version together.

## Boundaries

The implemented public API is `load_bundle`, `create`, and `dispatch`, plus validation
and error types exported by `determa.state`. It implements the exact `format: 1`
grammar. Do not restore abandoned draft field names or compatibility aliases.

The core is a pure foreground transform over one root ownership aggregate. It has no
hidden queues, timers, stores, snapshots, migration, enabled-event inspection, or
standardized execution CLI. Snapshot portability, machine definition migration or
hot-swap, package imports, and living tutorials are separate initiatives.

Layout:
- `src/determa/state/` — the engine package (`__about__.py` is the single version source).
- `tests/` — the implementation's own **unit tests** (hermetic, offline).
- `conformance/` — a **black-box harness** that fetches `determa-state-conformance` at the
tag matching this package's version into `.cache/` (override with `DETERMA_CONFORMANCE_DIR`;
spec schema override `DETERMA_SPEC_DIR`).
- `.github/workflows/` — `test.yml` (CI gate) and `release.yml` (tag → PyPI).

## Determa in one paragraph
**Determa** is a family for defining/running well-specified, verifiable behavior. **Determa
State** is a language-agnostic **statechart engine** (Harel/UML lineage, PSiCC RTC): one
YAML/JSON machine runs identically under any implementation, validated against a shared
conformance suite. Guards/action values are **CEL** (via the `cel-python`/`celpy` package,
lazily imported so the CLI starts fast). An umbrella `determa` launcher dispatches
`determa <product> …` → `determa-<product>` on PATH; this package also installs a
`determa-state-python` alias for explicit implementation selection.

## Repositories (org `fruwehq`, local folders `~/src/personal/`)
| Repo | Role |
|---|---|
| determa-state-spec | normative prose spec + schema. No CI. |
| determa-state-conformance | the conformance suite (arbiter). No CI. |
| **determa-state-python** (this) | Python impl — `determa-state` / `determa.state`. |
| determa-state-rust | Rust impl — crate `determa-state`. |
| determa | umbrella launcher (`python/`, `rust/`, `node/`). |

## Working rules (every Determa repo)
- **One issue → one PR**, branch → PR → **squash-merge**, linear history, resolve threads; `main` is protected (**and requires branches be up-to-date** — after a merge moves `main`, update other open PRs, which re-runs CI, before merging).
- **No AI/assistant attribution** anywhere (commits, PRs, comments, docs).
- **Conformance-first:** spec text → conformance case → this impl. Don't diverge from the pinned suite.
- **Synchronized SemVer** with spec + rust (currently **0.0.6**); bump `src/determa/state/__about__.py`.
- **No abbreviations** in JSON output / public identifiers (`definition` not `def`). Kept for now: `config`, machine-keywords (`esvs`, …), snapshot `def_id`/`def_version`, `spawn.def`.

## Gates (run before requesting review — this is the CI gate)

- `src/determa/state/` — loader, validator, CEL profile, model, and engine;
- `src/determa/state/data/machine.schema.json` — exact pinned normative schema;
- `tests/` — hermetic implementation tests;
- `conformance/` — black-box format-1 harness and immutable pins;
- `.github/workflows/test.yml` — unit and pinned conformance gates;
- `.github/workflows/release.yml` — tag-triggered PyPI publication.

## Working Rules

- One issue to one PR, squash merge, linear history, and resolved review threads.
- Never put assistant attribution in commits, PRs, comments, or documentation.
- Behavioral work is specification, then conformance, then implementations. The
conformance suite is the arbiter.
- Do not change the package version, tag, publish, or merge unless explicitly
authorized as release work.
- Keep JSON/public identifiers unabbreviated and use only exact normative grammar.
- Unit tests remain hermetic and offline. Conformance may use its immutable checkouts.
- Preserve lazy CEL and JSON Schema imports where practical.

## Gates

```sh
pip install -e '.[dev]'
python -m pip install -e '.[dev]'
ruff check .
mypy src/determa
pytest -q # unit tests (hermetic, offline) — CI job "test (ubuntu-24.04)"
pytest conformance # conformance suite (network, or DETERMA_CONFORMANCE_DIR) — CI job "conformance"
# convenience: `make check` (gate) and `make conformance`
pytest -q
DETERMA_CONFORMANCE_DIR=/path/to/conformance \
DETERMA_SPEC_DIR=/path/to/spec \
pytest conformance -q
```
Keep CEL/`jsonschema` imports lazy (they dominate startup); unit tests must not touch the network.

## Releasing
Tag `vX.Y.Z` → `release.yml` builds sdist+wheel and publishes to **PyPI via Trusted
Publishing (OIDC)** — **gated on the `pypi` GitHub Environment (manual approval)**. The
PyPI project name is `determa-state`. **A tag publishes**, so only tag when you intend to
release. After a spec release, the conformance fetch auto-targets the new `v{version}` tag.
`make check` runs lint, type checking, and unit tests. `make conformance` fetches or
reuses the immutable inputs recorded in `conformance/pins.py`.

## Release

## Pointers
- Library API (SPEC §2): `Host`, `Instance`, `load_definitions` (accepts YAML **or** a dict/mapping), `validate`, etc. — see `README.md` and `tests/test_library_api.py`.
- CLI (SPEC §13/§14): `src/determa/state/cli.py`. Spec: `determa-state-spec/SPEC.md`.
A `vX.Y.Z` tag triggers `release.yml`, builds the distribution, and publishes to PyPI
using Trusted Publishing through the manually approved `pypi` environment. A tag
publishes, so do not create one during ordinary implementation work.
110 changes: 41 additions & 69 deletions CONTRIBUTING.md
Original file line number Diff line number Diff line change
@@ -1,95 +1,67 @@
# Contributing to Determa State (Python)

**determa-state** is the Python reference implementation of the
[Determa State](https://github.com/fruwehq/determa-state-spec) statechart engine. It is correct **iff** it
passes the language-agnostic [conformance suite](https://github.com/fruwehq/determa-state-conformance).
The prose specification lives in [`fruwehq/determa-state-spec`](https://github.com/fruwehq/determa-state-spec)
(`SPEC.md`, the JSON Schema, and examples); the executable correctness target lives in
[`fruwehq/determa-state-conformance`](https://github.com/fruwehq/determa-state-conformance).
`determa-state` is the Python implementation of the
[Determa State specification](https://github.com/fruwehq/determa-state-spec). The
[language-neutral conformance suite](https://github.com/fruwehq/determa-state-conformance)
is the executable arbiter of behavior.

## Dev setup
## Development Setup

```sh
python -m venv .venv
source .venv/bin/activate # or `.venv\Scripts\activate` on Windows
pip install -e '.[dev]'
. .venv/bin/activate
python -m pip install -e '.[dev]'
```

Python ≥ 3.11. The package is import-named `determa.state`, distribution-named `determa-state`.
Python 3.11 or newer is supported. The distribution is `determa-state`; the import is
`determa.state`.

## The gate
## Gates

Before pushing, run all three and keep them clean (`make check` does exactly this):
Run the implementation gates and the full format-1 conformance suite before review:

```sh
ruff check .
mypy src
pytest # unit tests only — hermetic, offline
mypy src/determa
pytest -q
pytest conformance -q
```

CI runs `test (ubuntu-24.04)` on every PR and is **required** — a PR merges only once it
is green. This job runs the **unit tests only**.
`tests/` is hermetic and offline. `conformance/` uses the approved immutable
specification and suite commits recorded in `conformance/pins.py`. The harness caches
those checkouts under `.cache/`. For offline or local cross-repository work:

## Unit tests vs. conformance — kept separate
```sh
DETERMA_CONFORMANCE_DIR=/path/to/determa-state-conformance \
DETERMA_SPEC_DIR=/path/to/determa-state-spec \
pytest conformance -q
```

This implementation has its **own unit tests** (`tests/`), which are hermetic and offline
— `pytest` (or `make test`) runs only these, and they never touch the network. That is the
required PR gate.
CI checks out both immutable inputs directly and also verifies that the packaged schema
is byte-for-value equivalent to the pinned specification schema.

**Conformance is separate.** The language-agnostic suite is downloaded and run black-box
against the built CLI/engine, in its own directory (`conformance/`) and its own CI job
(`conformance`, non-blocking by default). Run it locally with:
## Workflow

```sh
make conformance # == pytest conformance
```
1. Read `AGENTS.md` and the linked specification/conformance changes first.
2. Create one branch and one pull request for one issue.
3. Never push directly to protected `main`.
4. Resolve every review thread and keep the branch current before squash-merging.
5. Do not add assistant attribution to commits, PRs, comments, or documentation.
6. Specify and add conformance behavior before changing an engine.

The suite is **not** a submodule. `conformance/conftest.py` clones
`fruwehq/determa-state-conformance` at the release tag matching this package's version (falling
back to `main` while the tag does not yet exist) into a gitignored `.cache/` directory and
reuses it. To force a refresh, delete `.cache/`.
Do not reintroduce compatibility aliases for abandoned pre-format-1 grammar or public
behavior.

- **Offline / local edits:** point the tests at a local checkout with
`DETERMA_CONFORMANCE_DIR=/path/to/determa-state-conformance` (and `DETERMA_SPEC_DIR=/path/to/determa-state-spec`
for the schema-parity test). If the suite cannot be obtained and no override is set, the
conformance tests **skip** rather than error.
- **Black-box CLI conformance** runs the implementation's `determa-state` (via `python -m determa.state`)
as a **subprocess** against `conformance/run_cli.py`, so packaging/entry-point regressions
are caught (SPEC §13.6).
## Versioning And Release

## Workflow
`src/determa/state/__about__.py` is the single package version source. Determa State
specification, conformance, Python, and Rust versions are synchronized. The current
package remains `0.0.6` while format 1 is pre-release.

1. Branch from `main`, open a Pull Request, and **squash-merge** — `main` stays linear.
2. Resolve all review threads before merging.
3. **Never push to `main` directly.**
4. **No AI/assistant attribution anywhere** — not in commits, PR bodies, comments, or
docs (no `Co-Authored-By:`, no "Generated with…"). Commits and PRs read as the
author's own work.
5. One issue → one PR. A behavior change usually pairs with a `determa-state-spec` edit and a
`determa-state-conformance` case; link them from the PR.

## Versioning

The version source of truth is **`pyproject.toml`** (`version = …`); the package derives
`determa.state.__version__` from the installed distribution metadata (no second copy to keep in
sync). The package version **is** the implemented Determa State spec version.

> determa-state-spec, determa-state-conformance, and determa-state share one synchronized SemVer version
> (currently pre-1.0 `0.0.x`). A release tags all three `vX.Y.Z` in lockstep; an
> implementation declares "implements Determa State spec vX.Y.Z" and pins the conformance suite
> at that tag.

### Releasing `vX.Y.Z` (lockstep)
1. Land all spec / conformance / implementation changes on the three `main` branches.
2. Bump the version in **`pyproject.toml`** (here) and the `VERSION` files in `determa-state-spec` and
`determa-state-conformance`.
3. Tag `vX.Y.Z` on **determa-state-spec** and **determa-state-conformance** (`gh api -X POST
repos/fruwehq/<repo>/git/refs -f ref=refs/tags/vX.Y.Z -f sha=$(gh api
repos/fruwehq/<repo>/commits/main --jq .sha)`), so this package pins the matching
conformance tag instead of falling back to `main`.
4. Tag **determa-state** `vX.Y.Z` only to publish to PyPI — it triggers `release.yml`
(Trusted Publishing).
A `vX.Y.Z` tag triggers `release.yml` and publishes to PyPI through Trusted Publishing,
gated by the manually approved `pypi` environment. Version bumps, tags, and publication
are separate release work and require explicit authorization.

## License

Contributions are made under the project's [MIT license](LICENSE).
Contributions are made under the [MIT license](LICENSE).
11 changes: 5 additions & 6 deletions Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,23 +4,22 @@
test:
pytest -q

# Conformance — the language-agnostic suite from fruwehq/determa-state-conformance, run
# black-box against this implementation. Downloads the suite (pinned to the release
# tag matching this package's version) into .cache/ on first run.
# Conformance — the language-agnostic format-1 core suite, pinned to the approved
# immutable pre-release commits in conformance/pins.py.
# Offline / against a local checkout: DETERMA_CONFORMANCE_DIR=/path/to/determa-state-conformance make conformance
conformance:
pytest conformance -q

# Refresh the bundled JSON Schema from fruwehq/determa-state-spec at the matching version tag
# (or DETERMA_SPEC_DIR=/path/to/determa-state-spec). The schema-drift test guards that they match.
# Refresh the bundled JSON Schema from the immutable format-1 specification pin
# (or DETERMA_SPEC_DIR=/path/to/determa-state-spec).
sync-schema:
python scripts/sync_schema.py

lint:
ruff check .

typecheck:
mypy src
mypy src/determa

# Everything a PR needs to pass locally (unit gate), plus conformance.
check: lint typecheck test
Expand Down
Loading