Commit Graph

898 Commits

Author SHA1 Message Date
bvandeusen c363a5a6df fix(retention): add cleanup sweeps + CalDAV orphan reconciliation
CI & Build / TypeScript typecheck (push) Successful in 37s
CI & Build / Python lint (push) Successful in 2s
CI & Build / Python tests (push) Successful in 42s
CI & Build / Build & push image (push) Successful in 1m29s
Drift-audit Group 4 (retention / unbounded growth):

- CalDAV pull now reconciles deletions: a previously-synced event whose
  caldav_uid no longer appears remotely within the synced window is
  soft-deleted (one batch_id per run, restorable), so a remote delete
  propagates locally instead of orphaning forever. Guarded on a non-empty
  fetch so a spurious empty result can't wipe every local copy. Also wrap
  the blocking fetch in a 120s wait_for and log run duration.
- Notifications: hourly loop now purges read notifications older than 30d
  (unread kept). Table no longer grows without bound.
- Auth tokens: new daily sweep deletes password-reset / invitation tokens
  whose validity window ended >7d ago; wired via start_auth_token_retention_loop
  in app startup. Both tables previously only flipped used=True, never pruned.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 18:55:23 -04:00
bvandeusen 5fe0fd126d fix(soft-delete): filter trashed rows across read/write paths
CI & Build / TypeScript typecheck (push) Has been cancelled
CI & Build / Python lint (push) Has been cancelled
CI & Build / Python tests (push) Has been cancelled
CI & Build / Build & push image (push) Has been cancelled
Drift-audit Group 3 (soft-delete lifecycle gaps). Trashed rows were
leaking into reads and being mutated/resurrected by writes:

- update SELECTs now exclude trashed rows: update_milestone,
  update_project, update_event, and get_milestone_in_project (the latter
  backs all four milestone routes). Mutating a trashed row silently
  persisted and reappeared on restore.
- MCP get_recent (notes/projects/events) and list_tags now filter
  deleted_at IS NULL, so trashed items stop surfacing in the agent's
  bootstrap context and tag counts.
- convert_task_to_note clears recurrence_rule + recurrence_next_spawn_at
  so a demoted note can't spawn children via the (now-live) sweep.
- caldav pull skips locally-trashed events (by caldav_uid) instead of
  resurrecting them via update or creating a duplicate live copy.
- trash _cascade now stamps the FULL sub-task subtree (iterative descent),
  not just direct children, so deeply nested sub-tasks restore as one
  batch. Test updated for the new descent query.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 18:51:40 -04:00
bvandeusen 8b49ea896a fix(schedulers): wire recurring-task spawn + deliver event reminders
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 35s
CI & Build / Python tests (push) Successful in 47s
CI & Build / Build & push image (push) Successful in 1m10s
Drift-audit Group 2 (Phase-8 amputation — live wiring, no consumer):

- Recurring tasks never recurred: spawn_recurring_tasks() had no caller.
  Register it as a 15-min interval job in the event scheduler (which
  app.py already starts/stops). Also add a deleted_at IS NULL guard to
  the spawn query in the same change, so a trashed recurring parent can
  never resurrect children once the sweep is live.
- Event reminders were stamped reminder_sent_at but never delivered.
  _fire_reminders now creates an 'event_reminder' in-app notification
  before stamping, so a delivery failure stays retryable. Frontend
  NotificationsPanel renders the new type ( + message); message logic
  pulled into a notifMessage() helper.
- Remove the dead _fire_push_notif no-op stub (push left in Phase 8) and
  its three create_task call sites — no more throwaway tasks per share.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 18:47:54 -04:00
bvandeusen e70fe545cc fix(trash): owner-scope all trash ops — close cross-tenant IDOR/disclosure
CI & Build / Python lint (push) Successful in 9s
CI & Build / TypeScript typecheck (push) Successful in 40s
CI & Build / Python tests (push) Successful in 50s
CI & Build / Build & push image (push) Successful in 2m18s
Drift-audit Group 1 (authz/IDOR). Multi-user is live, so these were
exploitable ACL bypasses:

- trash.py: add _owner_clause() and apply it to _exists_alive, restore,
  purge, list_trash, and purge_expired. A batch_id is a bearer token;
  without an owner predicate a leaked/guessed id let one tenant read
  (list_trash), restore, or PERMANENTLY purge another's content. Topics
  and rules carried no owner check at all (_OWNER mapped them to None) —
  ownership now derives through the parent rulebook (or owning project,
  for project-scoped rules).
