Skip to content

Notion Decommission, Phase 2 (interactive + write-back paths)

URL: https://mkdocs.justinsforge.com/memory/plans/notion-decommission-phase2-2026-07-03/

Date: 2026-07-03. Author: [Claude Code]. Follows notion-decommission-2026-07-03 (phase 1 repointed reads). This scopes the remaining Notion-page-id-keyed interactive + write paths.

Headline finding

Almost nothing needs to be BUILT in forge-suite. CreatorTrack already exposes every habit + task endpoint the bots need, all callable with the ctk_ Bearer PAT (which resolves AS the owner via currentUser() on the normal /api/* surface). The spine scripts/forge_workspace_capture.py ALREADY has the generic api_call(method, path, body) ctk_ helper (L368), _get (L347), habits_day (L451), list_tasks, create_project. So Phase 2 is overwhelmingly PART B (bot/cron repointing), not PART A.

Genuine product decisions for Justin: (1) time-blocks have no CreatorTrack home; (2) whether morning/wellness reports pull from CreatorTrack /api/fitness/data or straight from the lifeos wellness.* Postgres schema (already fed by forge-wellness-pg-sync).


PART A: CreatorTrack endpoints (forge-suite)

A0. NOTHING to build for habits/tasks/orient. Already live (ctk_ Bearer):

  • Habits read: GET /api/apps/habits?workspaceId=67&since=YYYY-MM-DD -> { ok, ids, habits[], logs[], groups[] } (app/api/apps/habits/route.ts:17). Day's completion state = filter logs[] by date.
  • Habit toggle done: POST /api/apps/habits { op:"upsert_log", workspaceId:67, defId, date, value:1 } (route L49). Un-do = { op:"clear_log", ... } (L78). Also skip, increment, set_note.
  • Task list: GET /api/tasks?view=<view>&workspace=<slug> (app/api/tasks/route.ts:9).
  • Task create: POST /api/tasks { title, workspaceId, due?, priority?, tags?, notes?, projectId?, reminder? } (L34).
  • Task update/complete: PATCH /api/tasks/{id}{ done:true } toggles the checkbox (app/api/tasks/[id]/route.ts:20); or { status?, title?, due?, priority?, projectId?, tags?, notes?, archived? } (L31-48). DELETE /api/tasks/{id} = archive.
  • Docs already cover all of the above: forge-suite/docs/API-BOTS.md (Tasks §39, Habits §88).

A1. (OPTIONAL, product decision) Morning-report / wellness reads

GET /api/fitness/data (app/api/fitness/data/route.ts:9) takes NO params (active-workspace scoped; for the ctk_ owner that resolves to justin). If the migrated forge_morning_report.py / forge_wellness_daily_summary.py need a specific date or explicit workspace, add optional ?workspaceId=&date= query params to that route. RECOMMENDATION: skip this build; point the reports at the lifeos wellness.* Postgres schema directly (already populated by forge-wellness-pg-sync), matching the "grep-from-script -> Forge/PG" routing test. Only build the fitness/data params if Justin wants the reports to read through CreatorTrack.

A2. (PRODUCT DECISION) Time-blocks

No CreatorTrack equivalent. Nearest surface: Calendar app (GET /api/apps/calendar?workspaceId&start&end -> {events, tasks, settings}, app/api/apps/calendar/route.ts:30) = timed tasks overlay (due date only, no duration/block object). Options for Justin: - (a) Retire time-blocks entirely (calendar = timed tasks is the replacement). - (b) Build a real time-blocks collection + /api/apps/timeblocks CRUD in forge-suite (new app; sizable). Do NOT migrate forge_time_blocks_* until Justin picks. This is the one true PART A build candidate and it is gated on his decision.


PART B: bot + cron repointing (forge/scripts). Do AFTER confirming A0 endpoints live in the target env.

Order matters: repoint reads+writes first, then flip the instantiate cron off.

B1. Habit checklist forms + callbacks (the core coupled path)

Files: forge_telegram_checklist.py, forge_habits_callbacks.py, and form builders tool_habits_form/tool_today_form/tool_habits_checkin in forge_telegram_inbox_brain.py. - Read side: replace the Notion habit-row fetch with _spine.habits_day(date) (already wraps GET /api/apps/habits). Each pending item now carries defId (from habits[]/logs[]) not a Notion page_id. - Form state: forge_telegram_checklist.py already persists form state to a file (_form_path, HABITS_LEDGER_PATH) because callback_data is 64-byte capped. Store {defId, date} per item there instead of the Notion page_id. Callback_data stays chk:t:<form_id>:<idx> (index into the stored list) — no wire-format change needed. - Write-back: forge_habits_callbacks.py:_handle_done (L91) currently does _n8n_post("notion-update-page", {page_id, properties:{Quantity:1}}). Replace with forge_workspace_capture.api_call("POST", "/api/apps/habits", {"op":"upsert_log","workspaceId":67,"defId":defId,"date":date,"value":1}). _handle_skip -> op:"skip". _handle_defer stays local-ledger-only (no CreatorTrack analog; keep as-is or drop). Delete _n8n_post/notion-update-page usage. - Task checklist (kind=="task", checklist L19): "Done" -> api_call("PATCH", f"/api/tasks/{id}", {"done": True}) instead of Notion Status=Done. Item id becomes the CreatorTrack task id from list_tasks. - Drop _notion_dbs()/_notion_db_url() and the Notion source-URL buttons; point URL buttons at tasks.justinsforge.com/CreatorTrack instead.

B2. tool_update_task (brain L1429)

Replace the Notion find+update with: resolve task via _spine.list_tasks(...), then api_call("PATCH", f"/api/tasks/{id}", patch). No new endpoint needed.

B3. forge_orient.py

  • Reads (inbox/tasks/goals/areas): swap NotionAPI for _spine.list_tasks + _spine.search_notes + GET /api/apps/goals.
  • Writes (task field updates on execute): api_call("PATCH", f"/api/tasks/{id}", {...}).
  • Remove NotionAPI/NOTION_DBS import once both sides are swapped.

B4. Wellness crons (repoint)

  • forge_morning_report.py, forge_wellness_daily_summary.py, forge_workout_recovery_stamp.py, forge_fitness_weekly_retro.py, forge_wellness_gap_repair.py, forge_heartbeat_random.py, forge_telegram_datepick.py: repoint Notion reads to the lifeos wellness.* PG schema (preferred) or GET /api/fitness/data per A1.
  • Timer forge-morning-report-snooze-check.timer: KEEP (repoint its script's reads only).
  • forge-wellness-pg-sync.timer: KEEP unchanged (canonical feed).

B5. Hevy->Notion mirror (RETIRE)

forge_hevy_backfill.py, forge_hevy_notion_setup.py, forge_hevy_poll.py, forge_hevy_read.py and tool_sync_workouts/sync_now in the brain. Superseded by forge-wellness-pg-sync -> CreatorTrack Fitness (read-only). No active systemd timer exists for these (already dormant). Delete the scripts + remove the two brain tools (add to RETIRED_TOOL_NAMES).

B6. Habit instantiation / streak crons (RETIRE, gated on B1)

CreatorTrack does NOT pre-instantiate daily habit rows: logs are created on demand, keyed by (defId, date) via upsert_log. Streaks are computed client/lib-side (components/apps/habits/habitsMath, lib/habits.ts). Therefore, once B1 lands: - forge-habits-instantiate.timer (04:00) — RETIRE (systemctl disable --now; the Notion rows it creates are read by nothing after B1). Do NOT disable before B1 or live forms break. - forge-habits-streaks.timer (23:55) — RETIRE (CreatorTrack computes streaks itself). - forge-habits-alias-backfill.timer (03:30) — RETIRE (Notion-alias-specific). - Remove the matching unit files under ~/.config/systemd/user/ (or /etc/systemd/system/): forge-habits-instantiate.{timer,service}, forge-habits-streaks.{timer,service}, forge-habits-alias-backfill.{timer,service}, plus their .service.d/ drop-ins. Delete forge_habits_instantiate.py and streak/alias scripts.

B7. Time-blocks (BLOCKED on A2 decision)

forge_time_blocks_tools.py, forge_time_blocks_provision.py, forge_time_daily_aggregator.py: do nothing until Justin decides retire vs build. No time-blocks timer is currently active.


Deletion gate: scripts/forge_notion_api.py

Delete NotionAPI/_notion_api/NOTION_DBS only after every live importer below is repointed/retired: | Importer | Cleared by | |---|---| | forge_habits_callbacks.py, forge_telegram_checklist.py | B1 | | brain tool_update_task, retired page tools, dormant n8n("notion-...") fallbacks | B2 (+ delete dormant fallbacks) | | forge_orient.py | B3 | | forge_food_log.py | already off Notion (phase 1) — confirm no residual import | | forge_hevy_*, forge_hevy_notion_setup.py | B5 (retire) | | forge_time_blocks_*, forge_time_daily_aggregator.py | B7 (blocked) | | forge_workout_recovery_stamp.py, forge_fitness_weekly_retro.py, forge_habits_streaks.py | B4 / B6 |

Final check before delete: grep -rniE 'notion' scripts/ returns zero live code refs (comments/handoffs OK). forge_notion_api.py is the LAST thing to go, after B7 is resolved.


  1. Confirm A0 endpoints answer to the bots' ctk_ token in the target env (dev :3060/:3070, then prod app.creatortrack.ai).
  2. B1 (forms + callbacks) — highest value, unblocks the instantiate crons.
  3. B2, B3 (task update + orient).
  4. B6 (disable/retire the three habit crons) — only after B1 verified live.
  5. B4 (wellness reports) + B5 (retire Hevy mirror).
  6. Get Justin's A2 decision -> B7.
  7. Delete forge_notion_api.py once the grep gate is clean.