diff options
| author | Craig Jennings <c@cjennings.net> | 2026-07-14 02:50:17 -0500 |
|---|---|---|
| committer | Craig Jennings <c@cjennings.net> | 2026-07-14 02:50:17 -0500 |
| commit | f14dd8798842dda026d2613791d77f44c258ade7 (patch) | |
| tree | 2ae0d71b2af6de99b2344409dfeee76412e55a43 | |
| parent | ad5bb0768a76ade4fbcb0f2ab99882478e55f4ba (diff) | |
| download | rulesets-f14dd8798842dda026d2613791d77f44c258ade7.tar.gz rulesets-f14dd8798842dda026d2613791d77f44c258ade7.zip | |
Spec for the overnight hygiene supervisor: ten serialized passes, mkdir-atomic locks with heartbeat under the runtime dir, a host-suffixed unpushed branch, interactive entry gates with no report-only mode, and roam-sync kept as the roam repo's only committer. All 10 decisions and 12 adversarial-review findings are resolved in the file; status stays DRAFT pending a deep read. The build task and three deferrals are filed.
| -rw-r--r-- | .ai/notes.org | 4 | ||||
| -rw-r--r-- | .ai/sessions/2026-07-14-02-50-sentry-spec-review-lint-org-fix.org | 84 | ||||
| -rw-r--r-- | docs/design/2026-07-14-sentry-workflow-proposal.org | 150 | ||||
| -rw-r--r-- | docs/specs/2026-07-14-sentry-workflow-spec.org | 275 | ||||
| -rw-r--r-- | inbox/lint-followups.org | 7 | ||||
| -rw-r--r-- | todo.org | 49 |
6 files changed, 549 insertions, 20 deletions
diff --git a/.ai/notes.org b/.ai/notes.org index 380d4a8..5ce7047 100644 --- a/.ai/notes.org +++ b/.ai/notes.org @@ -61,7 +61,7 @@ This section tracks decisions that need Craig's input before work can proceed. ** Current Reminders -(None currently — will be added as needed) +- =[2026-07-14]= Review the sentry spec (docs/specs/2026-07-14-sentry-workflow-spec.org) — Craig's explicit ask at wrap: strongly suggest he reviews it before ending the next session. All 12 review findings and 10 decisions are resolved and folded in; the spec is open in his Emacs; the READY flip and the [#B] build task both wait on his deep read. ** Instructions for This Section @@ -82,6 +82,6 @@ Format: Markers maintained by workflows to record when they last ran. Read by other workflows that gate their behavior on freshness. :LAST_AUDIT: 2026-07-04 -:LAST_INBOX_PROCESS: 2026-07-13 (three passes: morning 3 handoffs incl. two shared-asset rule proposals shipped; evening validate-el fix + 4 acks; roam inbox emptied twice, 10 foreign items routed to owners) +:LAST_INBOX_PROCESS: 2026-07-14 (3 handoffs: sentry proposal from work accepted + worked in-session; archsetup FYI acknowledged; lint-followups.org deleted per Craig) Format: one =:MARKER: YYYY-MM-DD= line per workflow. Workflows overwrite their own marker on completion. diff --git a/.ai/sessions/2026-07-14-02-50-sentry-spec-review-lint-org-fix.org b/.ai/sessions/2026-07-14-02-50-sentry-spec-review-lint-org-fix.org new file mode 100644 index 0000000..280ab5b --- /dev/null +++ b/.ai/sessions/2026-07-14-02-50-sentry-spec-review-lint-org-fix.org @@ -0,0 +1,84 @@ +#+TITLE: Session Context — 2026-07-14 +#+DATE: 2026-07-14 + +* Summary + +** Active Goal + +Take the work project's sentry proposal (one supervisor loop running a project's hygiene passes overnight, with locking) from inbox arrival to a fully-dispositioned spec: decision walk, spec-create, adversarial review, findings walk. Mid-walk, fix the lint-org data-corruption bug the review surfaced as a sentry prerequisite. Plus two inbox waves (8 handoffs total) processed along the way. + +** Decisions + +- Sentry design, all resolved live with Craig (recorded as 10 DONE decisions + 12 DONE findings in the spec): host-suffixed sentry branch (Craig's call — unpushed commits on main diverge across two daily drivers); host-local locks under /run/user/<uid>/agent-locks/ (a lock inside the roam repo would ride roam-sync's git add -A to the other machine); mkdir-atomic lock helper with heartbeat refresh + bounded-wait-then-defer (flock can't span tool calls); interactive entry gates, NO report-only mode anywhere (Craig's explicit direction — passes run fully or skip); roam-sync stays the roam repo's only committer; :COMMIT_AUTONOMY: yes gates sentry entirely; detection-over-configuration pass portability; launch contract with in-place checkout (worktrees rejected: untracked inbox drops invisible there); inbox pass under the no-approvals contract; suite at entry + conditional fire-end run only; KB promotion pass cut to vNext (heuristic task filed); wrap-up refuses during a live loop, "stop sentry" owns shutdown; entry ff-only reconcile; persistent stall notify after 2 skips. +- Craig corrected a misread mid-turn: the .emacs.d signel request meant "remove signel mentions from live workflows," not "run a broadcast + question smoke's scope." No broadcast ran; smoke's scope stands; nothing had been sent. +- Spec stays DRAFT deliberately — Craig reviews deeply before the READY flip. + +** Data Collected / Findings + +- The 2026-07-09 org-file corruption's true mechanism: wrap-org-table.el's load-time CLI dispatch fired when lint-org.el merely required it, running the table reformatter over lint-org's file arguments (--check runs were immune because the flag made wot's dispatch decline). The filed defects (block-blind scanning, mutate-by-default) were real but secondary. +- flock binds to a living process; agent Bash calls are short-lived shells, so locks for agent workflows need mkdir-atomicity + staleness semantics, never flock. +- rg skips hidden directories by default — a grep over .ai/ paths silently misses everything without --hidden (bit this session during the signel audit). +- No locking existed anywhere in .ai/scripts/ (verified); the roam sync-conflict forks are the observed cost. + +** Files Modified + +- docs/specs/2026-07-14-sentry-workflow-spec.org — NEW: the sentry spec, DRAFT, 10/10 decisions, 12/12 findings resolved, voice-passed. docs/design/2026-07-14-sentry-workflow-proposal.org — NEW: preserved origin proposal + cover note. +- Commit 951b6fc — lint-org/wrap-org-table corruption fix, test-first (6 new tests): block-type-aware scanning, lint-org CLI report-only by default (--fix required; wrap-it-up + /lint-org command updated), wot entry-script dispatch guard. Commit 32afa41 — work's .flac threading adopted into process-meeting-transcript.org. Plus the signel history-line chore. All pushed (ad5bb07). +- todo.org — sentry build task [#B] + cross-host [#D] + /schedule [#D] + KB-heuristic [#D] filed; org-table bug closed DONE and archived to Resolved. +- notes.org — :LAST_INBOX_PROCESS: 2026-07-14; Active Reminder: Craig reviews the sentry spec before ending the next session. + +** Next Steps + +- FIRST: Craig's deep read of the sentry spec (his explicit ask — surface the reminder at startup). READY flip, then spec-response decomposes the build. +- The paused task review: batch of 7 selected, task 1 (sentry build) walked but no action picked; 6 untouched. The sentry build task is also the one 30-day-staleness hit (never-reviewed, filed tonight) — the next review cycle covers it. +- Sentry build follows the spec's 4 phases once READY (agent-lock helper first). + +KB: promoted 2 / consulted no + +* Session Log + +** Startup + inbox processing + +Session started 2026-07-14 00:11 CDT on ratio. Fresh session, templates synced, no reminders, staleness 0, roam inbox 0. + +Inbox: three items. (1+2) Sentry workflow proposal from work (intro + full proposal) — passed value gate (Q2), skeptical review found: real problem (verified no flock anywhere in .ai/scripts/), sound invariants, but four issues: roam lock held across commit-push conflicts with inbox.org's edit-then-roam-sync discipline; flock can't span short-lived tool-call shells (needs PID/staleness lockfile or mkdir semantics); unattended per-pass commits need gating on :COMMIT_AUTONOMY:; passes 3 and 10 are sender-specific (need auto-skip/config). Craig picked option 2: work it now, walk decisions, spec/build tonight. Then a task review. (3) archsetup FYI (maint update-queue CVE item done, dotfiles 6757b51) — acknowledged, deleted. Craig also directed deleting the standing inbox/lint-followups.org. + +Proposal preserved to docs/design/2026-07-14-sentry-workflow-proposal.org (cover note merged in). Reply sent to work: accepted, being worked now. + +** Sentry decision walk (all decided with Craig, one by one) + +- D1 commit target: dedicated sentry branch, host-suffixed (sentry/YYYY-MM-DD-<host>). Craig's call, overriding my current-branch rec: unpushed commits on main across two daily drivers = diverged main, breaks startup ff + template sync. Morning flow: review, squash-merge, delete branch. Prior unmerged sentry branch at entry → skip fire. +- D2 roam-write lock scope: host-local only; cross-host conflict-surfacing filed as follow-up. +- D3 interval: hourly default, config knob. +- D4 gates: interactive at entry (Craig is at terminal when he launches) — dirty tree stops and surfaces what's dirty + options (finish the job / stash / rollback which changes); red suite same shape. NO report-only mode anywhere (Craig's explicit direction): unattended fire hitting unsafe state skips with one digest line; next fire retries. Morning-approval queue for destructive/judgment actions stays (permanent division of labor, not a degraded mode). +- D5 roam git: roam-sync stays the only committer; lock wraps edit + trigger. Reconciliation surfaced: knowledge-base.md + roam-sync.sh header still say agents pull/commit/push inline — join companion-file list. +- D6 locks: shared .ai/scripts/ lock helper, mkdir atomicity, PID/host/timestamp metadata, age-based staleness reclaim, acquire/release/status, bats-tested. (flock can't span tool calls.) +- D7 autonomy: :COMMIT_AUTONOMY: yes gates sentry entirely — absent/no → declines to start, names the marker. +- D8 portability: auto-skip by detection (existence probes per pass), no config. +- D9 path: spec-first (spec-create → docs/specs/2026-07-14-sentry-workflow-spec.org). + +** Sentry spec authored + +Wrote docs/specs/2026-07-14-sentry-workflow-spec.org (DRAFT, ID f6c51f27-d7a2-4b63-9ff9-5ba005a66dfb): full spine — summary, problem, goals/non-goals/scope, two-altitude design (user + implementer), 5 alternatives with why-nots, 9 DONE decisions, 4 implementation phases, acceptance criteria, readiness dimensions, risks. Voice pass ran (/voice general per spec-create Phase 4): ~20 em-dashes reduced to structural-only, 2 prose fragments completed, passives activated, one correctness catch (design said "never pulls" roam but pass 1 is an ff-only pull — reconciled). Self-rubric: Ready. Cross-linked: todo.org gets the [#B] build task + two [#D] vNext deferrals (cross-host conflict surfacing, unattended /schedule variant). Next: spec-review gate, then Craig's queued task review. + +** Adversarial spec review (Craig's weakness-loop method) + +Task review started (batch of 7, task 1 = sentry build) but Craig redirected: run the spec through review now via a weakness loop — 20 weaknesses, check each against the spec, loop until <5 new, then notify him for deep review. Ran 3 rounds (20 → 7 → <5). Recorded 10 blocking + 2 non-blocking findings in the spec's Review findings section. Heavy hitters: overnight working-tree ownership (sentry checkout vs Craig's live session — biggest open design question); roam-write lock is sentry-only unless inbox.org/knowledge-base.md writers also acquire it; lock dir inside roam repo would be committed by roam-sync's git add -A (relocate to /run/user); sentry's session-context writes trip its own dirty-tree skip; pass 8 depends on the filed lint-org mutate-on-lint [#B] bug; unattended inbox semantics; per-pass commits vs verification.md suite rule (needs a recorded decision); KB lesson source undefined; lock wait/heartbeat mechanics; wrap-up-during-loop undefined. Rubric: Not ready; status stays DRAFT. Iteration history entry added. Craig notified for his deeper review. Task review batch (7 tasks) still pending — resumes after. + +** Findings walk + lint-org fix shipped mid-walk + +Walking the 12 findings with Craig one by one. Decisions so far: F1 = launch contract (in-place checkout, sentry owns the repo overnight, dirty-skip backstop, Emacs revert caveat documented). F2 = all roam writers acquire the lock (inbox.org core §5 + knowledge-base.md write recipe gain acquire/release; graceful degradation when helper absent; bounded-wait-then-surface for interactive callers). F3 = both locks under /run/user/<uid>/agent-locks/, helper owns path scheme, ~/.cache fallback. F4 = spine-set exclusion in dirty check + fire-end digest commit + path via session-context-path. + +At F5 Craig redirected: fix the lint-org bug now. Done, TDD (red shown first): block-type-aware scanning in wrap-org-table.el wot-process-file + lint-org.el lo--check-tables (type-matched end marker so literal inner markers can't re-expose a block); lint-org CLI report-only by default, writes behind --fix (--check kept as alias; wrap-it-up + /lint-org command updated to pass --fix). Root cause discovered during red-phase debugging: wrap-org-table's load-time CLI dispatch fired on lint-org's require and ran the reformatter over lint-org's file args — THE 2026-07-09 corruption mechanism (--check runs were immune because the flag made wot's dispatch decline). Entry-script guard added (dispatch only when -l names wrap-org-table.el itself). /review-code --staged ran: one Important (boolean in-block flag cleared by literal inner end-marker) fixed in-review + regression test; verdict Approve. Suite 0 failures. Committed 951b6fc (unpushed). todo.org bug task closed DONE; spec finding F5 completed DONE. Minor follow-up noted (not filed): lo--check-indented-headings still has the naive boolean flag, benign (suppression-only). + +Second archsetup FYI (tailscale-amber fixed, dotfiles 4840463 + archsetup aed151b, velox TOML hand-refresh is archsetup's item) acknowledged + deleted. Inbox clean. + +Next: resume walk at F6 (unattended inbox semantics), then F7-F12, then Craig's deep spec review, then the paused task review (task 1 of 7 pending — sentry build task, now also needs the F5 resolution reflected... its body references the spec; fine as-is). + +** Findings walk completed — all 12 dispositioned, spec updated + +F6 = inbox pass under the no-approvals contract, everything to the morning queue. F7 = suite policy recorded as Decision 10 (entry run + conditional fire-end run when non-org files changed, no per-pass runs). F8 = KB promotion pass CUT to vNext (Craig), lesson-detection-heuristic task filed [#D] :spec: (blocks re-adding). F9 = bounded-wait-then-defer + heartbeat refresh, reclaims surfaced. F10 = wrap-up REFUSES during live sentry (Craig picked simple over shutdown-pre-step); "stop sentry" operation owns loop-cancel + branch/queue disposition. F11 = entry ff-only reconcile, diverged joins interactive gate. F12 = persistent notify after 2nd consecutive unmerged-branch skip, then at most daily. All 12 findings flipped DONE in place; design/scope/decisions/acceptance/risks updated to match (pass list now 10, Decisions [10/10], locks at /run/user/<uid>/agent-locks/, launch-contract paragraph in user altitude). Status stays DRAFT for Craig's deep read; responder entry added to iteration history. + +** Second inbox wave (5 handoffs) + Craig's broadcast correction + +Two more archsetup FYI closures (memory-killer = live-refresh hairline artifact, filed [#C] their side; net-panel speedtest history filed [#C] their side) deleted. work's process-meeting-transcript .flac update: diff-verified verbatim (12 hunks, pure .flac threading), adopted into canonical, committed 32afa41, confirmation sent to work. .emacs.d's signel-deprecation broadcast request: I misread it as run-a-broadcast + flag smoke's scope; Craig corrected mid-turn — NO broadcast, smoke's scope is well defined and needed, and the actual ask was removing signel mentions from live workflows. Broadcast draft deleted from /tmp before anything sent; nothing reached smoke. Grep gotcha: rg skips hidden dirs by default, so .ai/ paths need --hidden. One live-workflow mention found (triage-intake.org:417 incident history line) — rewritten to keep the lesson, drop the client name (commit next to 32afa41: chore(workflows): drop retired Signal-client name). Session archives + frozen design doc keep theirs as history. Reply sent to .emacs.d. Inbox clean (0 pending). diff --git a/docs/design/2026-07-14-sentry-workflow-proposal.org b/docs/design/2026-07-14-sentry-workflow-proposal.org new file mode 100644 index 0000000..d29ceb8 --- /dev/null +++ b/docs/design/2026-07-14-sentry-workflow-proposal.org @@ -0,0 +1,150 @@ +#+TITLE: Sentry Workflow — Proposal for rulesets +#+AUTHOR: Craig Jennings +#+DATE: 2026-07-13 + +* Status +Proposal from the work project to the rulesets session, for implementation as a +general (shared-layer) workflow: =.ai/workflows/sentry.org= plus a small +supporting script or two under =.ai/scripts/=. + +* Problem + +An agent session left running overnight can keep a project clean and healthy on +a cadence, but the current pieces don't coordinate. Two gaps: + +1. *No agent-vs-agent collision guard on shared public files.* =capture-guard= + only detects Emacs org-capture buffers (the human case) on one target file, + and it's advisory, not a lock. There is no flock anywhere in =.ai/scripts/=. + Multiple sessions (several projects, sometimes both daily drivers) can + read-modify-write =~/org/roam/inbox.org= and the =agents/= KB nodes at once. + The only coordination is the git pull/commit/push discipline in + knowledge-base.md, which is optimistic — a pull/push race forks a + =*sync-conflict*= file, and the KB search already globs those out, so we know + the forks happen and get dropped. + +2. *No single serialized runner.* Firing inbox-zero and triage-intake as + separate hourly crons means they can overlap each other and other agents. + +Sentry is one supervisor loop that runs a fixed sequence of hygiene passes +serially, holds locks so it never collides with itself or other agents, and +checkpoints aggressively for crash recovery. + +* What sentry is + +A single workflow, driven by one interactive =/loop= (one cron), that on each +fire walks an ordered list of passes inside a single turn. It replaces the +per-task crons. Design invariants: + +- *Serial.* Passes run one at a time, in order. They never overlap each other. +- *Single-runner lock.* Sentry takes a project-local flock + (=.ai/.sentry.lock=) at entry. If a prior fire is still running, this fire + skips instead of doubling up. +- *Shared-file lock.* Any pass that writes a public roam file first acquires a + roam-write lock (flock on =~/org/roam/.roam-write.lock=) held across the whole + read-modify-write-commit-push cycle, so it serializes against other agents on + the same host. =capture-guard= still runs underneath for the human case. (The + roam-write lock is new; it's the general guard the dropped KB-guard note would + have proposed, folded in here.) +- *Non-destructive by default.* Read-only and idempotent-filing passes run + fully. Anything destructive or judgment-heavy (triage trash/mark-read, task + regrades, spec flips) queues its proposal into the digest for morning + approval, never fires unattended. +- *Idempotent + isolated.* Every pass is safe to repeat. A pass that errors + doesn't abort the rest; its failure is a loud banner in the digest. + +* Crash-recovery spine (applies to every pass) + +Crash recovery has been a live consideration. Two enforced steps wrap each pass: + +1. *Session-context between passes.* Before the next pass starts, the current + pass appends a dated entry to =.ai/session-context.org= recording what it + did. A crash mid-sequence leaves a readable "got this far" trail, so the next + session resumes without re-deriving state. +2. *Local commit, no push, on any disk change.* Every pass that writes to disk + gets its own =chore(sentry): <pass> — <what changed>= commit on the current + branch, unpushed. A crash loses at most one pass. Craig reviews the stack in + the morning and pushes deliberately. Conventional messages, no AI + attribution. (Open decision: commit on the current branch vs a dedicated + =sentry/<date>= branch — see Open decisions.) + +The unit per pass is therefore: do the work → log to session-context → commit. +That triplet is the recovery boundary. + +* Passes (v1 — all ten, plus the first-run KB promotion) + +Ordered so read-only/pull passes precede writes, and the heaviest external +sweep sits mid-run: + +1. *Roam pull* — =git -C ~/org/roam pull --ff-only= so later reads are fresh. + Read-only. +2. *Inbox zero* — inbox.org roam mode. Route roam-inbox items this project + owns. Acquires the roam-write lock for the inbox edit. +3. *Triage intake* — triage-intake.org. External-accounts sweep, classify, + file Action tasks. Destructive actions (trash/mark-read) queue for morning; + =mbsync -a= and the sentinel advance run. +4. *Todo cleanup* — clean-todo.org + todo-cleanup.el + lint-org.el. Convert + level-3 done subtasks to dated logs, archive done subtrees, flag lint. +5. *Task audit* — task-audit.org. Re-check =:solo:=/=:quick:= tags, + priority-scheme conformance, bug severity×frequency grades, stale =DOING= + specs with closed parents, blocked/blocker reciprocity. Regrades queue for + morning. +6. *Working-files hygiene* — flag =working/<slug>/= dirs whose task is DONE but + not filed to =assets/=. Report only; filing is a morning decision. +7. *Spec status board* — the docs-lifecycle grep. Flag =DOING= specs with + closed parents, or specs stuck in =DRAFT=. +8. *Link integrity* — lint-org broken =file:= links across todo/notes/prep. +9. *Git health* — uncommitted drift, unpushed commits, stale local branches, + whether main is behind origin. +10. *Prep + symlink freshness* — verify tomorrow's prep and both symlinks + resolve. +11. *KB lesson promotion (first run of the session, and thereafter as new + lessons accrue)* — promote recent durable lessons out of the fast capture + layer. CLASSIFICATION-GATED: + - Personal project → write to the roam KB as =agents/= nodes (pull, lock, + write, commit, push), per knowledge-base.md. + - Work / denylisted project → NEVER touch roam. Promote to the project's own + durable store (e.g. =deepsat/knowledge.org=) instead, and emit the + one-line refusal note from the knowledge-base.md refusal contract so + nothing is lost silently. + +* Digest output + +One consolidated, timestamped summary per fire: run time first, then per-pass +one-liners (what it found / did), the morning-approval queue (proposed +trash/mark-read, regrades, files-to-move), and any failures as loud banners at +the top. Delta-only where a pass supports it (triage): a pass with nothing to +report is one line. + +* Cadence + +One hourly sentry to start (passes short-circuit fast when there's nothing to +do). A slower evening interval (every 2–3 hours) is a config knob if hourly +proves noisy. + +* Open decisions for rulesets + +1. *Commit target* — per-pass commits on the current branch (simplest, matches + "commit but don't push") vs a dedicated =sentry/<date>= branch (keeps the + working branch clean, costs a branch dance). Proposal leans current branch. +2. *Roam-write lock scope* — host-local flock only (solves same-host + multi-agent, the common case) vs also hardening roam-sync to surface + cross-host conflicts loudly. Proposal: ship the flock now, note the + cross-host limit, treat roam-sync conflict-surfacing as a separate follow-up. +3. *Interval default* — hourly vs evening-only. Proposal: hourly, config knob. +4. *Which passes gate on a green tree* — e.g. skip todo-cleanup commits if the + project suite is red, matching inbox monitor-mode's clean-tree gate. + +* Cover note from sender (work, 2026-07-14 00:02) + +Intro for the sentry-proposal.org file just sent. This is a NEW general workflow proposal (.ai/workflows/sentry.org + supporting scripts under .ai/scripts/), not an edit to an existing synced file. + +What it is: one serialized supervisor loop that runs a project's evening hygiene passes on a cadence, with locks so it never collides with itself or with other agents. It replaces firing inbox-zero + triage-intake as separate crons. + +Why now: an agent left running overnight can keep a project clean, but the current pieces don't coordinate. capture-guard only handles the Emacs-capture case on one file and holds no lock; there is no flock anywhere in .ai/scripts/; and separate crons let inbox-zero and triage overlap each other and other agents on shared roam files, which already fork *sync-conflict* files that the KB search globs out. + +Three design points worth your judgment: +1. Crash-recovery spine on every pass (crash recovery has been a live concern): enforce a session-context.org update BETWEEN passes, and a local commit (no push) after any disk change, so a crash loses at most one pass. +2. A new roam-write flock (~/org/roam/.roam-write.lock) as the shared-file guard. This absorbs a separate 'guard all public KB files' note we considered and dropped. The lock is the better home for that concern than a bespoke per-file guard. +3. The KB lesson-promotion pass is classification-gated: personal projects promote to roam agents/ nodes; a denylisted work project promotes to its own local store (deepsat/knowledge.org) and never touches roam, per knowledge-base.md's refusal contract. + +Companion files to reconcile: knowledge-base.md (the gated promotion + refusal contract), inbox.org (roam mode + the new lock), triage-intake.org (runs as a sentry pass; keep its own triggers), capture-guard (becomes one layer under the roam-write lock, not the whole guard), and clean-todo.org / task-audit.org / lint-org.el (invoked as passes). Open decisions for you are listed at the bottom of the proposal file: commit target, lock scope, interval default, and which passes gate on a green tree. diff --git a/docs/specs/2026-07-14-sentry-workflow-spec.org b/docs/specs/2026-07-14-sentry-workflow-spec.org new file mode 100644 index 0000000..a3d5a89 --- /dev/null +++ b/docs/specs/2026-07-14-sentry-workflow-spec.org @@ -0,0 +1,275 @@ +#+TITLE: Sentry Workflow — Spec +#+AUTHOR: Craig Jennings +#+DATE: 2026-07-14 +#+TODO: TODO | DONE +#+TODO: DRAFT READY DOING | IMPLEMENTED SUPERSEDED CANCELLED + +* DRAFT sentry workflow +:PROPERTIES: +:ID: f6c51f27-d7a2-4b63-9ff9-5ba005a66dfb +:END: +- 2026-07-14 Tue @ 02:03:28 -0500 — all 12 review findings dispositioned live with Craig and folded into the design; decisions now 10/10; still DRAFT pending Craig's deep read. +- 2026-07-14 Tue @ 00:52:03 -0500 — drafted, all nine design decisions resolved live with Craig during the authoring session. + +* Metadata +| Status | draft | +|----------+------------------------------------------| +| Owner | Craig Jennings | +|----------+------------------------------------------| +| Reviewer | (spec-review, next session) | +|----------+------------------------------------------| +| Related | [[file:../../todo.org][todo.org — sentry build task]] | +|----------+------------------------------------------| + +* Summary + +Sentry is one supervisor workflow that runs a project's hygiene passes serially on a cadence, holds locks so it never collides with itself or other agents, commits its work to a host-suffixed side branch for morning review, and checkpoints between passes so a crash loses at most one pass. It replaces the idea of firing inbox-zero and triage-intake as separate uncoordinated crons. + +* Problem / Context + +An agent session left running overnight can keep a project clean, but the pieces don't coordinate. There is no lock anywhere in .ai/scripts/ (verified 2026-07-14): capture-guard covers only the Emacs org-capture case on one file, and it's advisory. Multiple sessions (several projects, sometimes both daily drivers) can read-modify-write ~/org/roam/inbox.org and the agents/ KB nodes at once. The only coordination is git optimism, and we know it loses: the KB search already globs out *sync-conflict* files because pull/push races fork them. + +Separate crons compound it: inbox-zero and triage-intake fired independently can overlap each other and any interactive session on the same shared files. + +Proposal origin: the work project, 2026-07-13 (preserved at [[file:../design/2026-07-14-sentry-workflow-proposal.org][docs/design/2026-07-14-sentry-workflow-proposal.org]]). This spec supersedes the proposal where they differ: the skeptical review found four conflicts with existing discipline, each resolved as a Decision below. + +* Goals and Non-Goals + +** Goals +- One serialized runner per project: passes never overlap each other, and a fire that finds a prior fire still running skips. +- A shared-file lock that actually serializes same-host agents writing public roam files. +- Crash-lossless to one-pass granularity: session-context entry plus a commit after every disk-writing pass. +- Main stays clean: overnight commits land on a host-suffixed sentry branch so cross-machine pulls and the template sync keep working. +- Nothing destructive fires unattended: trash/mark-read, regrades, and file moves queue for morning approval. +- Portable across projects with zero configuration: passes detect their targets and skip where not applicable. + +** Non-Goals +- No report-only or degraded pass mode. A pass runs fully or it doesn't run (Craig's explicit direction). The morning-approval queue is a permanent division of labor, not a degraded mode. +- No cross-host locking. The lock is host-local; cross-host races stay with roam-sync's abort-loudly rebase. +- No unattended /schedule contract. Sentry runs inside a live session Craig starts, driven by /loop, the same boundary inbox.org draws for auto inbox zero. +- Sentry never runs git against the roam repo. roam-sync remains that repo's only committer. + +** Scope tiers +- v1: the sentry.org engine (ten mechanical passes, entry gates, branch mechanics, digest, the stop-sentry operation), the agent-lock helper script with bats tests, the roam-write lock adopted by every roam write path, companion-file reconciliations (knowledge-base.md write recipe, inbox.org core §5, roam-sync.sh header, triage-intake.org note, wrap-it-up.org active-sentry guard), INDEX.org entry. +- Out of scope: changes to roam-sync.sh behavior; changes to the wrap-up teardown feature. +- vNext (logged to todo.org): cross-host roam conflict surfacing; the KB lesson-promotion pass (blocked on the lesson-detection heuristic task); a fully-unattended /schedule variant if the interactive shape proves too narrow. + +* Design + +** For Craig (the user altitude) + +You type the trigger in a project session ("run sentry", or the /loop line it expands to). Sentry first checks its entry ticket: the project's notes.org must carry :COMMIT_AUTONOMY: yes, or sentry declines to start and names the marker. Then the entry gates run while you're still at the terminal: a dirty tracked tree stops and describes what's dirty, file by file, and offers numbered options (finish the job, stash it, or name changes to roll back); a red test suite stops the same way with an offer to investigate. The loop starts only once both are answered. + +Launching is a handoff — the launch contract: sentry owns the repo until the morning merge. Working in that repo mid-night means saying "stop sentry" first (it cancels the loop and walks the branch and queue disposition), and open Emacs buffers on project files want a revert after launch and again after the merge. "Wrap it up" while sentry is active refuses and points at "stop sentry". + +Each fire (hourly by default) walks the pass list serially and appends one timestamped digest to the session log: failures as banners on top, then one line per pass, then the morning-approval queue. A pass with nothing to do is one line. Anything destructive or judgment-shaped waits in that queue for you. + +In the morning you review the sentry/<date>-<host> branch (a stack of small unpushed chore(sentry) commits), squash-merge it into your working branch, delete it, and answer the approval queue. If a night's branch is still unmerged when sentry next starts, that fire skips and says so. + +** For the implementer + +*The engine.* A synced template workflow, .ai/workflows/sentry.org, driven by /loop <interval> (default hourly). Each fire acquires the single-runner lock, verifies branch state, walks the pass list, writes the digest, commits the spine residue, and releases the lock. The runner re-touches the lock between passes (the heartbeat), so a live run's lock is never older than one pass. Every pass follows one contract: probe, work, session-context entry, then a commit when the pass wrote to disk. The probe asks whether the pass's target exists in this project; when it doesn't, the pass is one skip line and nothing more. The session-context entry precedes the commit so a crash between them still leaves the trail. + +*Branch mechanics.* At loop start (after the entry gates and a fetch-and-ff-only reconcile of the project branch — a diverged branch joins the interactive gate), sentry creates sentry/YYYY-MM-DD-<host> from HEAD and checks it out; the host suffix prevents a same-date collision between the daily drivers. Commits are conventional, one per writing pass: chore(sentry): <pass> — <what changed>. Nothing pushes. At entry, an existing unmerged sentry/* branch means skip-and-note. No second branch stacks on the first. Morning teardown (review, squash-merge, delete) is Craig's, documented in the workflow, never automated. + +*Locks.* One helper script, .ai/scripts/agent-lock, serves both locks. flock is unusable here: every Bash call an agent makes is its own short-lived shell, so a flock dies with the call that acquired it. Instead: atomic mkdir as acquire (create-and-win or fail-and-lose), a metadata file inside recording PID, hostname, and ISO timestamp, and age-based staleness reclaim so a crashed run's lock expires instead of wedging every later fire. Subcommands: acquire <name> [--ttl], release <name>, status <name>. Lock homes: /run/user/<uid>/agent-locks/<name>/ for both locks — the helper owns the path scheme, callers pass names — with ~/.cache/agent-locks/ as the fallback where no runtime dir exists. tmpfs makes a lock host-local by construction, keeps it out of every repo (a lock inside ~/org/roam would ride roam-sync's git add -A to the other machine as a phantom hold), and clears it on reboot. Contention is a bounded wait (~30s), then defer-and-note. With the heartbeat keeping a live lock young, TTLs size to the longest single pass; every reclaim surfaces in the digest. + +*Roam writes.* A pass touching a public roam file acquires the roam-write lock, runs capture-guard --wait (the human-capture layer stays underneath), edits the working tree, triggers roam-sync with systemctl --user start roam-sync.service, and releases the lock. Sentry never runs a git write against ~/org/roam, and pass 1's ff-only pull is its only git read of that repo. The 06-24 one-git-owner rule holds. The lock spans only edit-plus-trigger; roam-sync itself is serialized by systemd (a oneshot unit never runs concurrently with itself). + +*Unattended safety.* With no one at the terminal, any unsafe state (unexpected dirty tree, red suite, lost lock, unmerged prior branch) makes the affected scope (the pass, or the whole fire, whichever the state poisons) skip with one digest line. The next fire retries; if the state persists to morning, the interactive entry gate handles it with Craig present. Skips are never silent and never partial: no pass runs in a reduced form. Two refinements keep the machinery honest: the dirty check excludes the spine set (session-context files, path resolved via session-context-path) so sentry's own bookkeeping can't trip it, and after the second consecutive fire skipped on an unmerged prior branch, sentry sends one persistent desktop notification naming the project and branch, then repeats at most daily — a multi-day stall never stays silent. + +*Pass list (v1), in order, each with its detection probe:* + +1. Roam pull — git -C ~/org/roam pull --ff-only, skipped when the tree is dirty (roam-sync owns that case) or the clone is absent. Read-only; the one narrow exception to "don't touch roam git," pull-only and ff-only, so later reads are fresh. +2. Inbox zero — inbox.org roam mode, run under the engine's no-approvals contract (quick+solo+agreed items execute; shared-asset proposals park; everything lands in the morning queue). Probe: roam clone or project inbox/ exists. +3. Triage intake — triage-intake.org. Probe: triage plugins present. Destructive actions queue. +4. Todo cleanup — clean-todo.org mechanics. Probe: root todo.org. +5. Task audit — task-audit.org. Probe: root todo.org. Regrades queue. +6. Working-files hygiene — flag working/<slug>/ dirs whose task is closed. Probe: working/ exists. Filing queues. +7. Spec status board — the docs-lifecycle grep. Probe: docs/specs/ exists. +8. Link integrity — broken file: links in the project's org files. Probe: lint-org.el present. +9. Git health — drift, unpushed commits, stale branches, main-behind-origin. Probe: .git. +10. Prep + symlink freshness. Probe: the prep dir / symlinks exist (work and home only, in practice). +(KB lesson promotion, the proposal's eleventh pass, is deferred to vNext — see the KB finding and the filed lesson-detection-heuristic task. v1 ships the ten mechanical passes above.) + +*Digest.* Appended per fire to the session-context.org Session Log (which the spine already writes), so it survives a crash, rides the session archive, and is on screen in the running session. The morning-approval queue accumulates under one heading in the same file — each item carries what, why, and the exact command or edit that fires on approval. + +* Alternatives Considered + +** Separate crons per task (status quo direction) +- Good, because each piece stays independently simple. +- Bad, because nothing serializes them: the observed sync-conflict forks are this cost, already paid. +- Bad, because N crons means N session lifecycles to manage instead of one. + +** flock for both locks (as proposed) +- Good, because it's the standard tool and kernel-enforced. +- Bad, because it binds to a living process; agent tool calls are short-lived shells and /loop turns share none, so the lock evaporates on return. Disqualifying. + +** Per-pass commits on the current branch (as proposed) +- Good, because commits sit where the edits apply; no morning merge step. +- Bad, because unpushed commits on main on two daily drivers diverge main by morning, the exact state startup's fast-forward refuses and the template-sync guard blocks on. Disqualifying with two machines. + +** Degraded report-only mode for unsafe states +- Good, because some findings still land when committing is unsafe. +- Bad, because it blurs the contract: a pass that half-ran reads as having run. Craig rejected it outright; skip-and-note is the replacement. + +** Per-project pass manifest instead of detection +- Good, because explicit control over what runs where. +- Bad, because it's one more thing to maintain and forget; detection activates a pass the day its target appears. Rejected for v1. + +* Decisions [10/10] + +** DONE Commit target: host-suffixed sentry branch +- Context: overnight commits must survive two daily drivers that both pull main; unpushed commits on main diverge it across machines, breaking startup fast-forward and the template-sync guard. +- Decision: We will commit each writing pass to sentry/YYYY-MM-DD-<host>, created from HEAD at loop start, never pushed. Morning flow: review, squash-merge, delete. An unmerged prior sentry branch at entry skips the fire. +- Consequences: easier — main's ref stays clean for cross-machine sync; a bad night is discarded by deleting one branch. Harder — a daily merge step; the working tree sits on a non-main branch overnight. + +** DONE Roam-write lock scope: host-local only +- Context: observed conflicts are same-host multi-agent; cross-host races go through roam-sync's rebase, which aborts loudly. +- Decision: We will ship the host-local lock and file cross-host conflict-surfacing as a separate task. +- Consequences: easier — small, testable v1. Harder — a true cross-host race still forks; we accept the rarity. + +** DONE Interval default: hourly, with a knob +- Context: passes short-circuit in seconds when idle; the real cost is digest noise, not compute. +- Decision: We will default to hourly and expose the interval as the /loop argument. +- Consequences: easier — projects stay clean through the night. Harder — a capture-heavy evening produces several small commits; the knob is the escape hatch. + +** DONE Entry gates are interactive, not silent +- Context: Craig types the sentry command in, so the first fire runs with him at the terminal. +- Decision: We will stop at entry on a dirty tracked tree (describe what's dirty; offer finish-the-job / stash / rollback-named-changes) and on a red suite (describe failures; offer to investigate). The loop starts only after both are answered. Untracked files never block. +- Consequences: easier — no guessing about his in-progress work. Harder — sentry can't start unattended from a dirty state; that's the point. + +** DONE No report-only mode; unsafe unattended states skip +- Context: a degraded pass mode blurs whether a pass ran; Craig rejected it explicitly. +- Decision: We will make every pass run fully or not at all. Unattended unsafe states (dirty tree, red suite, lost lock, unmerged prior branch) skip the poisoned scope with one digest line; the next fire retries. The morning-approval queue for destructive/judgment actions stays — it's a permanent contract, not degradation. +- Consequences: easier — a pass line in the digest means it fully ran. Harder — a persistent unsafe state means zero hygiene until morning; accepted. + +** DONE Roam git discipline: roam-sync stays the only committer +- Context: the proposal had sentry commit-and-push roam under the lock; the 06-24 fix made roam-sync the repo's single git owner because the tree is chronically dirty from live captures. +- Decision: We will wrap only edit-plus-trigger in the roam-write lock; sentry never runs git against ~/org/roam (pass 1's ff-only pull is the sole, read-only exception). +- Consequences: easier — one git owner, no mid-rebase states from agents. Harder — an edit lands remotely only when roam-sync fires; the manual trigger closes most of that gap. + +** DONE Lock mechanics: mkdir-atomic helper with staleness reclaim +- Context: flock can't span tool calls (short-lived shells, no shared process across /loop turns). +- Decision: We will build .ai/scripts/agent-lock — atomic mkdir acquire, PID/host/timestamp metadata, age-based reclaim, acquire/release/status subcommands, bats-tested — and use it for both locks. +- Consequences: easier — locks survive between calls and self-clear after crashes. Harder — TTL tuning; a reclaim during a genuinely slow pass is possible, so the helper surfaces every reclaim rather than reclaiming silently. + +** DONE Autonomy gate: :COMMIT_AUTONOMY: yes is the entry ticket +- Context: commits.md gates commits on approval; sentry commits unattended, so it needs standing, per-project authorization. +- Decision: We will have sentry decline to start in any project whose notes.org lacks :COMMIT_AUTONOMY: yes, naming the marker. +- Consequences: easier — running sentry somewhere is a deliberate grant; no half-running mode to reason about. Harder — read-only passes don't run in ungranted projects either; that's acceptable, since launch is one marker away. + +** DONE Pass portability: detection over configuration +- Context: several proposed passes are work/home-specific; run verbatim elsewhere they're vacuous or error. +- Decision: We will open every pass with a cheap existence probe and skip with one digest line when the target is absent. +- Consequences: easier — zero config, passes self-activate when targets appear. Harder — an intentionally-unwanted pass needs a vNext exclusion marker if that ever becomes real. + +* Review findings [/] + +** DONE Overnight working-tree ownership is undefined +Resolved 2026-07-14 (Craig): launch contract with in-place checkout. Launching sentry hands the repo to sentry until the morning merge; reclaiming it mid-night means stopping the loop first. The entry gate (clean tree, Craig present) fronts the handoff, the unattended dirty-skip backstops anything that slips, and the workflow documents the Emacs buffer-revert caveat at launch and after the morning merge. Worktree isolation was rejected because untracked inbox drops exist only in the main tree, which blinds the inbox pass; plumbing commits were rejected as fragile. + +** DONE Roam-write lock guards nothing unless every roam writer acquires it +Resolved 2026-07-14 (Craig): Phase 3 strengthened from notes to mandatory write-path changes. inbox.org core §5 gains acquire/release around the Phase D edit; knowledge-base.md's write recipe gains the same around its write block. Both degrade gracefully when the helper isn't installed (proceed unlocked — today's behavior); an interactive caller finding the lock busy does a bounded wait (~30s) then surfaces to the user rather than proceeding unlocked. + +** DONE Roam lock location gets committed by roam-sync +Resolved 2026-07-14 (Craig): both locks live under /run/user/<uid>/agent-locks/<name>/, with ~/.cache/agent-locks/ as the fallback where no runtime dir exists. The agent-lock helper owns the path scheme; callers pass names, never paths. tmpfs gives host-locality by construction and reboot self-clearing for free. + +** DONE Sentry's own spine writes trip its dirty-tree skip +Resolved 2026-07-14 (Craig), all three parts: the unsafe-state dirty check excludes the spine set (session-context.org / session-context.d/), each fire ends with one digest commit that sweeps accumulated spine writes so read-only fires still leave a clean tree, and the spine path resolves through .ai/scripts/session-context-path so a concurrent agent's anchor is never clobbered. + +** DONE Pass 8 depends on a known data-corrupting bug +Resolved 2026-07-14, mid-review: the lint-org fix landed in 951b6fc — block-type-aware scanning in both helpers, CLI report-only by default with writes behind --fix, and the true corruption path (wrap-org-table's load-time dispatch firing on lint-org's require) guarded to entry-script-only. Pass 8 runs against the fixed linter; the prerequisite is satisfied and no Implementation-phases dependency is needed. + +** DONE Unattended inbox-pass semantics are undefined +Resolved 2026-07-14 (Craig): sentry runs inbox processing under inbox.org's no-approvals contract verbatim — quick+solo+agreed items execute, shared-asset and convention proposals park (prepared diff, VERIFY task, sender reply). Everything the pass did lands in the morning queue, and anything the engine would ask interactively defers to the queue instead of blocking the fire. + +** DONE Per-pass commits skip the pre-commit suite run — undecided convention conflict +Resolved 2026-07-14 (Craig): recorded as a Decision (see Decisions — suite policy). Entry run establishes the green baseline; no per-pass runs; a fire-end suite run fires only when a pass modified files outside the org/spine set, so the rare code-touching pass is caught before its commits age overnight. Justification recorded with both halves of the consequences. + +** DONE KB promotion pass has no defined lesson source +Resolved 2026-07-14 (Craig): pass 11 is cut from v1 and deferred to vNext. KB promotion stays a wrap-up concern — the editorial moment where Craig answers the promotion prompt. An unattended judgment pass writing to the shared KB waits until sentry has quiet weeks behind it AND a designed detection heuristic; the heuristic design is filed in todo.org ([#D] "KB lesson-detection heuristic", which blocks re-adding the pass). + +** DONE Lock contention and reclaim mechanics are half-specified +Resolved 2026-07-14 (Craig): both mechanics adopted. Contention: bounded wait (~30s, capture-guard's --wait shape), then defer — the pass skips with a digest line and the next fire retries. Reclaim: heartbeat refresh — the runner re-touches the lock timestamp between passes, so a live run's lock is never older than one pass and the TTL sizes to the longest single pass (minutes). Every reclaim surfaces in the digest. + +** DONE Wrap-up while the loop is live is undefined +Resolved 2026-07-14 (Craig): wrap-up refuses while sentry is active. It detects the live single-runner lock and stops with "sentry is active — say 'stop sentry' first." The stop-sentry operation is defined in sentry.org and owns the shutdown: cancel the loop, walk the branch disposition (squash-merge now or leave named), walk-or-carry the approval queue. Wrap-up itself gains only the one guard; the shutdown logic lives with sentry. + +** DONE Entry should reconcile the project branch before branching +Resolved 2026-07-14 (Craig): adopted. After the entry gates pass, sentry runs the same fetch-and-ff-only reconcile startup uses, then creates the branch. A diverged branch joins the interactive entry gate (Craig is present at launch) rather than being auto-resolved. + +** DONE Multi-day unmerged branch stalls hygiene silently +Resolved 2026-07-14 (Craig): adopted. After the second consecutive fire skipped for the unmerged-branch reason, sentry sends one persistent desktop notification naming the project and branch ("sentry stalled: <branch> unmerged — merge or delete to resume"), then repeats at most daily. Persistent notify matches the paging convention: it stays on screen until dismissed. + +** DONE Suite policy: entry run plus conditional fire-end run, no per-pass runs +- Context: the verification discipline requires a full suite run before every commit, but sentry commits per pass, hourly, mostly touching org files the suite doesn't exercise; a per-commit run would turn a seconds-long fire into minutes, all night. +- Decision: We will run the suite once at entry (the green baseline the gates require) and again at fire-end only when a pass modified files outside the org/spine set. No per-pass runs. The deviation is justified by the unpushed branch and the morning review gating everything before push. +- Consequences: easier — idle fires stay cheap and the rare code-touching pass is still caught before its commits age. Harder — a suite break introduced by an org-only edit (possible via fixtures) surfaces at morning review rather than at the offending commit. + +* Implementation phases + +** Phase 1 — agent-lock helper +.ai/scripts/agent-lock (canonical: claude-templates/.ai/scripts/) with bats tests: acquire/release/status, contention, staleness reclaim, metadata. Tree stays working; nothing calls it yet. + +** Phase 2 — sentry.org engine +The workflow file: entry ticket, interactive gates, branch mechanics, pass runner with the probe→work→log→commit contract, digest + approval queue, skip semantics. INDEX.org entry. Mirror synced. + +** Phase 3 — companion reconciliations +knowledge-base.md write recipe (acquire/release the roam-write lock around the write block; edit-plus-trigger replaces inline pull/commit/push); inbox.org core §5 (acquire/release around the Phase D edit, capture-guard staying as the human layer underneath; graceful degradation when the helper is absent); roam-sync.sh header comment; triage-intake.org note (runs as a sentry pass; own triggers kept); wrap-it-up.org active-sentry guard (refuse and point at "stop sentry"). + +** Phase 4 — verification +make test green; a live trial night on rulesets (ratio): entry gates exercised, one fire observed end to end, morning branch review performed; follow-up tasks filed from what the trial surfaces. + +* Acceptance criteria +- [ ] agent-lock: two concurrent acquires produce exactly one winner; release frees; a stale lock (aged past TTL) is reclaimed with a surfaced note; a busy lock is a bounded wait then defer; heartbeat refresh keeps a live lock young; locks live under the runtime dir (cache fallback), never inside a repo; bats suite green. +- [ ] Sentry declines to start without :COMMIT_AUTONOMY: yes, naming the marker. +- [ ] Entry on a dirty tracked tree stops with the file-by-file description and the three options; untracked files don't trigger it. +- [ ] A fire on a clean project produces one digest with every pass either run or skipped-with-reason; no other output shape exists. +- [ ] Writing passes commit individually to sentry/<date>-<host>; main's ref is untouched; nothing is pushed. +- [ ] A roam-writing pass acquires the roam-write lock, passes capture-guard, edits, triggers roam-sync, releases, and never runs git write commands against ~/org/roam. +- [ ] An unmerged prior sentry branch at entry causes a skip-and-note, not a second branch. +- [ ] Destructive/judgment actions appear only in the approval queue, never executed unattended. +- [ ] On a project missing a pass target (no todo.org, no triage plugins), the pass skips with one line and the rest run. +- [ ] Entry reconciles the project branch ff-only before creating the sentry branch; a diverged branch stops at the interactive gate. +- [ ] Sentry's own spine writes never trip the dirty-tree skip (a full fire of read-only passes ends with a clean tree via the digest commit). +- [ ] "Wrap it up" during an active loop refuses and names "stop sentry"; "stop sentry" cancels the loop and walks the branch and queue disposition. +- [ ] The second consecutive unmerged-branch skip produces one persistent desktop notification; repeats are at most daily. +- [ ] The inbox pass parks a shared-asset proposal (VERIFY + prepared diff + sender reply) rather than applying it, and the morning queue lists everything the pass did. + +* Readiness dimensions +- Data model & ownership: locks own their directories under /run/user/<uid>/agent-locks/ (tmpfs, outside every repo); digest and queue live in session-context.org (session-owned, path via session-context-path); commits on the sentry branch are Craig-authored per commits.md. +- Errors, empty states & failure: every skip names its reason in the digest; a failed pass banners and the rest continue; the digest surfaces lock-reclaim events. No silent outcomes. +- Security & privacy: no credentials touched; KB promotion honors the knowledge-base.md work-project refusal contract; commit messages follow the no-tooling-enumeration rule where applicable. +- Observability: the digest is the observability surface (per-fire, timestamped, failures on top); session-context.org persists it across crashes. +- Performance & scale: idle fire is seconds (probes + short-circuits); heaviest pass is triage (network); hourly cadence assumed fine; the knob covers it. +- Reuse & lost opportunities: reuses inbox.org, triage-intake.org, clean-todo, task-audit, lint-org.el, capture-guard, roam-sync wholesale; the only new code is agent-lock and the engine file. +- Architecture fit & weak points: engine+passes mirrors the engine+plugin convention; weak points are lock TTL tuning (reclaim vs slow pass) and /loop session lifetime (a killed session ends sentry; the lock's TTL clears the residue). +- Config surface: /loop interval; :COMMIT_AUTONOMY: marker; lock TTLs as script flags with defaults. Nothing else. +- Documentation plan: the workflow file is the doc (When to Use + the morning-review section); INDEX.org entry; no README change needed. +- Dev tooling: bats for agent-lock via the existing glob-discovered suites; make test covers it with no Makefile edit. +- Rollout, compatibility & rollback: additive, new files plus comment-level reconciliations; no consuming project changes behavior until Craig launches sentry there. Rollback is deleting the branch and not launching. +- External APIs & deps: none beyond tools already in use (git, systemctl, bats). N/A for schema verification. + +* Risks, Rabbit Holes, and Drawbacks +- The heartbeat bounds the TTL question to a single pass, but a pass that legitimately outruns its TTL (a slow triage sweep on a bad network) can still be reclaimed under itself. Start generous, surface every reclaim, tune from the digest. +- /loop lifetime: sentry dies with its session (harness restart, machine sleep). Acceptable for v1: the morning state is a readable branch plus session-context, and nothing corrupts. +- Digest fatigue: hourly fires on an active evening could stack noise. The interval knob and the one-line-when-idle rule are the mitigations; revisit after a week of use. +- The sentry branch holds todo.org/notes.org edits overnight; Craig editing those files on main before the morning merge makes the squash-merge conflict. That conflict is small, self-inflicted, and resolvable by hand; the morning-review section documents it. + +* Review and iteration history + +** 2026-07-14 Tue @ 02:03:28 -0500 — Claude Code (rulesets) — responder +- What: all 12 findings walked with Craig one by one and dispositioned; each completed in place and folded into the body. Load-bearing calls: launch contract with in-place checkout (worktrees rejected — untracked inbox drops are invisible there); every roam writer adopts the lock; locks relocate to the XDG runtime dir; spine-set exclusion + fire-end digest commit; inbox pass runs the no-approvals contract; suite policy recorded as Decision 10; KB promotion pass cut to vNext (heuristic task filed); heartbeat + bounded-wait lock mechanics; wrap-up refuses during a live loop ("stop sentry" owns shutdown); entry ff-only reconcile; stall notify after two skips. Mid-walk, Craig redirected the pass-8 finding into fixing the lint-org bug immediately — landed as 951b6fc, which also found and closed the true 2026-07-09 corruption path (wrap-org-table's load-time dispatch firing on lint-org's require). +- Why: Craig chose to resolve every open question together rather than batch them into a written response round. +- Artifacts: findings section above (12/12 done); todo.org tasks (KB heuristic [#D], bug task closed); commit 951b6fc. + +** 2026-07-14 Tue @ 01:15:00 -0500 — Claude Code (rulesets) — reviewer +- What: adversarial weakness review at Craig's direction — batches of candidate weaknesses checked against the spec, looped until a round produced fewer than five substantive new ones. Round 1: 20 candidates, 11 answered or mitigated by the spec, 9 became findings. Round 2: 7 candidates, 3 answered, 4 folded into findings. Round 3 produced fewer than five substantive candidates; loop terminated. Result: 10 blocking + 2 non-blocking findings recorded above. Rubric: Not ready until the blocking findings are dispositioned. Status stays DRAFT for Craig's deeper review. +- Why: Craig asked for the weakness loop before his own deep review, in place of a standard first-pass spec-review. +- Artifacts: findings grounded in code reads — roam-sync.sh:28 (git add -A commits anything inside the roam repo), .ai/scripts/session-context-path (AI_AGENT_ID scoping), todo.org [#B] "Org-table helpers corrupt example blocks" (lint-org mutate-on-lint), inbox.org core §2 no-approvals park path, verification.md pre-commit suite rule. + +** 2026-07-14 Tue @ 00:52:03 -0500 — Craig Jennings — author +- What: initial draft. +- Why: work project's sentry proposal accepted after skeptical review; nine design decisions resolved live in-session, recorded under Decisions. +- Artifacts: [[file:../design/2026-07-14-sentry-workflow-proposal.org][origin proposal]]; todo.org sentry build task. diff --git a/inbox/lint-followups.org b/inbox/lint-followups.org deleted file mode 100644 index 8132c30..0000000 --- a/inbox/lint-followups.org +++ /dev/null @@ -1,7 +0,0 @@ -* 2026-07-02 Thu — Task-review health: 2 top-level [#A]/[#B]/[#C] tasks unreviewed for >30 days (daily review may have slipped) - -* 2026-07-04 Sat — Task-review health: 5 top-level [#A]/[#B]/[#C] tasks unreviewed for >30 days (daily review may have slipped) - -* lint-org follow-ups — todo.org (2026-07-11) - -* 2026-07-11 Sat — Task-review health: 11 top-level [#A]/[#B]/[#C] tasks unreviewed for >30 days (daily review may have slipped) @@ -39,6 +39,32 @@ Tags are assigned and refreshed by =task-audit=; =task-review= keeps them honest * Rulesets Open Work +** TODO [#B] Sentry workflow — build from spec :feature: +Build the sentry supervisor workflow from the READY-gated spec: +[[file:docs/specs/2026-07-14-sentry-workflow-spec.org][sentry workflow spec]] (DRAFT as of 2026-07-14; needs spec-review first). +Four phases: agent-lock helper + bats, sentry.org engine, companion +reconciliations (knowledge-base.md, roam-sync.sh header, inbox.org, +triage-intake.org), live trial night on rulesets. Origin: work project's +proposal, [[file:docs/design/2026-07-14-sentry-workflow-proposal.org][docs/design/2026-07-14-sentry-workflow-proposal.org]]. All nine +design decisions resolved with Craig 2026-07-14 (recorded in the spec). + +** TODO [#D] Cross-host roam conflict surfacing :feature: +Deferred from the sentry spec (Decision: host-local lock only). The +roam-write lock serializes same-host agents; a true cross-host race still +forks a sync-conflict file, surfaced only by roam-sync's rebase abort. +Investigate making the conflict loud (roam-sync retry-once, or a +cross-host lock node in the repo) when it proves frequent in practice. + +** TODO [#D] KB lesson-detection heuristic :spec: +Deferred from the sentry spec review (2026-07-14): sentry's KB promotion pass was cut from v1 because "promote recent durable lessons" had no defined lesson source, and an unattended judgment pass writing to the shared KB needs one that doesn't spam or go silent. Design the detection heuristic — candidate source: Session Log entries and memory-dir changes since the last fire, judged against knowledge-base.md's inclusion bar — and define how precision gets verified before the pass ships in a sentry vNext. Blocks re-adding the pass. + +** TODO [#D] Sentry unattended /schedule variant :feature: +Deferred from the sentry spec (Non-Goal). The v1 contract is interactive: +a live session Craig starts, /loop-driven. A cron variant firing with no +session needs its own contract (mutation rights, async surfacing, dedup +state across runs) — same open questions inbox.org logs for unattended +auto inbox zero. Take up only if the interactive shape proves too narrow. + ** TODO [#C] ai launcher hardening — bug hunt + refactor pass :refactor:solo: :PROPERTIES: :CREATED: [2026-07-13 Mon] @@ -74,17 +100,6 @@ From the roam inbox (2026-07-11): work in progress in one project shouldn't stop :END: From the roam inbox (2026-07-11): make install-ai launchable as =install-ai= (no =.sh=) from PATH. dotfiles needs a copy that stays in sync with the rulesets canonical — decide whether the startup script-sync already covers it or a dedicated mechanism is needed. -** TODO [#B] Org-table helpers corrupt example blocks :bug:solo: -:PROPERTIES: -:CREATED: [2026-07-11 Sat] -:LAST_REVIEWED: 2026-07-13 -:END: -=wrap-org-table.el= and =lint-org.el= both scan for =/^\s*|/= lines and rewrite them as org tables without skipping =#+begin_example=/=src=/=quote= regions, so ASCII art using pipe characters gets mangled into bordered tables. Reproduced 2026-07-09 in the work project against an architecture doc with a pipe/=v= flow diagram; a plain indented block became a table with =|---|= rules between every line. Two separable defects: (1) table detection is line-based — both helpers should use =org-element-at-point= / =org-in-block-p= to skip example/src/quote/verse blocks; (2) =lint-org.el= mutates its input on disk with no confirmation — passing five files reformatted all five (one by 1949 lines). A linter must report, not write; put the reformat behind an explicit =--fix= flag. - -Grading (severity × frequency, per todo-format.md): Critical severity (silent org data loss; recoverable here only because the content was git-staged) × rare-edge-case frequency (fires only when a mixed table+example file is passed to the helper) = P2 = [#B]. - -Regression test: run =wrap-org-table.el= against a file containing a =#+begin_example= block whose lines start with =|= and assert the block is byte-identical afterward. Source: work handoff 2026-07-09 (=inbox/2026-07-09-1341-from-work-bug-data-loss-wrap-org-table-el-and.org=). - ** TODO [#B] Document (and own) the Signal pager :feature:spec: :PROPERTIES: :CREATED: [2026-07-11 Sat] @@ -1224,3 +1239,15 @@ CLOSED: [2026-07-13 Mon] :END: Killed at the 2026-07-13 task review: home retired and tore down the ntfy channel on 2026-07-04, so this proposal's transport no longer exists. Its living successors are the Signal pager ([#B] task above — one identity on velox, runbook pending) and agent-page (shipped 2026-07-13), which cover the send half; two-way (read-replies) rides the Signal runbook. Proposal from the home project (2026-06-17): promote the self-hosted ntfy-over-Tailscale phone channel it built and verified on ratio into a general two-way agent-comms tool rulesets owns. Full proposal: [[file:docs/design/2026-06-17-ntfy-agent-comms-proposal.org]] (as-built runbook stays in the home project at =working/phone-notifications/spec.org=). What rulesets would decide: canonicalize =phone-notify= (send) plus a new =phone-recv= (check-since) as synced bin scripts; the per-machine config/secret convention (token in =~/.config/phone-notify/config= chmod 600 today, vs GPG-encrypted in dotfiles); a reference =ntfy-inbound-handler= plus systemd user-unit for event-driven delivery (Tier A subscriber routes inbound to inbox/notify, Tier B inbound spawns an agent session, Tier C notify a live session — harness research); approval-button workflows for the commits.md gates when Craig is away from the desk (tap-to-approve, the high-value concrete use); and the relationship to the retired cross-agent-comms scripts (ntfy may be the transport they lacked). Worked via =spec-create=. Blocks the triage-intake phone-push task below. +** DONE [#B] Org-table helpers corrupt example blocks :bug:solo: +CLOSED: [2026-07-14 Tue] +:PROPERTIES: +:CREATED: [2026-07-11 Sat] +:LAST_REVIEWED: 2026-07-13 +:END: +Fixed in 951b6fc, test-first. Both defects landed as filed (block-type-aware scanning in both helpers; lint-org CLI report-only by default, writes behind --fix), plus the true corruption path found during the work: wrap-org-table's load-time CLI dispatch fired on lint-org's require and reformatted lint-org's file arguments. Entry-script guard added. The named regression test (example block byte-identical) is in the suite. +=wrap-org-table.el= and =lint-org.el= both scan for =/^\s*|/= lines and rewrite them as org tables without skipping =#+begin_example=/=src=/=quote= regions, so ASCII art using pipe characters gets mangled into bordered tables. Reproduced 2026-07-09 in the work project against an architecture doc with a pipe/=v= flow diagram; a plain indented block became a table with =|---|= rules between every line. Two separable defects: (1) table detection is line-based — both helpers should use =org-element-at-point= / =org-in-block-p= to skip example/src/quote/verse blocks; (2) =lint-org.el= mutates its input on disk with no confirmation — passing five files reformatted all five (one by 1949 lines). A linter must report, not write; put the reformat behind an explicit =--fix= flag. + +Grading (severity × frequency, per todo-format.md): Critical severity (silent org data loss; recoverable here only because the content was git-staged) × rare-edge-case frequency (fires only when a mixed table+example file is passed to the helper) = P2 = [#B]. + +Regression test: run =wrap-org-table.el= against a file containing a =#+begin_example= block whose lines start with =|= and assert the block is byte-identical afterward. Source: work handoff 2026-07-09 (=inbox/2026-07-09-1341-from-work-bug-data-loss-wrap-org-table-el-and.org=). |