- purge_expired is now per-user; trash_scheduler iterates every user and
  applies that user's own trash_retention_days window, instead of
  applying user 1's window to everyone (early data loss for other users).
- rulebooks subscribe/unsubscribe_project now assert project ownership,
  matching the suppression endpoints.
- topic/rule DELETE routes return 404 when nothing owned was removed.

Regression test locks in that every model — including topics/rules —
gets a real owner clause.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 18:44:32 -04:00
bvandeusen 7861607fb8 feat(rules): project rule + topic suppressions
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 39s
CI & Build / Python tests (push) Successful in 43s
CI & Build / Build & push image (push) Successful in 1m18s
Lets a project mute individual rules or whole topics from rulebooks it
subscribes to, without unsubscribing the rulebook. Two new association
tables (migration 0060), 4 MCP tools (suppress/unsuppress × rule/topic),
4 REST endpoints, and an inline "× skip" affordance plus collapsed
"Suppressed (N)" section in the project's Rules tab.

get_applicable_rules now emits suppressed_rules and suppressed_topics
(detail objects with rulebook/topic context, not just IDs) so the UI
can render the suppressed list without a follow-up lookup. The main
rules projection grew topic_id and rulebook_id columns for the per-row
suppress affordance.

Project deletion cascades the suppression rows via hard DELETE — they
are pure associations with no soft-delete column, and restoring a
deleted project should start fresh, not inherit stale mutes.

Project-scoped rules (Rule.project_id) are deliberately not suppressible
— delete them with delete_rule instead.

Implements plan-task #187.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 02:26:20 -04:00
bvandeusen c5469214e3 feat(rules): enter_project handshake (S4)
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Failing after 4s
CI & Build / Python tests (push) Successful in 49s
CI & Build / Build & push image (push) Has been skipped
New enter_project(project_id) MCP tool composes get_project +
get_applicable_rules + get_project_milestone_summary + recent
open-tasks + recent notes into one round-trip, intended to be called
at session start (or whenever the active project changes) so Claude
has the full project context loaded before it starts mutating.

_INSTRUCTIONS now points Claude at enter_project for project-scoped
work, alongside the existing list_always_on_rules instruction. No
schema change; pure composition over existing services.

