goldenreq 0.3.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.
- goldenreq/__init__.py +30 -0
- goldenreq/cli.py +60 -0
- goldenreq/effects.py +105 -0
- goldenreq/queue.py +132 -0
- goldenreq/verdict.py +133 -0
- goldenreq-0.3.0.dist-info/METADATA +5 -0
- goldenreq-0.3.0.dist-info/RECORD +10 -0
- goldenreq-0.3.0.dist-info/WHEEL +5 -0
- goldenreq-0.3.0.dist-info/entry_points.txt +2 -0
- goldenreq-0.3.0.dist-info/top_level.txt +1 -0
goldenreq/__init__.py
ADDED
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
"""goldenreq — the ACCEPTANCE GATE for goldenization.
|
|
2
|
+
|
|
3
|
+
THE LAW (gauge ≠ handler). A *gauge* is an internal self-assessment: the
|
|
4
|
+
bandit's 2×🏆 on a chain, a rulecatcher candidate, a forged template, a
|
|
5
|
+
personapack grade claim. A gauge may detect quality but it may NEVER certify
|
|
6
|
+
itself. Promotion to golden/adopted/canon happens only through a *handler* — an
|
|
7
|
+
external acceptance authority. This package is that gate.
|
|
8
|
+
|
|
9
|
+
- `queue.file_request(...)` — a gauge FILES a request carrying its receipts
|
|
10
|
+
(evidence). That is the only move a gauge gets. The request sits pending.
|
|
11
|
+
- `verdict.accept / verdict.reject` — a handler rules. Only `accept` fires the
|
|
12
|
+
promotion effect (`effects.dispatch`). `reject` carries a reason.
|
|
13
|
+
|
|
14
|
+
Requests carry receipts; verdicts are the seed corpus for the learned acceptance
|
|
15
|
+
model that will one day staff this gate; rejections (with reasons) are its hard
|
|
16
|
+
negatives. The verdict record mirrors base/acceptance's `Verdict` schema so the
|
|
17
|
+
corpus is drop-in for that reader (field mapping: see `verdict.py`).
|
|
18
|
+
"""
|
|
19
|
+
from __future__ import annotations
|
|
20
|
+
|
|
21
|
+
__version__ = "0.3.0"
|
|
22
|
+
|
|
23
|
+
from .queue import KINDS, file_request, list_pending, get_request
|
|
24
|
+
from .verdict import accept, reject, list_verdicts
|
|
25
|
+
from . import effects
|
|
26
|
+
|
|
27
|
+
__all__ = [
|
|
28
|
+
"KINDS", "file_request", "list_pending", "get_request",
|
|
29
|
+
"accept", "reject", "list_verdicts", "effects", "__version__",
|
|
30
|
+
]
|
goldenreq/cli.py
ADDED
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
"""cli.py — `goldenreq list | accept | reject` (argparse, stdlib only)."""
|
|
2
|
+
from __future__ import annotations
|
|
3
|
+
|
|
4
|
+
import argparse
|
|
5
|
+
import json
|
|
6
|
+
import sys
|
|
7
|
+
|
|
8
|
+
from .queue import list_pending
|
|
9
|
+
from .verdict import accept, reject
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
def main(argv: list[str] | None = None) -> int:
|
|
13
|
+
p = argparse.ArgumentParser(prog="goldenreq",
|
|
14
|
+
description="the acceptance gate for goldenization — nothing self-promotes")
|
|
15
|
+
p.add_argument("--queue", default=None, help="queue JSONL path ($GOLDENREQ_QUEUE_PATH)")
|
|
16
|
+
sub = p.add_subparsers(dest="cmd", required=True)
|
|
17
|
+
|
|
18
|
+
lp = sub.add_parser("list", help="list pending requests (the handler's inbox)")
|
|
19
|
+
lp.add_argument("--json", action="store_true", help="emit raw JSON lines")
|
|
20
|
+
|
|
21
|
+
ap = sub.add_parser("accept", help="accept a request → fire its promotion effect")
|
|
22
|
+
ap.add_argument("request_id")
|
|
23
|
+
ap.add_argument("--by", required=True, help="ruling actor (human:<who> / model:<id>)")
|
|
24
|
+
ap.add_argument("--reason", default="")
|
|
25
|
+
|
|
26
|
+
rp = sub.add_parser("reject", help="reject a request (reason = hard negative)")
|
|
27
|
+
rp.add_argument("request_id")
|
|
28
|
+
rp.add_argument("--by", required=True, help="ruling actor (human:<who> / model:<id>)")
|
|
29
|
+
rp.add_argument("--reason", required=True)
|
|
30
|
+
|
|
31
|
+
args = p.parse_args(argv)
|
|
32
|
+
|
|
33
|
+
if args.cmd == "list":
|
|
34
|
+
pending = list_pending(args.queue)
|
|
35
|
+
if args.json:
|
|
36
|
+
for r in pending:
|
|
37
|
+
print(json.dumps(r, sort_keys=True))
|
|
38
|
+
elif not pending:
|
|
39
|
+
print("no pending requests")
|
|
40
|
+
else:
|
|
41
|
+
for r in pending:
|
|
42
|
+
print(f"{r['request_id']} {r['kind']:<16} {r['candidate_id']} "
|
|
43
|
+
f"→ {r['requested_promotion']} (by {r['requester']}, "
|
|
44
|
+
f"{len(r.get('evidence', []))} receipt(s))")
|
|
45
|
+
return 0
|
|
46
|
+
|
|
47
|
+
try:
|
|
48
|
+
if args.cmd == "accept":
|
|
49
|
+
v = accept(args.request_id, args.by, args.reason, queue_path=args.queue)
|
|
50
|
+
else:
|
|
51
|
+
v = reject(args.request_id, args.by, args.reason, queue_path=args.queue)
|
|
52
|
+
except (ValueError, FileNotFoundError, NotImplementedError) as e:
|
|
53
|
+
print(f"error: {e}", file=sys.stderr)
|
|
54
|
+
return 1
|
|
55
|
+
print(json.dumps(v, sort_keys=True, indent=2))
|
|
56
|
+
return 0
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
if __name__ == "__main__":
|
|
60
|
+
raise SystemExit(main())
|
goldenreq/effects.py
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
"""effects.py — the promotion effects, dispatched ONLY on an accepted verdict.
|
|
2
|
+
|
|
3
|
+
`dispatch` routes an accepted request to its effect by `kind`. An effect is the
|
|
4
|
+
thing the gauge was NOT allowed to do itself; here it runs because a handler
|
|
5
|
+
accepted. Each returns an `outcome` dict that verdict.py stores in the record.
|
|
6
|
+
|
|
7
|
+
Honesty rule: two effects are real (they have a testable local target), two are
|
|
8
|
+
stubs (they'd need cross-package wiring we cannot exercise here). The stubs
|
|
9
|
+
raise `NotImplementedError` and point at the exact upstream call to make — we do
|
|
10
|
+
NOT fake an integration we can't test.
|
|
11
|
+
"""
|
|
12
|
+
from __future__ import annotations
|
|
13
|
+
|
|
14
|
+
from pathlib import Path
|
|
15
|
+
|
|
16
|
+
# the personapack activation_grade ladder, BEST → WORST.
|
|
17
|
+
# Mirror of personapack.entry.GRADES (kept as a literal so goldenreq stays
|
|
18
|
+
# dependency-free; a drift here is caught by the pack-grade-bump test).
|
|
19
|
+
PACK_GRADES: tuple[str, ...] = ("tested-with-receipt", "proven-in-forge", "seed")
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def dispatch(request: dict, *, by: str, **kwargs) -> dict:
|
|
23
|
+
"""Route an ACCEPTED request to its promotion effect. Returns the outcome dict."""
|
|
24
|
+
kind = request.get("kind")
|
|
25
|
+
if kind == "chain-golden":
|
|
26
|
+
return mark_chain_golden(request, by=by, **kwargs)
|
|
27
|
+
if kind == "pack-grade-bump":
|
|
28
|
+
new_grade = bump_pack_grade(request.get("requested_promotion") or _current_from(request))
|
|
29
|
+
return {"effect": "pack-grade-bump", "new_grade": new_grade,
|
|
30
|
+
"note": "pure result — personapack applies it to the PackEntry"}
|
|
31
|
+
if kind == "rule-adopt":
|
|
32
|
+
return adopt_rule(request, by=by, **kwargs)
|
|
33
|
+
if kind == "template-promote":
|
|
34
|
+
return promote_template(request, by=by, **kwargs)
|
|
35
|
+
raise ValueError(f"no effect for kind {request.get('kind')!r}")
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
def _current_from(request: dict) -> str:
|
|
39
|
+
# if requested_promotion wasn't a grade, fall back to the weakest rung
|
|
40
|
+
return "seed"
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
# ── chain-golden: append an ACCEPTED-BY marker into the GBA KB note ──────────
|
|
44
|
+
def mark_chain_golden(request: dict, *, by: str, **_) -> dict:
|
|
45
|
+
"""Write the golden marker into the GBA KB.
|
|
46
|
+
|
|
47
|
+
`candidate_id` is the path to the graded KB note (a `kb/<topic>.md`, headed
|
|
48
|
+
with a `glyphs:` grade line — see chainaios bandit.kb_instructions). We
|
|
49
|
+
APPEND an ACCEPTED-BY line; we NEVER rewrite the agent's own notes. The
|
|
50
|
+
marker is what turns "the gauge claims 🏆🏆" into "a handler certified it
|
|
51
|
+
golden" — the thing self-folding used to do without authority.
|
|
52
|
+
"""
|
|
53
|
+
note = Path(request["candidate_id"])
|
|
54
|
+
if not note.exists():
|
|
55
|
+
raise FileNotFoundError(f"KB note not found: {note}")
|
|
56
|
+
marker = (f"\nACCEPTED-BY: {by} — goldenized on request {request['request_id']} "
|
|
57
|
+
f"(promotion: {request.get('requested_promotion')}); "
|
|
58
|
+
f"evidence: {', '.join(request.get('evidence', [])) or 'none'}. 🏆🏆 → golden\n")
|
|
59
|
+
with open(note, "a", encoding="utf-8") as f:
|
|
60
|
+
f.write(marker)
|
|
61
|
+
return {"effect": "chain-golden", "kb_note": str(note), "marker": marker.strip()}
|
|
62
|
+
|
|
63
|
+
|
|
64
|
+
# ── pack-grade-bump: a PURE function personapack applies itself ──────────────
|
|
65
|
+
def bump_pack_grade(current_grade: str) -> str:
|
|
66
|
+
"""Return the next-higher personapack `activation_grade`, or the same grade
|
|
67
|
+
if already at the top. PURE — personapack constructs the new PackEntry with it.
|
|
68
|
+
|
|
69
|
+
Ladder (best→worst): tested-with-receipt > proven-in-forge > seed.
|
|
70
|
+
A bump moves ONE rung toward tested-with-receipt.
|
|
71
|
+
"""
|
|
72
|
+
if current_grade not in PACK_GRADES:
|
|
73
|
+
raise ValueError(f"grade {current_grade!r} not in {PACK_GRADES}")
|
|
74
|
+
i = PACK_GRADES.index(current_grade)
|
|
75
|
+
return PACK_GRADES[max(0, i - 1)]
|
|
76
|
+
|
|
77
|
+
|
|
78
|
+
# ── honest stubs: real cross-package integration we cannot test here ─────────
|
|
79
|
+
def adopt_rule(request: dict, *, by: str, **_) -> dict:
|
|
80
|
+
"""STUB. A real rule-adopt would call rulecatcher's adopt path:
|
|
81
|
+
|
|
82
|
+
rulecatcher.engine.adopt_rules(connection, rule_ids, actor=by,
|
|
83
|
+
source="goldenreq", reason=<verdict reason>)
|
|
84
|
+
|
|
85
|
+
(see packages/rulecatcher/src/rulecatcher/engine.py, `adopt_rules`).
|
|
86
|
+
That needs a live rulecatcher sqlite `Connection` and real candidate rule ids
|
|
87
|
+
threaded through the request — wiring goldenreq cannot construct or exercise
|
|
88
|
+
in isolation. Not faked.
|
|
89
|
+
"""
|
|
90
|
+
raise NotImplementedError(
|
|
91
|
+
"rule-adopt: wire request.candidate_id (rule ids) + a rulecatcher "
|
|
92
|
+
"Connection to rulecatcher.engine.adopt_rules(actor=by, source='goldenreq'). "
|
|
93
|
+
"Deliberately unimplemented — no fake integration.")
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def promote_template(request: dict, *, by: str, **_) -> dict:
|
|
97
|
+
"""STUB. A real template-promote would move a forged language/template from
|
|
98
|
+
candidate into the canonical set (the chainaios `construct_language` /
|
|
99
|
+
template store). That store's promotion API is not defined yet, so there is
|
|
100
|
+
nothing honest to call. Not faked.
|
|
101
|
+
"""
|
|
102
|
+
raise NotImplementedError(
|
|
103
|
+
"template-promote: no canonical-template promotion API exists to call "
|
|
104
|
+
"yet; when one does, dispatch request.candidate_id to it here. "
|
|
105
|
+
"Deliberately unimplemented — no fake integration.")
|
goldenreq/queue.py
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"""queue.py — the goldenization request queue (append-only JSONL).
|
|
2
|
+
|
|
3
|
+
Same append-only ledger idiom as the KB reward record and base/acceptance's
|
|
4
|
+
verdict corpus (a `write` is one JSON line; nothing is ever mutated in place).
|
|
5
|
+
|
|
6
|
+
A REQUEST is a *gauge-passed, handler-pending* event: a gauge — the bandit's
|
|
7
|
+
2×🏆 on a constructed chain, a rulecatcher candidate rule, a forged
|
|
8
|
+
language/template, a personapack `activation_grade` claim — asks for a
|
|
9
|
+
promotion it may NOT grant itself. The request carries its RECEIPTS (evidence)
|
|
10
|
+
and sits pending until a handler rules on it (see ``verdict.py``). This is
|
|
11
|
+
JobWorld's ``supposedly_done`` made explicit, and base/acceptance's
|
|
12
|
+
``handler.verdict == "escalate"`` (gauge-passed, handler-pending) as a queue.
|
|
13
|
+
|
|
14
|
+
THE LAW: the gauge never certifies itself. Filing a request is the ONLY thing a
|
|
15
|
+
gauge may do toward promotion; the promotion effect fires only on an accepted
|
|
16
|
+
verdict.
|
|
17
|
+
"""
|
|
18
|
+
from __future__ import annotations
|
|
19
|
+
|
|
20
|
+
import json
|
|
21
|
+
import os
|
|
22
|
+
import uuid
|
|
23
|
+
from datetime import datetime, timezone
|
|
24
|
+
from pathlib import Path
|
|
25
|
+
|
|
26
|
+
# the four things that can be promoted (each has a real gauge upstream)
|
|
27
|
+
KINDS: tuple[str, ...] = (
|
|
28
|
+
"chain-golden", # a constructed bandit chain earned 🏆 twice (chainaios)
|
|
29
|
+
"rule-adopt", # a rulecatcher candidate rule wants adoption
|
|
30
|
+
"template-promote", # a forged language / template wants to become canon
|
|
31
|
+
"pack-grade-bump", # a personapack PackEntry wants a higher activation_grade
|
|
32
|
+
)
|
|
33
|
+
|
|
34
|
+
QUEUE_ENV = "GOLDENREQ_QUEUE_PATH"
|
|
35
|
+
VERDICTS_ENV = "GOLDENREQ_VERDICTS_PATH"
|
|
36
|
+
DEFAULT_QUEUE = "goldenreq_queue.jsonl"
|
|
37
|
+
DEFAULT_VERDICTS = "verdicts.jsonl"
|
|
38
|
+
|
|
39
|
+
|
|
40
|
+
def now_iso() -> str:
|
|
41
|
+
return datetime.now(timezone.utc).isoformat()
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
def resolve_queue_path(path: str | os.PathLike | None = None) -> Path:
|
|
45
|
+
return Path(path or os.environ.get(QUEUE_ENV) or DEFAULT_QUEUE)
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def resolve_verdicts_path(queue_path: str | os.PathLike | None = None) -> Path:
|
|
49
|
+
"""The verdicts corpus sits beside the queue (or at $GOLDENREQ_VERDICTS_PATH).
|
|
50
|
+
Mirrors base/acceptance's `resolve_corpus_path` — env override, else default."""
|
|
51
|
+
env = os.environ.get(VERDICTS_ENV)
|
|
52
|
+
if env:
|
|
53
|
+
return Path(env)
|
|
54
|
+
return resolve_queue_path(queue_path).parent / DEFAULT_VERDICTS
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
def _append(path: Path, record: dict) -> None:
|
|
58
|
+
path.parent.mkdir(parents=True, exist_ok=True)
|
|
59
|
+
with open(path, "a", encoding="utf-8") as f:
|
|
60
|
+
f.write(json.dumps(record, sort_keys=True) + "\n")
|
|
61
|
+
|
|
62
|
+
|
|
63
|
+
def _read_all(path: Path) -> list[dict]:
|
|
64
|
+
out: list[dict] = []
|
|
65
|
+
if not Path(path).exists():
|
|
66
|
+
return out
|
|
67
|
+
with open(path, "r", encoding="utf-8") as f:
|
|
68
|
+
for line in f:
|
|
69
|
+
line = line.strip()
|
|
70
|
+
if not line:
|
|
71
|
+
continue
|
|
72
|
+
try:
|
|
73
|
+
out.append(json.loads(line))
|
|
74
|
+
except Exception:
|
|
75
|
+
continue
|
|
76
|
+
return out
|
|
77
|
+
|
|
78
|
+
|
|
79
|
+
def new_request_id() -> str:
|
|
80
|
+
return "gr-" + uuid.uuid4().hex[:24]
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def file_request(kind: str, candidate_id: str, evidence: list[str],
|
|
84
|
+
requested_promotion: str, requester: str,
|
|
85
|
+
queue_path: str | os.PathLike | None = None) -> str:
|
|
86
|
+
"""Append one goldenization request to the queue. Returns its request_id.
|
|
87
|
+
|
|
88
|
+
`evidence` = the receipts (graded KB note paths, cert ids, forge reports).
|
|
89
|
+
A request with no evidence is still filed (honest — an unevidenced ask is a
|
|
90
|
+
weak ask a handler will likely reject), but evidence is what an acceptance
|
|
91
|
+
model later learns from, so gauges SHOULD attach it.
|
|
92
|
+
"""
|
|
93
|
+
if kind not in KINDS:
|
|
94
|
+
raise ValueError(f"kind {kind!r} not in {KINDS}")
|
|
95
|
+
rid = new_request_id()
|
|
96
|
+
rec = {
|
|
97
|
+
"request_id": rid,
|
|
98
|
+
"ts": now_iso(),
|
|
99
|
+
"kind": kind,
|
|
100
|
+
"candidate_id": candidate_id,
|
|
101
|
+
"evidence": list(evidence or []),
|
|
102
|
+
"requested_promotion": requested_promotion,
|
|
103
|
+
"requester": requester,
|
|
104
|
+
"status": "pending",
|
|
105
|
+
}
|
|
106
|
+
_append(resolve_queue_path(queue_path), rec)
|
|
107
|
+
return rid
|
|
108
|
+
|
|
109
|
+
|
|
110
|
+
def get_request(request_id: str, queue_path: str | os.PathLike | None = None) -> dict | None:
|
|
111
|
+
for rec in _read_all(resolve_queue_path(queue_path)):
|
|
112
|
+
if rec.get("request_id") == request_id:
|
|
113
|
+
return rec
|
|
114
|
+
return None
|
|
115
|
+
|
|
116
|
+
|
|
117
|
+
def ruled_request_ids(queue_path: str | os.PathLike | None = None) -> set[str]:
|
|
118
|
+
"""request_ids that already carry a handler verdict (accept OR reject)."""
|
|
119
|
+
vpath = resolve_verdicts_path(queue_path)
|
|
120
|
+
return {v.get("context_ref") for v in _read_all(vpath) if v.get("context_ref")}
|
|
121
|
+
|
|
122
|
+
|
|
123
|
+
def list_pending(queue_path: str | os.PathLike | None = None) -> list[dict]:
|
|
124
|
+
"""Requests with no handler verdict yet — the handler's inbox.
|
|
125
|
+
|
|
126
|
+
A request is pending ⇔ no verdict record points at it (verdict.context_ref
|
|
127
|
+
== request_id). That is base/acceptance's `escalate` state: gauge-passed,
|
|
128
|
+
handler-pending.
|
|
129
|
+
"""
|
|
130
|
+
ruled = ruled_request_ids(queue_path)
|
|
131
|
+
return [r for r in _read_all(resolve_queue_path(queue_path))
|
|
132
|
+
if r.get("request_id") not in ruled]
|
goldenreq/verdict.py
ADDED
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
"""verdict.py — the HANDLER ruling on a goldenization request.
|
|
2
|
+
|
|
3
|
+
`accept` / `reject` are the ONLY authority in this package. Nothing self-promotes:
|
|
4
|
+
a promotion effect (effects.py) fires only when a handler `accept`s. A `reject`
|
|
5
|
+
carries a reason — those reasons are the HARD NEGATIVES for the learned
|
|
6
|
+
acceptance model that later staffs this gate.
|
|
7
|
+
|
|
8
|
+
Verdict record shape — MIRRORS base/acceptance's `acceptance.schema.Verdict`
|
|
9
|
+
(read via `git show acceptance-handler:base/acceptance/acceptance/schema.py`).
|
|
10
|
+
We do NOT import across branches; we reproduce the shape as a plain dict so the
|
|
11
|
+
verdicts corpus is drop-in compatible with that reader. Field mapping:
|
|
12
|
+
|
|
13
|
+
base/acceptance Verdict field ← goldenreq source
|
|
14
|
+
------------------------------ ----------------------------------------
|
|
15
|
+
id ← a fresh verdict id ("vd-…")
|
|
16
|
+
ts ← ruling timestamp (ISO-8601)
|
|
17
|
+
domain ← "goldenize" (base lists this exact domain)
|
|
18
|
+
artifact_ref ← request.candidate_id (what was judged)
|
|
19
|
+
context_ref ← request.request_id (the request it belongs to)
|
|
20
|
+
gauge.source ← request.requester (the self-claiming gauge)
|
|
21
|
+
gauge.score ← request.requested_promotion (the gauge's claim)
|
|
22
|
+
gauge.reasons ← request.evidence (the receipts it filed)
|
|
23
|
+
handler.verdict ← "accept" | "reject" (base also has "escalate"
|
|
24
|
+
= still-pending; a pending request is exactly
|
|
25
|
+
that state, so it has NO verdict record yet)
|
|
26
|
+
handler.actor ← `by`, coerced to "human:<who>" / "model:<id>"
|
|
27
|
+
handler.reasons ← [reason] (rejections MUST carry one)
|
|
28
|
+
handler.edits_made ← None (goldenreq handlers rule, they don't edit)
|
|
29
|
+
outcome ← the promotion effect's result on accept, else None
|
|
30
|
+
"""
|
|
31
|
+
from __future__ import annotations
|
|
32
|
+
|
|
33
|
+
import os
|
|
34
|
+
import re
|
|
35
|
+
import uuid
|
|
36
|
+
|
|
37
|
+
from . import effects
|
|
38
|
+
from .queue import (
|
|
39
|
+
get_request, now_iso, resolve_queue_path, resolve_verdicts_path,
|
|
40
|
+
ruled_request_ids, _append, _read_all,
|
|
41
|
+
)
|
|
42
|
+
|
|
43
|
+
ACTOR_PATTERN = re.compile(r"^(human|model):.+$") # base/acceptance schema.ACTOR_PATTERN
|
|
44
|
+
|
|
45
|
+
|
|
46
|
+
def new_verdict_id() -> str:
|
|
47
|
+
return "vd-" + uuid.uuid4().hex[:24]
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def _actor(by: str) -> str:
|
|
51
|
+
"""Coerce `by` to the base schema's `^(human|model):.+$` actor form.
|
|
52
|
+
A bare name is assumed human (the default handler is a person doing QA)."""
|
|
53
|
+
by = (by or "").strip()
|
|
54
|
+
if not by:
|
|
55
|
+
raise ValueError("a verdict requires `by` (the ruling actor)")
|
|
56
|
+
return by if ACTOR_PATTERN.match(by) else f"human:{by}"
|
|
57
|
+
|
|
58
|
+
|
|
59
|
+
def _build_verdict(request: dict, decision: str, by: str,
|
|
60
|
+
reasons: list[str], outcome: dict | None) -> dict:
|
|
61
|
+
"""One acceptance event, in base/acceptance Verdict shape (see module docstring)."""
|
|
62
|
+
return {
|
|
63
|
+
"id": new_verdict_id(),
|
|
64
|
+
"ts": now_iso(),
|
|
65
|
+
"domain": "goldenize",
|
|
66
|
+
"artifact_ref": request["candidate_id"],
|
|
67
|
+
"context_ref": request["request_id"],
|
|
68
|
+
"gauge": {
|
|
69
|
+
"source": request.get("requester", ""),
|
|
70
|
+
"score": request.get("requested_promotion"),
|
|
71
|
+
"reasons": list(request.get("evidence", [])),
|
|
72
|
+
},
|
|
73
|
+
"handler": {
|
|
74
|
+
"verdict": decision,
|
|
75
|
+
"actor": _actor(by),
|
|
76
|
+
"reasons": list(reasons),
|
|
77
|
+
"edits_made": None,
|
|
78
|
+
},
|
|
79
|
+
"outcome": outcome,
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def _guard(request_id: str, queue_path) -> dict:
|
|
84
|
+
request = get_request(request_id, queue_path=queue_path)
|
|
85
|
+
if request is None:
|
|
86
|
+
raise ValueError(f"no such request: {request_id}")
|
|
87
|
+
if request_id in ruled_request_ids(queue_path):
|
|
88
|
+
raise ValueError(f"request {request_id} already has a verdict (no double-ruling)")
|
|
89
|
+
return request
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
def accept(request_id: str, by: str, reason: str = "", *,
|
|
93
|
+
queue_path: str | os.PathLike | None = None,
|
|
94
|
+
verdicts_path: str | os.PathLike | None = None,
|
|
95
|
+
effect_kwargs: dict | None = None) -> dict:
|
|
96
|
+
"""ACCEPT a request → fire its promotion effect, then record the verdict.
|
|
97
|
+
|
|
98
|
+
The effect runs BEFORE the record is written so its result is captured in
|
|
99
|
+
`outcome` (the audit trail). If the effect raises, nothing is recorded and
|
|
100
|
+
the request stays pending — no half-promotion. Returns the verdict record.
|
|
101
|
+
"""
|
|
102
|
+
from pathlib import Path
|
|
103
|
+
request = _guard(request_id, queue_path)
|
|
104
|
+
outcome = effects.dispatch(request, by=_actor(by), **(effect_kwargs or {}))
|
|
105
|
+
verdict = _build_verdict(request, "accept", by, [reason] if reason else [], outcome)
|
|
106
|
+
vpath = Path(verdicts_path) if verdicts_path is not None else resolve_verdicts_path(queue_path)
|
|
107
|
+
_append(vpath, verdict)
|
|
108
|
+
return verdict
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
def reject(request_id: str, by: str, reason: str, *,
|
|
112
|
+
queue_path: str | os.PathLike | None = None,
|
|
113
|
+
verdicts_path: str | os.PathLike | None = None) -> dict:
|
|
114
|
+
"""REJECT a request → NO effect, record the verdict with its reason.
|
|
115
|
+
|
|
116
|
+
The reason is mandatory: rejections are the hard negatives the learned
|
|
117
|
+
acceptance model trains on. Nothing is promoted.
|
|
118
|
+
"""
|
|
119
|
+
if not (reason or "").strip():
|
|
120
|
+
raise ValueError("reject requires a reason (it is the hard negative)")
|
|
121
|
+
request = _guard(request_id, queue_path)
|
|
122
|
+
verdict = _build_verdict(request, "reject", by, [reason], None)
|
|
123
|
+
from pathlib import Path
|
|
124
|
+
vpath = Path(verdicts_path) if verdicts_path is not None else resolve_verdicts_path(queue_path)
|
|
125
|
+
_append(vpath, verdict)
|
|
126
|
+
return verdict
|
|
127
|
+
|
|
128
|
+
|
|
129
|
+
def list_verdicts(queue_path: str | os.PathLike | None = None,
|
|
130
|
+
verdicts_path: str | os.PathLike | None = None) -> list[dict]:
|
|
131
|
+
from pathlib import Path
|
|
132
|
+
vpath = Path(verdicts_path) if verdicts_path is not None else resolve_verdicts_path(queue_path)
|
|
133
|
+
return _read_all(vpath)
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
goldenreq/__init__.py,sha256=Y95IRc1wCYU-X3rVkF4hcPVOtVDmbu9nSDXXdXRAG0Q,1391
|
|
2
|
+
goldenreq/cli.py,sha256=so4bcVTInq5oRsXf7hdTORBXe2VtNUZHQ29ELenDPdk,2306
|
|
3
|
+
goldenreq/effects.py,sha256=6O5twoewSxkHVU4RxLfBTxKP2QaHJz6hZqkOqbR8o0o,5208
|
|
4
|
+
goldenreq/queue.py,sha256=Qus0wAGuF_YdKK6HkXpzshg9KwXJob13TIl_QU1DxGQ,4991
|
|
5
|
+
goldenreq/verdict.py,sha256=SMqN2cxMkcXvvabjsqBw98H_TqSCMg-HGeI83Z2BIXg,6010
|
|
6
|
+
goldenreq-0.3.0.dist-info/METADATA,sha256=UOF9FDyZmLAG_7O7TiwX1N1GMGiVtB_8_ASd4cAQZJk,258
|
|
7
|
+
goldenreq-0.3.0.dist-info/WHEEL,sha256=mOdXR9C3wMVov7cSY8PY-XCzo3SmmWCHvGQr5csJoao,91
|
|
8
|
+
goldenreq-0.3.0.dist-info/entry_points.txt,sha256=fWR8LNGor1SxWas2Fj6AmVOxeDGvvjVYmkCboe5dlLc,49
|
|
9
|
+
goldenreq-0.3.0.dist-info/top_level.txt,sha256=zddDlHNcMlMYu0sgmXNVNleddIlCAeg2Oauf5c0KyAQ,10
|
|
10
|
+
goldenreq-0.3.0.dist-info/RECORD,,
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
goldenreq
|