Files

6.6 KiB

Row 49 / #1535 — bus comment cursor across restarts, round 1

Base: 7b166a0f1 (origin/refactor). Detached worktree: /mnt/storage/src/mosaic-stack-rocko-49. Source code is uncommitted; Sage lands build.patch. Reviewers: Darkwing and Filbert. Scope is comments only.

Root cause and change

The adapter's comment count and last ID caches vanished at restart. Startup reconcile seeded a new count without checking the comment list, and the next updated-window poll could miss a comment posted during a long outage.

The broker now stores the highest observed comment ID per business/task in a separate append-only table. Adapter startup restores IDs before reconciliation; the first reconcile checks comment lists even if no count is cached. Cursor rows, snapshots and comment events commit in one transaction. ID, expanded count and updated caches advance after success. A cursor advances for bot comments too, but only foreign comments contribute to external events. Project moves retain the task ID. Neither task_current, task digests, event kinds nor tracker client changes. recordTask's existing return shape remains unchanged.

Exact persisted state and growth bound

DDL.sql contains the exact 3b→3c DDL: task_comment_cursors with seq INTEGER PRIMARY KEY AUTOINCREMENT, business TEXT, positive task_id and positive last_comment; an index on business/task_id/seq; three append-only guards and one strictly-increasing-ID guard. No timestamp or count is stored per cursor. An immutable meta entry comment_cursor_epoch is inserted once at creation or migration; the existing version/digest metadata advances from 3b to 3c only in the explicit offline migration.

A row is appended only when the maximum observed ID increases. Empty polls, unchanged counts, restarts, already-seen IDs and comment deletions append none. Growth is bounded by the number of observed new comments, usually one row per batch, never by poll frequency. No pruning is introduced.

Live-copy schema evidence and ordered migrations

Confirmed from CODE, never the live database, at live-copy commit 2f57e3c5995dc1c75fb506b43bea602172908bb1: Store requires version 3b, and schema.sql SHA-256 is 179ffe356d4ff19a49b5ebad39b6c6bfd7771deb8e1b7c55b5e746f039d69e65. live-schema-evidence.txt records the commands/results. The same immutable DDL is pinned in src/migrations/3b.sql and independently in the test fixture.

The migration test creates a schema-current 3b fixture, closes it, copies it and migrates only the copy. Source bytes and existing event evidence are preserved. It checks ordinary startup refusal, explicit migration/reopen/idempotence, writer-lock refusal, schema drift and incorrect metadata refusal. A retained future3d fixture appends a named 3c→3d step and proves the actual engine executes 3b→3c→3d in order. Researcher's row 123 can append its step and leave prior SQL pinned. Each step validates its input digest/version and commits its DDL, metadata and optional initializer atomically, then the next step runs. A failed later step can resume from the last committed version. Normal startup never migrates. Migration DDL lives under src/migrations so stack-copy's existing bus/src allowlist includes it; no stack-copy.sh edit.

MIGRATION.md describes Mos's separately approved downtime and re-pin window. No seat reads or migrates the live store, or starts/stops a live unit.

Initial cutoff and clock skew

Before any durable ID exists, timestamps are compared to database creation/ migration epoch minus a stated 60-second margin, rounded down to tracker seconds. A test pins a comment 30 seconds behind host time. This favors a possible cutover duplicate over losing a comment within the margin. Historical comments older than that cutoff are baselined without an event. More than 60 seconds of tracker lag or downtime before the initial migration cutoff remains outside this initial guarantee; a clock ahead can replay already emitted comments once at migration. After the first ID is durable, timestamps never determine freshness and long outages do not reset the cursor.

Approved scope extension — sage-0304, 2026-10-15 16:05:10Z

packages/bus/src/store.mjs exports SCHEMA_VERSION. scripts/land-work/read.mjs imports it instead of its local 3b literal, and updates the explanatory comment. Without this, its digest check would follow 3c but every version check would refuse a valid database. No other land-work behavior changes. test-land-work.sh is included in the builder gate; Darkwing (row 116 owner) reviews this row.

Verification and honest corrections

Sage sage-0307 granted the slot. busy.py is chained with && and returns a failing exit if busy. DOCKER_HOST=unix:///nonexistent.sock for all checks; no Docker run is required by the targeted gate. Gate list: bus, tasks, cli, webui, harness and seat node suites, plus scripts/test-land-work.sh. Relative import searches find these Store/Broker/adapter consumers; test-land-work.sh is the shell reader of Store/schema and land-work/read.mjs. Other shell scripts do not read the changed paths; the brief's full shell gate remains Sage's landing gate.

Focused red logs show the missing restart behavior and migration before code. An initial gate lacked the worktree's node_modules link; the CLI launcher refused that setup. The link now points to canonical pinned dependencies, as prior seat worktrees do. A second gate caught a candidate packaging defect: SQL first lived outside bus/src and pinned copies omitted it. SQL now lives under src/migrations; the real pinned-install test verifies it. Both failed runs are retained. Neither was a known #258/#261/#262/#263 flake. Final counts/results follow below.

Final gate: seven steps green, zero failures/skips. Node counts: bus 178, tasks 97, cli 195, webui 112 (real browser tests), harness 84, seat 132. land-work selftests: 84 passed, zero failed. Bus was rerun after adding final migration metadata/command coverage. DOCKER_HOST remained unix:///nonexistent.sock; no Docker run, network or daemon action occurred. No known flake recurred. gate/results.json and per-suite logs are the evidence.

22/22 mutants killed; no survivor, timeout, syntax/import failure or invalid mutation. MUTANTS.md maps every fault to its behavioral test. All 17 candidate hashes were restored. A fresh detached reconstruction applied build.patch at the base and verified all 17 manifest paths; the temporary verification worktree was removed. The restored restart/skew file also passed 5/5 after the mutants. The cwd/argv-scoped node/Chromium sweep found zero owned runners. No live systemd, dataRoot, token, binding, Settings or bus endpoint was touched.