Closes the four-slice rules-consolidation plan (Scribe task #508):
S1+S2 (always_on flag + Scribe-first prompt, 658348f), S3 (project-
scoped rules, 43a860c), and now S4.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 01:14:37 -04:00
bvandeusen 43a860c3ac feat(rules): project-scoped rules (S3)
CI & Build / Python lint (push) Successful in 2s
CI & Build / TypeScript typecheck (push) Successful in 32s
CI & Build / Python tests (push) Successful in 49s
CI & Build / Build & push image (push) Successful in 1m7s
Rules can now belong to either a rulebook topic OR a single project,
enforced by a CHECK constraint (exactly-one of topic_id/project_id).
Adds the create_project_rule MCP tool + REST endpoint, surfaces
project-scoped rules in get_project/get_task/start_planning under a
new project_rules field, and adds a project Rules tab section with an
inline create form so the operator can author project rules from the
UI without rulebook ceremony.

- migration 0059: rules.project_id (FK projects ON DELETE CASCADE),
  topic_id now nullable, CHECK ck_rule_topic_xor_project, index on
  project_id
- model: Rule gains project_id; to_dict exposes it
- service: create_project_rule with project-ownership guard; list_rules
  with project_id filter UNIONs subscription-derived + project-scoped;
  get_applicable_rules adds a project_rules field; get_rule / update_rule
  / delete_rule fetch via a shared _fetch_owned_rule that handles both
  rulebook and project ownership paths
- trash: project delete cascades to project-scoped rules
- MCP: create_project_rule tool registered; _INSTRUCTIONS mentions both
  create_rule and create_project_rule paths
- REST: POST /api/projects/<id>/rules (statement required, title derived
  if omitted)
- frontend: Rule type gains nullable topic_id + project_id; createProjectRule
  client; ProjectRulesTab.vue gains a "Project rules" section with inline
  create form and per-rule expand/delete
- tests: register count → 18; create_project_rule unit tests (required
  fields, title derivation, explicit-title pass-through); applicable_rules
  shape tests now include project_rules; trash cascade test updated to
  expect 5 executions

S1+S2 (always_on flag + Scribe-first prompt) shipped in 658348f.
S4 (enter_project handshake) follows.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 01:10:18 -04:00
bvandeusen 658348f208 feat(rules): always_on rulebook flag + Scribe-first prompt
CI & Build / Python lint (push) Successful in 3s
CI & Build / TypeScript typecheck (push) Successful in 33s
CI & Build / Python tests (push) Successful in 46s
CI & Build / Build & push image (push) Successful in 1m1s
Adds rulebooks.always_on (migration 0058) and a new list_always_on_rules
MCP tool so a session-start eager pull can fetch standing rules without
needing an active-project notion. Updates _INSTRUCTIONS so Claude calls
the new tool at session start and codifies engineering rules in Scribe
rather than CLAUDE.md / auto-memory.

Seeds FabledSword family rulebook to always_on=true on migrate, matching
its design role as the cross-project standards rulebook.

Frontend: badge in RulebookListPane for always-on rulebooks; toggle in
RulebookDetailPane header bound to a new toggleAlwaysOn store action.

This is S1+S2 of the rules-consolidation plan (Scribe task #508). S3
(project-scoped rules) and S4 (enter_project handshake) follow.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-06-01 00:56:08 -04:00
bvandeusen fd20b67b22 docs: remove Flutter companion app from project surface
CI & Build / Python lint (push) Successful in 8s
CI & Build / TypeScript typecheck (push) Successful in 39s
CI & Build / Python tests (push) Successful in 52s
CI & Build / Build & push image (push) Successful in 3m43s
The Flutter app (separate fabled_app repo) no longer adds value over
web/PWA access. Strip the in-repo surface that referenced it:

- delete docs/android-app.md
- drop README docs-table row and feature-list mention
- drop the two Flutter roadmap bullets from docs/features.md
- remove the Flutter port subsection from docs/design-system.md

The standalone fabled_app repo is untouched here; archival/deletion
of that repo is a separate decision.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 23:37:35 -04:00
bvandeusen 7031e36670 chore(mcp): tighten plan-instruction to override .md plan habit
The prior wording ("not in local .md files") was a footer after a
how-to and lost to the much louder superpowers brainstorming /
writing-plans skill flow, which terminates by saving to
docs/superpowers/specs/*.md and docs/superpowers/plans/*.md.

Reorder so start_planning is named as the FIRST action, explicitly
override the .md skill paths, and extend the rule to cover specs as
well as plans (matches the rulebook's rule 27).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
2026-05-31 23:31:46 -04:00
bvandeusen 2f577fee58 fix(mcp): stateless HTTP transport so client reconnects after redeploy
Stateful session manager strands Claude Code after a container redeploy:
it reconnects with a now-unknown Mcp-Session-Id, the server 404s, and the
client won't re-initialize on a 404 (claude-code #60949). Stateless makes
each request self-contained (bearer-auth only) so post-deploy reconnect
works without a manual /mcp retry.
2026-05-29 13:16:38 -04:00
bvandeusen e5565b73dc feat(trash): Settings retention field (trash_retention_days, 0=keep forever) 2026-05-29 12:12:04 -04:00
bvandeusen b579aa1c88 feat(trash): TrashView page, nav links, and g+x shortcut 2026-05-29 12:10:27 -04:00
bvandeusen e5796b6f5c feat(trash): frontend trash API client + Pinia store 2026-05-29 12:07:44 -04:00
bvandeusen d8f577e753 feat(trash): daily retention purge scheduler (03:30 UTC) wired into app lifecycle 2026-05-29 11:47:02 -04:00
bvandeusen a40334312f test(trash): update delete-tool tests to soft-delete contract (patch trash_svc.delete) 2026-05-29 09:24:58 -04:00
bvandeusen bfeed67cfe feat(trash): /api/trash blueprint + flip REST DELETE handlers to soft-delete 2026-05-28 21:13:31 -04:00
bvandeusen 580e4a2c0a feat(trash): list_trash/restore/purge_trash MCP tools + register + instructions 2026-05-28 21:10:47 -04:00
bvandeusen c3af24ef51 feat(trash): MCP delete tools soft-delete via trash; add delete_task/project/milestone 2026-05-28 21:10:13 -04:00
bvandeusen eb41e772cd feat(trash): exclude trashed rows from events/projects/milestones/rulebooks/embeddings reads + filtering tests 2026-05-28 20:33:40 -04:00
bvandeusen e7f214fc80 feat(trash): exclude trashed rows from notes + knowledge read paths 2026-05-28 20:30:23 -04:00
bvandeusen f80c327ecf feat(trash): restore/list_trash/purge/purge_expired + alive() helper 2026-05-28 20:02:21 -04:00
bvandeusen ce47ebc7de feat(trash): services/trash.py — delete() + cascade-stamp by batch 2026-05-28 20:01:35 -04:00
bvandeusen 7f0d99d383 feat(trash): SoftDeleteMixin applied to the 7 soft-deletable models 2026-05-28 19:54:01 -04:00
bvandeusen 84b75f7a73 feat(trash): migration 0057 — deleted_at + deleted_batch_id on 7 tables 2026-05-28 19:53:19 -04:00
bvandeusen 30fbf7b117 docs(mcp): add conceptual primer (what each entity is for) to MCP instructions 2026-05-28 18:25:38 -04:00
bvandeusen d04b6f4bba style(rulebook): narrow the Topics column in /rules 2026-05-28 11:42:08 -04:00
bvandeusen 311322fdc8 fix(plan): export startPlanning from tasks store (type-check) 2026-05-28 11:20:44 -04:00
bvandeusen dc93675470 feat(plan): KnowledgeView Plans facet + plan badge (knowledge endpoints + UI) 2026-05-28 11:12:59 -04:00
bvandeusen 2f5ef9124a feat(plan): Start planning button on project view 2026-05-28 10:44:40 -04:00
bvandeusen b30cf06096 fix(plan): move Applicable Rules panel to TaskEditorView (the routed task surface) 2026-05-28 10:43:41 -04:00
bvandeusen 75d3d40038 feat(plan): plan-task viewer shows Applicable Rules panel 2026-05-28 10:42:03 -04:00
bvandeusen 1d5f49fe3b feat(plan): frontend startPlanning API + store action + task_kind type 2026-05-28 10:41:24 -04:00
bvandeusen b250141e15 feat(plan): REST /api/tasks/planning endpoint 2026-05-28 10:17:38 -04:00
bvandeusen 4609abacd8 feat(plan): start_planning MCP tool + get_task rules augmentation + instructions 2026-05-28 10:17:20 -04:00
bvandeusen e269ac9d5c feat(plan): services/planning — start_planning aggregator 2026-05-28 10:16:36 -04:00
bvandeusen 737467f996 feat(plan): REST task routes — kind on create + list 2026-05-28 08:22:54 -04:00
bvandeusen fc4a1627b5 feat(plan): MCP task tools — kind on create_task + list_tasks 2026-05-28 08:22:31 -04:00
bvandeusen 50d2a0e9c0 feat(plan): services/notes — task_kind create param + list filter 2026-05-28 08:21:59 -04:00
bvandeusen 8754b1c94d feat(plan): task_kind on Note model + to_dict 2026-05-28 08:15:29 -04:00
bvandeusen ac462d1203 feat(plan): migration 0056 — task_kind column on notes 2026-05-28 08:15:13 -04:00
bvandeusen 5f3da7c004 feat(rulebook): port script — parse FabledRulebook .md → Scribe DB 2026-05-27 22:39:50 -04:00
bvandeusen 9658e9a35c feat(rulebook): Project Rules tab — applicable rules + subscription chips 2026-05-27 22:20:19 -04:00
bvandeusen f2afb2a8bf feat(rulebook): /rules route, nav entry, g+r shortcut 2026-05-27 22:01:54 -04:00
bvandeusen 447adf816c feat(rulebook): subscription panel — toggle projects per rulebook 2026-05-27 22:01:03 -04:00
bvandeusen 75d8e7ab49 feat(rulebook): RulesView three-pane shell + child panes + rule editor 2026-05-27 22:00:32 -04:00
bvandeusen 605dd0a13a feat(rulebook): frontend API client + Pinia store 2026-05-27 21:59:23 -04:00
bvandeusen eab5c5a026 feat(rulebook): augment get_project with applicable_rules + MCP instructions 2026-05-27 21:52:18 -04:00
bvandeusen a1a6c5e47e feat(rulebook): MCP tools — 16 tools for rulebook/topic/rule/subscription 2026-05-27 21:51:39 -04:00
bvandeusen bbbd6b2f28 feat(rulebook): REST routes — rules, subscriptions, applicable rules 2026-05-27 21:34:53 -04:00