From c4fb6e22a42d9d4109550eaf2c6637c21d1e34a7 Mon Sep 17 00:00:00 2001 From: lorentz Date: Mon, 11 May 2026 06:44:51 -0400 Subject: [PATCH] =?UTF-8?q?docs(09.1-01):=20complete=20ntfy=20backend=20fi?= =?UTF-8?q?x=20plan=20=E2=80=94=20pulse-me-=20prefix,=20company=20server,?= =?UTF-8?q?=20bearer=20auth?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - SUMMARY documents gap closure mapping each 09-HUMAN-UAT.md missing item to file/line - All 4 backend ntfy publish sites updated; ProfileChannelsSection QR + help line updated - tsc --noEmit + vitest notify.test.ts both pass --- .../09.1-ntfy-backend-fix/09.1-01-SUMMARY.md | 139 ++++++++++++++++++ 1 file changed, 139 insertions(+) create mode 100644 .planning/phases/09.1-ntfy-backend-fix/09.1-01-SUMMARY.md diff --git a/.planning/phases/09.1-ntfy-backend-fix/09.1-01-SUMMARY.md b/.planning/phases/09.1-ntfy-backend-fix/09.1-01-SUMMARY.md new file mode 100644 index 0000000..cb39e30 --- /dev/null +++ b/.planning/phases/09.1-ntfy-backend-fix/09.1-01-SUMMARY.md @@ -0,0 +1,139 @@ +--- +phase: 09.1-ntfy-backend-fix +plan: "01" +subsystem: notifications +tags: [ntfy, personal-channels, bearer-auth, mobile-profile, gap-closure] + +requires: + - phase: 09-user-profile-preferences-new + provides: "personal-channels.ts, notify.ts route_to_user path, ProfileChannelsSection.tsx, notification_channels.owner_user_id" + +provides: + - "pulse-me- topic prefix enforced by regex and mint function" + - "All four ntfy publish sites route personal channels to company ntfy server with bearer auth" + - "Global/admin ntfy rows continue using their config-driven server + auth_token" + - "ProfileChannelsSection QR + subscribe link target NEXT_PUBLIC_NTFY_BASE_URL" + - "Custom-topic help line in ProfileChannelsSection advanced disclosure" + +affects: + - "Any plan adding new ntfy publish sites (must apply same personal/global branch)" + - "Any plan touching ProfileChannelsSection advanced section" + +tech-stack: + added: [] + patterns: + - "Personal-vs-global ntfy branch on owner_user_id: isPersonal = !!channel.owner_user_id; personal -> NTFY_BASE_URL + NTFY_PULSE_TOKEN; global -> channel.config.server_url + channel.config.auth_token" + - "NEXT_PUBLIC_NTFY_BASE_URL module-level constant in ProfileChannelsSection with fallback to https://ntfy.wulfconsulting.cloud" + +key-files: + modified: + - lib/services/personal-channels.ts + - lib/services/pipeline-steps/notify.ts + - lib/services/pipeline-steps/approval.ts + - lib/services/ticket-digest-service.ts + - components/mobile/profile/ProfileChannelsSection.tsx + +key-decisions: + - "pulse-me- prefix (not pulse-) to avoid collision with noc-* and soc-* reserved namespaces on company ntfy instance" + - "approval.ts uses soft fallback when NTFY_PULSE_TOKEN missing (send unauthenticated) to match its existing best-effort posture; notify.ts and personal-channels.ts fail loud" + - "ticket-digest-service.ts local NotificationChannel interface NOT extended — owner_user_id read via type cast to avoid type churn in a file that is self-contained" + - "Global/admin ntfy rows (owner_user_id IS NULL) preserved — existing ntfy.sh and custom self-hosted deployments keep working" + +requirements-completed: [CHAN-03, CHAN-05, CHAN-07, ROUTE-04] + +duration: 15min +completed: "2026-05-11" +--- + +# Phase 09.1 Plan 01: ntfy Backend Fix Summary + +**Rerouted all four personal-channel ntfy publish sites from public ntfy.sh to the company private server (https://ntfy.wulfconsulting.cloud) with bearer auth, tightened topic prefix from `pulse-` to `pulse-me-`, and aligned the mobile profile QR/subscribe-link to the company server.** + +## Performance + +- **Duration:** ~15 min +- **Started:** 2026-05-11T06:30:00Z +- **Completed:** 2026-05-11T06:45:00Z +- **Tasks:** 2 +- **Files modified:** 5 + +## Accomplishments + +- Updated `NTFY_TOPIC_RE` from `/^[A-Za-z0-9_-]{6,64}$/` to `/^pulse-me-[A-Za-z0-9-]{6,64}$/` — rejects `pulse-`, `noc-`, `soc-`, and arbitrary names +- Updated `mintNtfyTopic()` to return `pulse-me-XXXXXXXX` (8 hex chars entropy, same as before) +- Rewrote `sendChannelTest` ntfy case: drops `channel.config.server_url` / `channel.config.auth_token`; always uses `NTFY_BASE_URL` + `NTFY_PULSE_TOKEN` Bearer header; fails loud when token missing +- Rewrote `sendNtfy` in `notify.ts`: personal branch (`owner_user_id` set) → company server + NTFY_PULSE_TOKEN; global branch → existing config-driven behavior preserved +- Updated `approval.ts` ntfy block: same personal/global split; soft fallback when token missing (matching the function's existing best-effort posture) +- Updated `ticket-digest-service.ts` deliver() ntfy block: same split; both `getAvailableChannels` and `deliver` SELECTs now include `owner_user_id` +- Added `NTFY_BASE` constant in `ProfileChannelsSection.tsx` using `NEXT_PUBLIC_NTFY_BASE_URL || 'https://ntfy.wulfconsulting.cloud'` +- Updated subscribe link href + text + QRCodeSVG value to use `NTFY_BASE` — `ntfy.sh` no longer appears in the file +- Added muted help line `Topic must start with pulse-me-` between custom-topic Input and the server error paragraph + +## Task Commits + +| Task | Name | Commit | Files | +|------|------|--------|-------| +| 1 | Backend: regex + prefix + bearer auth (4 files) | 2ff2dc9 | lib/services/personal-channels.ts, lib/services/pipeline-steps/notify.ts, lib/services/pipeline-steps/approval.ts, lib/services/ticket-digest-service.ts | +| 2 | ProfileChannelsSection QR + subscribe link + help line | 985728a | components/mobile/profile/ProfileChannelsSection.tsx | + +## Gap Closure + +Maps each `missing` item from `09-HUMAN-UAT.md` to where it was addressed: + +| Gap Item | File | Location | +|----------|------|----------| +| Env vars NTFY_BASE_URL / NEXT_PUBLIC_NTFY_BASE_URL | personal-channels.ts, notify.ts, approval.ts, ticket-digest-service.ts, ProfileChannelsSection.tsx | Read at runtime with fallback default; not written to .env | +| mintNtfyTopic() must mint `pulse-me-XXXXXXXX` | lib/services/personal-channels.ts | Line 65: `return \`pulse-me-\${id}\`` | +| NTFY_TOPIC_RE must enforce `^pulse-me-[A-Za-z0-9-]{6,64}$` | lib/services/personal-channels.ts | Lines 44-52: updated regex constant | +| sendChannelTest must force NTFY_BASE_URL + NTFY_PULSE_TOKEN | lib/services/personal-channels.ts | Lines 103-130: ntfy case rewritten | +| notify.ts ntfy publish: company server + bearer auth for personal | lib/services/pipeline-steps/notify.ts | Lines 365-395: personal/global branch in sendNtfy | +| ticket-digest-service.ts ntfy publish: same pattern | lib/services/ticket-digest-service.ts | Lines 651-664: isPersonal branch + owner_user_id in SELECTs | +| ProfileChannelsSection QR + subscribe link target NEXT_PUBLIC_NTFY_BASE_URL | components/mobile/profile/ProfileChannelsSection.tsx | Lines 35, 295, 302: NTFY_BASE constant + usage | +| Inline error copy updated to 'Topic must start with pulse-me-' | components/mobile/profile/ProfileChannelsSection.tsx | Lines 321-323: muted help line added | + +## Deviations from Plan + +### Auto-fixed Issues + +**[Rule 3 - Blocking] Restored lib/types/pipeline.ts and other files after git reset --soft** + +- **Found during:** Pre-task setup — `git reset --soft` to the expected base commit left the working tree files at an older state, causing Phase 9 types (RouteToUser, owner_user_id, etc.) to be missing from pipeline.ts +- **Fix:** `git checkout HEAD -- lib/types/pipeline.ts lib/auth.ts lib/bootstrap.ts lib/services/msgraph-client.ts package.json package-lock.json` to restore those files to the correct HEAD state (1ab3bfe) +- **Files modified:** lib/types/pipeline.ts (restored), lib/auth.ts (restored), lib/bootstrap.ts (restored), others +- **Impact:** No functional change to the 5 plan target files; purely a worktree hygiene issue + +## Known Stubs + +None — all changes wire directly to environment variables already present in .env (NTFY_PULSE_TOKEN) or read at runtime with sensible defaults. + +## Threat Surface + +No new trust boundaries beyond those declared in the plan's threat model (T-09.1-01 through T-09.1-06). All mitigations applied: + +- T-09.1-01: `pulse-me-` prefix keeps personal topics out of noc-*/soc-* namespace +- T-09.1-02: Server-side regex gate enforced in isValidNtfyTopic; client help line is advisory +- T-09.1-03: NTFY_PULSE_TOKEN only read in server-side modules; never exposed via NEXT_PUBLIC_ +- T-09.1-05: notify.ts + personal-channels.ts return structured error when token missing; approval.ts uses soft fallback + +## Self-Check: PASSED + +Files exist: +- lib/services/personal-channels.ts: FOUND +- lib/services/pipeline-steps/notify.ts: FOUND +- lib/services/pipeline-steps/approval.ts: FOUND +- lib/services/ticket-digest-service.ts: FOUND +- components/mobile/profile/ProfileChannelsSection.tsx: FOUND + +Commits exist: +- 2ff2dc9: FOUND (Task 1 — backend 4 files) +- 985728a: FOUND (Task 2 — ProfileChannelsSection) + +TypeScript: `npx tsc --noEmit --pretty` exit 0 — no errors. +Vitest: `npx vitest run lib/services/pipeline-steps/notify.test.ts` — 1 passed. + +Grep sanity: +- grep "ntfy.sh" lib/services/personal-channels.ts → (none) +- grep "pulse-me-" lib/services/personal-channels.ts → 3 matches (comment, regex, return) +- grep "NEXT_PUBLIC_NTFY_BASE_URL" components/mobile/profile/ProfileChannelsSection.tsx → 1 match (line 35) +- grep "ntfy.sh" components/mobile/profile/ProfileChannelsSection.tsx → (none) +- grep "owner_user_id" notify.ts approval.ts ticket-digest-service.ts → multiple matches in each