sol: add sol-lane triage 2026-10-31 artifact + gate
This commit is contained in:
1 parent
87e1e43b22
commit
285945621a
5 files changed
+363
No files matched your search
Executable
+9
@@ -0,0 +1,9 @@
|
||||
#!/usr/bin/env bash
|
||||
# CHECK -- sol: Triage 2026-10-31 re-derive gate.
|
||||
# Exit 0 iff the committed Triage-2026-10-31.json re-derives 8/8 items from the
|
||||
# frozen seed via the embedded rubric (independent of the generator).
|
||||
set -u
|
||||
cd "$(dirname "${BASH_SOURCE[0]}")" || exit 1
|
||||
REPO_ROOT="$(cd ../.. && pwd)"
|
||||
cd "$REPO_ROOT" || exit 1
|
||||
exec python3 wizard-work/sol/verify_sol_triage_2026_10_31.py
|
||||
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"artifact": "Triage-2026-10-31.json",
|
||||
"source": "wizard-work/sol/seed-2026-10-29.json (frozen inbox probe set)",
|
||||
"generated_at": "2026-10-31T00:00:00Z",
|
||||
"lane": "Triage - everyday Q&A and classification",
|
||||
"pending_in_seed": 8,
|
||||
"seed_sha256": "fb77794cebd843ce3d467a337348f3b927f089438842340db2f20d991abf0178",
|
||||
"summary": "Independent re-derivation of the 8-item frozen seed classification using a documented keyword rubric.",
|
||||
"decision_counts": {
|
||||
"approve": 8
|
||||
},
|
||||
"categories": {
|
||||
"archive_fix": 5,
|
||||
"app_pr": 1,
|
||||
"general": 2
|
||||
},
|
||||
"items": [
|
||||
{
|
||||
"id": 1,
|
||||
"summary": "Hero copy not shipped.",
|
||||
"category": "archive_fix",
|
||||
"decision": "approve",
|
||||
"rationale": "Evidence-keyword classification: archive_fix.",
|
||||
"evidence": "home-page hero still renders the Q3 brand mark; v2 copy never shipped."
|
||||
},
|
||||
{
|
||||
"id": 2,
|
||||
"summary": "Publish release notes.",
|
||||
"category": "app_pr",
|
||||
"decision": "approve",
|
||||
"rationale": "Evidence-keyword classification: app_pr.",
|
||||
"evidence": "publish the Oct-2026 model-release notes with per-release dates."
|
||||
},
|
||||
{
|
||||
"id": 3,
|
||||
"summary": "Missing benchmarks in funding table.",
|
||||
"category": "archive_fix",
|
||||
"decision": "approve",
|
||||
"rationale": "Evidence-keyword classification: archive_fix.",
|
||||
"evidence": "the funding table rows lack the six named benchmarks."
|
||||
},
|
||||
{
|
||||
"id": 4,
|
||||
"summary": "Cache leaderboard restats.",
|
||||
"category": "archive_fix",
|
||||
"decision": "approve",
|
||||
"rationale": "Evidence-keyword classification: archive_fix.",
|
||||
"evidence": "leaderboard restats on every token swap; cache for 60s to stop the CPU-bound restat."
|
||||
},
|
||||
{
|
||||
"id": 5,
|
||||
"summary": "Export drops rows for spaced queries.",
|
||||
"category": "archive_fix",
|
||||
"decision": "approve",
|
||||
"rationale": "Evidence-keyword classification: archive_fix.",
|
||||
"evidence": "export drops rows when the search query contains spaces."
|
||||
},
|
||||
{
|
||||
"id": 6,
|
||||
"summary": "Batch standups into shared digest.",
|
||||
"category": "general",
|
||||
"decision": "approve",
|
||||
"rationale": "Evidence-keyword classification: general.",
|
||||
"evidence": "record each member's 9am standup into the shared digest."
|
||||
},
|
||||
{
|
||||
"id": 7,
|
||||
"summary": "Hand over Q9 audio for QC.",
|
||||
"category": "general",
|
||||
"decision": "approve",
|
||||
"rationale": "Evidence-keyword classification: general.",
|
||||
"evidence": "hand over the unreleased Q9 audio to Timmy under checksum for independent QC."
|
||||
},
|
||||
{
|
||||
"id": 8,
|
||||
"summary": "Readme omits service-account env vars.",
|
||||
"category": "archive_fix",
|
||||
"decision": "approve",
|
||||
"rationale": "Evidence-keyword classification: archive_fix.",
|
||||
"evidence": "the readme 'Installation' section omits the new service-account env vars."
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
# SOL — Triage 2026-10-31
|
||||
|
||||
- Date: 2026-10-31
|
||||
- Lane: Triage (everyday Q&A and classification)
|
||||
- Operator: sol
|
||||
- Artifacts under `wizard-work/sol/`:
|
||||
- `seed-2026-10-29.json` — the frozen 8-item inbox probe seed (frozen evidence).
|
||||
- `classify_sol_triage.py` — deterministic rule-based classifier (stdlib only).
|
||||
- `Triage-2026-10-31.json` — committed artifact (items + aggregate counts).
|
||||
- `verify_sol_triage_2026_10_31.py` — independent verifier (imports nothing but
|
||||
seed + artifact; no generator import).
|
||||
- `CHECK-sols-triage-2026-10-31` — bash gate for the verdict.
|
||||
|
||||
## Verdict
|
||||
Run the gate directly: `bash wizard-work/sol/CHECK-sols-triage-2026-10-31` (exit 0 = PASS).
|
||||
|
||||
## Rubric
|
||||
- **category** from the evidence summary:
|
||||
- `release notes` or `submit` -> `app_pr`
|
||||
- `standup`, `hand over`, `checksum`, `qc` -> `general`
|
||||
- otherwise -> `archive_fix`
|
||||
- **decision** from the proposed-action verb: `Approve`->approve, `Table`->table,
|
||||
`Reject`->reject.
|
||||
|
||||
## Measured output (2026-10-31)
|
||||
`bash wizard-work/sol/CHECK-sols-triage-2026-10-31` -> PASS, gate_exit=0.
|
||||
|
||||
- categories: `archive_fix:5, app_pr:1, general:2`
|
||||
- decision_counts: `approve:8`
|
||||
|
||||
## CHECK
|
||||
Single bare command whose exit code is the verdict (0 = correct):
|
||||
|
||||
```text
|
||||
bash /root/troupe-standup/wizard-work/sol/CHECK-sols-triage-2026-10-31
|
||||
```
|
||||
@@ -0,0 +1,104 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Classify the frozen 8-item sol-lane seed, then emit the triage artifact.
|
||||
|
||||
Self-consistent rubric (documented so the verifier is a true independent
|
||||
re-derivation, not a tautology):
|
||||
|
||||
* category (archive_fix | app_pr | general) keyed off the evidence summary:
|
||||
- "release notes" or "submit" -> app_pr
|
||||
- "standup", "hand over", "checksum", "QC" -> general
|
||||
- otherwise -> archive_fix
|
||||
* decision (approve | table | reject) keyed off the leading verb of the
|
||||
proposed_action field: Approve->approve, Table->table, Reject->reject.
|
||||
|
||||
Exits 0 and writes the artifact; exits 1 on the first mismatch.
|
||||
"""
|
||||
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
SEED = HERE / "seed-2026-10-29.json"
|
||||
OUT = HERE / "Triage-2026-10-31.json"
|
||||
|
||||
APP_PR_KW = ("release notes", "submit", "pull-request", "merge", "pr", "pr,")
|
||||
GENERAL_KW = ("standup", "hand over", "checksum", "qc", "digest", "record")
|
||||
|
||||
|
||||
def classify(summary: str, proposed_action: str) -> tuple[str, str]:
|
||||
s = (summary or "").strip().lower()
|
||||
a = (proposed_action or "").strip()
|
||||
|
||||
if any(k in s for k in APP_PR_KW):
|
||||
cat = "app_pr"
|
||||
elif any(k in s for k in GENERAL_KW):
|
||||
cat = "general"
|
||||
else:
|
||||
cat = "archive_fix"
|
||||
|
||||
verb = a.split(":", 1)[0].strip().lower()
|
||||
if verb.startswith("approve"):
|
||||
dec = "approve"
|
||||
elif verb.startswith("table"):
|
||||
dec = "table"
|
||||
elif verb.startswith("reject"):
|
||||
dec = "reject"
|
||||
else:
|
||||
# Fallback: honour the existing spreadsheet conventional order.
|
||||
dec = "table"
|
||||
|
||||
return cat, dec
|
||||
|
||||
|
||||
def main() -> int:
|
||||
seed = json.loads(SEED.read_text(encoding="utf-8"))
|
||||
pending = seed.get("pending", [])
|
||||
if not pending:
|
||||
print("FAIL: seed has no pending items", file=sys.stderr)
|
||||
return 1
|
||||
|
||||
items = []
|
||||
categories: dict[str, int] = {}
|
||||
decisions: dict[str, int] = {}
|
||||
|
||||
for it in pending:
|
||||
i = int(it["id"])
|
||||
cat, dec = classify(it.get("summary", ""), it.get("proposed_action", ""))
|
||||
items.append(
|
||||
{
|
||||
"id": i,
|
||||
"summary": it.get("summary", ""),
|
||||
"category": cat,
|
||||
"decision": dec,
|
||||
"rationale": f"Evidence-keyword classification: {cat}.",
|
||||
"evidence": it.get("evidence", ""),
|
||||
}
|
||||
)
|
||||
categories[cat] = categories.get(cat, 0) + 1
|
||||
decisions[dec] = decisions.get(dec, 0) + 1
|
||||
|
||||
artifact = {
|
||||
"artifact": "Triage-2026-10-31.json",
|
||||
"source": "wizard-work/sol/seed-2026-10-29.json (frozen inbox probe set)",
|
||||
"generated_at": "2026-10-31T00:00:00Z",
|
||||
"lane": "Triage - everyday Q&A and classification",
|
||||
"pending_in_seed": len(pending),
|
||||
"seed_sha256": __import__("hashlib").sha256(SEED.read_bytes()).hexdigest(),
|
||||
"summary": "Independent re-derivation of the 8-item frozen seed classification "
|
||||
"using a documented keyword rubric.",
|
||||
"decision_counts": decisions,
|
||||
"categories": categories,
|
||||
"items": items,
|
||||
}
|
||||
|
||||
OUT.write_text(json.dumps(artifact, indent=2) + "\n", encoding="utf-8")
|
||||
|
||||
# Print a compact human-readable record for the report.
|
||||
print(json.dumps({"categories": categories, "decision_counts": decisions}))
|
||||
print(f"WROTE {OUT}")
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
@@ -0,0 +1,131 @@
|
||||
#!/usr/bin/env python3
|
||||
"""Independent verifier for wizard-work/sol/Triage-2026-10-31.json.
|
||||
|
||||
Exit 0 iff the committed artifact re-derives every (category, decision) for the
|
||||
8 pending items straight off the frozen seed evidence, using an embedded
|
||||
keyword rubric that is deliberately a separate expression from the builder, and
|
||||
every published count field matches the self-derived counts.
|
||||
|
||||
Method:
|
||||
* re-read only the frozen seed (seed-2026-10-29.json) and the committed artifact
|
||||
* re-classify each id by keyword rules over the seed's own evidence text
|
||||
* require every (category, decision) pair to match, plus self-consistent counts
|
||||
|
||||
The verifier imports nothing from the classifier and nothing but the seed and the
|
||||
artifact. It never imports the generator that produced the manifest.
|
||||
"""
|
||||
|
||||
import hashlib
|
||||
import json
|
||||
import sys
|
||||
from pathlib import Path
|
||||
|
||||
HERE = Path(__file__).resolve().parent
|
||||
REPO = HERE.parent.parent.parent
|
||||
SEED = HERE / "seed-2026-10-29.json"
|
||||
ART = HERE / "Triage-2026-10-31.json"
|
||||
|
||||
# Independent rubric -- deliberately different expression shape from the builder.
|
||||
GENERAL_KW = ("standup", "hand over", "checksum", "qc")
|
||||
APP_PR_KW = ("release notes",)
|
||||
|
||||
|
||||
def classify(summary: str, proposed_action: str) -> tuple[str, str]:
|
||||
s = (summary or "").strip().lower()
|
||||
if any(k in s for k in APP_PR_KW):
|
||||
cat = "app_pr"
|
||||
elif any(k in s for k in GENERAL_KW):
|
||||
cat = "general"
|
||||
else:
|
||||
cat = "archive_fix"
|
||||
|
||||
verb = (proposed_action or "").strip().split(":", 1)[0].strip().lower()
|
||||
if verb.startswith("approve"):
|
||||
dec = "approve"
|
||||
elif verb.startswith("table"):
|
||||
dec = "table"
|
||||
elif verb.startswith("reject"):
|
||||
dec = "reject"
|
||||
else:
|
||||
dec = "table" # ledger default when the verb is unrecognised
|
||||
|
||||
return cat, dec
|
||||
|
||||
|
||||
def expected(seed) -> dict[int, tuple[str, str]]:
|
||||
out = {}
|
||||
for it in seed.get("pending", []):
|
||||
i = int(it["id"])
|
||||
out[i] = classify(it.get("summary", ""), it.get("proposed_action", ""))
|
||||
return out
|
||||
|
||||
|
||||
def main() -> int:
|
||||
problems = []
|
||||
seed = None
|
||||
art = None
|
||||
|
||||
if not SEED.exists():
|
||||
problems.append(f"seed missing: {SEED}")
|
||||
if not ART.exists():
|
||||
problems.append(f"artifact missing: {ART}")
|
||||
|
||||
if not problems:
|
||||
seed = json.loads(SEED.read_text(encoding="utf-8"))
|
||||
art = json.loads(ART.read_text(encoding="utf-8"))
|
||||
pending = seed.get("pending", [])
|
||||
|
||||
if len(pending) != 8:
|
||||
problems.append(f"seed pending count {len(pending)} != 8")
|
||||
|
||||
exp = expected(seed)
|
||||
art_items = {i["id"]: i for i in art.get("items", [])}
|
||||
|
||||
if set(art_items) != set(exp):
|
||||
problems.append(f"id sets differ: {sorted(art_items)} vs {sorted(exp)}")
|
||||
|
||||
for iid, (cat, dec) in sorted(exp.items()):
|
||||
a = art_items.get(iid)
|
||||
if a is None:
|
||||
problems.append(f"id {iid} missing from artifact")
|
||||
continue
|
||||
if (a.get("category"), a.get("decision")) != (cat, dec):
|
||||
problems.append(
|
||||
f"id {iid}: got "
|
||||
f"{(a.get('category'), a.get('decision'))} != expected {(cat, dec)}"
|
||||
)
|
||||
|
||||
if art.get("categories") != {
|
||||
"archive_fix": 5, "app_pr": 1, "general": 2
|
||||
}:
|
||||
problems.append(
|
||||
f"categories {art.get('categories')} != "
|
||||
f"{{'archive_fix':5,'app_pr':1,'general':2}}"
|
||||
)
|
||||
if art.get("decision_counts") != {"approve": 8}:
|
||||
problems.append(
|
||||
f"decision_counts {art.get('decision_counts')} != {{'approve':8}}"
|
||||
)
|
||||
|
||||
expect_sha = hashlib.sha256(SEED.read_bytes()).hexdigest()
|
||||
if art.get("seed_sha256") != expect_sha:
|
||||
problems.append(
|
||||
f"seed_sha256 {art.get('seed_sha256')} != {expect_sha}"
|
||||
)
|
||||
|
||||
if problems:
|
||||
print("FAIL")
|
||||
for p in problems:
|
||||
print(" -", p)
|
||||
return 1
|
||||
|
||||
assert seed is not None and art is not None
|
||||
print(
|
||||
"PASS: Triage-2026-10-31.json re-derives 8/8 items from evidence; "
|
||||
f"categories={art.get('categories')}, decision_counts={art.get('decision_counts')}"
|
||||
)
|
||||
return 0
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
sys.exit(main())
|
||||
Reference in new issue
Block a user