doneproof 0.1.0__py3-none-any.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- doneproof/__init__.py +102 -0
- doneproof/cli.py +53 -0
- doneproof/py.typed +0 -0
- doneproof-0.1.0.dist-info/METADATA +109 -0
- doneproof-0.1.0.dist-info/RECORD +10 -0
- doneproof-0.1.0.dist-info/WHEEL +5 -0
- doneproof-0.1.0.dist-info/entry_points.txt +2 -0
- doneproof-0.1.0.dist-info/licenses/LICENSE +21 -0
- doneproof-0.1.0.dist-info/licenses/NOTICE +9 -0
- doneproof-0.1.0.dist-info/top_level.txt +1 -0
doneproof/__init__.py
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
"""doneproof: accept a task only when its done-ness is proven by evidence.
|
|
2
|
+
|
|
3
|
+
An agent (or a plan) declares *doneproof* conditions in plain strings. This
|
|
4
|
+
package classifies each one as **passed**, **failed** or **unverifiable** by
|
|
5
|
+
checking real evidence — never the agent's own word that it finished.
|
|
6
|
+
|
|
7
|
+
Two evidence kinds are understood (the only two that verify without
|
|
8
|
+
interpreting free text):
|
|
9
|
+
|
|
10
|
+
- ``<path> exists`` — the file exists under the run's root directory. The path
|
|
11
|
+
is resolved and must stay inside the root (no ``../`` escape).
|
|
12
|
+
- ``human_gate_resolved on <step>`` — a human approval for ``<step>`` was
|
|
13
|
+
recorded. "Was it recorded?" is answered by a caller-supplied predicate
|
|
14
|
+
(``gate_resolved``), so this package never depends on any particular event
|
|
15
|
+
store, queue or database.
|
|
16
|
+
|
|
17
|
+
A condition in any other shape is **not failed** — it is reported as
|
|
18
|
+
``unverifiable`` so the caller can decide. Refusing to fail on what you cannot
|
|
19
|
+
check by evidence is as important as refusing to pass on an agent's say-so.
|
|
20
|
+
|
|
21
|
+
from pathlib import Path
|
|
22
|
+
from doneproof import evaluate, is_satisfied
|
|
23
|
+
|
|
24
|
+
result = evaluate(Path("runs/42"), ["artifacts/report.md exists"])
|
|
25
|
+
assert is_satisfied(result)
|
|
26
|
+
|
|
27
|
+
The return value is a plain JSON-serializable dict so it drops straight into a
|
|
28
|
+
log or an event. ``is_satisfied`` is true iff there are no failures;
|
|
29
|
+
unverifiable conditions do not make a run fail.
|
|
30
|
+
"""
|
|
31
|
+
|
|
32
|
+
from __future__ import annotations
|
|
33
|
+
|
|
34
|
+
import re
|
|
35
|
+
from collections.abc import Callable
|
|
36
|
+
from pathlib import Path
|
|
37
|
+
from typing import Any
|
|
38
|
+
|
|
39
|
+
__version__ = "0.1.0"
|
|
40
|
+
|
|
41
|
+
__all__ = ["GateResolved", "__version__", "evaluate", "is_satisfied"]
|
|
42
|
+
|
|
43
|
+
# "Was the human gate on <step> resolved?" — the one thing this package cannot
|
|
44
|
+
# check from the filesystem. The caller supplies it (bound to its own event log,
|
|
45
|
+
# queue, DB, …). Default: nothing is resolved, so gate conditions fail closed.
|
|
46
|
+
GateResolved = Callable[[str], bool]
|
|
47
|
+
|
|
48
|
+
_EXISTS = re.compile(r"^(?P<path>\S+)\s+exists$")
|
|
49
|
+
_GATE = re.compile(r"^human_gate_resolved\s+on\s+(?P<step>\S+)$")
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
def evaluate(
|
|
53
|
+
root: str | Path,
|
|
54
|
+
conditions: list[str],
|
|
55
|
+
gate_resolved: GateResolved | None = None,
|
|
56
|
+
) -> dict[str, list[Any]]:
|
|
57
|
+
"""Classify each condition into ``passed``, ``failed`` or ``unverifiable``.
|
|
58
|
+
|
|
59
|
+
``root`` is the run directory that ``<path> exists`` conditions resolve
|
|
60
|
+
against; a path that escapes it fails. ``gate_resolved(step) -> bool``
|
|
61
|
+
answers ``human_gate_resolved on <step>``; when omitted, no gate is
|
|
62
|
+
considered resolved (fail-closed).
|
|
63
|
+
|
|
64
|
+
Returns ``{"passed": [...], "failed": [...], "unverifiable": [...]}``.
|
|
65
|
+
``passed`` and ``failed`` items are ``{"kind", "target"}``
|
|
66
|
+
(kind = ``file_exists`` | ``gate_resolved``); ``failed`` items also carry
|
|
67
|
+
``{"condition", "reason"}``. ``unverifiable`` keeps the original strings.
|
|
68
|
+
Passed items omit the original text so a reader scanning for the literal
|
|
69
|
+
``human_gate_resolved`` does not confuse an accepted condition with the
|
|
70
|
+
real approval event.
|
|
71
|
+
"""
|
|
72
|
+
resolved = gate_resolved if gate_resolved is not None else (lambda _step: False)
|
|
73
|
+
result: dict[str, list[Any]] = {"passed": [], "failed": [], "unverifiable": []}
|
|
74
|
+
base = Path(root).resolve()
|
|
75
|
+
for raw in conditions:
|
|
76
|
+
cond = str(raw).strip()
|
|
77
|
+
m = _EXISTS.match(cond)
|
|
78
|
+
if m:
|
|
79
|
+
item = {"kind": "file_exists", "target": m.group("path")}
|
|
80
|
+
target = (base / m.group("path")).resolve()
|
|
81
|
+
if not target.is_relative_to(base):
|
|
82
|
+
result["failed"].append({**item, "condition": cond, "reason": "path outside the run directory"})
|
|
83
|
+
elif target.is_file():
|
|
84
|
+
result["passed"].append(item)
|
|
85
|
+
else:
|
|
86
|
+
result["failed"].append({**item, "condition": cond, "reason": "file missing"})
|
|
87
|
+
continue
|
|
88
|
+
m = _GATE.match(cond)
|
|
89
|
+
if m:
|
|
90
|
+
item = {"kind": "gate_resolved", "target": m.group("step")}
|
|
91
|
+
if resolved(m.group("step")):
|
|
92
|
+
result["passed"].append(item)
|
|
93
|
+
else:
|
|
94
|
+
result["failed"].append({**item, "condition": cond, "reason": "gate not resolved"})
|
|
95
|
+
continue
|
|
96
|
+
result["unverifiable"].append(cond)
|
|
97
|
+
return result
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
def is_satisfied(result: dict[str, list[Any]]) -> bool:
|
|
101
|
+
"""True iff nothing failed. Unverifiable conditions do not fail a run."""
|
|
102
|
+
return not result["failed"]
|
doneproof/cli.py
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"""doneproof CLI: check acceptance conditions by evidence, exit 0/1.
|
|
2
|
+
|
|
3
|
+
doneproof check <root> [CONDITION ...] [--resolved STEP ...] [--json]
|
|
4
|
+
|
|
5
|
+
Exit 0 when nothing failed (unverifiable conditions do not fail), 1 otherwise.
|
|
6
|
+
``--resolved STEP`` marks a human gate as resolved (repeatable); without it,
|
|
7
|
+
``human_gate_resolved on <step>`` fails closed. ``--json`` prints the full
|
|
8
|
+
structured result instead of the human summary.
|
|
9
|
+
"""
|
|
10
|
+
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import argparse
|
|
14
|
+
import json
|
|
15
|
+
import sys
|
|
16
|
+
|
|
17
|
+
from . import __version__, evaluate, is_satisfied
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
def main(argv: list[str] | None = None) -> int:
|
|
21
|
+
ap = argparse.ArgumentParser(prog="doneproof", description=__doc__.splitlines()[0])
|
|
22
|
+
ap.add_argument("--version", action="version", version=f"doneproof {__version__}")
|
|
23
|
+
sub = ap.add_subparsers(dest="cmd", required=True)
|
|
24
|
+
c = sub.add_parser("check", help="Check conditions against evidence under <root>")
|
|
25
|
+
c.add_argument("root", help="The run directory that `<path> exists` resolves against")
|
|
26
|
+
c.add_argument("conditions", nargs="*", help="doneproof conditions, e.g. 'artifacts/r.md exists'")
|
|
27
|
+
c.add_argument(
|
|
28
|
+
"--resolved", action="append", default=[], metavar="STEP", help="Mark a human gate as resolved (repeatable)"
|
|
29
|
+
)
|
|
30
|
+
c.add_argument("--json", action="store_true", help="Print the full structured result")
|
|
31
|
+
args = ap.parse_args(argv)
|
|
32
|
+
|
|
33
|
+
resolved = set(args.resolved)
|
|
34
|
+
result = evaluate(args.root, args.conditions, gate_resolved=lambda step: step in resolved)
|
|
35
|
+
|
|
36
|
+
if args.json:
|
|
37
|
+
print(json.dumps(result, ensure_ascii=False, indent=2))
|
|
38
|
+
else:
|
|
39
|
+
for item in result["passed"]:
|
|
40
|
+
print(f"ok {item['kind']}: {item['target']}")
|
|
41
|
+
for item in result["failed"]:
|
|
42
|
+
print(f"FAIL {item['kind']}: {item['target']} ({item['reason']})")
|
|
43
|
+
for cond in result["unverifiable"]:
|
|
44
|
+
print(f"? unverifiable: {cond}")
|
|
45
|
+
print(
|
|
46
|
+
f"doneproof: {'satisfied' if is_satisfied(result) else 'FAILED'} "
|
|
47
|
+
f"({len(result['failed'])} failed, {len(result['unverifiable'])} unverifiable)"
|
|
48
|
+
)
|
|
49
|
+
return 0 if is_satisfied(result) else 1
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
if __name__ == "__main__":
|
|
53
|
+
sys.exit(main())
|
doneproof/py.typed
ADDED
|
File without changes
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: doneproof
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Accept a task only when its done-ness is proven by evidence (a file exists, a human gate was resolved) — never the agent's own word. Plain data, zero dependencies, pluggable into any runner or agent.
|
|
5
|
+
Author: LRNSdigital
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Project-URL: Repository, https://github.com/souzalrns/network-agents-setup/tree/main/oss/doneproof
|
|
8
|
+
Project-URL: Issues, https://github.com/souzalrns/network-agents-setup/issues
|
|
9
|
+
Keywords: acceptance,done-criteria,evidence,agents,task-completion,verification,claude-code,plugin,automation,gate
|
|
10
|
+
Classifier: Development Status :: 3 - Alpha
|
|
11
|
+
Classifier: Environment :: Console
|
|
12
|
+
Classifier: Intended Audience :: Developers
|
|
13
|
+
Classifier: Operating System :: OS Independent
|
|
14
|
+
Classifier: Programming Language :: Python :: 3
|
|
15
|
+
Classifier: Programming Language :: Python :: 3.10
|
|
16
|
+
Classifier: Programming Language :: Python :: 3.11
|
|
17
|
+
Classifier: Programming Language :: Python :: 3.12
|
|
18
|
+
Classifier: Programming Language :: Python :: 3.13
|
|
19
|
+
Classifier: Topic :: Software Development :: Quality Assurance
|
|
20
|
+
Classifier: Topic :: Software Development :: Testing :: Acceptance
|
|
21
|
+
Requires-Python: >=3.10
|
|
22
|
+
Description-Content-Type: text/markdown
|
|
23
|
+
License-File: LICENSE
|
|
24
|
+
License-File: NOTICE
|
|
25
|
+
Provides-Extra: test
|
|
26
|
+
Requires-Dist: pytest>=8; extra == "test"
|
|
27
|
+
Dynamic: license-file
|
|
28
|
+
|
|
29
|
+
# doneproof
|
|
30
|
+
|
|
31
|
+
**Accept a task only when its done-ness is proven by evidence — never the agent's own word.**
|
|
32
|
+
|
|
33
|
+
An agent (or a plan) declares *doneproof* conditions as plain strings. `doneproof`
|
|
34
|
+
checks each one against real evidence and reports **passed**, **failed** or
|
|
35
|
+
**unverifiable**. Zero dependencies, pure stdlib, pluggable into any runner or agent.
|
|
36
|
+
|
|
37
|
+
```python
|
|
38
|
+
from pathlib import Path
|
|
39
|
+
from doneproof import evaluate, is_satisfied
|
|
40
|
+
|
|
41
|
+
result = evaluate(Path("runs/42"), [
|
|
42
|
+
"artifacts/report.md exists",
|
|
43
|
+
"human_gate_resolved on publish",
|
|
44
|
+
], gate_resolved=lambda step: step in resolved_steps)
|
|
45
|
+
|
|
46
|
+
if not is_satisfied(result):
|
|
47
|
+
raise SystemExit(f"not done: {result['failed']}")
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Or from the shell:
|
|
51
|
+
|
|
52
|
+
```console
|
|
53
|
+
$ doneproof check runs/42 "artifacts/report.md exists" "human_gate_resolved on publish" --resolved publish
|
|
54
|
+
ok file_exists: artifacts/report.md
|
|
55
|
+
ok gate_resolved: publish
|
|
56
|
+
doneproof: satisfied (0 failed, 0 unverifiable)
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
## Why it exists
|
|
60
|
+
|
|
61
|
+
Most agent frameworks accept `status: done` because the agent *said* it was done.
|
|
62
|
+
That is the failure mode `doneproof` refuses. A task is accepted only when its
|
|
63
|
+
completion is **verifiable**:
|
|
64
|
+
|
|
65
|
+
| Condition | Evidence checked |
|
|
66
|
+
|-----------|------------------|
|
|
67
|
+
| `<path> exists` | the file exists under the run's root (no `../` escape) |
|
|
68
|
+
| `human_gate_resolved on <step>` | a human approval for `<step>` was recorded |
|
|
69
|
+
|
|
70
|
+
Everything else is reported as **unverifiable** — not failed. Refusing to fail on
|
|
71
|
+
what you cannot check by evidence is as important as refusing to pass on a say-so.
|
|
72
|
+
The caller decides what to do with the unverifiable bucket (log it, require review,
|
|
73
|
+
or ignore it).
|
|
74
|
+
|
|
75
|
+
## The gate seam (store-agnostic)
|
|
76
|
+
|
|
77
|
+
`doneproof` never talks to an event store, queue or database. "Was the gate on
|
|
78
|
+
`<step>` resolved?" is answered by a predicate you pass in:
|
|
79
|
+
|
|
80
|
+
```python
|
|
81
|
+
evaluate(root, conditions, gate_resolved=lambda step: my_log.approved(step))
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
Omit it and gates **fail closed** (nothing is considered resolved). This one seam
|
|
85
|
+
is why the same engine runs inside a governed runner and standalone in a test.
|
|
86
|
+
|
|
87
|
+
## API
|
|
88
|
+
|
|
89
|
+
- `evaluate(root, conditions, gate_resolved=None) -> dict` — returns
|
|
90
|
+
`{"passed": [...], "failed": [...], "unverifiable": [...]}`, JSON-serializable.
|
|
91
|
+
`passed`/`failed` items are `{"kind", "target"}` (kind = `file_exists` |
|
|
92
|
+
`gate_resolved`); `failed` items also carry `{"condition", "reason"}`.
|
|
93
|
+
- `is_satisfied(result) -> bool` — true iff nothing failed (unverifiable does not fail).
|
|
94
|
+
- `GateResolved` — the `Callable[[str], bool]` type of the gate predicate.
|
|
95
|
+
|
|
96
|
+
## How it compares
|
|
97
|
+
|
|
98
|
+
`doneproof` is an **acceptance primitive** for agent/task runs, not a test runner
|
|
99
|
+
or a BDD framework. See `POSITIONING.md` for the evidence-based comparison with
|
|
100
|
+
pytest, BDD (Gherkin) and shell CI gates, and for what it deliberately does not do.
|
|
101
|
+
|
|
102
|
+
## Install
|
|
103
|
+
|
|
104
|
+
```console
|
|
105
|
+
pip install doneproof
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
MIT licensed. Part of the [network-agents-setup](https://github.com/souzalrns/network-agents-setup)
|
|
109
|
+
ecosystem; extracted from its governed runner so any project can accept tasks by evidence.
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
doneproof/__init__.py,sha256=SpLsJznW3cvk-Bx5hJkhicVq5fyfpeQUw9iOgddFQbk,4376
|
|
2
|
+
doneproof/cli.py,sha256=qzYuXURzdCkwJppIaZXzGgfHVMkoR1qSJXCTvpuq5hQ,2201
|
|
3
|
+
doneproof/py.typed,sha256=47DEQpj8HBSa-_TImW-5JCeuQeRkm5NMpJWZG3hSuFU,0
|
|
4
|
+
doneproof-0.1.0.dist-info/licenses/LICENSE,sha256=P6Zc-2fxHBXsuWu4oyLoFCnSLMQ_A0m6jji3nvbskAk,1080
|
|
5
|
+
doneproof-0.1.0.dist-info/licenses/NOTICE,sha256=QuyPrql_2DSoi_E90JpMeXcwhWmDaWW4_wzviDSClHI,519
|
|
6
|
+
doneproof-0.1.0.dist-info/METADATA,sha256=X4zYvOeHEnY8IJfqZsegxjGoX0iJxclJRRKaEu_U5jQ,4408
|
|
7
|
+
doneproof-0.1.0.dist-info/WHEEL,sha256=YVMoNqKzERt-wjUZwJ33xBGAwnFl-4cqbYkTtWa4itE,91
|
|
8
|
+
doneproof-0.1.0.dist-info/entry_points.txt,sha256=tqXUNU7mhcvzwJEQh9iI95jJavX5pqBOEddExv3qYd0,49
|
|
9
|
+
doneproof-0.1.0.dist-info/top_level.txt,sha256=2muWm1JbGh-o9-p1n8Pvs6xhgL2TVwcsxZakZ6pe3Ng,10
|
|
10
|
+
doneproof-0.1.0.dist-info/RECORD,,
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 LRNSdigital (souzalrns)
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
doneproof
|
|
2
|
+
Copyright (c) 2026 LRNSdigital (souzalrns). Licensed under the MIT License (see LICENSE).
|
|
3
|
+
|
|
4
|
+
doneproof has no third-party runtime dependencies. The conditions are plain strings and the
|
|
5
|
+
engine is the Python standard library only (re, pathlib). Nothing is bundled or vendored.
|
|
6
|
+
|
|
7
|
+
Extracted from the governed runner at github.com/souzalrns/network-agents-setup
|
|
8
|
+
(runner/plan_runner/done_when.py). The runner keeps the glue that binds this engine to its
|
|
9
|
+
event log; this package is the standalone, store-agnostic core.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
doneproof
|