Metadata-Version: 2.4
Name: signalsafe-tree-spec
Version: 0.4.3
Summary: SignalSafe TreeSpec wire contract for Python: models, parsing, lint, and patch helpers.
Author: Josh Martin
License-Expression: MIT
Project-URL: Homepage, https://github.com/SignalSafeSoftware/tree-spec
Project-URL: Repository, https://github.com/SignalSafeSoftware/tree-spec
Project-URL: Documentation, https://github.com/SignalSafeSoftware/tree-spec/blob/main/python/README.md
Project-URL: Issues, https://github.com/SignalSafeSoftware/tree-spec/issues
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Typing :: Typed
Requires-Python: <4.0,>=3.12
Description-Content-Type: text/markdown
License-File: LICENSE
Requires-Dist: pydantic<3,>=2.11.3
Provides-Extra: dev
Requires-Dist: pytest>=8; extra == "dev"
Requires-Dist: pytest-cov>=6; extra == "dev"
Requires-Dist: build>=1.2; extra == "dev"
Requires-Dist: twine>=6.1; extra == "dev"
Requires-Dist: ruff<1,>=0.17; extra == "dev"
Requires-Dist: mypy<3,>=1.17; extra == "dev"
Requires-Dist: bandit>=1.7; extra == "dev"
Dynamic: license-file

# signalsafe-tree-spec (Python)

Current source and planned customer-subdomain impact: [current-state summary](../docs/current-state-summary.md).

