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
2 changes: 1 addition & 1 deletion .codd/canon.lock
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,4 @@ docs/requirements/extractor-requirements.md sha256:fa02e6eddbb42e96e693aae72ee82

docs/requirements/require-command-requirements.md sha256:5ef134be5650dd10e77de818a019f02beabec763a1794358639c706baa635b37 v3.39.0-canon-tripwire

docs/requirements/system-requirements.md sha256:9c34a9e4e20ec5d2abc99d582aeeaab2b93347c101e3edd5ab9636b4ed228aae v3.39.0-canon-tripwire
docs/requirements/system-requirements.md sha256:9f7f5168b587ff0467c936cca54427ec2605faf2d898e1479a165c82253e6aa1 issue-43
144 changes: 139 additions & 5 deletions .codd/dag.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"version": "1",
"built_at": "2026-09-11T11:20:56.349213+00:00",
"built_at": "2026-09-14T13:21:51.898973+00:00",
"project_root": ".",
"nodes": [
{
Expand Down Expand Up @@ -49,9 +49,11 @@
"__future__",
"re",
"dataclasses",
"datetime",
"pathlib",
"typing",
"codd.requirement_reconciliation",
"codd.runtime_record",
"codd.dag.builder",
"codd.parsing.filesystem_routes"
],
Expand Down Expand Up @@ -156,6 +158,19 @@
"runtime_evidence": []
}
},
{
"id": "codd/ansi.py",
"kind": "impl_file",
"path": "codd/ansi.py",
"attributes": {
"language": "python",
"imports": [
"__future__",
"re"
],
"runtime_evidence": []
}
},
{
"id": "codd/artifact_contract.py",
"kind": "impl_file",
Expand Down Expand Up @@ -1961,6 +1976,7 @@
"pathlib",
"typing",
"yaml",
"codd.ansi",
"codd.deployment.providers"
],
"runtime_evidence": []
Expand Down Expand Up @@ -2941,6 +2957,7 @@
"shlex",
"typing",
"yaml",
"codd.path_safety",
"codd.stack.lock",
"codd.stack.project",
"codd.stack.command_authenticity",
Expand Down Expand Up @@ -3761,6 +3778,7 @@
"subprocess",
"dataclasses",
"pathlib",
"codd.ansi",
".builtin_adapters",
".registry",
".verify_plan",
Expand Down Expand Up @@ -4521,6 +4539,7 @@
"typing",
"yaml",
"codd.generator",
"codd.path_safety",
"codd.project_types",
"codd.validator",
"codd.config",
Expand Down Expand Up @@ -5010,10 +5029,11 @@
"language": "python",
"imports": [
"__future__",
"re",
"dataclasses",
"pathlib",
"re",
"typing"
"typing",
"codd.ansi"
],
"runtime_evidence": []
}
Expand Down Expand Up @@ -5043,6 +5063,7 @@
"codd.languages.verify_executor",
"codd.languages.verify_plan",
"yaml",
"codd.ansi",
"codd.config",
"codd.dag",
"codd.dag.builder",
Expand Down Expand Up @@ -5292,6 +5313,28 @@
"runtime_evidence": []
}
},
{
"id": "codd/runtime_record.py",
"kind": "impl_file",
"path": "codd/runtime_record.py",
"attributes": {
"language": "python",
"imports": [
"__future__",
"hashlib",
"json",
"os",
"re",
"dataclasses",
"datetime",
"pathlib",
"typing",
"yaml",
"codd.config"
],
"runtime_evidence": []
}
},
{
"id": "codd/runtime_smoke/__init__.py",
"kind": "impl_file",
Expand Down Expand Up @@ -5371,7 +5414,8 @@
"pathlib",
"codd.runtime_smoke.checks",
"codd.runtime_smoke.config",
"codd.runtime_smoke.report"
"codd.runtime_smoke.report",
"codd.runtime_record"
],
"runtime_evidence": []
}
Expand Down Expand Up @@ -8481,6 +8525,7 @@
"__future__",
"json",
"textwrap",
"datetime",
"pathlib",
"yaml",
"codd.acceptance_evidence",
Expand All @@ -8489,6 +8534,7 @@
"codd.dag.checks.acceptance_evidence",
"codd.dag.runner",
"codd.config",
"codd.runtime_record",
"codd.acceptance_record",
"codd.acceptance_sync",
"codd.verifiable_behavior_audit",
Expand Down Expand Up @@ -9961,6 +10007,7 @@
"dataclasses",
"pathlib",
"pytest",
"tests.greenfield.conftest",
"codd.cli",
"codd.greenfield.pipeline",
"codd.greenfield.test_rederivation",
Expand All @@ -9980,6 +10027,7 @@
"dataclasses",
"pathlib",
"pytest",
"tests.greenfield.conftest",
"codd.greenfield.test_rederivation"
]
}
Expand Down Expand Up @@ -12287,6 +12335,21 @@
]
}
},
{
"id": "tests/test_ansi_sanitized_runner_output.py",
"kind": "test_file",
"path": "tests/test_ansi_sanitized_runner_output.py",
"attributes": {
"language": "python",
"imports": [
"__future__",
"pathlib",
"codd.ansi",
"codd.deployment.providers.verification.pytest_http",
"codd.repair.test_failure_attribution"
]
}
},
{
"id": "tests/test_api_contract_proto_enum_render.py",
"kind": "test_file",
Expand Down Expand Up @@ -16176,7 +16239,8 @@
"codd.runtime_smoke.checks",
"codd.runtime_smoke.config",
"codd.runtime_smoke.report",
"codd.runtime_smoke.runner"
"codd.runtime_smoke.runner",
"codd.runtime_record"
]
}
},
Expand Down Expand Up @@ -17460,6 +17524,11 @@
"to_id": "codd/requirement_reconciliation.py",
"kind": "imports"
},
{
"from_id": "codd/acceptance_evidence.py",
"to_id": "codd/runtime_record.py",
"kind": "imports"
},
{
"from_id": "codd/acceptance_evidence.py",
"to_id": "tests/dag/test_acceptance_evidence.py",
Expand Down Expand Up @@ -17560,6 +17629,11 @@
"to_id": "tests/test_implement_syntax_gate.py",
"kind": "tested_by"
},
{
"from_id": "codd/ansi.py",
"to_id": "tests/test_ansi_sanitized_runner_output.py",
"kind": "tested_by"
},
{
"from_id": "codd/artifact_contract.py",
"to_id": "codd/artifact_ids.py",
Expand Down Expand Up @@ -21720,11 +21794,21 @@
"to_id": "tests/test_deployment_verification_templates.py",
"kind": "tested_by"
},
{
"from_id": "codd/deployment/providers/verification/pytest_http.py",
"to_id": "codd/ansi.py",
"kind": "imports"
},
{
"from_id": "codd/deployment/providers/verification/pytest_http.py",
"to_id": "codd/deployment/providers/__init__.py",
"kind": "imports"
},
{
"from_id": "codd/deployment/providers/verification/pytest_http.py",
"to_id": "tests/test_ansi_sanitized_runner_output.py",
"kind": "tested_by"
},
{
"from_id": "codd/deployment/providers/verification/pytest_http.py",
"to_id": "tests/test_e2e_harness.py",
Expand Down Expand Up @@ -23640,6 +23724,11 @@
"to_id": "codd/operational_e2e_audit.py",
"kind": "imports"
},
{
"from_id": "codd/greenfield/pipeline.py",
"to_id": "codd/path_safety.py",
"kind": "imports"
},
{
"from_id": "codd/greenfield/pipeline.py",
"to_id": "codd/planner.py",
Expand Down Expand Up @@ -25445,6 +25534,11 @@
"to_id": "tests/test_stack_profile.py",
"kind": "tested_by"
},
{
"from_id": "codd/languages/verify_executor.py",
"to_id": "codd/ansi.py",
"kind": "imports"
},
{
"from_id": "codd/languages/verify_executor.py",
"to_id": "codd/languages/adapters/runner_report.py",
Expand Down Expand Up @@ -26540,6 +26634,11 @@
"to_id": "codd/lexicon.py",
"kind": "imports"
},
{
"from_id": "codd/planner.py",
"to_id": "codd/path_safety.py",
"kind": "imports"
},
{
"from_id": "codd/planner.py",
"to_id": "codd/project_types.py",
Expand Down Expand Up @@ -27680,6 +27779,11 @@
"to_id": "tests/test_verify_integrity.py",
"kind": "tested_by"
},
{
"from_id": "codd/repair/test_failure_attribution.py",
"to_id": "codd/ansi.py",
"kind": "imports"
},
{
"from_id": "codd/repair/test_failure_attribution.py",
"to_id": "tests/repair/test_env_failure_defense.py",
Expand All @@ -27690,6 +27794,16 @@
"to_id": "tests/repair/test_test_failure_attribution.py",
"kind": "tested_by"
},
{
"from_id": "codd/repair/test_failure_attribution.py",
"to_id": "tests/test_ansi_sanitized_runner_output.py",
"kind": "tested_by"
},
{
"from_id": "codd/repair/verify_runner.py",
"to_id": "codd/ansi.py",
"kind": "imports"
},
{
"from_id": "codd/repair/verify_runner.py",
"to_id": "codd/config.py",
Expand Down Expand Up @@ -28240,6 +28354,21 @@
"to_id": "tests/test_extract_routes.py",
"kind": "tested_by"
},
{
"from_id": "codd/runtime_record.py",
"to_id": "codd/config.py",
"kind": "imports"
},
{
"from_id": "codd/runtime_record.py",
"to_id": "tests/dag/test_acceptance_evidence.py",
"kind": "tested_by"
},
{
"from_id": "codd/runtime_record.py",
"to_id": "tests/test_runtime_smoke.py",
"kind": "tested_by"
},
{
"from_id": "codd/runtime_smoke/__init__.py",
"to_id": "codd/runtime_smoke/config.py",
Expand Down Expand Up @@ -28290,6 +28419,11 @@
"to_id": "tests/test_runtime_smoke.py",
"kind": "tested_by"
},
{
"from_id": "codd/runtime_smoke/runner.py",
"to_id": "codd/runtime_record.py",
"kind": "imports"
},
{
"from_id": "codd/runtime_smoke/runner.py",
"to_id": "codd/runtime_smoke/checks.py",
Expand Down
20 changes: 20 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,26 @@ Install or upgrade with:
pip install -U codd-dev
```

## [3.42.0] - 2026-09-14 — Fix: a switch is a declaration, not evidence — the runtime stage has to have RUN

3.41.0 closed the *demotion* hole: an acceptance criterion whose only evidence is a runtime behaviour, in a project whose `runtime_smoke` stage is switched off, is a criterion proved by nothing. It left the mirror image open. Turning the stage **on** costs one line of YAML, and the gate read that line as evidence.

Measured on a real project: adding `runtime_smoke.enabled: true` — no dev server, no database, `codd verify --runtime` never invoked once — moved `runtime_evidence_not_executable` **50 → 0**, `unbound_acceptance` **61 → 20**, and the total finding count **180 → 89**. Half the findings cleared because a boolean was typed. The cheapest way through the acceptance gate was to declare rather than to verify, which is the incentive exactly backwards, and the check that exists to catch missing evidence was the one accepting a claim in its place.

**The rule now: only an EXECUTED runtime obligation binds.**

- **`codd/runtime_record.py` (NEW) — the runtime execution ledger.** The runtime smoke runner writes `<codd-dir>/runtime_ledger.json` when the stage is enabled **and at least one check actually ran**; an all-skipped run and a disabled stage write nothing, because a record of nothing is not a record. It holds `recorded_at` (UTC), `passed`, each check's `name` / `category` / `passed` / `skipped`, the `target_url` the run actually went against (`--runtime-base-url` may have pointed it elsewhere), and `config_digest` — a hash of the project's **own** `runtime_smoke` (minus `report.log_to_file` / `report.file_path`, which decide where output goes; `report.fail_fast` stays in, because it decides whether the checks after the first failure run at all) and `runtime` sections, read from `codd.yaml` rather than the defaults-merged view so that upgrading CoDD does not expire every recorded run at once. Missing, malformed, unversioned, or listing no executed check reads as **no evidence**, never as evidence — the same rule `acceptance_ledger.json` already follows. Written through a temp file and renamed, so two runs racing leave one whole record rather than half of two. Like that ledger it is a project artifact meant to be committed and read in diffs; unlike it, it is written automatically, and that difference is the point — a manual record is a human verdict that only a human may refresh, this one records that a machine ran, which only the machine that ran can honestly write.
- **`runtime_evidence_not_executed` (NEW finding).** The stage is on but no run discharged the obligation: no ledger (`no_record`), a ledger written against a runtime configuration the project has since changed (`config_changed`) or against another deployment (`target_changed`), a ledger past the project's freshness window — or dated in the future, which no window can judge (`stale`), a run that failed (`run_failed`), or — for an explicitly declared `verified_by: runtime:<case>` — a ledger in which no check answering to that name passed (`target_not_executed`). `runtime_evidence_not_executable` is unchanged and still means the other thing: the stage is off. Two holes, two remedies, two findings.
- **The criterion goes back to `unbound_acceptance` as well** — and, while the stage is enabled, at `runtime_execution_severity` rather than `unbound_severity`. An enabled-but-never-run obligation binds exactly as much as a switched-off one: nothing. But its unbound-ness is *caused by* the not-executed-ness — one defect, one remedy ("run it") — and pricing it as an ordinary unbound would strand a strict project: red before Step 8 runs, and Step 8 is what writes the record that clears it. With the stage OFF the ordinary `unbound_severity` still applies; such a project has no runtime path to run at all.
- **Explicit and inferred targets are treated differently, as they already were.** An explicit `runtime:<case>` names a case, so a run in which no non-skipped check answers to that name has not discharged it (`print_sheet` / `Print Sheet` / `print-sheet` are one name, not three). An **inferred** obligation — an `operation_flow.<id>` reference in a project that never adopted the `verified_by` column — declares no such correspondence, so any recorded run satisfies it. CoDD does not invent a mapping the project never wrote down. A run that FAILED is executed but proves nothing (`run_failed`): within the run Step 8 reports it in red, and the ledger is read by every LATER verification, which is where "it ran, and it was broken" would otherwise pass for proof. A failure in one check does not sink another — an explicitly named case is discharged when the check answering to that name passed, whatever else failed beside it.
- **`acceptance_evidence.runtime_execution_severity` (NEW, default amber even under `mode: strict`)** and **`acceptance_evidence.runtime_max_age_hours` (NEW, unset by default)**. A project that never persisted the ledger is **unproven**, not proven wrong, and upgrading CoDD must not turn an existing build red on its own; a project that wants the hard failure sets the key. With no age declared, the clock alone never expires a run — a configuration change still does.

A ledger the runner could not write (read-only checkout, full disk) is a lost artifact, not a false green — the next verification honestly reports `no_record` — so Step 8 says so loudly on stderr instead of failing the run. The one branch that IS a false green, an EARLIER passing record surviving a run that could not overwrite it, is not left standing: the stale record is removed, and if even that is refused Step 8 FAILS and names the file.

Not closed, and stated rather than left to be discovered: runtime execution evidence is **not yet bound to implementation content** the way a manual record is. A rewritten implementation under an unchanged runtime configuration does not invalidate a recorded run. Closing that needs a content digest both the runner and the gate can compute, and the runner has no dependency graph; until then the ledger proves the stage RAN, not that it ran against today's code.

Tests: 34 new (`tests/dag/test_acceptance_evidence.py`, `tests/test_runtime_smoke.py`), all `tmp_path` synthetic miniatures — no framework, URL or project literal. Dogfood on the real project that produced the measurement, same tree, same requirement documents: `enabled: true` with no recorded run now reports **180** findings (`runtime_evidence_not_executed` 50, `unbound_acceptance` 61) instead of 89 — identical to the stage being off; seeding an execution record drops it to 89; changing the runtime configuration after that run puts it straight back to 180.

## [3.41.0] - 2026-09-11 — Feature: the acceptance criterion the customer signed, wired to something a machine runs

A requirement shipped with a passing test suite and the feature was still wrong. Reconstructed afterwards: the requirement said "15 per sheet", the acceptance document said 20, the code said 20; one test existed and it exercised a command-line script, while the thing users pressed was a button on an admin page that no test touched; the runtime verification stage was disabled, so it was skipped **without printing anything**; and a manual "looks right" check from two weeks earlier still read as evidence for an implementation that had since been rewritten.
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -201,6 +201,8 @@ CoDD ships **39 ready-made "lexicons"** — checklists drawn from real industry
**What becomes visible.** Every acceptance criterion in your requirement tables is reconciled against evidence a machine runs: `codd verify` now reports criteria bound to no test, evidence that asserts nothing or is skipped, runtime obligations in a project whose runtime stage is off, values retyped instead of read by name, and evidence that never runs through the entry point users actually reach.
Findings are **advisory (amber) by default** — upgrading CoDD never turns your build red on its own — and they are never silent: each one names the criterion and what to do about it.

**A switch is a declaration, not evidence.** A criterion whose evidence is "the running system does X" is discharged by a runtime stage that RAN, not by one that is configured. Each `codd verify --runtime` writes `<codd-dir>/runtime_ledger.json` — what ran, whether it passed, and a digest of the runtime configuration it ran against — and the check reads that record: no record, a record written against a configuration you have since changed, a record older than `acceptance_evidence.runtime_max_age_hours` (unset by default), or a `verified_by: runtime:<case>` no check answered to, and the criterion is reported as `runtime_evidence_not_executed` and still bound to nothing. Commit the ledger like any other evidence artifact. This class stays amber even under `mode: strict` unless you set `runtime_execution_severity: red`: no record means unproven, not proven wrong.

**Turning it into a gate.** Put `acceptance_evidence: {mode: strict}` in `codd.yaml` (`codd init` writes it for new projects) and the same findings become red. Per-class overrides let you hard-fail one class while the rest stay advisory: `acceptance_evidence: {unbound_severity: red}`.
Opt out entirely with `acceptance_evidence: {enabled: false}` — the findings disappear with it, which is the point of choosing.

Expand Down
2 changes: 1 addition & 1 deletion codd/__init__.py
Original file line number Diff line number Diff line change
@@ -1,3 +1,3 @@
"""CoDD — Coherence-Driven Development."""

__version__ = "3.41.0"
__version__ = "3.42.0"
Loading
Loading