Commit Graph

30 Commits

Author SHA1 Message Date
Documenter
c5760db43d docs: Phase 8 documentation - Gitea, Obsidian, Outline, Vikunja, map (AUD-74)
- README: architecture Mermaid, Phase 8 status, Phase 7 endpoints, Documentation index
- docs/outline/: AI-context collection (index, system, config, code map, runbook)
- docs/email-agent-architecture.mmd + rendered PNG + Archify map (showcase pass)
- docs/vikunja-tasks.json (Vikunja project 17, tasks 179-185)
- docs/phase8-documentation.md evidence
2026-10-13 10:46:21 +11:00
77febe6470 docs: record the retention decision and evidence (AUD-75)
docs/retention.md covers the 30-day body window, the actions_log row cap, the
audit-row ordering, configuration, unit + deployed verification, deployment on
.13 (commit 453d8a2), scheduling, and rollback. Phase 7's deferred note now
points at the completed work.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-13 10:45:26 +11:00
d9f8d34449 retention: compose the audit detail in the store with the real body count
RetainWithAudit now takes only the actor; the store builds the detail after
body clearing so bodies_cleared is a real number, and still writes the row
before the cap.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-13 10:45:16 +11:00
3766ce1f3f retention: write the audit row before the actions_log cap
RetainWithAudit appends the 'retain' audit row inside the same transaction and
before the cap, so the cap counts it. After a run, actions_log holds at most
EMAIL_AGENT_ACTIONS_LOG_MAX_ROWS rows, including the audit row. Adds a test.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-13 10:45:16 +11:00
17d605dfd5 Phase 7 follow-up: 1-month body retention and actions_log cap (AUD-75)
Implements the AGENTS.md §7 data rule: keep message bodies for one month, and
size-cap the append-only action log.

- internal/store/retention.go: Store.Retain clears messages.body_text older
  than the window (metadata stays) and deletes actions_log rows beyond the
  newest N, in one transaction.
- internal/config: EMAIL_AGENT_RETENTION_DAYS (30) and
  EMAIL_AGENT_ACTIONS_LOG_MAX_ROWS (50000).
- cmd/email-agent: bounded -retain flag for the n8n schedule.
- Tests for body clearing, metadata retention, log cap, and disabled policy.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-13 10:45:16 +11:00
55a08524b5 docs: Phase 7 complete - summary email delivered and Hub registered (AUD-73)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-13 10:37:23 +11:00
9f5e60039d docs: Phase 7 summary + Hub evidence (AUD-73)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 22:39:32 +11:00
a349b42b1f Phase 7: daily summary and read-only Hub API (AUD-73)
- internal/hub: project side of the frozen ask-contract. GET /capabilities,
  read-only POST /ask answerer (no LLM, <3s), and the outbound POST /notify
  client.
- internal/apprise: daily-summary delivery through the house Apprise API.
- internal/summary: deterministic plain-text daily summary builder.
- internal/store: CountByCategory, MessagesByCategory, and summary CRUD.
- internal/server: /capabilities and /ask (bearer-token protected), plus the
  Reports screen (/reports, build, send, delete).
- main.go: -summary, -summary-send, and -notify CLI entry points for n8n.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 22:38:49 +11:00
c4213ebed1 docs: Phase 6 unsubscribe + rules editor evidence (AUD-72)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 22:27:14 +11:00
00bb0e05c4 Phase 6: rules editor + unsubscribe detection (AUD-72)
- FR-11 rules editor: plain-text area at /rules, saved and versioned
  (SaveRules appends a version; restore re-saves an old version), parse
  errors surfaced, one-click re-classify. The reply model now reads the
  rules document too (FR-11).
- FR-7 unsubscribe: parse and store List-Unsubscribe / List-Unsubscribe-Post
  (migration v6), detect email and web links (internal/unsubscribe), a
  domain-level ignore list, and an explicit record of what was marked. No
  code path contacts an unsubscribe link; acting on one needs Sam's approval.
- Migration v6 is additive; unsubscribe gains url/updated_at.
- Tests: detector, parser, store migration/records/candidates, rules editor,
  unsubscribe handlers, message-detail rendering.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 22:27:08 +11:00
Paperclip Builder
7c66e4ad31 Phase 5 follow-up: discover the \Sent mailbox; record the approved real send (AUD-71)
The approved real test reply was sent and the thread updated (same
X-GM-THRID as the original). The live test exposed one bug: the thread
check hardcoded Gmail's sent mailbox as "[Gmail]/Sent Mail", but this
account exposes it as "[Google Mail]/Sent Mail".

- internal/mailfetch: LIST parser, ListMailboxes, and SentMailbox that
  finds the mailbox flagged \Sent (RFC 6154) with name fallbacks.
