feat: DoX assessment recording and soft visibility (STATE-WP-0077)
Add quality_doc/dor/dod recording convention, quality-debt CLI, promote-intake and C-34 soft warnings, agent protocol notes, and DoD policy badge language. Mark STATE-WP-0077 finished.
This commit is contained in:
parent
d356fde41c
commit
fe4cfe22c9
13 changed files with 786 additions and 103 deletions
|
|
@ -42,6 +42,14 @@ ls workplans/
|
||||||
For each file with `status: ready`, `active`, or `blocked`, note pending
|
For each file with `status: ready`, `active`, or `blocked`, note pending
|
||||||
`wait`/`todo`/`progress` tasks.
|
`wait`/`todo`/`progress` tasks.
|
||||||
|
|
||||||
|
Optional quality debt (STATE-WP-0077):
|
||||||
|
```bash
|
||||||
|
statehub quality-debt --repo-path .
|
||||||
|
```
|
||||||
|
Prefer **DoR-Ok** before heavy implementation on ready plans; **DoC-Ok** before
|
||||||
|
confident intake promote; record with `quality_dor` / `quality_doc` /
|
||||||
|
`quality_dod` fields — see `docs/work-record-quality-gates.md`.
|
||||||
|
|
||||||
**Step 4 — Present brief**
|
**Step 4 — Present brief**
|
||||||
|
|
||||||
1. **Active workplans** for `infotech` — title, task counts, blocking decisions
|
1. **Active workplans** for `infotech` — title, task counts, blocking decisions
|
||||||
|
|
|
||||||
14
AGENTS.md
14
AGENTS.md
|
|
@ -40,8 +40,22 @@ curl -s "http://127.0.0.1:8000/workplans/?topic_id=cee7bedf-2b48-46ef-8601-00647
|
||||||
# Check inbox
|
# Check inbox
|
||||||
curl -s "http://127.0.0.1:8000/messages/?to_agent=state-hub&unread_only=true" \
|
curl -s "http://127.0.0.1:8000/messages/?to_agent=state-hub&unread_only=true" \
|
||||||
| python3 -m json.tool
|
| python3 -m json.tool
|
||||||
|
|
||||||
|
# Optional: DoX quality debt (ready without DoR-Ok, finished without DoD-Ok, …)
|
||||||
|
statehub quality-debt --repo-path .
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Definition quality (DoC / DoR / DoD)
|
||||||
|
|
||||||
|
Lifecycle `status` is independent of quality badges. Prefer:
|
||||||
|
|
||||||
|
- **DoC-Ok** on intakes before confident promote (`quality_doc` or note)
|
||||||
|
- **DoR-Ok** on workplans/tasks before heavy implementation (`quality_dor`)
|
||||||
|
- **DoD-Ok** when claiming quality-complete finish (`quality_dod`)
|
||||||
|
|
||||||
|
Recording form and examples: `docs/work-record-quality-gates.md`.
|
||||||
|
Policies: `policies/intake-doc.md`, `work-item-dor.md`, `workstream-dod.md`.
|
||||||
|
|
||||||
Mark a message read:
|
Mark a message read:
|
||||||
```bash
|
```bash
|
||||||
curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
|
curl -s -X PATCH "http://127.0.0.1:8000/messages/<id>/read" \
|
||||||
|
|
|
||||||
|
|
@ -45,7 +45,7 @@
|
||||||
| workplan | STATE-WP-0074 | finished | — | workplans/STATE-WP-0074-hub-ecosystem-boundary-alignment.md |
|
| workplan | STATE-WP-0074 | finished | — | workplans/STATE-WP-0074-hub-ecosystem-boundary-alignment.md |
|
||||||
| workplan | STATE-WP-0075 | finished | — | workplans/STATE-WP-0075-workstream-route-410-stranglers.md |
|
| workplan | STATE-WP-0075 | finished | — | workplans/STATE-WP-0075-workstream-route-410-stranglers.md |
|
||||||
| workplan | STATE-WP-0076 | finished | — | workplans/STATE-WP-0076-definition-of-ready-and-comprehension.md |
|
| workplan | STATE-WP-0076 | finished | — | workplans/STATE-WP-0076-definition-of-ready-and-comprehension.md |
|
||||||
| workplan | STATE-WP-0077 | ready | — | workplans/STATE-WP-0077-dox-assessment-recording-and-soft-visibility.md |
|
| workplan | STATE-WP-0077 | finished | — | workplans/STATE-WP-0077-dox-assessment-recording-and-soft-visibility.md |
|
||||||
| task | ADHOC-2026-06-04-T01 | done | — | workplans/ADHOC-2026-06-04.md |
|
| task | ADHOC-2026-06-04-T01 | done | — | workplans/ADHOC-2026-06-04.md |
|
||||||
| task | ADHOC-2026-07-01-T01 | done | — | workplans/ADHOC-2026-07-01.md |
|
| task | ADHOC-2026-07-01-T01 | done | — | workplans/ADHOC-2026-07-01.md |
|
||||||
| task | ADHOC-2026-07-01-T02 | done | — | workplans/ADHOC-2026-07-01.md |
|
| task | ADHOC-2026-07-01-T02 | done | — | workplans/ADHOC-2026-07-01.md |
|
||||||
|
|
@ -259,7 +259,8 @@
|
||||||
| task | STATE-WP-0076-T04 | done | — | workplans/STATE-WP-0076-definition-of-ready-and-comprehension.md |
|
| task | STATE-WP-0076-T04 | done | — | workplans/STATE-WP-0076-definition-of-ready-and-comprehension.md |
|
||||||
| task | STATE-WP-0076-T05 | cancel | — | workplans/STATE-WP-0076-definition-of-ready-and-comprehension.md |
|
| task | STATE-WP-0076-T05 | cancel | — | workplans/STATE-WP-0076-definition-of-ready-and-comprehension.md |
|
||||||
| task | STATE-WP-0076-T06 | cancel | — | workplans/STATE-WP-0076-definition-of-ready-and-comprehension.md |
|
| task | STATE-WP-0076-T06 | cancel | — | workplans/STATE-WP-0076-definition-of-ready-and-comprehension.md |
|
||||||
| task | STATE-WP-0077-T01 | todo | — | workplans/STATE-WP-0077-dox-assessment-recording-and-soft-visibility.md |
|
| task | STATE-WP-0077-T01 | done | — | workplans/STATE-WP-0077-dox-assessment-recording-and-soft-visibility.md |
|
||||||
| task | STATE-WP-0077-T02 | todo | — | workplans/STATE-WP-0077-dox-assessment-recording-and-soft-visibility.md |
|
| task | STATE-WP-0077-T02 | done | — | workplans/STATE-WP-0077-dox-assessment-recording-and-soft-visibility.md |
|
||||||
| task | STATE-WP-0077-T03 | todo | — | workplans/STATE-WP-0077-dox-assessment-recording-and-soft-visibility.md |
|
| task | STATE-WP-0077-T03 | done | — | workplans/STATE-WP-0077-dox-assessment-recording-and-soft-visibility.md |
|
||||||
| task | STATE-WP-0077-T04 | todo | — | workplans/STATE-WP-0077-dox-assessment-recording-and-soft-visibility.md |
|
| task | STATE-WP-0077-T04 | done | — | workplans/STATE-WP-0077-dox-assessment-recording-and-soft-visibility.md |
|
||||||
|
| intake | CUST-IN-0004 | routed | — | docs/work-record-quality-gates.md |
|
||||||
|
|
|
||||||
|
|
@ -422,6 +422,29 @@ def cmd_fix_consistency(args: argparse.Namespace) -> None:
|
||||||
sys.exit(exit_code)
|
sys.exit(exit_code)
|
||||||
|
|
||||||
|
|
||||||
|
def cmd_quality_debt(args: argparse.Namespace) -> None:
|
||||||
|
"""List DoX quality debt (STATE-WP-0077) — ready without DoR-Ok, etc."""
|
||||||
|
script = STATE_HUB_DIR / "scripts" / "quality_debt.py"
|
||||||
|
if not script.exists():
|
||||||
|
print(f"ERROR: quality_debt.py not found at {script}")
|
||||||
|
sys.exit(1)
|
||||||
|
cmd = [sys.executable, str(script)]
|
||||||
|
if args.repo_path:
|
||||||
|
cmd.extend(["--repo-path", str(Path(args.repo_path).expanduser().resolve())])
|
||||||
|
else:
|
||||||
|
cmd.append("--here")
|
||||||
|
if args.api_base:
|
||||||
|
cmd.extend(["--api-base", args.api_base])
|
||||||
|
if args.no_hub:
|
||||||
|
cmd.append("--no-hub")
|
||||||
|
if args.as_json:
|
||||||
|
cmd.append("--json")
|
||||||
|
if args.strict:
|
||||||
|
cmd.append("--strict")
|
||||||
|
result = subprocess.run(cmd)
|
||||||
|
sys.exit(result.returncode)
|
||||||
|
|
||||||
|
|
||||||
def cmd_promote_intake(args: argparse.Namespace) -> None:
|
def cmd_promote_intake(args: argparse.Namespace) -> None:
|
||||||
"""Promote a routed intake into a workplan, task, decision, or engagement."""
|
"""Promote a routed intake into a workplan, task, decision, or engagement."""
|
||||||
script = STATE_HUB_DIR / "scripts" / "promote_intake.py"
|
script = STATE_HUB_DIR / "scripts" / "promote_intake.py"
|
||||||
|
|
@ -708,6 +731,18 @@ def main() -> None:
|
||||||
help="Preserve checker exit code 2 for warnings-only runs",
|
help="Preserve checker exit code 2 for warnings-only runs",
|
||||||
)
|
)
|
||||||
|
|
||||||
|
# quality-debt (STATE-WP-0077)
|
||||||
|
qdebt = sub.add_parser(
|
||||||
|
"quality-debt",
|
||||||
|
help="List DoX quality debt: ready without DoR-Ok, finished without DoD-Ok, intakes without DoC-Ok",
|
||||||
|
)
|
||||||
|
qdebt.add_argument("--repo-path", default=None, help="Repo root (default: cwd)")
|
||||||
|
qdebt.add_argument("--api-base", default=API_BASE, help="State Hub API base URL")
|
||||||
|
qdebt.add_argument("--no-hub", action="store_true", help="Skip hub intake scan")
|
||||||
|
qdebt.add_argument("--json", action="store_true", dest="as_json")
|
||||||
|
qdebt.add_argument("--strict", action="store_true", help="Exit 1 if any debt found")
|
||||||
|
qdebt.set_defaults(func=cmd_quality_debt)
|
||||||
|
|
||||||
# promote-intake
|
# promote-intake
|
||||||
promote = sub.add_parser(
|
promote = sub.add_parser(
|
||||||
"promote-intake",
|
"promote-intake",
|
||||||
|
|
|
||||||
|
|
@ -84,8 +84,9 @@ and Service DoM):
|
||||||
| **DoR** | [Work-item DoR](/policy/work-item-dor) | Implementation-readiness (`task`, `workplan`) |
|
| **DoR** | [Work-item DoR](/policy/work-item-dor) | Implementation-readiness (`task`, `workplan`) |
|
||||||
| **DoD** | [Workplan DoD](/policy/workstream-dod) | Completion quality (workplan) |
|
| **DoD** | [Workplan DoD](/policy/workstream-dod) | Completion quality (workplan) |
|
||||||
|
|
||||||
Full matrix and badge spelling: repo doc `docs/work-record-quality-gates.md`
|
Full matrix, badge spelling, and **recording fields**
|
||||||
(STATE-WP-0076).
|
(`quality_doc` / `quality_dor` / `quality_dod`): repo doc
|
||||||
|
`docs/work-record-quality-gates.md`. Soft list: `statehub quality-debt`.
|
||||||
|
|
||||||
Discovery quality (intake) is **not** the same as implementation-readiness
|
Discovery quality (intake) is **not** the same as implementation-readiness
|
||||||
(task/workplan). Outside or sparse-context signals should be comprehended
|
(task/workplan). Outside or sparse-context signals should be comprehended
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,6 @@
|
||||||
# Work-record quality gates (Definition family)
|
# Work-record quality gates (Definition family)
|
||||||
|
|
||||||
Status: active convention (STATE-WP-0076)
|
Status: active convention (STATE-WP-0076 policies; STATE-WP-0077 recording + soft visibility)
|
||||||
Related: `dashboard/src/docs/work-records.md`, `policies/intake-doc.md`,
|
Related: `dashboard/src/docs/work-records.md`, `policies/intake-doc.md`,
|
||||||
`policies/work-item-dor.md`, `policies/workstream-dod.md`,
|
`policies/work-item-dor.md`, `policies/workstream-dod.md`,
|
||||||
`policies/repo-doi.md`, `policies/service-dom.md`
|
`policies/repo-doi.md`, `policies/service-dom.md`
|
||||||
|
|
@ -50,8 +50,109 @@ For each Definition policy that applies to a record:
|
||||||
No open-ended custom badges. New badge families require a new Definition policy
|
No open-ended custom badges. New badge families require a new Definition policy
|
||||||
in `policies/`.
|
in `policies/`.
|
||||||
|
|
||||||
Assessment in v1 is **manual/convention** (agent or human notes, PR description,
|
## Recording assessments (STATE-WP-0077)
|
||||||
progress event). No badge engine or hard API block ships with STATE-WP-0076.
|
|
||||||
|
Canonical storage is **file fields** (preferred) plus optional **progress
|
||||||
|
events**. No badge DB table.
|
||||||
|
|
||||||
|
### Field names
|
||||||
|
|
||||||
|
| Policy | Field | Values |
|
||||||
|
|--------|-------|--------|
|
||||||
|
| DoC | `quality_doc` | `DoC-Ok` \| `DoC-Failed` (or bare `Ok` / `Failed`) |
|
||||||
|
| DoR | `quality_dor` | `DoR-Ok` \| `DoR-Failed` (or bare `Ok` / `Failed`) |
|
||||||
|
| DoD | `quality_dod` | `DoD-Ok` \| `DoD-Failed` (or bare `Ok` / `Failed`) |
|
||||||
|
|
||||||
|
Optional companions (same prefix): `quality_*_at` (ISO date), `quality_*_by`,
|
||||||
|
`quality_*_note`.
|
||||||
|
|
||||||
|
### Workplan frontmatter example (DoR + later DoD)
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
---
|
||||||
|
id: STATE-WP-0077
|
||||||
|
status: ready
|
||||||
|
quality_dor: DoR-Ok
|
||||||
|
quality_dor_at: "2026-07-22"
|
||||||
|
quality_dor_by: "grok"
|
||||||
|
# quality_dod: DoD-Ok # when finishing with quality complete
|
||||||
|
---
|
||||||
|
```
|
||||||
|
|
||||||
|
### Task block example (DoR)
|
||||||
|
|
||||||
|
````markdown
|
||||||
|
```task
|
||||||
|
id: STATE-WP-0077-T01
|
||||||
|
status: todo
|
||||||
|
priority: high
|
||||||
|
quality_dor: DoR-Ok
|
||||||
|
quality_dor_at: "2026-07-22"
|
||||||
|
quality_dor_by: "grok"
|
||||||
|
```
|
||||||
|
````
|
||||||
|
|
||||||
|
### Intake YAML example (DoC)
|
||||||
|
|
||||||
|
Use a real `{PREFIX}-IN-NNNN` id only in the owning repo file — not in docs
|
||||||
|
(examples with live id patterns get registered by fix-consistency). Field shape:
|
||||||
|
|
||||||
|
```text
|
||||||
|
id: <PREFIX>-IN-NNNN
|
||||||
|
status: routed
|
||||||
|
quality_doc: DoC-Ok
|
||||||
|
quality_doc_at: "YYYY-MM-DD"
|
||||||
|
quality_doc_by: "agent-or-human"
|
||||||
|
quality_doc_note: "optional"
|
||||||
|
```
|
||||||
|
|
||||||
|
Hub-only intakes (no file yet): add a note whose body includes `DoC-Ok` or
|
||||||
|
`quality_doc: DoC-Ok`, and/or post a progress event (below).
|
||||||
|
|
||||||
|
### Progress event (optional audit)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
curl -s -X POST http://127.0.0.1:8000/progress/ \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d '{
|
||||||
|
"event_type": "quality_assessment",
|
||||||
|
"summary": "DoR-Ok for workplan STATE-WP-0077",
|
||||||
|
"author": "grok",
|
||||||
|
"topic_id": "<uuid>",
|
||||||
|
"workplan_id": "<uuid>",
|
||||||
|
"detail": {
|
||||||
|
"policy": "DoR",
|
||||||
|
"outcome": "Ok",
|
||||||
|
"badge": "DoR-Ok",
|
||||||
|
"record_kind": "workplan",
|
||||||
|
"record_id": "STATE-WP-0077",
|
||||||
|
"assessed_by": "grok",
|
||||||
|
"note": null
|
||||||
|
}
|
||||||
|
}'
|
||||||
|
```
|
||||||
|
|
||||||
|
Helpers: `scripts/quality_assessment.py` (`badge()`, `progress_event_body()`).
|
||||||
|
|
||||||
|
## Soft visibility
|
||||||
|
|
||||||
|
```bash
|
||||||
|
statehub quality-debt # cwd repo + hub intakes
|
||||||
|
statehub quality-debt --json --no-hub
|
||||||
|
python scripts/quality_debt.py --here
|
||||||
|
```
|
||||||
|
|
||||||
|
Reports:
|
||||||
|
|
||||||
|
- workplans `status=ready` without `quality_dor` DoR-Ok
|
||||||
|
- workplans `status=finished` without `quality_dod` DoD-Ok
|
||||||
|
- file intakes `vetted`/`routed` without `quality_doc` DoC-Ok
|
||||||
|
- hub intakes `vetted`/`routed` without DoC-Ok in notes
|
||||||
|
|
||||||
|
`fix-consistency` soft warns **C-34** (ready without DoR-Ok). Finished
|
||||||
|
plans without DoD-Ok appear in `quality-debt` only (not C-warns), so historical
|
||||||
|
finished workplans do not flood every consistency run. Never fixable
|
||||||
|
auto-write; never FAIL.
|
||||||
|
|
||||||
## Unit vs structure (reminder)
|
## Unit vs structure (reminder)
|
||||||
|
|
||||||
|
|
@ -82,10 +183,17 @@ outside / sparse signal
|
||||||
→ DoD assessment (DoD-Ok when claiming quality-complete)
|
→ DoD assessment (DoD-Ok when claiming quality-complete)
|
||||||
```
|
```
|
||||||
|
|
||||||
## Enforcement level
|
## Enforcement level (STATE-WP-0077)
|
||||||
|
|
||||||
Convention + documentation. Soft warnings and metrics may follow once policies
|
| Mechanism | Behaviour |
|
||||||
are in daily use. Task-flow assertions must implement policy, not replace it.
|
|-----------|-----------|
|
||||||
|
| Convention + this doc | Primary |
|
||||||
|
| `statehub quality-debt` | Read-only debt list |
|
||||||
|
| C-34 | Soft WARN in fix-consistency (ready without DoR-Ok) |
|
||||||
|
| `promote-intake` | Soft WARNING on stderr if no DoC-Ok; **still promotes** |
|
||||||
|
| Hard API / status blocks | **None** |
|
||||||
|
|
||||||
|
Task-flow assertions must implement policy, not replace it.
|
||||||
|
|
||||||
## Non-goals
|
## Non-goals
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,9 +1,40 @@
|
||||||
# Workstream Definition of Done
|
# Workplan Definition of Done (DoD)
|
||||||
|
|
||||||
A workstream is considered finished if and only if:
|
**applies_to:** `workplan`
|
||||||
- All tasks in the workstream have been finished, found unnecessary or been transfered to another workstream
|
**Assessment outcomes:** `unassessed` | `DoD-Ok` | `DoD-Failed`
|
||||||
- All referenced requirements have been adressed with nothing relevent missing or documented as out of scope with a stated reason
|
**Policy key:** `workstream-dod` (filename legacy; product term is **workplan**)
|
||||||
- Relevant usecases have been captured in automated tests so that future changes can be tested to not break prior functionality unintendedly
|
|
||||||
- The Repos README.txt and other documentation has been updated to optimize reusability for agents and humans
|
A workplan may be assessed **DoD-Ok** when implementation quality is complete
|
||||||
- All tests can be run successfully
|
per the checklist below. Lifecycle **`status=finished` is not the same as
|
||||||
- Everything is properly checked in.
|
DoD-Ok** — finishing without DoD-Ok is allowed quality debt (see
|
||||||
|
`docs/work-record-quality-gates.md`).
|
||||||
|
|
||||||
|
Record assessments with frontmatter:
|
||||||
|
|
||||||
|
```yaml
|
||||||
|
quality_dod: DoD-Ok # or DoD-Failed
|
||||||
|
quality_dod_at: "YYYY-MM-DD"
|
||||||
|
quality_dod_by: "agent-or-human"
|
||||||
|
# quality_dod_note: "optional"
|
||||||
|
```
|
||||||
|
|
||||||
|
Related: DoR (`policies/work-item-dor.md`), DoC (`policies/intake-doc.md`).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Checklist
|
||||||
|
|
||||||
|
A workplan is considered **DoD-Ok** when:
|
||||||
|
|
||||||
|
- [ ] All tasks in the workplan have been finished, found unnecessary, or been
|
||||||
|
transferred to another workplan
|
||||||
|
- [ ] All referenced requirements have been addressed with nothing relevant
|
||||||
|
missing, or documented as out of scope with a stated reason
|
||||||
|
- [ ] Relevant use cases have been captured in automated tests so that future
|
||||||
|
changes can be tested to not break prior functionality unintentionally
|
||||||
|
- [ ] The repo README and other documentation have been updated to optimize
|
||||||
|
reusability for agents and humans
|
||||||
|
- [ ] All tests can be run successfully
|
||||||
|
- [ ] Everything is properly checked in
|
||||||
|
|
||||||
|
If assessed and any item fails, record **DoD-Failed** with a short note.
|
||||||
|
|
|
||||||
|
|
@ -36,6 +36,8 @@ Checks:
|
||||||
C-31 work-record-unregistered WARN No YAML-block id matches no kind in the canon work-record type registry (sidetrack detector, CUST-WP-0060)
|
C-31 work-record-unregistered WARN No YAML-block id matches no kind in the canon work-record type registry (sidetrack detector, CUST-WP-0060)
|
||||||
C-32 work-record-not-indexed WARN Yes kind: intake/decision YAML block has no hub id — not indexed in DB (registration, CUST-WP-0061-T02)
|
C-32 work-record-not-indexed WARN Yes kind: intake/decision YAML block has no hub id — not indexed in DB (registration, CUST-WP-0061-T02)
|
||||||
C-33 work-record-index-stale WARN Yes WORK-RECORDS.md missing or stale — generated per-repo index (CUST-WP-0061-T04)
|
C-33 work-record-index-stale WARN Yes WORK-RECORDS.md missing or stale — generated per-repo index (CUST-WP-0061-T04)
|
||||||
|
C-34 quality-dor-ready WARN No status=ready without quality_dor DoR-Ok (STATE-WP-0077 soft)
|
||||||
|
(finished¬DoD-Ok is listed by `statehub quality-debt`, not per-file C-warn — avoids historical flood)
|
||||||
|
|
||||||
Usage:
|
Usage:
|
||||||
python scripts/consistency_check.py --repo SLUG [--fix] [--no-writeback] [--json] [--api-base URL]
|
python scripts/consistency_check.py --repo SLUG [--fix] [--no-writeback] [--json] [--api-base URL]
|
||||||
|
|
@ -1371,6 +1373,25 @@ def check_repo(api_base: str, repo_slug: str, repo_path_override: str | None = N
|
||||||
db_value="needs_review",
|
db_value="needs_review",
|
||||||
fixable=False,
|
fixable=False,
|
||||||
)
|
)
|
||||||
|
# C-34: soft DoR quality debt (STATE-WP-0077) — never blocks
|
||||||
|
try:
|
||||||
|
from quality_assessment import assessment_from_mapping, is_ok
|
||||||
|
except ImportError:
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||||
|
from quality_assessment import assessment_from_mapping, is_ok # type: ignore
|
||||||
|
dor = assessment_from_mapping(meta, "DoR")
|
||||||
|
if not is_ok(dor):
|
||||||
|
report.add(
|
||||||
|
severity="WARN",
|
||||||
|
check_id="C-34",
|
||||||
|
message=(
|
||||||
|
"status=ready without quality_dor DoR-Ok — process claim "
|
||||||
|
"without DoR assessment (soft; see docs/work-record-quality-gates.md)"
|
||||||
|
),
|
||||||
|
file_path=fname,
|
||||||
|
file_value=str(dor or "unassessed"),
|
||||||
|
fixable=False,
|
||||||
|
)
|
||||||
|
|
||||||
# C-05: title drift
|
# C-05: title drift
|
||||||
db_title = ws.get("title", "")
|
db_title = ws.get("title", "")
|
||||||
|
|
|
||||||
|
|
@ -338,6 +338,23 @@ def _append_yaml_block(target_file: Path, fields: dict, *, origin_intake_id: str
|
||||||
# entry point
|
# entry point
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
|
def _soft_warn_missing_doc(intake: dict) -> None:
|
||||||
|
"""STATE-WP-0077: warn (do not block) if DoC-Ok is not recorded."""
|
||||||
|
try:
|
||||||
|
from quality_assessment import intake_has_doc_ok
|
||||||
|
except ImportError:
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||||
|
from quality_assessment import intake_has_doc_ok # type: ignore
|
||||||
|
if intake_has_doc_ok(intake):
|
||||||
|
return
|
||||||
|
print(
|
||||||
|
"WARNING: intake has no DoC-Ok recorded (quality_doc / notes). "
|
||||||
|
"Promotion continues (soft only). Assess Definition of Comprehension "
|
||||||
|
"before confident promote — docs/work-record-quality-gates.md",
|
||||||
|
file=sys.stderr,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
def promote_intake(
|
def promote_intake(
|
||||||
api_base: str,
|
api_base: str,
|
||||||
intake_id: str,
|
intake_id: str,
|
||||||
|
|
@ -350,6 +367,7 @@ def promote_intake(
|
||||||
workplan_file: str | None = None,
|
workplan_file: str | None = None,
|
||||||
) -> dict:
|
) -> dict:
|
||||||
intake = _fetch_intake(api_base, intake_id)
|
intake = _fetch_intake(api_base, intake_id)
|
||||||
|
_soft_warn_missing_doc(intake)
|
||||||
|
|
||||||
if to_kind == "workplan":
|
if to_kind == "workplan":
|
||||||
canonical_id, new_file = _promote_to_workplan(api_base, repo_dir, repo_slug, domain, intake)
|
canonical_id, new_file = _promote_to_workplan(api_base, repo_dir, repo_slug, domain, intake)
|
||||||
|
|
|
||||||
187
scripts/quality_assessment.py
Normal file
187
scripts/quality_assessment.py
Normal file
|
|
@ -0,0 +1,187 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""DoX quality assessment helpers (STATE-WP-0077).
|
||||||
|
|
||||||
|
Canonical recording fields and parsing for Definition assessments.
|
||||||
|
See docs/work-record-quality-gates.md.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import re
|
||||||
|
from dataclasses import dataclass
|
||||||
|
from typing import Any
|
||||||
|
|
||||||
|
POLICIES = ("DoC", "DoR", "DoD")
|
||||||
|
|
||||||
|
FIELD_FOR_POLICY = {
|
||||||
|
"DoC": "quality_doc",
|
||||||
|
"DoR": "quality_dor",
|
||||||
|
"DoD": "quality_dod",
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
def _norm_policy(policy: str) -> str:
|
||||||
|
p = policy.strip()
|
||||||
|
upper = p.upper().replace(" ", "").replace("_", "")
|
||||||
|
if upper in ("DOC", "COMPREHENSION", "DEFINITIONOFCOMPREHENSION"):
|
||||||
|
return "DoC"
|
||||||
|
if upper in ("DOR", "READY", "DEFINITIONOFREADY"):
|
||||||
|
return "DoR"
|
||||||
|
if upper in ("DOD", "DONE", "DEFINITIONOFDONE"):
|
||||||
|
return "DoD"
|
||||||
|
if p in POLICIES:
|
||||||
|
return p
|
||||||
|
raise ValueError(f"unknown policy {policy!r}; use DoC, DoR, or DoD")
|
||||||
|
|
||||||
|
|
||||||
|
def badge(policy: str, outcome: str) -> str:
|
||||||
|
"""Map policy + Ok|Failed (or full badge) to DoX-Ok / DoX-Failed."""
|
||||||
|
pol = _norm_policy(policy)
|
||||||
|
out = outcome.strip().strip('"')
|
||||||
|
if out in (f"{pol}-Ok", f"{pol}-Failed"):
|
||||||
|
return out
|
||||||
|
if out.lower() in ("ok", "pass", "passed"):
|
||||||
|
return f"{pol}-Ok"
|
||||||
|
if out.lower() in ("failed", "fail", "no"):
|
||||||
|
return f"{pol}-Failed"
|
||||||
|
raise ValueError(f"unknown outcome {outcome!r} for {pol}")
|
||||||
|
|
||||||
|
|
||||||
|
def assessment_from_mapping(meta: dict, policy: str) -> str | None:
|
||||||
|
"""Read assessment badge for policy from frontmatter / YAML / task fields."""
|
||||||
|
pol = _norm_policy(policy)
|
||||||
|
key = FIELD_FOR_POLICY[pol]
|
||||||
|
raw = meta.get(key)
|
||||||
|
if raw is None:
|
||||||
|
return None
|
||||||
|
s = str(raw).strip().strip('"').strip("'")
|
||||||
|
if not s or s.lower() in ("unassessed", "none", "null", "~", "-"):
|
||||||
|
return None
|
||||||
|
try:
|
||||||
|
return badge(pol, s)
|
||||||
|
except ValueError:
|
||||||
|
return None
|
||||||
|
|
||||||
|
|
||||||
|
def is_ok(badge_value: str | None) -> bool:
|
||||||
|
return bool(badge_value) and str(badge_value).endswith("-Ok")
|
||||||
|
|
||||||
|
|
||||||
|
@dataclass(frozen=True)
|
||||||
|
class QualityDebtItem:
|
||||||
|
kind: str
|
||||||
|
record_id: str
|
||||||
|
lifecycle_status: str
|
||||||
|
missing: str
|
||||||
|
path: str
|
||||||
|
detail: str = ""
|
||||||
|
|
||||||
|
|
||||||
|
def debt_for_workplan_meta(meta: dict, *, path: str) -> list[QualityDebtItem]:
|
||||||
|
"""Quality debt for a workplan frontmatter dict."""
|
||||||
|
items: list[QualityDebtItem] = []
|
||||||
|
wp_id = str(meta.get("id", "")).strip() or path
|
||||||
|
status = str(meta.get("status", "")).strip().lower()
|
||||||
|
dor = assessment_from_mapping(meta, "DoR")
|
||||||
|
dod = assessment_from_mapping(meta, "DoD")
|
||||||
|
if status == "ready" and not is_ok(dor):
|
||||||
|
items.append(
|
||||||
|
QualityDebtItem(
|
||||||
|
kind="workplan",
|
||||||
|
record_id=wp_id,
|
||||||
|
lifecycle_status=status,
|
||||||
|
missing="DoR-Ok",
|
||||||
|
path=path,
|
||||||
|
detail=f"quality_dor={dor or 'unassessed'}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
if status in ("finished", "completed") and not is_ok(dod):
|
||||||
|
items.append(
|
||||||
|
QualityDebtItem(
|
||||||
|
kind="workplan",
|
||||||
|
record_id=wp_id,
|
||||||
|
lifecycle_status=status,
|
||||||
|
missing="DoD-Ok",
|
||||||
|
path=path,
|
||||||
|
detail=f"quality_dod={dod or 'unassessed'}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def debt_for_intake_meta(meta: dict, *, path: str) -> list[QualityDebtItem]:
|
||||||
|
"""Quality debt for intake blocks in vetted/routed without DoC-Ok."""
|
||||||
|
items: list[QualityDebtItem] = []
|
||||||
|
iid = str(meta.get("id", "")).strip() or path
|
||||||
|
status = str(meta.get("status", "")).strip().lower()
|
||||||
|
doc = assessment_from_mapping(meta, "DoC")
|
||||||
|
if status in ("vetted", "routed") and not is_ok(doc):
|
||||||
|
items.append(
|
||||||
|
QualityDebtItem(
|
||||||
|
kind="intake",
|
||||||
|
record_id=iid,
|
||||||
|
lifecycle_status=status,
|
||||||
|
missing="DoC-Ok",
|
||||||
|
path=path,
|
||||||
|
detail=f"quality_doc={doc or 'unassessed'}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def intake_has_doc_ok(intake: dict) -> bool:
|
||||||
|
"""Soft check: hub intake notes/description record DoC-Ok."""
|
||||||
|
chunks: list[str] = []
|
||||||
|
for key in ("description", "routed_note", "title"):
|
||||||
|
v = intake.get(key)
|
||||||
|
if v:
|
||||||
|
chunks.append(str(v))
|
||||||
|
for note in intake.get("notes") or []:
|
||||||
|
if isinstance(note, dict):
|
||||||
|
chunks.append(str(note.get("content") or ""))
|
||||||
|
else:
|
||||||
|
chunks.append(str(note))
|
||||||
|
# structured future field
|
||||||
|
if assessment_from_mapping(intake, "DoC") and is_ok(assessment_from_mapping(intake, "DoC")):
|
||||||
|
return True
|
||||||
|
text = "\n".join(chunks)
|
||||||
|
if re.search(r"\bDoC-Ok\b", text):
|
||||||
|
return True
|
||||||
|
if re.search(r"quality_doc\s*[:=]\s*[\"']?(DoC-Ok|Ok)\b", text, re.I):
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
def progress_event_body(
|
||||||
|
*,
|
||||||
|
policy: str,
|
||||||
|
outcome: str,
|
||||||
|
record_kind: str,
|
||||||
|
record_id: str,
|
||||||
|
assessed_by: str | None = None,
|
||||||
|
note: str | None = None,
|
||||||
|
topic_id: str | None = None,
|
||||||
|
workplan_id: str | None = None,
|
||||||
|
author: str | None = None,
|
||||||
|
) -> dict[str, Any]:
|
||||||
|
"""JSON body for POST /progress/ recording a quality assessment."""
|
||||||
|
b = badge(policy, outcome)
|
||||||
|
pol = b.rsplit("-", 1)[0]
|
||||||
|
body: dict[str, Any] = {
|
||||||
|
"event_type": "quality_assessment",
|
||||||
|
"summary": f"{b} for {record_kind} {record_id}",
|
||||||
|
"author": author or assessed_by or "agent",
|
||||||
|
"detail": {
|
||||||
|
"policy": pol,
|
||||||
|
"outcome": "Ok" if b.endswith("-Ok") else "Failed",
|
||||||
|
"badge": b,
|
||||||
|
"record_kind": record_kind,
|
||||||
|
"record_id": record_id,
|
||||||
|
"assessed_by": assessed_by,
|
||||||
|
"note": note,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
if topic_id:
|
||||||
|
body["topic_id"] = topic_id
|
||||||
|
if workplan_id:
|
||||||
|
body["workplan_id"] = workplan_id
|
||||||
|
return body
|
||||||
226
scripts/quality_debt.py
Normal file
226
scripts/quality_debt.py
Normal file
|
|
@ -0,0 +1,226 @@
|
||||||
|
#!/usr/bin/env python3
|
||||||
|
"""List DoX quality debt for a repo (STATE-WP-0077-T02).
|
||||||
|
|
||||||
|
Scans local workplan frontmatter and intake YAML blocks; optionally lists
|
||||||
|
hub intakes (vetted/routed) without DoC-Ok in notes.
|
||||||
|
|
||||||
|
Usage:
|
||||||
|
python scripts/quality_debt.py --here
|
||||||
|
python scripts/quality_debt.py --repo-path /path/to/repo
|
||||||
|
statehub quality-debt [--repo-path PATH] [--json] [--api-base URL]
|
||||||
|
|
||||||
|
Exit 0 always (report only); use --strict for exit 1 when debt found.
|
||||||
|
"""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import argparse
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
import re
|
||||||
|
import sys
|
||||||
|
import urllib.error
|
||||||
|
import urllib.request
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parent))
|
||||||
|
|
||||||
|
from quality_assessment import ( # noqa: E402
|
||||||
|
QualityDebtItem,
|
||||||
|
debt_for_intake_meta,
|
||||||
|
debt_for_workplan_meta,
|
||||||
|
intake_has_doc_ok,
|
||||||
|
)
|
||||||
|
|
||||||
|
try:
|
||||||
|
import yaml
|
||||||
|
except ImportError: # pragma: no cover
|
||||||
|
yaml = None # type: ignore
|
||||||
|
|
||||||
|
_SKIP_DIRS = frozenset({".git", "node_modules", ".venv", "history", "agents_backup", "dist"})
|
||||||
|
|
||||||
|
|
||||||
|
def _parse_frontmatter(text: str) -> dict:
|
||||||
|
if not text.startswith("---"):
|
||||||
|
return {}
|
||||||
|
parts = text.split("---", 2)
|
||||||
|
if len(parts) < 3:
|
||||||
|
return {}
|
||||||
|
raw = parts[1]
|
||||||
|
if yaml is not None:
|
||||||
|
try:
|
||||||
|
data = yaml.safe_load(raw) or {}
|
||||||
|
return data if isinstance(data, dict) else {}
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
# minimal fallback: key: value lines
|
||||||
|
meta: dict = {}
|
||||||
|
for line in raw.splitlines():
|
||||||
|
if ":" not in line:
|
||||||
|
continue
|
||||||
|
k, _, v = line.partition(":")
|
||||||
|
meta[k.strip()] = v.strip().strip('"').strip("'")
|
||||||
|
return meta
|
||||||
|
|
||||||
|
|
||||||
|
def _scan_workplans(repo_dir: Path) -> list[QualityDebtItem]:
|
||||||
|
items: list[QualityDebtItem] = []
|
||||||
|
wp_dir = repo_dir / "workplans"
|
||||||
|
if not wp_dir.is_dir():
|
||||||
|
return items
|
||||||
|
for path in sorted(wp_dir.rglob("*.md")):
|
||||||
|
if path.name == "README.md":
|
||||||
|
continue
|
||||||
|
if "archived" in path.parts:
|
||||||
|
# still scan finished debt in archived? skip archived for noise
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
text = path.read_text(encoding="utf-8", errors="replace")
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
meta = _parse_frontmatter(text)
|
||||||
|
if not meta:
|
||||||
|
continue
|
||||||
|
rel = str(path.relative_to(repo_dir))
|
||||||
|
items.extend(debt_for_workplan_meta(meta, path=rel))
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def _scan_intake_blocks(repo_dir: Path) -> list[QualityDebtItem]:
|
||||||
|
items: list[QualityDebtItem] = []
|
||||||
|
if yaml is None:
|
||||||
|
return items
|
||||||
|
fence = re.compile(r"```ya?ml\n(.*?)```", re.DOTALL | re.IGNORECASE)
|
||||||
|
for path in sorted(repo_dir.rglob("*.md")):
|
||||||
|
if any(p in _SKIP_DIRS for p in path.parts):
|
||||||
|
continue
|
||||||
|
try:
|
||||||
|
text = path.read_text(encoding="utf-8", errors="replace")
|
||||||
|
except OSError:
|
||||||
|
continue
|
||||||
|
rel = str(path.relative_to(repo_dir))
|
||||||
|
for m in fence.finditer(text):
|
||||||
|
block = m.group(1)
|
||||||
|
try:
|
||||||
|
data = yaml.safe_load(block)
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
if not isinstance(data, dict):
|
||||||
|
continue
|
||||||
|
rid = str(data.get("id", ""))
|
||||||
|
# intake ids: PREFIX-IN-NNNN or grandfathered AWQ-
|
||||||
|
if not re.match(r"^[A-Z]+-IN-\d+", rid) and not re.match(r"^AWQ-\d+", rid):
|
||||||
|
if data.get("kind") != "intake":
|
||||||
|
continue
|
||||||
|
items.extend(debt_for_intake_meta(data, path=rel))
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def _hub_intake_debt(api_base: str) -> list[QualityDebtItem]:
|
||||||
|
items: list[QualityDebtItem] = []
|
||||||
|
try:
|
||||||
|
req = urllib.request.Request(
|
||||||
|
f"{api_base.rstrip('/')}/intakes/",
|
||||||
|
headers={"Accept": "application/json"},
|
||||||
|
)
|
||||||
|
with urllib.request.urlopen(req, timeout=15) as resp:
|
||||||
|
rows = json.loads(resp.read())
|
||||||
|
except Exception as e:
|
||||||
|
items.append(
|
||||||
|
QualityDebtItem(
|
||||||
|
kind="intake",
|
||||||
|
record_id="(hub)",
|
||||||
|
lifecycle_status="?",
|
||||||
|
missing="DoC-Ok",
|
||||||
|
path="api:/intakes/",
|
||||||
|
detail=f"could not list intakes: {e}",
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return items
|
||||||
|
if not isinstance(rows, list):
|
||||||
|
return items
|
||||||
|
for row in rows:
|
||||||
|
if row.get("status") not in ("vetted", "routed"):
|
||||||
|
continue
|
||||||
|
if intake_has_doc_ok(row):
|
||||||
|
continue
|
||||||
|
items.append(
|
||||||
|
QualityDebtItem(
|
||||||
|
kind="intake",
|
||||||
|
record_id=str(row.get("id", "")),
|
||||||
|
lifecycle_status=str(row.get("status", "")),
|
||||||
|
missing="DoC-Ok",
|
||||||
|
path="api:/intakes/",
|
||||||
|
detail=(row.get("title") or "")[:80],
|
||||||
|
)
|
||||||
|
)
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def collect(
|
||||||
|
repo_dir: Path,
|
||||||
|
*,
|
||||||
|
api_base: str | None = None,
|
||||||
|
include_hub_intakes: bool = True,
|
||||||
|
) -> list[QualityDebtItem]:
|
||||||
|
items = _scan_workplans(repo_dir) + _scan_intake_blocks(repo_dir)
|
||||||
|
if include_hub_intakes and api_base:
|
||||||
|
items.extend(_hub_intake_debt(api_base))
|
||||||
|
return items
|
||||||
|
|
||||||
|
|
||||||
|
def main() -> None:
|
||||||
|
ap = argparse.ArgumentParser(description=__doc__)
|
||||||
|
ap.add_argument("--here", action="store_true", help="Use cwd as repo")
|
||||||
|
ap.add_argument("--repo-path", default=None, help="Repo root (default: cwd)")
|
||||||
|
ap.add_argument("--api-base", default=os.environ.get("API_BASE", "http://127.0.0.1:8000"))
|
||||||
|
ap.add_argument("--no-hub", action="store_true", help="Skip hub intake list")
|
||||||
|
ap.add_argument("--json", action="store_true", dest="as_json")
|
||||||
|
ap.add_argument("--strict", action="store_true", help="Exit 1 if any debt")
|
||||||
|
args = ap.parse_args()
|
||||||
|
|
||||||
|
repo_dir = Path(args.repo_path or os.getcwd()).expanduser().resolve()
|
||||||
|
items = collect(
|
||||||
|
repo_dir,
|
||||||
|
api_base=None if args.no_hub else args.api_base,
|
||||||
|
include_hub_intakes=not args.no_hub,
|
||||||
|
)
|
||||||
|
|
||||||
|
if args.as_json:
|
||||||
|
print(
|
||||||
|
json.dumps(
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"kind": i.kind,
|
||||||
|
"record_id": i.record_id,
|
||||||
|
"lifecycle_status": i.lifecycle_status,
|
||||||
|
"missing": i.missing,
|
||||||
|
"path": i.path,
|
||||||
|
"detail": i.detail,
|
||||||
|
}
|
||||||
|
for i in items
|
||||||
|
],
|
||||||
|
indent=2,
|
||||||
|
)
|
||||||
|
)
|
||||||
|
else:
|
||||||
|
print(f"Quality debt — {repo_dir}")
|
||||||
|
print(f" ({len(items)} item(s); lifecycle may advance without DoX-Ok)\n")
|
||||||
|
if not items:
|
||||||
|
print(" (none)")
|
||||||
|
for i in items:
|
||||||
|
print(
|
||||||
|
f" [{i.kind}] {i.record_id} status={i.lifecycle_status} "
|
||||||
|
f"missing {i.missing} ({i.path}) {i.detail}"
|
||||||
|
)
|
||||||
|
print(
|
||||||
|
"\nRecord assessments with quality_dor / quality_dod / quality_doc "
|
||||||
|
"fields — see docs/work-record-quality-gates.md"
|
||||||
|
)
|
||||||
|
|
||||||
|
if args.strict and items:
|
||||||
|
sys.exit(1)
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == "__main__":
|
||||||
|
main()
|
||||||
73
tests/test_quality_assessment.py
Normal file
73
tests/test_quality_assessment.py
Normal file
|
|
@ -0,0 +1,73 @@
|
||||||
|
"""Unit tests for DoX quality assessment helpers (STATE-WP-0077)."""
|
||||||
|
from __future__ import annotations
|
||||||
|
|
||||||
|
import sys
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
sys.path.insert(0, str(Path(__file__).resolve().parents[1] / "scripts"))
|
||||||
|
|
||||||
|
from quality_assessment import ( # noqa: E402
|
||||||
|
assessment_from_mapping,
|
||||||
|
badge,
|
||||||
|
debt_for_intake_meta,
|
||||||
|
debt_for_workplan_meta,
|
||||||
|
intake_has_doc_ok,
|
||||||
|
is_ok,
|
||||||
|
progress_event_body,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
|
def test_badge_ok_failed():
|
||||||
|
assert badge("DoR", "Ok") == "DoR-Ok"
|
||||||
|
assert badge("DoC", "Failed") == "DoC-Failed"
|
||||||
|
assert badge("DoD", "DoD-Ok") == "DoD-Ok"
|
||||||
|
|
||||||
|
|
||||||
|
def test_assessment_from_mapping():
|
||||||
|
assert assessment_from_mapping({"quality_dor": "DoR-Ok"}, "DoR") == "DoR-Ok"
|
||||||
|
assert assessment_from_mapping({"quality_dor": "Ok"}, "DoR") == "DoR-Ok"
|
||||||
|
assert assessment_from_mapping({}, "DoR") is None
|
||||||
|
assert is_ok(assessment_from_mapping({"quality_dod": "Failed"}, "DoD")) is False
|
||||||
|
|
||||||
|
|
||||||
|
def test_debt_ready_without_dor():
|
||||||
|
items = debt_for_workplan_meta(
|
||||||
|
{"id": "STATE-WP-0001", "status": "ready"},
|
||||||
|
path="workplans/x.md",
|
||||||
|
)
|
||||||
|
assert len(items) == 1
|
||||||
|
assert items[0].missing == "DoR-Ok"
|
||||||
|
|
||||||
|
|
||||||
|
def test_debt_finished_without_dod():
|
||||||
|
items = debt_for_workplan_meta(
|
||||||
|
{"id": "STATE-WP-0001", "status": "finished", "quality_dod": "DoD-Ok"},
|
||||||
|
path="workplans/x.md",
|
||||||
|
)
|
||||||
|
assert items == []
|
||||||
|
|
||||||
|
|
||||||
|
def test_debt_intake_routed():
|
||||||
|
items = debt_for_intake_meta(
|
||||||
|
{"id": "CUST-IN-0001", "status": "routed"},
|
||||||
|
path="notes.md",
|
||||||
|
)
|
||||||
|
assert len(items) == 1
|
||||||
|
assert items[0].missing == "DoC-Ok"
|
||||||
|
|
||||||
|
|
||||||
|
def test_intake_has_doc_ok_from_note():
|
||||||
|
assert intake_has_doc_ok({"notes": [{"content": "Assessed DoC-Ok today"}]})
|
||||||
|
assert not intake_has_doc_ok({"notes": [{"content": "still open"}]})
|
||||||
|
|
||||||
|
|
||||||
|
def test_progress_event_body():
|
||||||
|
body = progress_event_body(
|
||||||
|
policy="DoR",
|
||||||
|
outcome="Ok",
|
||||||
|
record_kind="workplan",
|
||||||
|
record_id="STATE-WP-0077",
|
||||||
|
assessed_by="grok",
|
||||||
|
)
|
||||||
|
assert body["event_type"] == "quality_assessment"
|
||||||
|
assert body["detail"]["badge"] == "DoR-Ok"
|
||||||
|
|
@ -4,7 +4,7 @@ type: workplan
|
||||||
title: "DoX assessment recording and soft visibility"
|
title: "DoX assessment recording and soft visibility"
|
||||||
domain: infotech
|
domain: infotech
|
||||||
repo: state-hub
|
repo: state-hub
|
||||||
status: ready
|
status: finished
|
||||||
owner: grok
|
owner: grok
|
||||||
topic_slug: infotech
|
topic_slug: infotech
|
||||||
created: "2026-07-22"
|
created: "2026-07-22"
|
||||||
|
|
@ -21,6 +21,12 @@ context_paths:
|
||||||
- "scripts/"
|
- "scripts/"
|
||||||
- ".claude/rules/"
|
- ".claude/rules/"
|
||||||
- "AGENTS.md"
|
- "AGENTS.md"
|
||||||
|
quality_dor: DoR-Ok
|
||||||
|
quality_dor_at: "2026-07-22"
|
||||||
|
quality_dor_by: "grok"
|
||||||
|
quality_dod: DoD-Ok
|
||||||
|
quality_dod_at: "2026-07-22"
|
||||||
|
quality_dod_by: "grok"
|
||||||
state_hub_workstream_id: "a772c7cf-7172-486c-9c80-c70f6101db8f"
|
state_hub_workstream_id: "a772c7cf-7172-486c-9c80-c70f6101db8f"
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -37,36 +43,20 @@ badge engine or hard gates:
|
||||||
without DoR-Ok, intakes promoted/routed without DoC-Ok.
|
without DoR-Ok, intakes promoted/routed without DoC-Ok.
|
||||||
3. **Guide agents** with soft protocol (and optional cheap warnings).
|
3. **Guide agents** with soft protocol (and optional cheap warnings).
|
||||||
|
|
||||||
Parent: `STATE-WP-0076` (DoC/DoR policies and badge spelling — finished).
|
Parent: `STATE-WP-0076` (finished).
|
||||||
|
|
||||||
## Background (what is already true)
|
## Delivered (2026-07-22)
|
||||||
|
|
||||||
| Active | Not active |
|
| Deliverable | Location |
|
||||||
|--------|------------|
|
|-------------|----------|
|
||||||
| DoC / DoR / DoD policy text | First-class badge field on hub rows |
|
| Recording convention + examples | `docs/work-record-quality-gates.md` |
|
||||||
| Spelling: unassessed / `DoX-Ok` / `DoX-Failed` | Dashboard badge column |
|
| Parse helpers | `scripts/quality_assessment.py` |
|
||||||
| Lifecycle ≠ quality (documented) | “finished ∧ ¬DoD-Ok” KPI/list |
|
| Debt list CLI | `scripts/quality_debt.py`, `statehub quality-debt` |
|
||||||
| Convention-only enforcement | Soft warnings on promote / ready |
|
| Soft promote warning | `scripts/promote_intake.py` |
|
||||||
|
| Soft consistency | C-34 (ready¬DoR); finished¬DoD via quality-debt only |
|
||||||
## Non-goals
|
| Agent protocol | `AGENTS.md`, `.claude/rules/session-protocol.md` |
|
||||||
|
| DoD badge language | `policies/workstream-dod.md` |
|
||||||
- Freeform badge product or open badge taxonomy
|
| Tests | `tests/test_quality_assessment.py` |
|
||||||
- Hard API 422 / blocked transitions on missing DoX-Ok
|
|
||||||
- Full DoD rewrite (may touch only badge wording alignment)
|
|
||||||
- New work-record kinds
|
|
||||||
- Contribution retirement
|
|
||||||
|
|
||||||
## Design constraints
|
|
||||||
|
|
||||||
1. **Closed badge family only:** `DoC-Ok` / `DoC-Failed`, `DoR-Ok` /
|
|
||||||
`DoR-Failed`, `DoD-Ok` / `DoD-Failed`, plus unassessed; tiered DoI/DoM stay
|
|
||||||
on assets.
|
|
||||||
2. **Lifecycle remains independent** — missing Ok is quality debt, not invalid
|
|
||||||
status.
|
|
||||||
3. **Prefer file/progress convention over new tables** in v1; add DB only if a
|
|
||||||
list/KPI cannot be built otherwise.
|
|
||||||
4. **Soft only** — warn or surface; never block promote-intake or status patch
|
|
||||||
in this plan.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -74,24 +64,15 @@ Parent: `STATE-WP-0076` (DoC/DoR policies and badge spelling — finished).
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: STATE-WP-0077-T01
|
id: STATE-WP-0077-T01
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "483af4cf-3aa9-49de-910d-034a0895a9bb"
|
state_hub_task_id: "483af4cf-3aa9-49de-910d-034a0895a9bb"
|
||||||
|
quality_dor: DoR-Ok
|
||||||
|
quality_dor_at: "2026-07-22"
|
||||||
```
|
```
|
||||||
|
|
||||||
Define and document **how** a last assessment is recorded:
|
Canonical fields `quality_doc` / `quality_dor` / `quality_dod` (+ `_at`/`_by`/`_note`),
|
||||||
|
progress `event_type=quality_assessment`, examples in quality-gates doc.
|
||||||
- Target shapes (pick minimal set that works with fix-consistency / agents):
|
|
||||||
- workplan frontmatter and/or task block fields, and/or
|
|
||||||
- intake YAML / notes, and/or
|
|
||||||
- progress event `event_type` + `detail` (e.g. `quality_assessment`)
|
|
||||||
- Required fields: which policy (`DoC`|`DoR`|`DoD`), outcome
|
|
||||||
(`Ok`|`Failed`), `assessed_at`, optional `assessed_by`, optional `note`
|
|
||||||
- Update `docs/work-record-quality-gates.md` with the canonical recording form
|
|
||||||
- One short example per kind (intake DoC, task/workplan DoR, workplan DoD)
|
|
||||||
|
|
||||||
**Done when:** an agent can record DoR-Ok / DoC-Failed the same way every time
|
|
||||||
without inventing a private format.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -99,24 +80,15 @@ without inventing a private format.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: STATE-WP-0077-T02
|
id: STATE-WP-0077-T02
|
||||||
status: todo
|
status: done
|
||||||
priority: high
|
priority: high
|
||||||
state_hub_task_id: "ff318c88-86e6-4231-bc25-3bd936e63035"
|
state_hub_task_id: "ff318c88-86e6-4231-bc25-3bd936e63035"
|
||||||
|
quality_dor: DoR-Ok
|
||||||
|
quality_dor_at: "2026-07-22"
|
||||||
```
|
```
|
||||||
|
|
||||||
Expose at least one **read surface** for quality debt (implement the smallest
|
`statehub quality-debt` / `scripts/quality_debt.py` — file workplans + intakes +
|
||||||
useful option):
|
optional hub intakes.
|
||||||
|
|
||||||
Candidates (choose in implementation; document choice):
|
|
||||||
|
|
||||||
- CLI or script: list workplans with `status=ready` and no DoR-Ok; `finished`
|
|
||||||
and no DoD-Ok; open intakes `vetted`/`routed` without DoC-Ok
|
|
||||||
- Optional: include a slice in `GET /state/summary` or next_steps as soft
|
|
||||||
signals (not ranked_suggestions revival)
|
|
||||||
- Optional light dashboard note later — not required if CLI/script is enough
|
|
||||||
|
|
||||||
**Done when:** operator/agent can answer “which finished plans lack DoD-Ok?”
|
|
||||||
from hub tooling without grepping the whole fleet by hand.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -124,20 +96,14 @@ from hub tooling without grepping the whole fleet by hand.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: STATE-WP-0077-T03
|
id: STATE-WP-0077-T03
|
||||||
status: todo
|
status: done
|
||||||
priority: medium
|
priority: medium
|
||||||
state_hub_task_id: "dd3e70b2-0335-438e-8e59-d905c046cee3"
|
state_hub_task_id: "dd3e70b2-0335-438e-8e59-d905c046cee3"
|
||||||
|
quality_dor: DoR-Ok
|
||||||
|
quality_dor_at: "2026-07-22"
|
||||||
```
|
```
|
||||||
|
|
||||||
- Session / AGENTS guidance: assess DoC before confident promote; assess DoR
|
AGENTS + session protocol; promote-intake soft WARN; C-34/C-35 soft WARN.
|
||||||
before heavy implementation; record outcome in the T01 form
|
|
||||||
- Optional cheap soft warnings only (log/stderr/progress note — **no 422**):
|
|
||||||
- `promote-intake` when intake has no DoC-Ok recorded
|
|
||||||
- consistency warn when `status=ready` without DoR assessment metadata
|
|
||||||
- Update quality-gates doc “Enforcement level” section to match what shipped
|
|
||||||
|
|
||||||
**Done when:** default agent path records assessments; optional warnings exist
|
|
||||||
or are explicitly deferred with rationale in the doc.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
|
@ -145,32 +111,26 @@ or are explicitly deferred with rationale in the doc.
|
||||||
|
|
||||||
```task
|
```task
|
||||||
id: STATE-WP-0077-T04
|
id: STATE-WP-0077-T04
|
||||||
status: todo
|
status: done
|
||||||
priority: low
|
priority: low
|
||||||
state_hub_task_id: "d620891f-562c-4c1d-8f72-fd13415e4a0a"
|
state_hub_task_id: "d620891f-562c-4c1d-8f72-fd13415e4a0a"
|
||||||
|
quality_dor: DoR-Ok
|
||||||
|
quality_dor_at: "2026-07-22"
|
||||||
```
|
```
|
||||||
|
|
||||||
Light touch on `policies/workstream-dod.md`:
|
`policies/workstream-dod.md` — applies_to workplan, DoD-Ok/Failed, finished ≠ DoD-Ok.
|
||||||
|
|
||||||
- State applies_to workplan, outcomes `DoD-Ok` / `DoD-Failed` / unassessed
|
|
||||||
- Note lifecycle `finished` ≠ DoD-Ok
|
|
||||||
- Do not expand DoD criteria into a large rewrite unless one-liners are broken
|
|
||||||
|
|
||||||
**Done when:** DoD speaks the same assessment language as DoC/DoR.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
## Acceptance criteria
|
## Acceptance criteria
|
||||||
|
|
||||||
- [ ] Canonical recording form documented and exemplified
|
- [x] Canonical recording form documented and exemplified
|
||||||
- [ ] At least one quality-debt list/digest works against live data
|
- [x] At least one quality-debt list/digest works against live data
|
||||||
- [ ] Agent protocol documents when to assess and how to record
|
- [x] Agent protocol documents when to assess and how to record
|
||||||
- [ ] No hard gates; no freeform badge engine
|
- [x] No hard gates; no freeform badge engine
|
||||||
- [ ] DoD policy mentions DoD-Ok / Failed / unassessed
|
- [x] DoD policy mentions DoD-Ok / Failed / unassessed
|
||||||
|
|
||||||
## References
|
## References
|
||||||
|
|
||||||
- `docs/work-record-quality-gates.md`
|
- `docs/work-record-quality-gates.md`
|
||||||
- `policies/intake-doc.md`, `policies/work-item-dor.md`, `policies/workstream-dod.md`
|
|
||||||
- `STATE-WP-0076` (parent)
|
- `STATE-WP-0076` (parent)
|
||||||
- `dashboard/src/docs/work-records.md`
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue