diff --git a/docs/plans/M5d2/01-loopd-session-slot.md b/docs/plans/M5d2/01-loopd-session-slot.md index 8b5dfd6..107d2a0 100644 --- a/docs/plans/M5d2/01-loopd-session-slot.md +++ b/docs/plans/M5d2/01-loopd-session-slot.md @@ -80,10 +80,11 @@ become Then `git status --short` lists one new file, `crates/loopd/tests/session_slot.rs`. - [ ] **2. See it fail.** `cargo test -p loopd --test session_slot`, with a shell timeout of - 600000 ms: the four tests compile (they call `session.slot()`, which exists) and + 600000 ms: the four tests compile (they call `session.slot()`, which exists) and three fail. `a_session_created_on_the_background_slot_sends_that_slot` fails: `id_slot` is `0`, not `1`. - `a_main_slot_session_still_sends_main` may pass. `the_request_slot_follows_session_start_not_the_config_main` - fails: `id_slot` is `0`, not `7`. + `a_main_slot_session_still_sends_main` passes. `the_request_slot_follows_session_start_not_the_config_main` + fails: `id_slot` is `0`, not `7`. `a_heartbeat_turn_between_two_main_turns_does_not_change_the_main_prefix` + fails: the heartbeat request's `id_slot` is `0`, not `1`. - [ ] **3. The edit.** Make edit T1. `cargo check -p loopd` prints no error and no warning. - [ ] **4. The tests.** `cargo test -p loopd --test session_slot --test turn --test channel --test compact_turn`: every test passes, 4 in `session_slot` and the others as before. diff --git a/docs/plans/M5d2/04-loopd-cron.md b/docs/plans/M5d2/04-loopd-cron.md index 1ba41bb..42d4ee4 100644 --- a/docs/plans/M5d2/04-loopd-cron.md +++ b/docs/plans/M5d2/04-loopd-cron.md @@ -165,28 +165,29 @@ If `is_multiple_of` is not in scope on `u32` in this toolchain, use `value % n = target compiles. - [ ] **3. The module.** Write `cron.rs` from the skeleton. Add `pub mod cron;` to `lib.rs` where rustfmt will put it. `cargo check -p loopd` prints no error and no warning. -- [ ] **4. The tests.** `cargo test -p loopd --test cron`: every test passes, 10 tests. +- [ ] **4. The tests.** `cargo test -p loopd --test cron`: every test passes, 11 tests. - [ ] **5. Check.** `git diff --stat` lists `crates/loopd/src/cron.rs` and `crates/loopd/src/lib.rs`. - [ ] **6. Run the gate.** `cargo fmt --all`, then `make gate`, timeout 600000 ms. Last line - `gate: ok`. Test count **1536** (1526 before, plus 10). + `gate: ok`. Test count **1537** (1526 before, plus 11). - [ ] **7. Log and commit.** Task cell `M5d2/04-loopd-cron`. Stage with `git add crates/loopd docs/implementer-log.md Cargo.lock`, then commit as `AGENTS.md` says. ## Done when -- `make gate` prints `gate: ok` with 1536 tests. +- `make gate` prints `gate: ok` with 1537 tests. - `git diff HEAD~1 --stat` lists only `crates/loopd/src/cron.rs`, `crates/loopd/src/lib.rs`, `crates/loopd/tests/cron.rs` and the log. ## Stop and report if - The branch check fails; step 2 differs; `cargo check` warns; a test fails; the gate count is - not 1536; you add a dependency; you treat DOM and DOW as OR; you accept `@daily` or a sixth + not 1537; you add a dependency; you treat DOM and DOW as OR; you accept `@daily` or a sixth field; `cron.rs` exceeds 500 lines. ## What the given tests pin `0 7 * * *` matches that minute only; `* * * * *` matches; `*/5` is multiples from 0; `0-10` -is inclusive; Sunday is 0 on 2026-10-04; the 1st of the month; four or six fields error; +is inclusive; Sunday is 0 on 2026-10-04; the 1st of the month; DOM and DOW are AND +(`0 7 1 * 0` matches 2026-11-01, not 2026-10-04 or 2026-10-01); four or six fields error; minute 60, hour 24, DOW 7 error; `@daily` errors; `Display` names the schedule. diff --git a/docs/plans/M5d2/05-loopd-jobs.md b/docs/plans/M5d2/05-loopd-jobs.md index ba61c94..d14c601 100644 --- a/docs/plans/M5d2/05-loopd-jobs.md +++ b/docs/plans/M5d2/05-loopd-jobs.md @@ -194,21 +194,21 @@ Replace the four stub bodies in the same task. Do not leave a stub that returns - [ ] **6. Check.** `git diff --stat` lists `crates/loopd/src/jobs.rs`, `crates/loopd/src/lib.rs`, `crates/loopd/src/main.rs`, `docs/runbook.md`. - [ ] **7. Run the gate.** `cargo fmt --all`, then `make gate`, timeout 600000 ms. Last line - `gate: ok`. Test count **1544** (1536 before, plus 8). + `gate: ok`. Test count **1545** (1537 before, plus 8). - [ ] **8. Log and commit.** Task cell `M5d2/05-loopd-jobs`. Stage with `git add crates/loopd docs/runbook.md docs/implementer-log.md Cargo.lock`, then commit as `AGENTS.md` says. ## Done when -- `make gate` prints `gate: ok` with 1544 tests. +- `make gate` prints `gate: ok` with 1545 tests. - `git diff HEAD~1 --stat` lists only `crates/loopd/src/jobs.rs`, `crates/loopd/src/lib.rs`, `crates/loopd/src/main.rs`, `crates/loopd/tests/jobs.rs`, `docs/runbook.md` and the log. ## Stop and report if - The branch check fails; step 2 differs; `cargo check` warns; a test fails; the gate count is - not 1544; you add a cron crate; a missing jobs directory is an error; `Display` for `Invalid` + not 1545; you add a cron crate; a missing jobs directory is an error; `Display` for `Invalid` does not end with the runbook pointer; `jobs.rs` exceeds 500 lines; you write a job file. ## What the given tests pin diff --git a/docs/plans/M5d2/06-loopd-scheduler-heartbeat.md b/docs/plans/M5d2/06-loopd-scheduler-heartbeat.md index da0e76f..4012084 100644 --- a/docs/plans/M5d2/06-loopd-scheduler-heartbeat.md +++ b/docs/plans/M5d2/06-loopd-scheduler-heartbeat.md @@ -190,14 +190,14 @@ pass so it runs once. `grep -c '^## heartbeat-prompt-unreadable$' docs/runbook.md` prints `1`. - [ ] **6. Run the gate.** `cargo fmt --all`, then `make gate`, timeout 600000 ms. Last line - `gate: ok`. Test count **1550** (1544 before, plus 6). + `gate: ok`. Test count **1551** (1545 before, plus 6). - [ ] **7. Log and commit.** Task cell `M5d2/06-loopd-scheduler-heartbeat`. Stage with `git add crates/loopd docs/runbook.md docs/implementer-log.md Cargo.lock`, then commit as `AGENTS.md` says. ## Done when -- `make gate` prints `gate: ok` with 1550 tests. +- `make gate` prints `gate: ok` with 1551 tests. - `git diff HEAD~1 --stat` lists only `crates/loopd/src/channel.rs`, `crates/loopd/src/scheduler.rs`, `crates/loopd/src/lib.rs`, `crates/loopd/src/main.rs`, `crates/loopd/tests/scheduler_heartbeat.rs`, `docs/runbook.md` and the log. @@ -205,7 +205,7 @@ pass so it runs once. ## Stop and report if - The branch check fails; a "before" block is not found exactly once; step 2 differs; `cargo - check` warns; a test fails; the gate count is not 1550; you sleep in a test; you post to + check` warns; a test fails; the gate count is not 1551; you sleep in a test; you post to Mattermost; you send `slots.main`; you set a speaker; you `chmod 000`; `Held` stays private. ## What the given tests pin diff --git a/docs/plans/M5d2/07-loopd-scheduler-cron.md b/docs/plans/M5d2/07-loopd-scheduler-cron.md index c8d90a0..8d6e297 100644 --- a/docs/plans/M5d2/07-loopd-scheduler-cron.md +++ b/docs/plans/M5d2/07-loopd-scheduler-cron.md @@ -82,20 +82,20 @@ Do not change heartbeat behaviour. Do not add a sleep. --test jobs --test cron`: every test passes, 6 in `scheduler_cron`. - [ ] **5. Check.** `git diff --stat` lists exactly `crates/loopd/src/scheduler.rs`. - [ ] **6. Run the gate.** `cargo fmt --all`, then `make gate`, timeout 600000 ms. Last line - `gate: ok`. Test count **1556** (1550 before, plus 6). + `gate: ok`. Test count **1557** (1551 before, plus 6). - [ ] **7. Log and commit.** Task cell `M5d2/07-loopd-scheduler-cron`. Stage with `git add crates/loopd docs/implementer-log.md Cargo.lock`, then commit as `AGENTS.md` says. ## Done when -- `make gate` prints `gate: ok` with 1556 tests. +- `make gate` prints `gate: ok` with 1557 tests. - `git diff HEAD~1 --stat` lists only `crates/loopd/src/scheduler.rs`, `crates/loopd/tests/scheduler_cron.rs` and the log. ## Stop and report if - The branch check fails; step 2 differs (tests do not compile, or a different assertion - fails); `cargo check` warns; a test fails; the gate count is not 1556; you reuse a session + fails); `cargo check` warns; a test fails; the gate count is not 1557; you reuse a session across runs; you send `slots.main`; you set a speaker; you skip leftovers that are not busy; a heartbeat test fails. diff --git a/docs/plans/M5d2/08-loopd-tool-summary.md b/docs/plans/M5d2/08-loopd-tool-summary.md index 398dd74..19959ae 100644 --- a/docs/plans/M5d2/08-loopd-tool-summary.md +++ b/docs/plans/M5d2/08-loopd-tool-summary.md @@ -231,14 +231,14 @@ Facts). The `Err(TurnError::SessionFull)` arm gets the same `missing` call befor - [ ] **5. Check.** `git diff --stat` lists `crates/loopd/src/summarise.rs`, `crates/loopd/src/lib.rs`, `crates/loopd/src/compact.rs`. - [ ] **6. Run the gate.** `cargo fmt --all`, then `make gate`, timeout 600000 ms. Last line - `gate: ok`. Test count **1562** (1556 before, plus 6). + `gate: ok`. Test count **1563** (1557 before, plus 6). - [ ] **7. Log and commit.** Task cell `M5d2/08-loopd-tool-summary`. Stage with `git add crates/loopd config/summarise.md docs/implementer-log.md Cargo.lock`, then commit as `AGENTS.md` says. ## Done when -- `make gate` prints `gate: ok` with 1562 tests. +- `make gate` prints `gate: ok` with 1563 tests. - `git diff HEAD~1 --stat` lists only `crates/loopd/src/summarise.rs`, `crates/loopd/src/lib.rs`, `crates/loopd/src/compact.rs`, `crates/loopd/tests/tool_summary_call.rs`, `config/summarise.md` and the log. @@ -246,7 +246,7 @@ Facts). The `Err(TurnError::SessionFull)` arm gets the same `missing` call befor ## Stop and report if - The branch check fails; a "before" block is not found exactly once; step 2 differs; `cargo - check` warns; a test fails; the gate count is not 1562; you send tools on the summary + check` warns; a test fails; the gate count is not 1563; you send tools on the summary request; you send `slots.main`; you replay the summary in `messages()`; you summarise a name not in the list; you summarise twice; you compact before summarising. diff --git a/docs/plans/M5d2/09-memoryd-summary-index.md b/docs/plans/M5d2/09-memoryd-summary-index.md index a5e7e33..890897f 100644 --- a/docs/plans/M5d2/09-memoryd-summary-index.md +++ b/docs/plans/M5d2/09-memoryd-summary-index.md @@ -117,21 +117,21 @@ results are indexed. A comment that does not fit its code: stop. test passes, 5 in `tool_summary_index`. - [ ] **5. Check.** `git diff --stat` lists exactly `crates/memoryd/src/index.rs`. - [ ] **6. Run the gate.** `cargo fmt --all`, then `make gate`, timeout 600000 ms. Last line - `gate: ok`. Test count **1567** (1562 before, plus 5). + `gate: ok`. Test count **1568** (1563 before, plus 5). - [ ] **7. Log and commit.** Task cell `M5d2/09-memoryd-summary-index`. Stage with `git add crates/memoryd docs/implementer-log.md Cargo.lock`, then commit as `AGENTS.md` says. ## Done when -- `make gate` prints `gate: ok` with 1567 tests. +- `make gate` prints `gate: ok` with 1568 tests. - `git diff HEAD~1 --stat` lists only `crates/memoryd/src/index.rs`, `crates/memoryd/tests/tool_summary_index.rs` and the log. ## Stop and report if - The branch check fails; a "before" block is not found exactly once; step 2 differs; `cargo - check` warns; a test fails; the gate count is not 1567; you index `ToolResult` content; you + check` warns; a test fails; the gate count is not 1568; you index `ToolResult` content; you leave `kind` as `"session"`; a secret summary is searchable on a private index. ## What the given tests pin diff --git a/docs/plans/M5d2/10-memoryd-tiers.md b/docs/plans/M5d2/10-memoryd-tiers.md index 1278f62..5af8542 100644 --- a/docs/plans/M5d2/10-memoryd-tiers.md +++ b/docs/plans/M5d2/10-memoryd-tiers.md @@ -203,20 +203,20 @@ becomes - [ ] **5. Check.** `git diff --stat` lists the files in "Every place that changes" except the new tests and starter (those are untracked until `git add`). - [ ] **6. Run the gate.** `cargo fmt --all`, then `make gate`, timeout 600000 ms. Last line - `gate: ok`. Test count **1573** (1567 before, plus 6). + `gate: ok`. Test count **1574** (1568 before, plus 6). - [ ] **7. Log and commit.** Task cell `M5d2/10-memoryd-tiers`. Stage with `git add crates/bxcore crates/memoryd crates/bxctl config/jobs docs/implementer-log.md Cargo.lock`, then commit as `AGENTS.md` says. ## Done when -- `make gate` prints `gate: ok` with 1573 tests. +- `make gate` prints `gate: ok` with 1574 tests. - `git diff HEAD~1 --stat` lists only the files this task names plus the log. ## Stop and report if - The branch check fails; a "before" block is not found exactly once; step 2 differs; `cargo - check` warns; a test fails; the gate count is not 1573; you edit `cli_memory.rs`; identity + check` warns; a test fails; the gate count is not 1574; you edit `cli_memory.rs`; identity entries show a non-zero hit count; untrusted notes are omitted; you add a cron crate; you special-case the name `tier` in `loopd`. diff --git a/docs/plans/M5d2/README.md b/docs/plans/M5d2/README.md index 56a41d4..4722590 100644 --- a/docs/plans/M5d2/README.md +++ b/docs/plans/M5d2/README.md @@ -63,13 +63,13 @@ its own. Before task 01 it is **1508** (straylight, rustc 1.98.1 / clippy 0.1.98 | 01 | `run_turn` sends `session.slot()`; a session created on the background slot has `id_slot` of that slot; `mm-*` stays main | loopd | 1512 | 4 | | 02 | `LogRecord::ToolSummary`; `messages()` ignores it; `types` names it; JSON round-trip | bxcore, loopd | 1518 | 6 | | 03 | `[heartbeat]` (`every_s` 0, `prompt` `heartbeat.md`); unknown `[compact] summarise` names are a config error; starter `heartbeat.md` | loopd | 1526 | 8 | -| 04 | Five-field UTC cron matcher: `*`, `n`, `n-m`, `*/n`; day-of-week 0 is Sunday | loopd | 1536 | 10 | -| 05 | `{home}/jobs/.toml`, `runs.jsonl`, `jobs-invalid` pointer and runbook | loopd | 1544 | 8 | -| 06 | Heartbeat tick: time line, standing session `heartbeat`, skip if busy, unreadable prompt logged once | loopd | 1550 | 6 | -| 07 | Cron tick: new session per run, skip if previous live, leftover `started` becomes `failed` | loopd | 1556 | 6 | -| 08 | After a turn, opted-in `ToolResult` without a `ToolSummary` gets a background summary appended with the result's labels | loopd | 1562 | 6 | -| 09 | `memoryd` indexes `ToolSummary` as kind `summary`; `recall` finds it and taints when untrusted | memoryd | 1567 | 5 | -| 10 | `MemoryCommand::Tiers`, `bxctl memory tiers`, starter `config/jobs/tier.toml.example` | bxcore, memoryd, bxctl | 1573 | 6 | +| 04 | Five-field UTC cron matcher: `*`, `n`, `n-m`, `*/n`; day-of-week 0 is Sunday | loopd | 1537 | 11 | +| 05 | `{home}/jobs/.toml`, `runs.jsonl`, `jobs-invalid` pointer and runbook | loopd | 1545 | 8 | +| 06 | Heartbeat tick: time line, standing session `heartbeat`, skip if busy, unreadable prompt logged once | loopd | 1551 | 6 | +| 07 | Cron tick: new session per run, skip if previous live, leftover `started` becomes `failed` | loopd | 1557 | 6 | +| 08 | After a turn, opted-in `ToolResult` without a `ToolSummary` gets a background summary appended with the result's labels | loopd | 1563 | 6 | +| 09 | `memoryd` indexes `ToolSummary` as kind `summary`; `recall` finds it and taints when untrusted | memoryd | 1568 | 5 | +| 10 | `MemoryCommand::Tiers`, `bxctl memory tiers`, starter `config/jobs/tier.toml.example` | bxcore, memoryd, bxctl | 1574 | 6 | After task 10, the design model runs the M5d-2 device check (spec 7) on straylight: a heartbeat turn on the background slot leaves a following main-slot turn's `cache_n` near its previous @@ -129,4 +129,4 @@ OPENCODE_MODEL=llama.cpp/ornith-1.5-35b-a3b RESUME=1 \ tools/run-plan.sh docs/plans/M5d2 01 ``` -After task 10, the gate count is 1573. Then the device check above. +After task 10, the gate count is 1574. Then the device check above. diff --git a/docs/plans/M5d2/files/crates/loopd/tests/cron.rs b/docs/plans/M5d2/files/crates/loopd/tests/cron.rs index 0aa422b..6a4c964 100644 --- a/docs/plans/M5d2/files/crates/loopd/tests/cron.rs +++ b/docs/plans/M5d2/files/crates/loopd/tests/cron.rs @@ -52,6 +52,15 @@ fn the_first_of_the_month() { assert!(!due("0 7 1 * *", "2026-10-02T07:00:00.000Z")); } +#[test] +fn day_of_month_and_day_of_week_are_both_required() { + // 2026-10-04 is a Sunday, not the 1st. 2026-10-01 is a Thursday. + assert!(!due("0 7 1 * 0", "2026-10-04T07:00:00.000Z")); + assert!(!due("0 7 1 * 0", "2026-10-01T07:00:00.000Z")); + // 2026-11-01 is a Sunday and the 1st. + assert!(due("0 7 1 * 0", "2026-11-01T07:00:00.000Z")); +} + #[test] fn four_or_six_fields_are_errors() { assert!(matches!( diff --git a/docs/plans/M5d2/files/crates/loopd/tests/scheduler_cron.rs b/docs/plans/M5d2/files/crates/loopd/tests/scheduler_cron.rs index bc21e08..c4356f7 100644 --- a/docs/plans/M5d2/files/crates/loopd/tests/scheduler_cron.rs +++ b/docs/plans/M5d2/files/crates/loopd/tests/scheduler_cron.rs @@ -64,11 +64,8 @@ fn a_due_job_starts_a_new_session_on_the_background_slot() { let sent = server.requests_to(CHAT); assert_eq!(sent.len(), 1); assert_eq!(sent[0].json()["id_slot"], 1); - let last = sent[0].json()["messages"] - .as_array() - .unwrap() - .last() - .unwrap(); + let body = sent[0].json(); + let last = body["messages"].as_array().unwrap().last().unwrap(); let content = last["content"].as_str().unwrap(); assert!( content.starts_with("It is 2026-10-03T07:00:00.000Z."), diff --git a/docs/plans/M5d2/files/crates/loopd/tests/scheduler_heartbeat.rs b/docs/plans/M5d2/files/crates/loopd/tests/scheduler_heartbeat.rs index a6b1b46..303250e 100644 --- a/docs/plans/M5d2/files/crates/loopd/tests/scheduler_heartbeat.rs +++ b/docs/plans/M5d2/files/crates/loopd/tests/scheduler_heartbeat.rs @@ -65,11 +65,8 @@ fn a_due_heartbeat_runs_on_the_background_slot_with_a_time_line() { let sent = server.requests_to(CHAT); assert_eq!(sent.len(), 1); assert_eq!(sent[0].json()["id_slot"], 1); - let last = sent[0].json()["messages"] - .as_array() - .unwrap() - .last() - .unwrap(); + let body = sent[0].json(); + let last = body["messages"].as_array().unwrap().last().unwrap(); assert_eq!(last["role"], "user"); let content = last["content"].as_str().unwrap(); assert!( diff --git a/docs/plans/M5d2/files/crates/loopd/tests/session_slot.rs b/docs/plans/M5d2/files/crates/loopd/tests/session_slot.rs index b4db135..d70b542 100644 --- a/docs/plans/M5d2/files/crates/loopd/tests/session_slot.rs +++ b/docs/plans/M5d2/files/crates/loopd/tests/session_slot.rs @@ -161,7 +161,7 @@ fn a_heartbeat_turn_between_two_main_turns_does_not_change_the_main_prefix() { assert_eq!(sent[1].json()["id_slot"], 1); assert_eq!(sent[2].json()["id_slot"], 0); let first = sent[0].json()["messages"].as_array().unwrap().clone(); - let third = sent[2].json()["messages"].as_array().unwrap(); + let third = sent[2].json()["messages"].as_array().unwrap().clone(); assert!( third.len() > first.len(), "the main session's second turn is a longer prefix"