Python implementation of the **TreeSpec wire contract**: Pydantic models, parsing, lint, patch helpers, and constants. Backend counterpart to [`@signalsafe/tree-spec`](https://github.com/SignalSafeSoftware/tree-spec) on npm.

| | |
|---|---|
| **PyPI distribution name** | `signalsafe-tree-spec` |
| **Import path** | `tree_spec` |
| **GitHub** | [SignalSafeSoftware/tree-spec](https://github.com/SignalSafeSoftware/tree-spec/tree/main/python) |
| **Python** | 3.12+ (`requires-python >=3.12,<4.0`) |
| **Runtime dependency** | Pydantic v2 (`pydantic>=2.11.3,<3`) |

> **License:** MIT — see [LICENSE](./LICENSE). See [SECURITY.md](./SECURITY.md) for vulnerability reporting.

## What this package does

- Validate and parse **TreeSpec wire JSON** into typed Pydantic models (`TreeSpec`, `Node`, `Transition`, …).
- **Lint** wire payloads (`lint_tree_spec`) with structured issues, including
  duplicate choices, invalid references, unreachable nodes, and reachable paths
  that cannot reach `END`.
- **Build** wire documents programmatically (`TreeSpecBuilder`).
- **Apply JSON patches** to wire dicts (`apply_patch_to_spec_dict`).
- Expose wire constants (`END_NODE_ID`, `TREESPEC_WIRE_VERSION`).

See [docs/compatibility.md](./docs/compatibility.md) for how **unknown JSON fields** are handled (`extra="ignore"`) and how that compares to TypeScript compile/decompile.

## What this package does not do

- HTTP APIs, authentication, persistence, or training UI.
- Scenario simulation (use `@signalsafe/simulator-core` / `simulator-react` in TypeScript stacks).
- Graph editor UI (use `@signalsafe/tree-spec-editor-*` packages).
- General sandboxing or authorization — contract validation only; host apps decide trust and access control.

## Install

```bash
pip install signalsafe-tree-spec
```

Development (this repo uses [uv](https://docs.astral.sh/uv/)):

```bash
uv sync --extra dev
uv run pytest
```

## Distribution name vs import path

```bash
pip install signalsafe-tree-spec
```

```python
import tree_spec
from tree_spec import TreeSpec, lint_tree_spec, TreeSpecBuilder
```

Install the PyPI distribution `signalsafe-tree-spec`; import its generic `tree_spec` package.

## Quick start

```python
from tree_spec import (
    END_NODE_ID,
    TREESPEC_WIRE_VERSION,
    TreeSpec,
    TreeSpecBuilder,
    lint_tree_spec,
)

wire = {
    "wire_version": TREESPEC_WIRE_VERSION,
    "start_node": "start",
    "nodes": {
        "start": {
            "type": "prompt",
            "prompt": "Verify the sender before clicking?",
            "choices": [{"id": "inspect", "label": "Inspect sender"}],
        }
    },
    "transitions": [
        {"from": ["start", "inspect"], "to": END_NODE_ID, "outcome": "safe"},
    ],
}

issues = lint_tree_spec(wire)
assert not issues

spec = TreeSpec.model_validate(wire)
builder = TreeSpecBuilder()
# builder.add_node(...) — see tests/ for full builder flows
```

## Public API

All symbols below are exported from the top-level `tree_spec` package:

| Category | Symbols |
|---|---|
| Constants | `END_NODE_ID`, `TREESPEC_WIRE_VERSION` |
| Models | `TreeSpec`, `Node`, `Transition`, `Choice`, `Delta`, `MicroFeedback`, `ABMeta`, `ABVariant`, … |
| Lint | `lint_tree_spec`, `TreeSpecIssue` (`level`/`severity`, stable `code`, optional `path`) |
| Builder | `TreeSpecBuilder`, `TreeSpecError` |
| Patch | `apply_patch_to_spec_dict`, `PatchApplyError`, `PatchDict`, `ReplacePatch`, … |

The top-level `tree_spec` package exposes common helpers; submodules such as `tree_spec.models` and `tree_spec.patch` remain available.

## Wire contract (high level)

- **`wire_version`:** optional; omit for implicit v1. Constant `TREESPEC_WIRE_VERSION` documents the current version string.
- **`start_node`:** entry node id.
- **`nodes`:** map of node id → node object (`type`, `prompt`, `choices` or legacy `options`, optional `render_hints`).
- **`transitions`:** list of `{ from: [nodeId, choiceId], to, outcome? }`. Terminal transitions target `END_NODE_ID` and require `outcome`.
- **`_meta`:** optional tree-level metadata (for example graph-editor viewport persisted by editor packages).

Legacy payloads may use `options` instead of `choices` and legacy terminal ids; both implementations normalize these to canonical `choices` and `END` on read. Python lint validates the wire shape your backend accepts.

## Python / TypeScript parity

| Concern | Python (`python/`) | TypeScript (`@signalsafe/tree-spec`) |
|---|---|---|
| Wire models & lint | Pydantic models, `lint_tree_spec` | `TreeSpecWire`, `lintTreeSpecWire`, `lintTreeSpecGraph` |
| Authoring graph compile | — | `compileTreeSpec` / `decompileTreeSpec` |
| Cross-language fixtures | Share JSON fixtures in your product repo or CI | Same |

Keep fixture JSON in sync when changing wire rules in either language. Graph
diagnostics use the same canonical codes and JSON-like paths in both languages;
Python exposes the legacy `level` field and the equivalent read-only `severity`
property. Full compile/decompile parity lives in TypeScript today; Python
focuses on validation, lint, builder, and patch flows used by backends.

## Development

```bash
uv sync --extra dev
uv run pytest
uv run ruff check .
uv run python -m build
uv run python scripts/smoke_package.py
```

## Security

See [SECURITY.md](./SECURITY.md). TreeSpec parsing is **contract validation**, not a sandbox. Validate and authorize TreeSpec JSON in your application layer before treating content as trusted.

## Changelog and releases

- [CHANGELOG.md](./CHANGELOG.md)
- [RELEASING.md](./RELEASING.md)

## Related packages

- [`@signalsafe/tree-spec`](https://github.com/SignalSafeSoftware/tree-spec) — TypeScript wire contract and compile/lint.
- [`tree-spec-editor-*`](https://github.com/SignalSafeSoftware/tree-spec-editor) — authoring UI (TypeScript/React).

## Python 0.4 migration

Replace former product-specific imports with `tree_spec` (including submodules).
The PyPI distribution remains `signalsafe-tree-spec`; upgrade consumers to 0.4.x.
No compatibility alias is provided.

## Source layout

Runtime code and the packaged version/typing markers live in
`src/tree_spec/`. Tests, developer scripts and release metadata
remain outside `src/`. Run `uv sync --frozen --extra dev` before local checks;
tests use the installed editable package rather than adding `src` to `PYTHONPATH`.
Wheel and source archives are built into `dist/`.
