17 KiB
phase, plan, subsystem, tags, requires, provides, affects, actuals, plan_head_before, plan_head_after, tech-stack, key-files, key-decisions, patterns-established, requirements-completed, coverage, duration, completed, status
| phase | plan | subsystem | tags | requires | provides | affects | actuals | plan_head_before | plan_head_after | tech-stack | key-files | key-decisions | patterns-established | requirements-completed | coverage | duration | completed | status | ||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| 11-jobs-realtime-and-search-infrastructure | 02 | infra |
|
|
|
|
|
77b8ff177a |
2237a640d2 |
|
|
|
|
|
|
385min | 2026-09-29 | complete |
Phase 11 Plan 02: scheduler on River periodic jobs Summary
pact.HasSchedule with Daily/DailyAt/Every cadences becomes River periodic jobs on wall-clock schedules in app.timezone. Each run is a one-attempt job on the scheduled queue that calls the command in-process through a bonfire.Catalog the generated main publishes. schedule:run runs a scheduler process, and schedule:run --once serves system cron.
Performance
- Duration: 385 min wall-clock. The clock ran from 14:09Z to 20:34Z; the active work was much shorter, and most of the span was idle time between tool calls.
- Started: 2026-09-29T14:09:06Z
- Completed: 2026-09-29T20:34:36Z
- Tasks: 2
- Files modified: 21 (19 in summercms.go, 2 in fonoteka.go)
Accomplishments
pacthas the schedule capability:HasSchedule,ScheduledCommand, the opaqueCadence(IsZero,Interval,At) and theDaily,DailyAtandEveryconstructors. pact still imports no River code.bonfire.Callandbonfire.Catalogrun any registered command in-process with empty stdin. The generated app main publishesbonfire.NewCatalog(commands)before building the root. The hello and fonoteka mains were regenerated.conga.Dailyandconga.Everyareriver.PeriodicScheduleimplementations:Dailyfires at the next wall-clockhh:mm. On DST days the runs are 23h or 25h apart but stay at 12:00 local.Everyfires at the next multiple of its interval since local midnight and rolls over to the next midnight.- Both fire strictly after an exact boundary.
- Every worker carries one periodic job per schedule entry, with id
<plugin id>[<index>]:<command>. Runs go to thescheduledqueue (queue.queues.scheduled, default 1) with MaxAttempts 1 and uniqueness by args within the cadence period. - The worker runs a job only when it matches a compiled entry exactly (T-11-09). A missing catalog or an unregistered command is logged at Warn and skipped (user decision 5). Command output is logged line by line with a
commandattribute. schedule:runruns a scheduler-only worker until SIGINT or SIGTERM.schedule:run --onceruns the entries due this minute without River, printsRunning scheduled command: …orNo scheduled commands are ready to run., and returns the first command error. Thesummerdelegate forwards--once.- fonoteka declares
fonoteka:prune-notificationsdaily inschedule.go.plugin.gois untouched, so plan 11-03 can edit it.
Task Commits
summercms.go:
- Task 1: an interval command runs on schedule inside a worker through bonfire.Call -
d9f939a(feat) - Task 2: schedule:run as a dedicated process and from system cron -
2237a64(feat)
fonoteka.go:
- Task 1 -
37133f9(feat: regenerated main.go publishes the catalog) - Task 2 -
94e95de(feat: schedule.go with the daily prune entry)
Files Created/Modified
modules/pact/capabilities.go,modules/pact/README.md: the schedule capability and cadencesmodules/bonfire/call.go,call_test.go,README.md:Call,Catalog,NewCatalogandErrUnknownCommandmodules/conga/schedule.go:Daily,Every,scheduleForandappLocationmodules/conga/scheduler.go:QueueScheduled,ScheduledCommandArgs, entry compilation, periodic jobs, the built-in job, the T-11-09 check and the log writermodules/conga/worker.go,conga.go:StartWorkersetsPeriodicJobs, registers the built-in job and adds thescheduledqueuemodules/conga/commands.go:schedule:run [--once]modules/conga/schedule_test.go: seven tests.listen_test.go: the serve worker now also listsscheduledinternal/build/build.go,build_test.go: the catalog publish in the generated maincmd/summer/main.go,runtime.go,main_test.go: theschedule:rundelegateexamples/hello/main.goand../fonoteka.go/main.go(regenerated), and../fonoteka.go/plugins/golem15/fonoteka/schedule.go
Decisions Made
See key-decisions in the frontmatter.
TDD Gate Compliance
Task 2 is tdd="true". The tests were written first against a stub schedule:run that returned "not implemented".
- RED:
TestScheduleRunOncefailed on its assertions in all 6 subtests.gsd-tools check tdd-red-evidencereturnedRED_EVIDENCE_OK(target_test_failed). The command wasgo test ./modules/conga -run '^(TestScheduleRunOnce|TestScheduledEntryMismatchSkipped|TestScheduleUniqueByPeriod)$' -count=1 -json, converted to TAP. TestScheduledEntryMismatchSkippedandTestScheduleUniqueByPeriodpassed at RED. Task 1's action required the exact-match check (T-11-09) and the ByPeriod insert options, so both mitigations already existed. To prove the tests are real, I ran a mutation check and restored the code afterwards:- Running the job's own command instead of the compiled entry failed
TestScheduledEntryMismatchSkipped("no skip warning"). - Dropping
ByArgsfailedTestScheduleUniqueByPeriod("river_job rows = 1, want 2").
- Running the job's own command instead of the compiled entry failed
- Gate violation, flagged: there is no separate
test(11-02)RED commit. The project CLAUDE.md requiresgo vetandgo test ./...to be green at every commit, so the tests and implementation landed together in2237a64. Plan 11-01 made the same call.
Deviations from Plan
Auto-fixed Issues
1. [Rule 1 - Bug] Periodic job id separator
- Found during: Task 1 (reading River's
PeriodicJob.validate) - Issue: River requires
PeriodicJob.IDto match\A[\w][\w\-\[\]<>\/.·:+]+\z, which has no#. The plan's<plugin id>#<index>:<command>would have failed every worker start that had a schedule. - Fix: Ids use
<plugin id>[<index>]:<command>, for examplegolem15.fonoteka[0]:fonoteka:prune-notifications. Ordering, uniqueness and readability are unchanged. - Files modified: modules/conga/scheduler.go
- Verification:
TestScheduleEntrieschecks the ids.TestScheduleRunsCommandstarts a worker with them. - Committed in:
d9f939a
2. [Rule 1 - Bug] ByPeriod alone deduplicates across entries
- Found during: Task 1 (River
UniqueOptsdocs: withoutByArgs, uniqueness applies to all jobs of the kind) - Issue: Every entry shares the kind
summer.scheduled_command, soByPeriodalone would drop the second entry due in the same period. - Fix:
UniqueOpts{ByArgs: true, ByPeriod: period}. The args hold the entry id, so the dedupe is per entry. - Files modified: modules/conga/scheduler.go
- Verification:
TestScheduleUniqueByPeriodchecks both parts: the same entry twice gives 1 row, and a second entry gives 2 rows. The mutation check confirms the second part catches the bug. - Committed in:
d9f939a
3. [Rule 2 - Missing validation] Cadence bounds
- Found during: Task 1
- Issue: River rejects
ByPeriodunder 1s.DailyAt(25, 0)would silently normalize to 01:00 the next day. - Fix:
scheduleForalso rejects intervals under 1s and daily times outside 00:00-23:59, with the plugin id and entry index in the error. - Files modified: modules/conga/schedule.go
- Verification:
TestScheduleNextcovers the invalid cadences. - Committed in:
d9f939a
4. [Rule 3 - Blocking] schedule:run --once needs the database for real commands
- Found during: Task 2 (fonoteka commands call
app.Lookup[*gorm.DB]()) - Issue: The plan says "no River" for
--oncebut does not say how commands reach the database. - Fix: When an entry is due,
--onceopens and publishes the database the wayqueue:workdoes, or reuses an already published*gorm.DB. With nothing due it touches no database. - Files modified: modules/conga/commands.go
- Verification: The
TestScheduleRunOncesubtests.fonoteka schedule:run --onceexits 0. - Committed in:
2237a64
5. [Behaviour change, documented] Serve and queue:work workers list the scheduled queue
- The plan requires the
scheduledqueue on every known-queue set, soTestStartServeWorkernow expects[default scheduled]. The unknown-queue error lists it too.TestQueueWorkCommandstill passes because the list starts withdefault.
6. [Additions] Extra tests and small internals
- Added
TestScheduleEntries(the empty, invalid and ordering edges) andTestScheduleRunForeground(the foregroundschedule:run). - The unexported
scheduleNowclock serves the--oncetests. gofmtreordered the pre-existing out-of-order imports ofcmd/summer/main.goandruntime.go. Both files were already being edited.
Total deviations: 4 auto-fixed (2 bugs, 1 missing validation, 1 blocking), plus 2 documented notes. Impact on plan: Fixes 1 and 2 were needed for schedules to start at all and to run every entry. No scope creep.
Issues Encountered
internal/build/registry.gostill has gofmt drift from before this plan. It is out of scope and was left untouched.
Known Stubs
None. The fonoteka entry names fonoteka:prune-notifications, which Phase 14 registers. Until then the scheduler warns and skips it by design (user decision 5, research Open Question 5).
User Setup Required
None. For system cron, run * * * * * cd /app && ./bin/<app> schedule:run --once. Alternatively run a schedule:run process, or rely on the worker inside serve/queue:work, which already processes the scheduled queue.
Next Phase Readiness
- Plan 11-03 can register the broadcast job through
conga.Managerand edit fonotekaplugin.go. This plan left both alone. - Plan 11-07 should add coverage for:
logWriterpartial-line flushing- the missing-catalog skip path
dueAtwithEvery(d)over 1 minute that does not divide an hour (for example 90s)- the
summer schedule:run --onceargv forwarding
- In Phase 14, registering
fonoteka:prune-notificationsmakes the daily entry run with no scheduler change.
Self-Check: PASSED
- All six created files exist on disk.
- summercms.go commits
d9f939aand2237a64exist, and so do fonoteka.go commits 37133f9 and 94e95de. - After Task 2,
go vet ./... && go test ./...passed in summercms.go, and the fonoteka.go vet and test command for all three modules passed. - All acceptance-criteria greps and
go docchecks for both tasks passed.
Phase: 11-jobs-realtime-and-search-infrastructure Completed: 2026-09-29