docs+fix: reconcile stale docs with shipped M4–M6+V1 surface; wire approval_expired; prune dead logic - #3
Merged
Merged
Conversation
First slice of the PRD→tasks workflow: scaffold the shape, not the decomposition. `ctl prd init [--title <t>]` prints a structured PRD template to stdout (`ctl prd init > prd.md`) for the grill/LLM step to fill. The template's `## Tasks` section is a deliberate, parseable convention — id / objective / write-allow / gates per vertical task — so a later `ctl prd plan` step can map each item to a `ctl task create`. The "grill" (requirement interrogation) is an LLM activity, not ctl-core; ctl owns only the deterministic shape. Test pins the section headers, per-task field keys, and title substitution so the convention can't silently drift before the parser lands. Verified live: `prd init --title "Payments v2"` emits the filled template. Task: workflow-prd-to-tasks-v1 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
ralph-safe-run-v1, scoped as a principled adjustment. A true "loop an executor unattended" command would violate ctl's core invariant (it never spawns an executor) and needs a run/executor lifecycle that doesn't exist yet — the same root cause as the lease-TTL deferral. So this ships the part of a bounded autonomous loop that is real and safe today: the governance envelope, not the executor. `ctl ralph run --id <task>` loops a READ-ONLY GO/NO-GO safety check with three independent hard stops — a kill-switch file, a wall-clock deadline (--max-secs), and a max-cycle cap (--max-iters) — and halts the instant the check reports NO-GO. It spawns nothing and writes no code; the agent keeps running in the operator's own loop, with ralph as the dead-man's-switch around it. ralph_safety_check composes this session's guards into one verdict: task hold/terminality, cross-ledger consistency (cross_ledger_findings), shared-.git locks (scan_shared_git_risk), and drift (next_action != Pass). All read-only. Tests: GO on a clean active task, NO-GO on a terminal task, NO-GO on a global cross-ledger inconsistency. Verified live: GO cycles then max-iters stop on an active task; immediate NO-GO on a terminal task; kill-switch stops at 0 cycles. Deliberately NOT built (needs executor lifecycle + explicit risk sign-off): spawning/looping an actual unattended executor. Task: ralph-safe-run-v1 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Gives run-lease TTL real teeth without crossing the lines that made it report-only. Previously `ttl_seconds` was stored but never enforced: the run reducer had no `lease_expired` transition, and `recover` only reported `lease_stale`. (max_uses was already enforced via consume().) - Run reducer: add the `lease_expired` transition (Active→Expired), idempotent, mirroring lease_revoked. The wall-clock judgement stays OUT of the deterministic reducer. - `ctl run expire-lease --run <id> [--apply]`: explicit, operator-invoked. It appends `lease_expired` ONLY when the lease is genuinely past its wall-clock TTL (age from the lease_created event's occurred_at, same source as recover's lease_stale). Refuses a within-TTL or non-active lease; preview by default. The staleness check is read at the application layer, never auto in a read path — replay stays deterministic, the recorded event carries its own occurred_at. Deliberately NOT done (unchanged from the deferral rationale): no auto-expiry, no process termination. Expiring a lease does not wind the run down — that remains `run recover --abort`. ttl_exceeded() is a pure, unit-tested helper. Tests: 9 — reducer (expire transition + no-lease error), ttl_exceeded (strict-greater, saturating), and expire_run_lease (stale→expired, preview no-mutate, within-ttl refuse, no-native-lease) via an injected clock. Verified live: aborted run → not_active, fresh scheduled run → within_ttl (age 0/ttl 3600). Task: capability-lease-ttl-enforce-v1 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…proval_expired; prune dead logic Project-wide review pass. Docs (drift vs actual CLI/code): - DESIGN.md: telemetry/drift/control.json are shipped (M5), not "规划中"; status snapshot now M0-M6+V1, not "stops at M3"; replace dead `control *` namespace with real `ctl board/report/drift/agent-report/run ingest`; fix module layout + opencode. - ROADMAP.md: fix dogfood workflow (control status -> ctl board; real `task create` flags); drop non-existent `ctl assignment create`; `capabilities omp` -> `--adapter`; refresh TaskDefinition (depends_on, kind); add V1 section + 已知缺口; mark review-rule distribution gap resolved (include_str! verified). - GLOB_WORKFLOW.md / AGENTS.md / REPORT.md: control -> ctl; binary/scope/gate/dep corrections; reconcile REPORT Finding 5 self-contradiction. Code: - Wire approval_expired: add expire_stale_approvals (mirrors expire_stale_leases), called in workspace_apply, so expiry is recorded as an event instead of only lazily read as invalid. Schema-correct payload, idempotent, + test. - Remove dead field RunInfo.started_at_seq (written, never read). - Remove always-true `if spec_dir.exists()` dead branch in cmd_hook_spec_status. - Fix stale `control approval grant` -> `ctl approval grant` in error string. - Document deferred reducer scaffolding (M6 task-level run mirror; run-scoped gate/evidence duplicates) as forward-looking, not dead code. 417 tests pass; cargo fmt + clippy clean. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
neostfox
added a commit
that referenced
this pull request
Jun 20, 2026
gate-decision-log-v1 (audit A1 / sharpest-three #3). The gate computed allow/deny verdicts but recorded none of them — the audit trail had a hole exactly at the enforcement point. Wire the existing `ctl hook record-decision` into all three host gate hooks so blocked/flagged calls become evidence. - ctl: `record-decision` now stamps `canonical: false` on every entry (testable `decision_entry`); new `ctl decisions [--limit N] [--json]` viewer renders the log behind a prominent NON-CANONICAL banner (testable `format_decisions`). - gate: the bash_write verdict carries `record: true` (a bash write is never path-scoped, so every attempt is noteworthy). - hooks (.claude py / .omp ts / .opencode ts): after the gate verdict, record when `!allowed || record` — every deny plus bash_write allows. Best-effort and non-blocking: an advisory log must never break the gate it observes. - tests: Rust unit tests for the label/format/limit/empty/json paths; opencode bun tests for shouldRecord / buildRecordArgs and the record-on-deny / record-on-bash_write / no-record-on-plain-allow wiring. The log is non-canonical evidence: not a task event, not hash-chained, not covered by `ctl validate`. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
neostfox
added a commit
that referenced
this pull request
Jul 26, 2026
…cript Updates the V1 cognitive-layer bullet (ctl spec fact ✅ -> 'knowledge/memory layer externalized'), the memory-carrier table (atomic facts writer is now scripts/knowledge.py fact add), marks #1/S + #2/S done, and reframes the remaining memory-system backlog (#3/#4/#5/#6) to target scripts/knowledge.py instead of the removed ctl memory / ctl spec fact commands — consistent with the decision that ctl Rust stays governance-only and the knowledge layer is workflow-side.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Project-wide review pass: update outdated docs to match the actual shipped surface, and handle fractured/dead code logic. Code was already healthy (clean build, clippy clean), so the bulk of the drift was documentation.
Documentation (drift vs actual CLI/code)
control.jsonare shipped (M5 ✅), not "规划中"; status snapshot now M0–M6 + V1 (not "stops at M3"); replaced the deadcontrol *command namespace with real commands (ctl board/report/drift/agent-report/run ingest); fixed superseded module layout and the "OpenCode planned" claim.control status→ctl board; wrong--mode/--title/--scope/--gateflags → realtask createsyntax); removed non-existentctl assignment create;ctl adapter capabilities omp→--adapter omp; refreshedTaskDefinition(depends_on,kind); added a V1 cognitive-layer section + a 已知缺口 section; marked the review-rule distribution gap ✅ resolved (verified theinclude_str!fix is present).control→ctl; corrected binary name / milestone scope / "gates stubbed" / dependency list / protected-paths set; reconciled REPORT.md Finding 5 self-contradiction.Code
approval_expired— addedexpire_stale_approvals(mirrors the testedexpire_stale_leases), called inworkspace_apply, so an expired approval is now recorded as an event instead of only lazily read as invalid. Schema-correct payload (onlyrequest_id), idempotent, + a new test.RunInfo.started_at_seq(written, never read).if spec_dir.exists()incmd_hook_spec_status.control approval grant→ctl approval grant).Verification
cargo fmt --checkandcargo clippy --all-targetsclean.ctlgovernance (taskdocs-logic-audit-fixup, gatescargo_check/cargo_testPASS).🤖 Generated with Claude Code