Metadata-Version: 2.4
Name: dpa-icsd
Version: 1.0.7
Summary: Reference Python implementation and tooling baseline for ICSD.
Author: DPA Cloud Services
License-Expression: Apache-2.0
Project-URL: Homepage, https://git.dragon-phoenix.co.uk/DPA_Cloud_Services/ICSD_Python_Tooling
Project-URL: Repository, https://git.dragon-phoenix.co.uk/DPA_Cloud_Services/ICSD_Python_Tooling
Project-URL: Issues, https://git.dragon-phoenix.co.uk/DPA_Cloud_Services/ICSD_Python_Tooling/issues
Project-URL: Changelog, https://git.dragon-phoenix.co.uk/DPA_Cloud_Services/ICSD_Python_Tooling/src/branch/main/CHANGELOG.md
Keywords: audit-trail,compliance,evidence,icsd,invariants
Classifier: Development Status :: 5 - Production/Stable
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Topic :: Software Development :: Libraries
Requires-Python: >=3.10
Description-Content-Type: text/markdown
License-File: LICENSE
Provides-Extra: dev
Requires-Dist: pytest==9.0.2; extra == "dev"
Requires-Dist: ruff==0.15.4; extra == "dev"
Dynamic: license-file

# ICSD Python Tooling

Reference Python implementation for Invariant-Closed System Design (ICSD).
This repository provides:

- Core primitives for invariant validation, transitions, and deterministic evidence.
- A CI-friendly CLI with stable non-zero exit semantics.
- A workstream-based collaboration model for concurrent contributors.

## Status

As of 2026-03-04, `v1.0.0` includes implemented core modules and CLI commands:

- `icsd-dpa verify <evidence.json>`
- `icsd-dpa lint <path>`
- `icsd-dpa lint` profile selection and JSON output for CI/audit pipelines.
- Versioned invariant policy profiles selectable by profile name.
- Authority-source metadata and transition-time authority validation hooks.
- Transparency-log adapter hooks for append/verify workflows.