- internal/mailops: VerifyThreadUpdated selects the discovered \Sent
  mailbox instead of a hardcoded name.
- cmd/email-agent: -check-thread <id> exercises the check against real
  Gmail without sending mail.
- Tests for LIST parsing, sent-mailbox discovery, and the discovery
  error path.
- docs/phase5-replies.md: record send-as verification, the sent reply's
  thread id, the app confirmation (sent_reply_in_thread=true), and the
  fix.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 22:18:05 +11:00
Paperclip Builder
7ca888f0c6 Phase 5: reply drafts — template/LLM draft, edit, SMTP send (AUD-71)
Implements Phase 5 of AGENTS.md §11 (FR-8, FR-15):

- Message detail pane (GET /partials/message) with body, recommended
  action, reply identity, template picker, editable draft, and history.
- internal/reply: OmniRoute (OpenAI-compatible) chat client and a
  template-based draft generator. All non-JEV models go through
  OmniRoute (§10).
- internal/mailops: SMTP send over smtp.gmail.com:587 with STARTTLS and
  the app password, forcing IPv4 (§9); FR-15 reply identity (send as the
  arrived-at X-Pm-Original-To address, else "Sam Rolfe"); threading via
  In-Reply-To/References; -check-smtp safe verification; and a
  [Gmail]/Sent Mail thread check.
- Migration v5 stores messages.message_id_header and drafts.sent_at;
  mailfetch parses Message-ID.
- Send is user-triggered only: CSRF + UI confirm + confirm=yes guard,
  no auto-send, duplicate-send refused. A sent reply files the original
  under Replied-to/<address> and logs every step (FR-13).
- Tests for reply, mailops SMTP/identity/thread check, store drafts and
  migration v5, and the server draft/edit/send guards.
- docs/phase5-replies.md, README, .env.example, docker-compose updated.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 22:05:35 +11:00
ae53374ad1 docs: Phase 4 triage evidence and deploy log (AUD-70)
Record the DoD evidence for Phase 4: select-all toolbar, live delete ->
Delete label -> restore of message 100 over public HTTPS, the missing-CSRF
403, the two confirmation gates, and the FR-13 action-log rows.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 21:47:36 +11:00
318b7b4e1e Fix migration v4 ordering and add a v3->v4 regression test (AUD-70)
schema.sql runs before the incremental migrations, so the folder index must
not live there: on a v3 database the column does not exist yet and startup
failed with "no such column: folder". The migration still creates the index.
The regression test opens a hand-built v3 database with the current code.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 21:46:19 +11:00
8c0416750e Phase 4: triage actions (delete, move, mark; bulk select) (AUD-70)
Bulk select in the triage list, with delete/move/mark and a recoverable
delete (FR-6, FR-9).

- migration v4: messages.folder + messages.mailbox; folder/category views
- internal/mailops: app folder -> Gmail label moves over IMAP (IPv4), locate
  by X-GM-MSGID, create labels lazily, move/save/reply/restore, flag marks
- mailfetch: CREATE, UID MOVE (COPY+EXPUNGE fallback), STORE flags, X-GM-MSGID search
- server: POST /actions/bulk with CSRF, UI delete confirmation, second
  confirmation for messages classified Spam (never auto-deleted), per-message
  and summary action-log rows (FR-13)
- inbox: select-all toolbar, checkboxes, folder nav, Restore on non-inbox rows

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 21:44:52 +11:00
735934dab2 docs: record Phase 3 commit hash in the phase log (AUD-69)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 21:37:48 +11:00
af3b5e5f0e Phase 3: JEV classification, rules table, editable categories (AUD-69)
Implements FR-3, FR-4, FR-5 and FR-14.

- internal/classify: TypeSafe System One (JEV) HTTP client with the confirmed
  request/response shape, retry with backoff on 429/529/5xx, a plain-text rule
  engine (first match wins, overrides only the named fields), and bounded
  orchestration with a per-run cap.
- internal/store: migration v3 adds an editable `categories` table (name =
  JEV Choice option and classifications key; rename cascades, remove reassigns)
  and seeds a starter rules version once. Classification persistence, coverage,
  and pending/all message queries.
- internal/server: POST /actions/classify and /actions/reclassify, plus the
  FR-14 category editor (GET /categories and add/rename/remove). The triage list
  now groups by the live category list and shows action + confidence.
- CLI: -classify and -classify-all for n8n.
- Docs: docs/phase3-classify.md with the confirmed JEV shape, a live read-only
  classification, and the DoD evidence.

