goldenreq 0.3.0__tar.gz

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.
@@ -0,0 +1,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: goldenreq
3
+ Version: 0.3.0
4
+ Summary: The acceptance gate for goldenization: gauges FILE requests carrying receipts; a handler ACCEPTS/REJECTS; only an accept fires the promotion effect. Nothing self-promotes.
5
+ Requires-Python: >=3.11
@@ -0,0 +1,51 @@
1
+ # goldenreq β€” the acceptance gate for goldenization
2
+
3
+ **The gauge is not the handler.** A *gauge* is an internal self-assessment β€” the
4
+ bandit's 2Γ—πŸ† on a constructed chain, a rulecatcher candidate rule, a forged
5
+ template, a personapack `activation_grade` claim. A gauge can detect quality; it
6
+ may **never** certify itself into golden/adopted/canon. That certification is a
7
+ *handler*'s job β€” an external acceptance authority. `goldenreq` is that gate.
8
+
9
+ The two-knob / CC loop is acceptable **only if** goldenization goes through this
10
+ gate. Before, chainaios's bandit rule told the agent to self-fold a 2Γ—πŸ† chain
11
+ into golden β€” self-certification. Now the rule says: **file a goldenization
12
+ request; promotion happens only on an accepted verdict.**
13
+
14
+ ## The flow
15
+
16
+ ```
17
+ gauge ──file_request(kind, candidate_id, evidence, requested_promotion, requester)──► queue (pending)
18
+ β”‚
19
+ handler ──accept(request_id, by, reason) ──► fires the promotion effect ──► golden / grade-bump / …
20
+ └─reject(request_id, by, reason) ──► NO effect; reason recorded (a hard negative)
21
+ ```
22
+
23
+ - **Requests carry receipts.** `evidence` is the graded KB notes / cert ids /
24
+ forge reports behind the claim. An unevidenced ask is a weak ask.
25
+ - **Verdicts are the seed corpus** for the learned acceptance model that will
26
+ one day staff this gate automatically. **Rejections (with reasons) are its
27
+ hard negatives.** No trace β†’ no handler β†’ not annealable, only believable.
28
+
29
+ ## Kinds β†’ effects
30
+
31
+ | kind | gauge upstream | effect on accept |
32
+ |---|---|---|
33
+ | `chain-golden` | a bandit chain earned πŸ† twice | append an `ACCEPTED-BY` marker into the GBA KB note (never rewrites agent notes) |
34
+ | `pack-grade-bump` | a personapack PackEntry | **pure** helper returns the new `activation_grade`; personapack applies it |
35
+ | `rule-adopt` | a rulecatcher candidate | **stub** β†’ points at `rulecatcher.engine.adopt_rules` (no fake integration) |
36
+ | `template-promote` | a forged language/template | **stub** β†’ no canonical-template promotion API to call yet |
37
+
38
+ ## Record shape
39
+
40
+ The verdict record mirrors `base/acceptance`'s `acceptance.schema.Verdict`
41
+ (read via `git show acceptance-handler:…`) so the verdicts JSONL is drop-in for
42
+ that reader β€” **by shape, not by import** (no cross-branch coupling). The
43
+ field-by-field mapping is documented at the top of `verdict.py`.
44
+
45
+ ## CLI
46
+
47
+ ```
48
+ goldenreq list # the handler's inbox (pending requests)
49
+ goldenreq accept <id> --by human:alice # rule accept β†’ fire the effect
50
+ goldenreq reject <id> --by human:alice --reason "…" # rule reject (reason required)
51
+ ```
@@ -0,0 +1,18 @@
1
+ [build-system]
2
+ requires = ["setuptools>=68"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "goldenreq"
7
+ version = "0.3.0"
8
+ description = "The acceptance gate for goldenization: gauges FILE requests carrying receipts; a handler ACCEPTS/REJECTS; only an accept fires the promotion effect. Nothing self-promotes."
9
+ requires-python = ">=3.11"
10
+ # Deliberately dependency-free (stdlib only). The verdict record MIRRORS
11
+ # base/acceptance's schema by shape, not by import (no cross-branch coupling).
12
+ dependencies = []
13
+
14
+ [project.scripts]
15
+ goldenreq = "goldenreq.cli:main"
16
+
17
+ [tool.setuptools.packages.find]
18
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -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
+ ]
@@ -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())
@@ -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.")
@@ -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]
@@ -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,5 @@
1
+ Metadata-Version: 2.4
2
+ Name: goldenreq
3
+ Version: 0.3.0
4
+ Summary: The acceptance gate for goldenization: gauges FILE requests carrying receipts; a handler ACCEPTS/REJECTS; only an accept fires the promotion effect. Nothing self-promotes.
5
+ Requires-Python: >=3.11
@@ -0,0 +1,13 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/goldenreq/__init__.py
4
+ src/goldenreq/cli.py
5
+ src/goldenreq/effects.py
6
+ src/goldenreq/queue.py
7
+ src/goldenreq/verdict.py
8
+ src/goldenreq.egg-info/PKG-INFO
9
+ src/goldenreq.egg-info/SOURCES.txt
10
+ src/goldenreq.egg-info/dependency_links.txt
11
+ src/goldenreq.egg-info/entry_points.txt
12
+ src/goldenreq.egg-info/top_level.txt
13
+ tests/test_goldenreq.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ goldenreq = goldenreq.cli:main
@@ -0,0 +1 @@
1
+ goldenreq
@@ -0,0 +1,166 @@
1
+ """goldenreq β€” the acceptance gate. Nothing self-promotes; only an accepted
2
+ verdict fires an effect; the verdict record mirrors base/acceptance's schema."""
3
+ from __future__ import annotations
4
+
5
+ import json
6
+ from pathlib import Path
7
+
8
+ import pytest
9
+
10
+ import goldenreq
11
+ from goldenreq import effects
12
+ from goldenreq.queue import resolve_verdicts_path
13
+
14
+
15
+ def _q(tmp_path) -> Path:
16
+ return tmp_path / "goldenreq_queue.jsonl"
17
+
18
+
19
+ # ── file β†’ list β†’ accept β†’ effect (chain-golden marker lands in a GBA KB) ────
20
+ def test_file_list_accept_chain_golden_marker_lands(tmp_path):
21
+ q = _q(tmp_path)
22
+ # a real GBA KB note, headed with a glyphs grade line (chainaios kb_instructions shape)
23
+ kb = tmp_path / "kb"; kb.mkdir()
24
+ note = kb / "rollback-chain.md"
25
+ original = "glyphs: πŸ†πŸ†\n\n# rollback chain\n\nConstructed chain that earned πŸ† twice.\n"
26
+ note.write_text(original)
27
+
28
+ rid = goldenreq.file_request(
29
+ "chain-golden", str(note),
30
+ evidence=[str(note), "round-7 reward log"],
31
+ requested_promotion="golden", requester="triage-bandit", queue_path=q)
32
+ assert rid.startswith("gr-")
33
+
34
+ pending = goldenreq.list_pending(q)
35
+ assert len(pending) == 1 and pending[0]["request_id"] == rid
36
+ assert pending[0]["status"] == "pending"
37
+
38
+ verdict = goldenreq.accept(rid, "human:alice", "chain proved out twice", queue_path=q)
39
+
40
+ # the promotion effect actually ran: marker APPENDED, original NOT rewritten
41
+ body = note.read_text()
42
+ assert body.startswith(original) # agent's note untouched
43
+ assert "ACCEPTED-BY: human:alice" in body
44
+ assert "πŸ†πŸ† β†’ golden" in body
45
+ assert verdict["handler"]["verdict"] == "accept"
46
+ assert verdict["outcome"]["effect"] == "chain-golden"
47
+
48
+ # once ruled, it leaves the pending inbox
49
+ assert goldenreq.list_pending(q) == []
50
+
51
+
52
+ # ── reject β†’ no effect + reason recorded as a hard negative ──────────────────
53
+ def test_reject_records_reason_and_fires_no_effect(tmp_path):
54
+ q = _q(tmp_path)
55
+ kb = tmp_path / "kb"; kb.mkdir()
56
+ note = kb / "flaky-chain.md"
57
+ original = "glyphs: πŸ†\n\n# flaky chain\n"
58
+ note.write_text(original)
59
+
60
+ rid = goldenreq.file_request("chain-golden", str(note), evidence=[str(note)],
61
+ requested_promotion="golden", requester="triage-bandit",
62
+ queue_path=q)
63
+ verdict = goldenreq.reject(rid, "human:alice", "only one πŸ†, evidence thin", queue_path=q)
64
+
65
+ assert verdict["handler"]["verdict"] == "reject"
66
+ assert verdict["handler"]["reasons"] == ["only one πŸ†, evidence thin"]
67
+ assert verdict["outcome"] is None
68
+ assert note.read_text() == original # NO effect ran
69
+ assert goldenreq.list_pending(q) == [] # ruled β†’ out of inbox
70
+
71
+
72
+ def test_reject_requires_a_reason(tmp_path):
73
+ q = _q(tmp_path)
74
+ rid = goldenreq.file_request("chain-golden", "/x", evidence=[], requested_promotion="golden",
75
+ requester="b", queue_path=q)
76
+ with pytest.raises(ValueError):
77
+ goldenreq.reject(rid, "human:alice", "", queue_path=q)
78
+
79
+
80
+ def test_no_double_ruling(tmp_path):
81
+ q = _q(tmp_path)
82
+ note = tmp_path / "n.md"; note.write_text("glyphs: πŸ†πŸ†\n")
83
+ rid = goldenreq.file_request("chain-golden", str(note), evidence=[], requested_promotion="golden",
84
+ requester="b", queue_path=q)
85
+ goldenreq.accept(rid, "human:alice", queue_path=q)
86
+ with pytest.raises(ValueError):
87
+ goldenreq.reject(rid, "human:alice", "changed my mind", queue_path=q)
88
+
89
+
90
+ # ── verdict records match the documented base/acceptance field mapping ───────
91
+ def test_verdict_record_mirrors_base_acceptance_schema(tmp_path):
92
+ q = _q(tmp_path)
93
+ note = tmp_path / "n.md"; note.write_text("glyphs: πŸ†πŸ†\n")
94
+ rid = goldenreq.file_request(
95
+ "chain-golden", str(note), evidence=["ev-a", "ev-b"],
96
+ requested_promotion="golden", requester="triage-bandit", queue_path=q)
97
+ v = goldenreq.accept(rid, "alice", "ok", queue_path=q) # bare name β†’ coerced to human:
98
+
99
+ # the exact keys base/acceptance.schema.Verdict defines
100
+ assert set(v) == {"id", "ts", "domain", "artifact_ref", "context_ref",
101
+ "gauge", "handler", "outcome"}
102
+ assert v["id"].startswith("vd-")
103
+ assert v["domain"] == "goldenize" # base lists this domain
104
+ assert v["artifact_ref"] == str(note) # ← request.candidate_id
105
+ assert v["context_ref"] == rid # ← request.request_id
106
+ # gauge = the internal claim (never authority)
107
+ assert set(v["gauge"]) == {"source", "score", "reasons"}
108
+ assert v["gauge"]["source"] == "triage-bandit" # ← request.requester
109
+ assert v["gauge"]["score"] == "golden" # ← request.requested_promotion
110
+ assert v["gauge"]["reasons"] == ["ev-a", "ev-b"] # ← request.evidence
111
+ # handler = the external authority; actor matches base's ^(human|model):.+$
112
+ assert set(v["handler"]) == {"verdict", "actor", "reasons", "edits_made"}
113
+ assert v["handler"]["actor"] == "human:alice"
114
+ assert v["handler"]["verdict"] in {"accept", "reject"}
115
+ assert v["handler"]["edits_made"] is None
116
+
117
+ # it round-trips as a JSONL corpus line (drop-in for the base reader)
118
+ vpath = resolve_verdicts_path(q)
119
+ line = vpath.read_text().splitlines()[-1]
120
+ assert json.loads(line)["id"] == v["id"]
121
+
122
+
123
+ # ── pack-grade-bump is a pure ladder step ────────────────────────────────────
124
+ def test_bump_pack_grade_is_pure_and_matches_personapack_ladder():
125
+ # ladder best→worst: tested-with-receipt > proven-in-forge > seed
126
+ assert effects.bump_pack_grade("seed") == "proven-in-forge"
127
+ assert effects.bump_pack_grade("proven-in-forge") == "tested-with-receipt"
128
+ assert effects.bump_pack_grade("tested-with-receipt") == "tested-with-receipt" # capped at top
129
+ with pytest.raises(ValueError):
130
+ effects.bump_pack_grade("made-up")
131
+ # drift guard: our literal ladder equals personapack's GRADES
132
+ from personapack.entry import GRADES
133
+ assert effects.PACK_GRADES == GRADES
134
+
135
+
136
+ def test_pack_grade_bump_via_accept(tmp_path):
137
+ q = _q(tmp_path)
138
+ rid = goldenreq.file_request("pack-grade-bump", "some-persona", evidence=["gba-receipt"],
139
+ requested_promotion="proven-in-forge", requester="personapack",
140
+ queue_path=q)
141
+ v = goldenreq.accept(rid, "human:alice", queue_path=q)
142
+ assert v["outcome"]["effect"] == "pack-grade-bump"
143
+ assert v["outcome"]["new_grade"] == "tested-with-receipt"
144
+
145
+
146
+ # ── the honest stubs raise NotImplementedError (no fake integration) ─────────
147
+ def test_rule_adopt_and_template_promote_are_honest_stubs(tmp_path):
148
+ q = _q(tmp_path)
149
+ for kind in ("rule-adopt", "template-promote"):
150
+ rid = goldenreq.file_request(kind, "cand", evidence=[], requested_promotion="adopt",
151
+ requester="g", queue_path=q)
152
+ with pytest.raises(NotImplementedError):
153
+ goldenreq.accept(rid, "human:alice", queue_path=q)
154
+ # the failed effect left the request PENDING (no half-promotion, no verdict)
155
+ assert any(r["request_id"] == rid for r in goldenreq.list_pending(q))
156
+
157
+
158
+ # ── the chainaios bandit.py rule text no longer self-promotes (grep test) ────
159
+ def test_bandit_rule_text_files_a_request_not_self_fold():
160
+ bandit = (Path(__file__).resolve().parents[2] / "chainaios" / "src" / "chainaios" / "bandit.py")
161
+ text = bandit.read_text()
162
+ assert "FILE A GOLDENIZATION REQUEST" in text
163
+ assert "never self-fold to golden" in text
164
+ assert "promotion" in text and "accepted verdict" in text
165
+ # the old self-certifying instruction is gone
166
+ assert "fold it into a golden chain" not in text