The package-level public API remains intentionally narrow at the `v1.0.0` boundary.
See [Stability Policy](#stability-policy-v1x) before depending on import paths.

## Install

Published package identity:

- Distribution name: `dpa-icsd`
- Import module: `icsd`
- CLI entry point: `icsd-dpa`

Install from PyPI:

```bash
python -m pip install dpa-icsd
```

Install from local source (development):

```bash
python -m pip install -e ".[dev]"
```

## v1.0.0 Migration Guide (from v0.x prerelease)

- Upgrade or pin to the stable release:

```bash
python -m pip install --upgrade dpa-icsd==1.0.0
```

- Use `icsd-dpa` as the CLI entry point in scripts/CI (`verify`, `lint`,
  and exit codes `0`/`1`/`2` are stable across `v1.x`).
- Keep package-root usage minimal (`icsd.__version__`); import runtime
  primitives from `icsd.core` and CLI internals from `icsd.cli`.
- Compatibility retained in `v1.0.0`: evidence schema `0.1` and lint profile
  name `policy-v0.1`.
- Breaking-change policy: documented public-contract breaks require a major
  release (`2.0.0`).

## Domain-Neutral Examples

Runnable non-finance examples live in [`examples/`](examples/README.md):

- legal case lifecycle with statutory policy cutover and authority-backed audit trail
- construction tolerance gating with inspection authority checks and evidence-level inspector/measurement provenance
- schema contract evolution with migration and temporal policy cutover
- supply-chain provenance with cold-chain custody continuity, per-hop provenance receipts, and out-of-range rejection
- IAM access governance with least privilege, approval evidence enforcement, and trustless authority-proposal refusal
- healthcare/lab sample custody with calibration-window policy cutover and release-authority gating
- safety-critical robotics leash flow with agent-constrained execution and emergency-stop override
- agent guardrails workflow with deterministic refusal reporting for unsafe autonomous actions
- event-pipeline schema governance with v1->v2 cutover, migration/quarantine, and stream-event emission
- D&D rules cutover demo with temporal profile provenance (`Standard` tier)
- trustless D&D dice-plugin proposal refusal demo (`Standard` tier)
- lite examples pack for minimal adoption (`invariants + transition + one rejection`)
- fun D&D lite examples for combat and character-sheet invariant learning

Run from repository root:

```bash
python examples/legal_case_lifecycle.py
python examples/construction_tolerance_gate.py
python examples/schema_evolution_contracts.py
python examples/supply_chain_provenance.py
python examples/iam_access_governance.py
python examples/lab_sample_workflow.py
python examples/robotics_safety_leash.py
python examples/agent_guardrails.py
python examples/event_pipeline_schema_governance.py
python examples/dnd_rules_cutover_standard.py
python examples/dnd_trustless_dice_proposal.py
python examples/lite_supply_chain_temperature_gate.py
python examples/lite_legal_case_required_fields.py
python examples/lite_construction_tolerance.py
python examples/lite_schema_contracts.py
python examples/dnd_combat_lite.py
python examples/dnd_character_sheet_lite.py
```

Each script emits JSON artifacts under `examples/out/` (gitignored).

## Learning ICSD via Toy Domains

ICSD is not limited to compliance-heavy systems. To learn the invariant and
refusal model quickly, start with toy game-state examples first:

- `python examples/dnd_combat_lite.py`
- `python examples/dnd_character_sheet_lite.py`

Then move to regulated-system examples and their conformance suites in
[docs/domains.md](docs/domains.md) (legal, construction, schema governance,
supply chain, IAM, healthcare, robotics, and event pipelines).

## Regulated Systems Guides

High-signal docs for domain mapping and rollout:

- [docs/domains.md](docs/domains.md): domain index mapping invariants,
  evidence/provenance sources, and policy-cutover patterns to runnable examples.
- [docs/adoption_ladder.md](docs/adoption_ladder.md): progressive adoption tiers
  (`Lite -> Standard -> Full`) with feature-to-tier mapping and concrete exit
  criteria.
- [docs/ai_agents.md](docs/ai_agents.md): AI-agent guardrail integration workflow
  (`proposal -> context -> transition -> evidence/refusal`) with refusal handling
  and anti-pattern guidance.
- [docs/fastapi_integration.md](docs/fastapi_integration.md): docs-only wrapper
  pattern for request schema validation, authority proposal ingestion, and
  deterministic evidence/refusal API responses.
- [docs/adoption_metrics.md](docs/adoption_metrics.md): manual-first PyPI download
  snapshot workflow via pypistats (`dpa-icsd`, daily/weekly/monthly counts).

## Conformance Scenario Tests

Narrative non-finance conformance suites live in `tests/`:

- `tests/test_conformance_runner.py`
- `tests/test_conformance_legal.py`
- `tests/test_conformance_construction.py`
- `tests/test_conformance_schema.py`
- `tests/test_conformance_supply_chain.py`
- `tests/test_conformance_iam.py`
- `tests/test_conformance_lab.py`
- `tests/test_conformance_robotics.py`
- `tests/test_conformance_agent_guardrails.py`
- `tests/test_conformance_event_pipeline.py`
- `tests/test_conformance_dnd.py`

These scenarios intentionally validate ICSD behaviour claims without asserting formal
standards compliance:

- valid-by-construction state setup via `InvariantSet.construct_state(...)`
- transition application via `Transition.apply_with_context(...)` or `AgentExecutionContext`
- deterministic evidence integrity + provenance verification
- explicit rejection of invalid/unrepresentable transitions

Run only the conformance suites:

```bash
pytest -q tests/test_conformance_legal.py tests/test_conformance_construction.py tests/test_conformance_schema.py tests/test_conformance_supply_chain.py tests/test_conformance_iam.py tests/test_conformance_lab.py tests/test_conformance_robotics.py tests/test_conformance_agent_guardrails.py tests/test_conformance_event_pipeline.py tests/test_conformance_dnd.py
```

Single-entry umbrella conformance run:

```bash
pytest -q tests/test_conformance_runner.py
```

## Import Policy (v1.x)

- Package root import contract remains intentionally narrow in v1.x: `icsd.__version__`.
- Prefer deep imports for runtime primitives in v1.x, for example:
  `from icsd.core import EvidenceBundle`.
- Do not assume `from icsd import EvidenceBundle` is part of the stable root-import contract.

## PyPI Name Decision and Claim Checklist

Name decision (WS-0022, checked on 2026-03-03):

- Keep distribution name as `dpa-icsd` for first publish.
- Keep import module as `icsd` and CLI command as `icsd-dpa`.
- Treat the distribution name as sticky after first successful PyPI upload.

Availability check references:

- `https://pypi.org/pypi/icsd/json` returns `200` (name already occupied).
- `https://pypi.org/pypi/dpa-icsd/json` returns `404` (name available at check time).

Pre-release namespace checklist:

1. Re-run the JSON endpoint check immediately before creating a release tag.
2. If `dpa-icsd` returns `200` before first publish, stop the release and open a new workstream to select and apply a new distribution name.
3. After first successful publish, do not rename the distribution.

Baseline validation:

```bash
python -c "import icsd; print(icsd.__version__)"
icsd-dpa --version
ruff format --check .
ruff check .
pytest -q
```

Deterministic CI/local parity validation:

```bash
python -m pip install -e ".[dev]"
python -m pip check
python -m ruff format --check .
python -m ruff check .
python -c "from pathlib import Path; Path('.ci_tmp').mkdir(exist_ok=True)"
python -m pytest --basetemp .ci_tmp/pytest
```

## Release Rehearsal (TestPyPI Readiness)

This project keeps release verification manual-first for the `v1.0.0` release flow.
For a local one-command rehearsal (without upload), run `scripts/release_check.sh`.

1. Install release tooling.

```bash
python -m pip install --upgrade build twine
```

2. Build source and wheel distributions.

```bash
python -m build
```

3. Validate built metadata before upload.

```bash
python -m twine check dist/*
```

4. Smoke-test install from the built wheel in an isolated virtual environment.

```bash
python -m venv .rehearsal-venv
.rehearsal-venv/bin/python -m pip install --upgrade pip
.rehearsal-venv/bin/python -m pip install --force-reinstall dist/dpa_icsd-*.whl
.rehearsal-venv/bin/icsd-dpa --version
.rehearsal-venv/bin/python -c "import icsd; print(icsd.__version__)"
```

PowerShell equivalent:

```powershell
$PY = ".\.rehearsal-venv\Scripts\python.exe"
& $PY -m pip install --upgrade pip
& $PY -m pip install --force-reinstall dist\dpa_icsd-*.whl
& ".\.rehearsal-venv\Scripts\icsd-dpa.exe" --version
& $PY -c "import icsd; print(icsd.__version__)"
```

5. Upload rehearsal artifacts to TestPyPI.

```bash
export TWINE_USERNAME="__token__"
export TWINE_PASSWORD="<testpypi-token>"
python -m twine upload --repository-url https://test.pypi.org/legacy/ dist/*
```

PowerShell equivalent:

```powershell
$env:TWINE_USERNAME = "__token__"
$env:TWINE_PASSWORD = "<testpypi-token>"
python -m twine upload --repository-url https://test.pypi.org/legacy/ dist/*
```

6. Verify installation from TestPyPI.

```bash
python -m pip install --index-url https://test.pypi.org/simple/ --extra-index-url https://pypi.org/simple dpa-icsd==1.0.0
icsd-dpa --version
```

## Release Automation (Gitea Tags)

Release publishing is separated from normal CI:

- CI workflow: `.gitea/workflows/ci.yml` (branches/PRs only).
- Release workflow: `.gitea/workflows/release.yml` (tag push `v*` and manual `workflow_dispatch`).

Release workflow stages:

1. Build distributions and run `twine check`.
2. Publish artifacts to TestPyPI.
3. Install from TestPyPI and smoke-test `icsd-dpa`.
4. Publish the same validated artifacts to PyPI (`publish` mode only).

Dry-run gate (no PyPI publish):

- Trigger `Release Publish` manually (`workflow_dispatch`) with `release_mode=dry-run` (default).
- Optional input `tag_name` can be set to a target release tag (`vX.Y.Z`).
- If `tag_name` is omitted in dry-run mode, the workflow derives `v<package_version>` from `src/icsd/_version.py`.
- Dry-run mode still executes build, TestPyPI publish, and TestPyPI install verification.
- Dry-run mode skips the `publish-pypi` job, so no live PyPI upload occurs.

Tagging rule:

- Create tags matching package version with a `v` prefix, for example `v1.0.0`.
- The release workflow validates that `tag[1:] == icsd._version.__version__` before any publish flow.
- Release workflow tag resolution prefers `GITEA_REF_NAME`, falls back to `GITHUB_REF_NAME`,
  then falls back to parsing `GITEA_REF` / `GITHUB_REF`, then `workflow_dispatch` input `tag_name`.
- In manual `publish` mode, a release tag (or `tag_name`) is required; in `dry-run` mode, missing tag input is auto-derived from package version.

Required Gitea secrets:

- `TESTPYPI_API_TOKEN`: API token for TestPyPI (`__token__` user).
- `PYPI_API_TOKEN`: API token for PyPI (`__token__` user, required only for `publish` mode).

## CLI Quick Reference

```bash
icsd-dpa --version
icsd-dpa verify <evidence.json>
icsd-dpa lint <path>
icsd-dpa lint <path> --profile policy-v0.1 --format json
```

Exit codes:

- `0`: success / no findings
- `1`: lint findings
- `2`: tool error (invalid args, unreadable file, invalid JSON, schema/integrity failure)

## Lint Profiles and Output

- `baseline` (default): Python/JSON syntax checks only.
- `policy-v0.1`: Adds stricter structural checks:
  - Blocks dynamic execution calls (`eval`, `exec`) in Python sources.
  - Requires JSON root objects and validates evidence-shaped JSON payloads.
  - Profile name retains the `v0.1` suffix for backward compatibility in v1.x.
- `--format text` (default): human-readable stderr findings.
- `--format json`: machine-readable report on stdout with profile, status, exit code, and findings.

## Python Example: Invariants

```python
from icsd.core.errors import InvariantFailure
from icsd.core.invariants import Invariant, InvariantSet


def owner_required(state: dict[str, str]) -> bool | InvariantFailure:
    owner = state.get("owner", "").strip()
    if owner:
        return True
    return InvariantFailure(
        invariant="owner_required",
        code="missing_owner",
        message="Owner must be set before transition execution.",
    )


invariants = InvariantSet(
    [
        Invariant(name="status_known", check=lambda s: s.get("status") in {"draft", "approved"}),
        Invariant(name="owner_required", check=owner_required),
    ]
)

failures = invariants.validate({"status": "draft", "owner": ""})
assert len(failures) == 1
```

## Python Example: Invariant-Safe Construction Helpers

```python
from icsd.core.evidence import AuthoritySource
from icsd.core.invariants import Invariant, InvariantSet
from icsd.core.transition import Transition

invariants = InvariantSet(
    [Invariant(name="non_negative_balance", check=lambda state: state["balance"] >= 0)]
)

transition = Transition(
    name="debit_account",
    mutate=lambda state: state.update({"balance": state["balance"] - 20}),
    invariants=invariants,
    authority_validator=Transition.require_authority_sources(minimum=1),
)

# State objects are copied and validated before use.
safe_state = invariants.construct_state({"balance": 100})

# Authority/profile checks can be validated before apply-time.
context = transition.build_context(
    authority="policy/account-debit",
    authority_sources=[
        AuthoritySource(
            source_type="registry",
            source_id="uk-companies-house",
            reference="company/00000006",
        )
    ],
)
result = transition.apply_with_context(
    state=safe_state,
    entity_type="account",
    entity_id="acct-001",
    actor="user:ops",
    context=context,
)
```

## Python Example: Legacy Migration and Quarantine

```python
from icsd.core.invariants import Invariant, InvariantSet
from icsd.core.migration import migrate_legacy_states

invariants = InvariantSet(
    [
        Invariant(name="non_negative_balance", check=lambda state: state["balance"] >= 0),
        Invariant(name="owner_required", check=lambda state: bool(state["owner"].strip())),
    ]
)


def repair_legacy(state: dict[str, object]) -> None:
    if isinstance(state.get("balance"), int) and state["balance"] < 0:
        state["balance"] = 0
    if isinstance(state.get("owner"), str) and not state["owner"].strip():
        state["owner"] = "migrated-owner"


report = migrate_legacy_states(
    [{"balance": -10, "owner": ""}, {"balance": 50, "owner": "ops"}],
    invariants=invariants,
    transform=repair_legacy,
)
assert report.migrated_count == 2
assert report.quarantined_count == 0
```

## Python Example: Versioned Invariant Profiles

```python
from icsd.core.invariants import (
    Invariant,
    InvariantProfile,
    InvariantProfileRegistry,
    InvariantSet,
)
from icsd.core.transition import Transition

registry = InvariantProfileRegistry(
    [
        InvariantProfile(
            name="uk-payroll",
            policy="policy/payroll-tax",
            policy_version="2026.03",
            invariants=InvariantSet(
                [Invariant(name="non_negative_pay", check=lambda state: state["pay"] >= 0)]
            ),
        )
    ]
)

transition = Transition(
    name="apply_payroll_adjustment",
    mutate=lambda state: state.update({"pay": state["pay"] - 20}),
    profile_registry=registry,
    default_profile="uk-payroll",
)
result = transition.apply(
    state={"pay": 100},
    entity_type="payroll_record",
    entity_id="pay-001",
    actor="user:ops",
    policy_version="2026.03",
)

assert result.after_state == {"pay": 80}
assert result.evidence.authority == "policy/payroll-tax@2026.03"
```

## Python Example: Temporal Policy Activation and Cutover

```python
from icsd.core.invariants import Invariant, InvariantProfile, InvariantProfileRegistry, InvariantSet
from icsd.core.transition import Transition

registry = InvariantProfileRegistry(
    [
        InvariantProfile(
            name="uk-payroll",
            policy="policy/payroll-tax",
            policy_version="2026.03",
            active_from="2026-01-01T00:00:00Z",
            active_until="2026-06-01T00:00:00Z",
            invariants=InvariantSet(
                [Invariant(name="non_negative_pay", check=lambda state: state["pay"] >= 0)]
            ),
        ),
        InvariantProfile(
            name="uk-payroll",
            policy="policy/payroll-tax",
            policy_version="2026.06",
            active_from="2026-06-01T00:00:00Z",
            invariants=InvariantSet(
                [Invariant(name="non_negative_pay", check=lambda state: state["pay"] >= 0)]
            ),
        ),
    ]
)
transition = Transition(
    name="apply_payroll_adjustment",
    mutate=lambda state: state.update({"pay": state["pay"] - 20}),
    profile_registry=registry,
    default_profile="uk-payroll",
)
before_cutover = transition.apply(
    state={"pay": 100},
    entity_type="payroll_record",
    entity_id="pay-001",
    actor="user:ops",
    timestamp="2026-05-31T23:59:59Z",
)
after_cutover = transition.apply(
    state={"pay": 100},
    entity_type="payroll_record",
    entity_id="pay-001",
    actor="user:ops",
    timestamp="2026-06-01T00:00:00Z",
)
assert before_cutover.evidence.authority == "policy/payroll-tax@2026.03"
assert after_cutover.evidence.authority == "policy/payroll-tax@2026.06"
```

## Python Example: Transition and Evidence

```python
from datetime import datetime, timezone

from icsd.core.evidence import AuthoritySource
from icsd.core.invariants import Invariant, InvariantSet
from icsd.core.transition import Transition

invariants = InvariantSet(
    [Invariant(name="non_negative_balance", check=lambda state: state["balance"] >= 0)]
)


def debit(state: dict[str, int]) -> None:
    state["balance"] -= 20


transition = Transition(name="debit_account", mutate=debit, invariants=invariants)
result = transition.apply(
    state={"balance": 100},
    entity_type="account",
    entity_id="acct-001",
    actor="user:ops",
    authority="policy/account-debit",
    authority_sources=[
        AuthoritySource(
            source_type="registry",
            source_id="uk-companies-house",
            reference="company/00000006",
        )
    ],
    transition_id="11111111-1111-4111-8111-111111111111",
    timestamp=datetime(2026, 3, 3, 10, 0, tzinfo=timezone.utc),
)

assert result.before_state == {"balance": 100}
assert result.after_state == {"balance": 80}
assert result.evidence.verify_integrity(
    before_state=result.before_state,
    after_state=result.after_state,
)
```

## Python Example: Minimal Transition Helper

```python
from icsd.core.invariants import Invariant, InvariantSet
from icsd.core.transition import Transition

transition = Transition.simple(
    name="debit_account",
    mutate=lambda state: state.update({"balance": state["balance"] - 20}),
    invariants=InvariantSet(
        [Invariant(name="non_negative_balance", check=lambda state: state["balance"] >= 0)]
    ),
)
```

`Transition.simple(...)` is constructor sugar for direct-invariant transitions.
It does not bypass invariant checks or authority validation hooks.

## Python Example: Authority Validation Hook

```python
from icsd.core.invariants import Invariant, InvariantSet
from icsd.core.transition import Transition

transition = Transition(
    name="debit_account",
    mutate=lambda state: state.update({"balance": state["balance"] - 20}),
    invariants=InvariantSet(
        [Invariant(name="non_negative_balance", check=lambda state: state["balance"] >= 0)]
    ),
    authority_validator=Transition.require_authority_sources(minimum=1),
)
```

## Python Example: Authority Adapter (Trustless Proposal Verification)

```python
from icsd.core.invariants import Invariant, InvariantSet
from icsd.core.transition import Transition


class ExternalAuthorityAdapter:
    def propose(self, *, current_state, authority_response):
        # External responses are proposal inputs, not direct truth.
        return {
            "changes": {
                "balance": authority_response["approved_balance"],
            },
            "source": "authority-api",
            "reference": authority_response["response_id"],
        }


transition = Transition(
    name="debit_account",
    mutate=lambda state: state.update({"balance": state["balance"] - 5}),
    invariants=InvariantSet(
        [Invariant(name="non_negative_balance", check=lambda state: state["balance"] >= 0)]
    ),
    authority_adapter=ExternalAuthorityAdapter(),
)
result = transition.apply(
    state={"balance": 100},
    entity_type="account",
    entity_id="acct-001",
    actor="user:ops",
    authority="policy/account-debit",
    authority_response={"approved_balance": 60, "response_id": "resp-001"},
)
assert result.after_state == {"balance": 95}
```

For a deterministic refusal-report pattern when an adapter proposes impossible
state, see `tests/test_trustless_authority_refusal.py`.

## Python Example: Schema Contract Adapters (JSON Schema + Pydantic)

```python
from pydantic import BaseModel, Field

from icsd.adapters import JsonSchemaInvariant, PydanticModelInvariant
from icsd.core.invariants import Invariant, InvariantSet

json_contract = JsonSchemaInvariant(
    schema={
        "type": "object",
        "required": ["status", "count"],
        "properties": {
            "status": {"type": "string", "enum": ["draft", "review", "approved"]},
            "count": {"type": "integer", "minimum": 0},
        },
        "additionalProperties": False,
    }
)


class ContractModel(BaseModel):
    status: str = Field(min_length=1)
    count: int = Field(ge=0)


pydantic_contract = PydanticModelInvariant(model=ContractModel)

invariants = InvariantSet(
    [
        Invariant(name="json_contract", check=json_contract),
        Invariant(name="pydantic_contract", check=pydantic_contract),
    ]
)
invariants.assert_valid({"status": "review", "count": 2})
```

`JsonSchemaInvariant` uses `jsonschema` when installed and falls back to a deterministic
subset validator (`type`, `required`, `properties`, `additionalProperties`, `enum`,
`const`, length/number bounds, and array item checks) when it is not.
`PydanticModelInvariant` requires optional `pydantic` to be installed.

## Python Example: Agent-Constrained Execution Context

```python
from icsd.core.context import AgentExecutionContext, AgentPolicyEnforcement
from icsd.core.invariants import Invariant, InvariantProfile, InvariantProfileRegistry, InvariantSet
from icsd.core.transition import Transition

registry = InvariantProfileRegistry(
    [
        InvariantProfile(
            name="retail-banking",
            policy="policy/account-debit",
            policy_version="2026.03",
            invariants=InvariantSet(
                [Invariant(name="non_negative_balance", check=lambda state: state["balance"] >= 0)]
            ),
        )
    ]
)
transition = Transition(
    name="debit_account",
    mutate=lambda state: state.update({"balance": state["balance"] - 5}),
    profile_registry=registry,
)
agent_context = AgentExecutionContext(
    transition,
    enforcement=AgentPolicyEnforcement(
        require_profile=True,
        require_policy_version=True,
        minimum_authority_sources=1,
    ),
)
result = agent_context.apply(
    state={"balance": 100},
    entity_type="account",
    entity_id="acct-001",
    actor="user:ops",
    profile="retail-banking",
    policy_version="2026.03",
    authority_sources=[
        {
            "source_type": "registry",
            "source_id": "uk-companies-house",
            "reference": "company/00000006",
        }
    ],
)
assert result.after_state == {"balance": 95}
```

## Python Example: Transparency Log Adapter

```python
from icsd.adapters import TransparencyLogReceipt, TransparencyVerification
from icsd.core.invariants import Invariant, InvariantSet
from icsd.core.transition import Transition


class MemoryLogAdapter:
    def append(self, *, evidence_payload):
        return TransparencyLogReceipt(adapter="memory-log", reference="entry/1")

    def verify(self, *, evidence_payload, receipt=None):
        return TransparencyVerification(verified=True, checkpoint="checkpoint-1")


transition = Transition(
    name="debit_account",
    mutate=lambda state: state.update({"balance": state["balance"] - 20}),
    invariants=InvariantSet(
        [Invariant(name="non_negative_balance", check=lambda state: state["balance"] >= 0)]
    ),
    transparency_log=MemoryLogAdapter(),
    verify_transparency_after_append=True,
)
```

## Python Example: Real-time Evidence Stream Adapter

```python
from icsd.adapters.transparency_stream import TransparencyStreamReceipt
from icsd.core.invariants import Invariant, InvariantSet
from icsd.core.transition import Transition


class AuditorFeedAdapter:
    def emit(self, *, event_payload):
        # event_payload includes evidence, signable_hash, and transition metadata.
        return TransparencyStreamReceipt(
            adapter="auditor-feed",
            event_id=f"evt/{event_payload['transition_id']}",
        )


transition = Transition(
    name="debit_account",
    mutate=lambda state: state.update({"balance": state["balance"] - 20}),
    invariants=InvariantSet(
        [Invariant(name="non_negative_balance", check=lambda state: state["balance"] >= 0)]
    ),
    transparency_stream=AuditorFeedAdapter(),
)
```

## Invisible Compliance Layer Guides

Deployment-oriented guidance for evidence-stream and auditor workflows:

- [docs/transparency_feed.md](docs/transparency_feed.md)
- [docs/auditor_subscribe.md](docs/auditor_subscribe.md)
- [docs/domains.md](docs/domains.md)
- [docs/adoption_ladder.md](docs/adoption_ladder.md)
- [docs/fastapi_integration.md](docs/fastapi_integration.md)
- [docs/whitepaper_integration.md](docs/whitepaper_integration.md)

Operational pattern:

1. A transition emits deterministic evidence and a stream event payload.
2. A stream adapter publishes the event payload to subscriber infrastructure.
3. A transparency-log adapter stores/returns inclusion receipts for later checks.
4. Auditors subscribe to the feed and verify evidence locally.

Concrete CLI walkthrough:

```bash
python examples/legal_case_lifecycle.py
icsd-dpa verify examples/out/legal_case_04_appealed.evidence.json
```

## Whitepaper Integration (Non-Normative)

For whitepaper usage, see [docs/whitepaper_integration.md](docs/whitepaper_integration.md)
for a ready-to-use "Reference Implementation and Conformance Testing" section and
claim-to-test/example mapping.

The package is a reference implementation only; it does not define the ICSD
normative specification.

## Evidence Payload Shape (Schema v0.1, Supported in v1.x)

`icsd-dpa verify` validates the required evidence fields and hash format.

```json
{
  "schema_version": "0.1",
  "transition_id": "11111111-1111-4111-8111-111111111111",
  "entity_type": "account",
  "entity_id": "acct-001",
  "actor": "user:ops",
  "authority": "policy/account-debit",
  "authority_sources": [
    {
      "source_type": "registry",
      "source_id": "uk-companies-house",
      "reference": "company/00000006"
    }
  ],
  "timestamp": "2026-03-03T10:00:00Z",
  "before_hash": "d2ec8f050e788f96279be8f1a1d7e56ce3f180ebea047a1bf6f019f06f5f0a18",
  "after_hash": "55459e863d1c8fcdaff7ac18549f96db4495ea3a74c4d659e410cd554d65ff43"
}
```

## Philosophy Linkouts

- [ORIGINAL_DIRECTION.md](ORIGINAL_DIRECTION.md): v0.1 blueprint and design intent.
- [AGENTS.md](AGENTS.md): contributor rules and commit requirements.
- [WORKSTREAMS.md](WORKSTREAMS.md): reservation ledger and execution tracking.
- [CHANGELOG.md](CHANGELOG.md): release notes and update discipline.

## Stability Policy (v1.x)

- `1.x` releases follow semantic versioning: breaking changes to documented
  public contracts require a `2.0.0` release.
- Package root API guarantee in v1.x remains intentionally narrow:
  `icsd.__version__`.
- `icsd.core.*` and `icsd.cli.*` are supported import paths in v1.x;
  incompatible changes require a major-version bump.
- CLI command surface (`icsd-dpa --version`, `verify`, `lint`) and exit-code
  semantics (`0`, `1`, `2`) are stable for v1.x automation.
- Evidence schema `0.1` and lint profile `policy-v0.1` remain supported in
  v1.x for compatibility.
- Breaking behavior changes should be labelled `Kind/Breaking` in issue/commit metadata.

## Changelog Discipline

- Add all user-visible changes to the `[Unreleased]` section in `CHANGELOG.md`.
- Use categories (`Added`, `Changed`, `Fixed`, `Removed`, `Security`) consistently.
- Keep entries concrete, short, and scoped to behavior, API, or operational impact.

## Repository Layout

```text
icsd-python/
  AGENTS.md
  WORKSTREAMS.md
  README.md
  CHANGELOG.md
  pyproject.toml
  ruff.toml
  src/icsd/
  tests/
```

## Credits

- Framework: [https://datatracker.ietf.org/doc/draft-dpa-icsd/](https://datatracker.ietf.org/doc/draft-dpa-icsd/)
- Author: [Benjamin Anthony Fisher](https://datatracker.ietf.org/person/b.fisher@dpa-cloud.co.uk)

## License

Apache-2.0. See [LICENSE](LICENSE).
