`core proposal-queue {status,list,show,review}`: a read + review-state-only
CLI over the two proposal sinks that had a Python API but no operator
surface — teaching/proposals/comprehension_failures/ (N5 contemplation)
and teaching/proposals/derived_close_facts/ (idle_tick PR-2 bridge).
"review" appends to a sidecar log (teaching/proposals/review_log.jsonl)
recording that a human looked at an artifact; it never mutates the
artifact, never ratifies, never mounts, never flips a flag. The existing
ratification corridor (teaching/proposals.py, `core teaching
proposals`/`review`, a different sink) is untouched.
Found and fixed a real bug while building the second sink's reader:
generate/determine/derived_close_proposals.py's DEFAULT_SINK used
parents[3] instead of parents[2], resolving one directory ABOVE the repo
instead of inside it. Silent because the feature is default-off and no
test asserted the path — confirmed live via a stray artifact sitting
outside the repository since 2026-06-16. Pinned by
test_derived_close_default_sink_resolves_inside_the_repo.
5.3 KiB
HITL Proposal-Queue CLI — core proposal-queue
Tier S4 (generalization-arc-2026-07-24 §6). A core CLI surface for
listing and reviewing pending proposals from the contemplation/idle sinks —
teaching/proposals/comprehension_failures/ (the N5 contemplation pass) and
teaching/proposals/derived_close_facts/ (the idle_tick PR-2 bridge). Read +
review-state transitions only: no command here ratifies, mutates the
teaching corpus, mounts anything, or flips a flag. That corridor
(teaching/proposals.py's accept_proposal/reject_proposal, a DIFFERENT
sink — teaching/proposals/proposals.jsonl, ADR-0057) already has its own
CLI (core teaching proposals / core teaching review) and is untouched.
What shipped
core/proposal_review/queue.py— a sink-agnostic scanner (scan_all), an append-only human-review sidecar log (teaching/proposals/review_log.jsonl, viarecord_review/reviewed_keys), and a triage summary (queue_status).core/cli_proposal_queue.py—core proposal-queue {status,list,show,review}, registered via the sameregister(subparsers)patterncore/cli_ingest.pyandformation/cli.pyuse.tests/test_proposal_queue.py(13 tests, registered in theteachingsuite), fully isolated from the real sinks viaroots=/path=overrides — never touches the realteaching/proposals/review_log.jsonl.
core proposal-queue status [--sink NAME] [--json]
core proposal-queue list [--sink NAME] [--pending-only] [--json]
core proposal-queue show <sink> <content-hash>
core proposal-queue review <sink> <content-hash> [--note TEXT]
review only appends a {sink, content_hash, note, reviewed_at} record to
the sidecar log after confirming the hash resolves in a fresh scan (a typo
cannot silently "review" nothing); it never opens the artifact file for
writing. test_record_review_does_not_touch_the_artifact pins that
byte-for-byte.
A real bug found while building the second sink's reader
generate/determine/derived_close_proposals.py::DEFAULT_SINK computed
Path(__file__).resolve().parents[3] / "teaching" / "proposals" / "derived_close_facts". From <repo>/generate/determine/derived_close_proposals.py,
parents[2] is the repo root (0=determine/, 1=generate/, 2=<repo>) —
parents[3] is one directory above the repository. Confirmed live: a
core proposal-queue list run before the fix listed an entry at
/Users/kaizenpro/Projects/teaching/proposals/derived_close_facts/… — a
directory sitting outside core/ entirely, containing one stray artifact
dated 2026-06-16, untracked by git, invisible to anything that searches
inside the repository. Fixed to parents[2]
(test_derived_close_default_sink_resolves_inside_the_repo pins it).
Why nothing caught this before: the sink is gated by a default-off flag
(review_derived_close_proposals) and no existing test asserted
DEFAULT_SINK resolves inside the repo — its own tests, where they exist,
pass an explicit sink= override. The bug was silent because the feature
has essentially never run in its default configuration. The stray file
outside the repo was left as-is (it is not tracked by git and this session
has no standing to delete files outside the repository without being
asked); a future real run of emit_derived_close_proposals will now write
to the correct, in-repo location.
Design choices, made explicit
- No typed dataclass for
derived_close_facts. Unlikecomprehension_failures(which has a hardenedPendingProposalreader and an independent safetydry_check, both reused unchanged here), this queue readsderived_close_factsgenerically — only the shared minimal contract (status/requires_review/mounted) is validated. Committing to a schema-specific reader for a sink that is currently empty and default-off would be speculative; the generic reader is honest about that and will not silently misclassify whatever the emitter's schema settles into. - No new safety dry-check for
derived_close_facts. The existingcore/proposal_review/safety.py::dry_checkis scoped tocomprehension_failures's specific content-address formula. Extending it to the second sink's different dedupe-key formula would be a real design decision about what "inert" means for that family — out of scope for a read/review CLI, and not requested. - Review vocabulary stays minimal: a note, not a state machine. No spec
exists for review-state vocabulary (accept/flag/defer/etc.); inventing
one would risk becoming ratification-adjacent policy.
record_reviewanswers exactly one question — "has a human looked at this?" — with an optional free-text note, which is the smallest thing that satisfies "review-state transitions" without overstepping into ratification.
Non-goals
- Does not touch
teaching/proposals.py,accept_proposal, orcore teaching proposals/review— that corridor is unchanged. - Does not run
emit_derived_close_proposalsor otherwise populate either sink; both are read as they currently stand (one realcomprehension_failuresentry, zeroderived_close_factsentries once read from the corrected path). - Does not wire
core proposal-queueintoidle_tickor any automated loop — operator-invoked only, per the brief's HITL framing.
Relates to project-generalization-arc.