Phase 11: Jobs, realtime and search infrastructure - Discussion Log
Audit trail only. Do not use as input to planning, research, or execution agents.
Decisions are captured in CONTEXT.md — this log preserves the alternatives considered.
Date: 2026-09-28
Phase: 11-jobs-realtime-and-search-infrastructure
Areas discussed: Job manager & job records, Broadcast delivery path, Realtime package split, Worker & scheduler model (incl. search sync)
Job manager & job records
The first question set was interrupted; the user ran /gsd-explore on whether to port Apparatus as a plugin or fold it into the framework. Outcome: .planning/notes/apparatus-dissolved-into-framework.md.
| Option |
Description |
Selected |
(a) keep golem15_apparatus_jobs |
Straight copy at cutover; framework carries a plugin prefix |
|
(b) summer_jobs |
Framework-owned name, same columns and int ids, rows copied at cutover |
✓ |
| (c) configurable table name |
Per-app name |
|
| Option |
Description |
Selected |
| Mirror PHP exactly |
Same integers; skip = COMPLETE + {skipped:true}; ERROR only on River discard |
✓ |
| Add SKIPPED status |
New integer 5 |
|
| Option |
Description |
Selected |
| Both |
is_canceled + STOPPED and River JobCancel; long jobs still poll |
✓ |
| Poll is_canceled only |
Literal PHP port |
|
| River cancel + ctx only |
Go-idiomatic |
|
User's choice: Dissolve Apparatus into the framework; summer_jobs; mirror PHP outcomes; cancel via both.
Broadcast delivery path
| Option |
Description |
Selected |
| River job in the write tx |
Published only after commit; broadcasts queue |
✓ |
| Direct publish after commit |
Goroutine after-commit callback |
|
| Synchronous publish |
Inline in request |
|
| Option |
Description |
Selected |
| One attempt, best-effort |
tries=1, 5 s timeout, warn on failure |
✓ |
| Small retry |
River backoff, e.g. 3 attempts |
|
| Option |
Description |
Selected |
| Per model type, ctx-scoped |
WithoutBroadcasting[T](ctx, fn) |
✓ |
| All broadcasts in ctx |
One switch |
|
| Option |
Description |
Selected |
| Capture from PHP Centrifugo |
tide subscribes during recorded flows; goldens |
✓ |
| Goldens derived from PHP code |
Hand-written expected payloads |
|
Realtime package split
| Option |
Description |
Selected |
| Framework package + thin plugin |
Generic realtime package in summercms.go |
|
| All in fonoteka.go plugin |
Wholesale app port |
|
| Framework-bundled plugin |
First-party plugin in summercms.go |
|
User's choice: Free text: concerned about hard dependency on Centrifugo; wants transports pluggable so a project can replace it. Claude proposed a transport-neutral realtime package with drivers (centrifugo, memory, log, null), a Centrifugo sub-package, and Web Push behind its own interface. User: "yep that's perfect."
| Option |
Description |
Selected |
| Hand-rolled net/http |
Stdlib-first, exact request bytes |
✓ |
| gocent/v3 |
Official client |
|
| Option |
Description |
Selected |
| Health check only |
Defer push/VAPID |
|
| Port all of it |
Web Push + both VAPID commands + health check |
✓ |
| None |
|
|
| Option |
Description |
Selected |
| Only what's called |
Connection + subscription tokens |
|
| Full parity with PHP class |
All generator variants |
✓ |
Route mounting: Claude first suggested app-mounted handlers (wristband precedent). User asked: "won't that be a problem if we implement multiple drivers in future?" Claude refined it: drivers declare Routes() by surface (UserAuth / ServerToServer / Public) and the app maps surfaces to its groups and buckets through realtime.Mount. User's choice: Lock it.
Worker & scheduler model
| Option |
Description |
Selected |
| In serve by default + queue:work |
queue.work_in_serve toggle |
✓ |
| Only queue:work |
Separate process always |
|
| Option |
Description |
Selected |
| River periodic jobs |
HasSchedule → periodic jobs, leader-elected; schedule:run foreground + --once |
✓ |
| Laravel-style schedule:run |
System cron every minute, own locking |
|
| Option |
Description |
Selected |
| Driver interface, Typesense driver |
Searchable interface + engine driver; hand-rolled Typesense |
✓ |
| Typesense client only |
Direct port, no abstraction |
|
| Option |
Description |
Selected |
| After commit, inline, non-fatal |
Like Scout queue=false |
✓ |
| River job in the write tx |
Retryable, lags the write |
|
Claude's Discretion
- Package names and layout; ctx-to-GORM-hook plumbing for suppression; actor capture; River client config and timed-test harness; whether the Phase 8 expiry sweep moves to a periodic job; tide Centrifugo capture mechanics.
Deferred Ideas
- The admin extension point ("Phase 10.1") is not on the roadmap. It stays deferred, and the references that call it a phase need rewording.
- More realtime/search drivers; a Jobs admin screen; backend admin API tokens; guarded HTTP client and redacting slog handler (pending todos).