diff --git a/.gitignore b/.gitignore index c375bc0..90ac033 100644 --- a/.gitignore +++ b/.gitignore @@ -19,7 +19,8 @@ bench/real/ # tool working dirs (not release content) .claude/ .gitnexus/ -.dorian/local/ # ephemeral host-hook runtime packet (last-decision.json); never tracked +# ephemeral host-hook runtime packet (last-decision.json); never tracked +.dorian/local/ # internal program/audit working docs — provenance only, never shipped in the release /docs/design/C4_IMPORT_BINDING_REPORT.md diff --git a/CHANGELOG.md b/CHANGELOG.md index 284f453..a51d1d1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,7 +6,9 @@ semantics have been stable since 1.0.0. ## [Unreleased] -### Added — Dorian Loop Guard (vNext / 1.4.0) +## [1.4.0] — 2026-07-02 + +### Added — Dorian Loop Guard (1.4.0) - **`dorian loop`** (`src/dorian/loop.py`, `cmd_loop` in `commands.py`) — a deterministic, token-free **steering layer for AI coding loops**, built as a thin classifier on top of `revalidate` (no new verification, **no model at check time**): diff --git a/README.md b/README.md index 59201c7..028ec84 100644 --- a/README.md +++ b/README.md @@ -458,7 +458,7 @@ jobs: with: fetch-depth: 0 # revalidate diffs against the PR base sha persist-credentials: false # the Action only reads the diff + posts via GITHUB_TOKEN - - uses: ajaysurya1221/dorian/action@v1.3.0 + - uses: ajaysurya1221/dorian/action@v1.4.0 with: fail_on: revoked # install defaults to the published PyPI package (dorian-vwp); pin a @@ -625,7 +625,7 @@ work perishable, so you find out when it expired. **reproducible on those frozen SHAs only** — not a real-world performance claim; the trigger and truth layers are reported separately. - **PyPI trusted publishing** — `dorian-vwp` is published to PyPI via a Trusted Publisher - (latest: **`v1.3.0`**); `pip install dorian-vwp` installs the released package. + (latest: **`v1.4.0`**); `pip install dorian-vwp` installs the released package. Non-goals stay non-goals: no servers, no dashboards, no hosted control plane, no model at check time. Local-first is the design center. diff --git a/action/README.md b/action/README.md index 99caf64..823bd73 100644 --- a/action/README.md +++ b/action/README.md @@ -26,7 +26,7 @@ jobs: fetch-depth: 0 # REQUIRED: revalidate diffs against the PR base # sha, which a shallow clone does not contain persist-credentials: false # the Action reads the diff + posts via GITHUB_TOKEN - - uses: ajaysurya1221/dorian/action@v1.3.0 + - uses: ajaysurya1221/dorian/action@v1.4.0 with: fail_on: revoked # install defaults to the published PyPI package (dorian-vwp); @@ -78,7 +78,7 @@ self-attested-verdict problem for *non-executable* checkers — that is what ```yaml # untrusted / public-fork posture -- uses: ajaysurya1221/dorian/action@v1.3.0 +- uses: ajaysurya1221/dorian/action@v1.4.0 with: deny_exec: "true" # C4/C5 ERROR instead of executing ``` @@ -94,7 +94,7 @@ executed). Implemented and proven by the ```yaml # public / forked-PR posture: trusted checker specs + no code execution -- uses: ajaysurya1221/dorian/action@v1.3.0 +- uses: ajaysurya1221/dorian/action@v1.4.0 with: checker_trust: base # run only base-approved checker specs deny_exec: "true" # and refuse to execute even those (belt and braces) diff --git a/docs/BENCHMARK_CURRENT.md b/docs/BENCHMARK_CURRENT.md index 7ebd584..ac6ad8b 100644 --- a/docs/BENCHMARK_CURRENT.md +++ b/docs/BENCHMARK_CURRENT.md @@ -10,9 +10,9 @@ and are kept as-is for provenance. | field | value | | --- | --- | -| dorian version | `1.3.0` | +| dorian version | `1.4.0` | | metric commit | `33e9eaf` (the benchmark figures were measured here, during the release audit) | -| release commit | `81cebbc` (1.0.1) → v1.0.2 announcement hotfix. The 1.0.1 changes (C4 leading-dash nodeid rejection, C5 reconcile per-query timeout, a byte-identical index-once `verify` refactor, the `suggest-claims` / `export --in-toto` commands) plus the v1.0.2 hotfix (export `.warrant` filename disambiguation, `suggest-claims` PEP 263 encoding read, a `symbol_index` non-git `GitError` guard, and CI/SCA/credential/doc hardening) touch no checker numeric behavior; both suites below were **re-run at 1.0.2 and reproduce the metric-commit figures exactly** — binding-lifecycle to the same content-derived `run_id` `168b50d9aa631d52` — so these changes do not move what the suites measure. v1.1.0 added the `dorian init` scaffolder, the PR-comment renderer enhancements (a status line, trust-change counts, sealed-at, and remediation), and build/VCS guards against editor/file-sync `… 2.py` duplicate files (untracked local artifacts — never tracked, never in a CI wheel or on PyPI); v1.1.1 makes the `dorian init` starter claim load-bearing (a scaffold default). All of this is a new command plus output formatting, scaffold defaults, and packaging hygiene only — touching no checker/binding/fold code — so the figures stand unchanged at 1.1.1 (the suites were last executed at 1.0.2, not re-run since). **v1.2.0** adds C4 import-aware binding (a trigger-axis watch widening that affects only C4 `pytest:` claims — not the C1/C3/C5 symbol/regex/string/path/data paths these suites exercise) and the opt-in, default-off `--strength-gate` (advisory; changes no checker verdict or binding). All three suites were **re-run at v1.2.0 and reproduce the metric-commit figures exactly** — binding-lifecycle again landing on the same content-derived `run_id 168b50d9aa631d52` — so 1.2.0 does not move what they measure. **v1.3.0** adds the Claude Code claim-warrants scaffolder (`dorian claude-code install-claim-warrants`: a new CLI subcommand, packaged skill/hook/settings templates, an opt-in reminder Stop hook, and docs) — a CLI/packaging/docs addition that touches **no checker, binding, or fold code**, so the figures stand unchanged at 1.3.0 (the suites were last executed at v1.2.0, not re-run since) | +| release commit | `81cebbc` (1.0.1) → v1.0.2 announcement hotfix. The 1.0.1 changes (C4 leading-dash nodeid rejection, C5 reconcile per-query timeout, a byte-identical index-once `verify` refactor, the `suggest-claims` / `export --in-toto` commands) plus the v1.0.2 hotfix (export `.warrant` filename disambiguation, `suggest-claims` PEP 263 encoding read, a `symbol_index` non-git `GitError` guard, and CI/SCA/credential/doc hardening) touch no checker numeric behavior; both suites below were **re-run at 1.0.2 and reproduce the metric-commit figures exactly** — binding-lifecycle to the same content-derived `run_id` `168b50d9aa631d52` — so these changes do not move what the suites measure. v1.1.0 added the `dorian init` scaffolder, the PR-comment renderer enhancements (a status line, trust-change counts, sealed-at, and remediation), and build/VCS guards against editor/file-sync `… 2.py` duplicate files (untracked local artifacts — never tracked, never in a CI wheel or on PyPI); v1.1.1 makes the `dorian init` starter claim load-bearing (a scaffold default). All of this is a new command plus output formatting, scaffold defaults, and packaging hygiene only — touching no checker/binding/fold code — so the figures stand unchanged at 1.1.1 (the suites were last executed at 1.0.2, not re-run since). **v1.2.0** adds C4 import-aware binding (a trigger-axis watch widening that affects only C4 `pytest:` claims — not the C1/C3/C5 symbol/regex/string/path/data paths these suites exercise) and the opt-in, default-off `--strength-gate` (advisory; changes no checker verdict or binding). All three suites were **re-run at v1.2.0 and reproduce the metric-commit figures exactly** — binding-lifecycle again landing on the same content-derived `run_id 168b50d9aa631d52` — so 1.2.0 does not move what they measure. **v1.3.0** adds the Claude Code claim-warrants scaffolder (`dorian claude-code install-claim-warrants`: a new CLI subcommand, packaged skill/hook/settings templates, an opt-in reminder Stop hook, and docs) — a CLI/packaging/docs addition that touches **no checker, binding, or fold code**, so the figures stand unchanged at 1.3.0 (the suites were last executed at v1.2.0, not re-run since). **v1.4.0** adds the Dorian Loop Guard (`dorian loop preflight|prompt|install`) and the governance foundation (`dorian goal`/`gate`/`governance install`, the atomic sidecar writer, the deterministic-core import-firewall test) — loop-steering/preflight commands, host-adapter templates, and docs that touch **no checker, binding, or fold code** — so the figures stand unchanged at 1.4.0 (the suites were last executed at v1.2.0, not re-run since) | | Python | 3.12.4 | | platform | darwin (CI matrix: 3.11 / 3.12 / 3.13) | | reproduce | `dorian bench large-mutation` · `dorian bench binding-lifecycle` · `dorian bench realworld-usecases` | diff --git a/docs/CLAUDE_CODE_DORIAN_WORKFLOW.md b/docs/CLAUDE_CODE_DORIAN_WORKFLOW.md index 007a438..696659c 100644 --- a/docs/CLAUDE_CODE_DORIAN_WORKFLOW.md +++ b/docs/CLAUDE_CODE_DORIAN_WORKFLOW.md @@ -76,7 +76,7 @@ dorian status # trust state of every warranted arti ## GitHub Action ```yaml -- uses: ajaysurya1221/dorian/action@v1.3.0 +- uses: ajaysurya1221/dorian/action@v1.4.0 with: fail_on: revoked # block the PR when a sealed claim breaks # for semi-trusted contributors, also: checker_source: base + deny_exec: true diff --git a/docs/SECURITY_AND_SAFE_RUNNERS.md b/docs/SECURITY_AND_SAFE_RUNNERS.md index e0482be..2ee0605 100644 --- a/docs/SECURITY_AND_SAFE_RUNNERS.md +++ b/docs/SECURITY_AND_SAFE_RUNNERS.md @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 # the action needs full history to revalidate --since base - - uses: ajaysurya1221/dorian/action@v1.3.0 + - uses: ajaysurya1221/dorian/action@v1.4.0 with: checker_trust: base # resolve checker SPECs from the trusted base ref deny_exec: "true" # C4 pytest / C5 shell ERROR instead of executing @@ -113,7 +113,7 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 0 - - uses: ajaysurya1221/dorian/action@v1.3.0 + - uses: ajaysurya1221/dorian/action@v1.4.0 # checker_trust defaults to head, deny_exec defaults to false: # executing checkers run, because contributors are trusted. with: diff --git a/docs/releases/v1.4.0.md b/docs/releases/v1.4.0.md new file mode 100644 index 0000000..9723080 --- /dev/null +++ b/docs/releases/v1.4.0.md @@ -0,0 +1,121 @@ +# dorian v1.4.0 — Governance Foundation + +A feature release: the deterministic spine of a **governance layer** for long-running AI coding loops — +a human-authored goal record, a path-derived coverage check, a host-mappable preflight **gate**, and +one concrete **Claude Code governance adapter** — plus the **Dorian Loop Guard** steering layer. +**No breaking changes** — purely additive. The warrant format, checker grammar, exit codes, fold +policy, and security posture are unchanged; the core stays zero-dependency; and **no model is added to +the verification path** (a build-time import firewall now proves it). + +## Summary + +dorian is growing from *"deterministically verify AI claims about code"* into a **deterministic +governance kernel** for agentic engineering. The guiding thesis: *dorian does not train agents to be +good — it makes bad actions non-continuable, non-mergeable, or human-reviewable.* "Crime" means an +observable contract violation; "punishment" means a mechanical loss of affordance (a blocked tool +call, a forced repair, a failed gate, a revoked warrant, a human escalation). dorian is the court; +host adapters and CI/branch-protection are the enforcement. + +## What shipped + +### Dorian Loop Guard (`dorian loop`) +- **`dorian loop preflight`** — re-checks the claim warrants a change touched and emits a + **CONTINUE / REPAIR / ESCALATE** decision packet (`--format json|md|text|prompt`). A pure function + of the `revalidate` result, `--policy` (`cautious|assist|unattended`), the repair-attempt cap, and + the `--scope`/`--deny-path` lane. Exits **0** by default; `--fail-on repair|escalate` opts into a + hard exit-4 gate. +- **`dorian loop prompt`** renders the packet as a next-iteration instruction. +- **`dorian loop install`** scaffolds the `/dorian-loop-guard` Claude Code skill (+ example + `LOOP.md`/`STATE.md`, `--with-action`). + +### Governance foundation +- **`dorian goal add` / `show` / `check`** — a **human-authored goal record** (`.dorian/goals/.goal.json`, + `schema_version: 1`) plus a deterministic, **path-derived coverage diff** (`goals.coverage_diff` → + `{covered, uncovered}` over git-changed, in-scope paths vs. warranted paths). `goal check + --fail-on-uncovered` exits **4**. The goal's `statement` is **human context only** — never fed to a + model, never used to decide a verdict or "completion". +- **`dorian gate`** — a host-mappable preflight body: reads a tool-call JSON on stdin, runs the same + pure loop preflight, and prints the `continue`/`repair`/`escalate` packet. Emits only contract codes + (`0`/`4`, plus `2` for malformed stdin); **never** uses exit 2 as a veto, reads a clock, or reads a + nonce. +- **Claude Code governance adapter** via **`dorian governance install`** — one concrete adapter: a + `SubagentStop` hook that shells `dorian gate` and writes an ephemeral + `.dorian/local/last-decision.json`, and a **fail-closed** `PreToolUse` veto (the exit-2 tool-block + lives in the host hook; fails closed under `unattended`/`godmode`, fails open when attended; + `FRESH_SECONDS = 900`), plus a settings example and bundle docs. +- **Safe sidecar writer** (`src/dorian/sidecars.py`) — atomic (temp + `os.replace`), deterministic + (sorted-key JSON), path-safe (`ensure_within`). +- **Deterministic core import firewall** (`tests/test_firewall_import_closure.py`) — an AST + import-closure guard proving no model/network import sits on the verdict path (covers the new + `sidecars`, `goals`, `governance` modules). +- **Release hardening** — functional PreToolUse-veto subprocess tests (identity mismatch, godmode, + freshness boundary, empty-path escalate, malformed-stdin open/closed) and an installed-wheel + `dorian governance install` scaffold test. +- **Docs** — [`GOVERNANCE_DATA_MODEL.md`](../GOVERNANCE_DATA_MODEL.md), + [`DORIAN_PANE.md`](../DORIAN_PANE.md) (pane vision — deferred), [`DORIAN_LOOP_GUARD.md`](../DORIAN_LOOP_GUARD.md), + and gate/C4-flakiness notes in [`SECURITY_BOUNDARY.md`](../SECURITY_BOUNDARY.md) / + [`VALIDATION_HONESTY.md`](../VALIDATION_HONESTY.md). + +## Deterministic boundary (unchanged invariants) + +No model on the verdict path (firewall-enforced); the core stays **zero-dependency**; the exit-code +contract, warrant schema, checker grammar, and fold policy are all **unchanged**. Every governance +detector is a pure function of observable artifacts (git diffs, paths, scope/deny globs, warrants, +`revalidate`/fold results, coverage diff, decision packets). + +## Adapter boundary + +`dorian gate` / core emit only `0`/`4`. The **exit-2 tool-block veto lives only in the Claude Code +`PreToolUse` host hook**; wall-clock and nonce are stamped only by the `SubagentStop` hook into +`.dorian/local/last-decision.json`. The adapter is **one concrete Claude Code adapter** — no generic +provider abstraction is introduced. + +## Validation + +- Full `uv run pytest` (incl. slow) green on CI across Python 3.11 / 3.12 / 3.13; `ruff check` and + `ruff format --check` clean. +- New tests cover the sidecar writer, the goal record + coverage diff, `dorian gate` (0/4, 2 only on + malformed), the firewall, and — as a real `pip install` path — a built wheel installed into a clean + venv that scaffolds the governance adapter and carries the exit-2 fail-closed veto. +- Landed via three merged PRs (#25 Loop Guard, #26 Governance Foundation, #27 hardening), each rebased + clean and CI-green before merge. + +## Deferred / cut (not in v1.4) + +- Claim **provenance** sidecars → **v1.5** (only if a deterministic consumer appears). +- **Effort presets** → **cut from core** (the binding-floor/breadth mapping lives in adapter docs). +- The **pane / TUI** → **deferred** (see Claude Design boundary below). +- A **generic provider abstraction** → **deferred** until a second concrete adapter exists. +- An authoritative ledger / hash-chain / off-repo signing → deferred (only meaningful with an external + trust root). + +## Upgrade notes + +Purely additive; **no migration required.** New subcommands are inert unless invoked; new sidecars are +written only by the new commands. Existing warrants, `verify`/`revalidate`, and the claim-warrants +integration are untouched. To adopt governance in a repo: `dorian governance install`, then merge the +`.claude/settings.dorian-governance.example.json` hooks into `.claude/settings.json`. Removing the +feature = delete `.claude/hooks/dorian_*.py` and the settings entries; the core is unaffected. + +## Security & honesty notes + +- **Not a sandbox.** C4 (`pytest:`) / C5 (`shell:`) checkers execute code; governance is policy and + steering, not isolation. +- **Host hooks enforce, but a repo-controlling actor can disable them.** The `PreToolUse` veto is a + Claude Code host hook — an agent that controls the repository can delete it, edit + `.claude/settings.json`, or rewrite its own goals/warrants. dorian's real strength is transparency + and auditability, not containment. +- **CI / GitHub branch protection is the real merge boundary.** Governance repos should branch-protect + `.dorian/` and `.claude/` and require the `dorian gate` status check. (Branch protection is not a + hard gate against administrators — human/admin bypass is a live surface to log.) +- **`.dorian/local/` is ephemeral and gitignored** — a transient steering signal, never an + authoritative ledger, and never read back into a verdict. +- **Coverage is a structural floor**, not a judgment of correctness; goal completion is **not** + model-evaluated. Weak binding or weak checker strength means low confidence, **not** a false claim. + +## Claude Design boundary + +The **pane / TUI / brand** are **deferred**. `docs/DORIAN_PANE.md` is a future-facing vision and data +contract, **not** an implementation — no UI/TUI code ships in this release. **Claude Design owns the +final TUI/pane/brand design** (layout, typography, color, interaction, README polish, banner, logo); +dorian/Opus owns the deterministic data contracts and governance invariants. diff --git a/pyproject.toml b/pyproject.toml index 1edc1f2..e66a5ad 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -4,7 +4,7 @@ build-backend = "hatchling.build" [project] name = "dorian-vwp" -version = "1.3.0" +version = "1.4.0" description = "Hold AI agents to what they said they did: deterministic, token-free verification of claims about a change." readme = "README.md" requires-python = ">=3.11" diff --git a/src/dorian/__init__.py b/src/dorian/__init__.py index b57ad48..bf3ef08 100644 --- a/src/dorian/__init__.py +++ b/src/dorian/__init__.py @@ -3,4 +3,4 @@ PyPI distribution: `dorian-vwp`; import package: `dorian`; CLI: `dorian`. """ -__version__ = "1.3.0" +__version__ = "1.4.0" diff --git a/src/dorian/cli.py b/src/dorian/cli.py index 3fbb334..8d2e2d0 100644 --- a/src/dorian/cli.py +++ b/src/dorian/cli.py @@ -499,7 +499,8 @@ def _add_goal_parser(sub: argparse._SubParsersAction) -> None: add.add_argument( "--min-strength", default=None, - help="minimum checker strength for load-bearing claims (structural coverage contract)", + help="minimum checker strength for load-bearing claims — recorded in the goal " + "record only; not yet enforced (planned v1.5)", ) show = gp_sub.add_parser("show", help="print a goal record as JSON") diff --git a/tests/test_version_sync.py b/tests/test_version_sync.py index 1c2d45e..4727e99 100644 --- a/tests/test_version_sync.py +++ b/tests/test_version_sync.py @@ -118,6 +118,31 @@ def test_no_dorian_action_at_main_in_public_snippets() -> None: ) +# Live copy-paste pins must also track the released version: an immutable but STALE pin +# (e.g. @v1.3.0 shipped in the v1.4.0 README) hands users an action missing the release's +# commands. Asserted against the package version, not the tag (the tag exists only after +# release — the brief merged-but-untagged window is accepted). docs/releases/ and CHANGELOG +# are historical provenance and deliberately not scanned. +_ACTION_PIN_SURFACES = ( + "README.md", + "action/README.md", + "docs/CLAUDE_CODE_DORIAN_WORKFLOW.md", + "docs/SECURITY_AND_SAFE_RUNNERS.md", +) + + +def test_dorian_action_pins_match_package_version() -> None: + version = _pyproject_version() + offenders: list[str] = [] + for rel in _ACTION_PIN_SURFACES: + text = (REPO_ROOT / rel).read_text(encoding="utf-8") + for lineno, line in enumerate(text.splitlines(), start=1): + for pin in re.findall(r"dorian/action@v(\d+\.\d+\.\d+)", line): + if pin != version: + offenders.append(f"{rel}:{lineno}: @v{pin} != package v{version}") + assert not offenders, "stale dorian/action version pins:\n" + "\n".join(offenders) + + # The attestation-interop example must not pin a fixed dorianVersion (Codex FINDING-07): a # hardcoded "1.0.x" silently drifts every release. It is kept version-neutral instead. def test_attestation_interop_dorian_version_is_version_neutral() -> None: diff --git a/uv.lock b/uv.lock index eb58ce6..56d896e 100644 --- a/uv.lock +++ b/uv.lock @@ -184,7 +184,7 @@ wheels = [ [[package]] name = "dorian-vwp" -version = "1.3.0" +version = "1.4.0" source = { editable = "." } [package.optional-dependencies]