DoD: all 100 stored messages have a non-null category, importance, and suggested
action (0 nulls); JEV picked a newly added/renamed category for 6 real messages
over HTTP. One live JEV call recorded in the phase doc.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 21:37:38 +11:00
bf43b6bbef docs: Phase 2 verification and deploy log (AUD-67)
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 20:59:53 +11:00
1d88f962f6 Phase 2: Gmail IMAP fetch (IPv4), FR-2 source address, real mail in dashboard (AUD-67)
- internal/mailfetch: stdlib-only read-only IMAP client (LOGIN, SELECT,
  UID SEARCH, UID FETCH) that always dials tcp4; IPv6 to Gmail is broken
  from .13 and caused a socket EOF.
- MIME parsing to text, RFC2047 header decoding, FR-2 source address:
  X-Pm-Original-To -> To -> Received 'for <addr>' -> Return-Path, never
  Delivered-To. The matched header is recorded for audit.
- store: Message upsert/list/count + LogAction; schema migration v2 adds
  imap_uid and imap_uidvalidity.
- server: dashboard and /partials/inbox render real mail grouped by
  category with a recommended action; POST /actions/fetch runs a scan
  (CSRF-checked); nav counts; 'Check mail' button.
- config: GMAIL_USER, GMAIL_APP_PASSWORD, IMAP host/port/lookback/max.
- cmd: -fetch runs one scan and exits (n8n/CLI entry point, FR-1).
- compose/.env.example expose the Gmail + IMAP settings.
- tests: FR-2 precedence, MIME bodies, IMAP literal/parser, store
  upsert/migration, inbox rendering.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 20:59:53 +11:00
e846400338 docs: Phase 1 deploy verified, session secret configured (AUD-61)
Login and dashboard verified over public HTTPS; record the secret path and
the Paperclip card cascade limitation that forced direct configuration.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 20:17:15 +11:00
3bca0fb279 docs: record Phase 1 deployment result (AUD-61)
Container email-agent live on .13:8790; Caddy route on .35 serves
mail.lab.audasmedia.com.au with a Let's Encrypt cert. Login + dashboard
await the board-gated EMAIL_AGENT_SESSION_SECRET.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 20:02:19 +11:00
3c0030c9a3 Phase 1.2: session login + empty HTMX dashboard
- Session login for the single user Sam: server-side session store with an
  HMAC-signed session cookie, POST /login, POST /logout, and double-submit
  CSRF on every form post. Unauthenticated requests to / and /partials/*
  redirect to /login?next=...
- Empty dashboard shell in the Family Home Lab design system: left nav
  (category and folder placeholders), centre list, right detail pane.
- Copy tokens.css and the self-hosted Inter fonts from the family-home-lab
  design system into web/static; vendor htmx 2.0.4. No cross-project imports
  at runtime.
- Dark mode via prefers-color-scheme plus a persisted toggle; phone-first
  layout with 44px touch targets, hairline borders, 8px grid, pill CTAs.
- Config: EMAIL_AGENT_PASSWORD for the login password, falling back to
  EMAIL_AGENT_SESSION_SECRET so one configured secret is enough.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 19:58:25 +11:00
1dc755a740 docs: bound and shell-safe docker run callout in AGENTS.md
Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 19:56:05 +11:00
a4c5b24f20 Add prominent Gmail build callouts to AGENTS.md
Call out two facts that already caused a confusing failure, before the
build phases begin:

1. Force IPv4 for every Gmail connection (IMAP and SMTP).
2. The source address header is X-Pm-Original-To; never use Delivered-To.

Additive only. No FR id, phase id, or other section is changed.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 19:53:02 +11:00
9efa9e9c89 Track cmd/email-agent/main.go (fix .gitignore shadowing)
The unanchored `email-agent` ignore rule matched the `cmd/email-agent/`
directory as well as the root build artifact, so the service entry point was
never committed and a fresh clone could not build. Anchor the rule to the repo
root (`/email-agent`).

This completes the Phase 1.1 tracked tree (AUD-58): entry point, config, SQLite
store, HTTP server, Dockerfile and compose.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 19:13:32 +11:00
ce2b8ac18f docs: phase 1 deploy runbook for mail.lab.audasmedia.com.au
Capture the AUD-60 deploy facts (port 8790, Caddy on .35, wildcard DNS,
reload command) as a repo runbook. Prepared for AUD-61; do not execute
until AUD-58 and AUD-59 are done.

Co-Authored-By: Paperclip <noreply@paperclip.ing>
2026-10-11 19:12:54 +11:00
5e89c1118f Note send-as: reported set up, confirm at Phase 5 2026-10-11 19:07:15 +11:00
fa340a6cb5 FR-2 confirmed: source address is X-Pm-Original-To (PurelyMail); forwarding verified 2026-10-11 18:57:26 +11:00
93f61d9aef Mail access: IMAP + app password (IPv4 only), not Gmail API 2026-10-11 18:40:16 +11:00
a69aaa3f80 Initial spec: AGENTS.md build spec + archived original notes 2026-10-11 17:24:15 +11:00