diff --git a/docs/AUDIT_REPORT.md b/docs/AUDIT_REPORT.md index 151c23bf2..01e4c6ded 100644 --- a/docs/AUDIT_REPORT.md +++ b/docs/AUDIT_REPORT.md @@ -6,6 +6,10 @@ **Commit**: 326bfbf9 **Previous audit**: 2026-07-09 (overall 7/10) β€” see "Delta since last audit" below. +> **⏱️ 2026-07-20 note:** the body below is the 2026-07-13 snapshot and is kept +> as-is. Several of its 🟠/πŸ”΄ items have since been resolved β€” see +> **"Delta since 2026-07-13 β†’ 2026-07-20"** immediately below the Health Score. + ## Executive Summary OrangeCat is a **structurally healthy, well-disciplined codebase** whose real gaps are in _product surface area_ (what actually transacts) rather than engineering hygiene. The hard SSOT wins are genuinely done and enforced: **0** hardcoded route strings, **0** leaked entity-table strings, **0** raw-hex design-token violations, a respected entity registry, and only **1.09%** code duplication across ~200k lines. Type-check (non-incremental) and lint are clean; **1018 of 1019 tests pass**. @@ -25,6 +29,21 @@ The **most important findings are two live correctness/security bugs** surfaced | UI/UX & Responsive | 8/10 | No breakage, good touch/async/a11y; token migration unfinished, god components | | **Overall** | **7.7/10** | Ship-quality engineering; product depth + payments are the frontier | +## Delta since 2026-07-13 β†’ 2026-07-20 + +Re-verified against code on 2026-07-20. Several 2026-07-13 findings are resolved: + +- βœ… **Dead 7,468-line `database.generated.ts`** β€” deleted (canonical `database.ts` remains, 36 importers). +- βœ… **Three status-config files re-declaring labels** β€” collapsed to `STATUS_LABELS` SSOT in `4dd6465e`; all three (`status-config`, `entity-status`, `project-statuses`) now derive labels from it and colors from `badge-colors.ts`. Last straggler (`ai-assistants.ts` dropdown) β†’ PR #407. The three files are **not** merged and should not be β€” distinct SoC (badge styling / per-entity variant + transitions / project validation). +- βœ… **Duplicated `AiService` interface** β€” deduped (`4dd6465e`). +- βœ… **Two live bugs** (PII-exposure via full `profiles` row; partial-PUT silent-unpublish) β€” both fixed in the first-payment sprint (denylistβ†’allowlist; status-default guard). +- βœ… **Money-path test coverage** (Phase 4.1, money half) β€” added `assistant-charge` + `credit-topup` + `credits` ledger-wrapper suites (PR #405, 46 assertions); the Cat Credits money loop is now wired **and** tested ahead of go-live. +- βœ… **Entity detail redesign** β€” every marketplace type (incl. wishlist, PR #406) renders through the unified `PublicEntityDetailPage`; the flat column-dump owner view is gone. +- ⏳ **Still open:** `MockPaymentProvider`/`getPaymentProvider` stack still present (4.3); SSR `/discover` still `'use client'` (SEO/crawler gap); remaining `AnySupabaseClient` casts; OIDC/governance test coverage; `strategic-plan.md` still claims transaction fees (business-model reconciliation, plan 2.4). +- πŸ”΄ **Unchanged and paramount:** **the platform still has never processed a payment.** The money loop is fully wired + tested, but the single blocker remains founder-side β€” provisioning `PLATFORM_NWC_URI`. Every payments-dependent finding below is gated on that one env action, not on code. + +--- + ## Delta since last audit (2026-07-09 β†’ 2026-07-13) - βœ… **Loans hardened** β€” 5 prod bugs fixed via live testing (edit no-op, display_name drift, `loan_offers` RLS recursion, entity-wallets refetch loop, `show_on_profile` no-op). diff --git a/docs/business/executive/master-plan-2026-07.md b/docs/business/executive/master-plan-2026-07.md index ca277b687..56b17770c 100644 --- a/docs/business/executive/master-plan-2026-07.md +++ b/docs/business/executive/master-plan-2026-07.md @@ -5,6 +5,34 @@ --- +> ## ⏱️ Status update β€” 2026-07-20 (code ground truth) +> +> Re-verified this plan against the codebase. **Most of the code-ownable work is +> already shipped; the plan's own tables lagged reality.** The critical path is +> unchanged and it is not engineering β€” it is Phase 0.1 (the platform wallet). +> +> **Done (verified in code):** +> +> - **2.1 Cat Credit `usage` metering** β€” wired (`chat-orchestrator.ts` β†’ `meterCreditUsage`) **and now tested** (PR #405: assistant-charge + top-up + ledger-wrapper, 46 money-path assertions). +> - **3.2 generic edit** β€” done: `EntityCreateEditPage` + `useEntityCreateEdit` (`?edit=`) across 13 entity types (the "3 of 15" figure was stale). +> - **3.3 entity detail redesign** β€” done for all marketplace types via `PublicEntityDetailPage`; the flat column-dump is deleted. Wishlist owner page was the last holdout β†’ PR #406. **(SSR `/discover` is still `'use client'` β€” the SEO/crawler half of 3.3 is still open.)** +> - **0.3 fee copy** β€” reconciled to one SSOT (`FEE_CLAIMS` in `config/landing-page.ts`); 0% = P2P, 95% = Cat-Credits/paid-AI. No user-facing contradiction. _(But see 2.4 β€” `strategic-plan.md` still says "transaction fees"; not yet reconciled.)_ +> - **Hardening:** dead 7,468-line `database.generated.ts` deleted; status-label + `AiService` SSOT collapse done (`4dd6465e`, straggler PR #407); money-path tests (4.1, money half). +> - **Phase 1 (FC⇄OC bridge)** continues shipping: FC feedback widget embedded as "customer #2", notify-sibling on settled project/cause funding (commits `415d5177`, `24089501`). +> +> **Still open β€” the real backlog:** +> +> - **0.1 `PLATFORM_NWC_URI`** (founder) β€” the single blocker. Go-live is env-driven: set it on the box β†’ `npx tsx scripts/bitcoin/verify-platform-wallet.ts` β†’ `NEXT_PUBLIC_CAT_CREDITS_LIVE=true` β†’ one live top-up. Unblocks top-ups + frontier metering + paid-assistant 95/5 at once. **0.2** (first real payment) and **2.2** (first live top-up) are gated on this. +> - **2.4** reconcile `strategic-plan.md` Β§Business Model (still claims transaction fees β€” line 22); **2.5** Swiss attorney (founder). +> - **3.3** SSR `/discover`; **3.4** search across all entity types; **3.5** LNURL-verify; **3.6** seed real supply; **3.1** Cat economic interviewer (partially built: `economic-profile.ts` + interview handler). +> - **4.3** dead `MockPaymentProvider` stack still present; **4.1** OIDC/governance test coverage; **4.2** remaining `AnySupabaseClient` casts. +> +> Bottom line: **one founder env action stands between OrangeCat and its first-ever +> economic event.** Everything downstream of it is wired and, for the money paths, +> tested. + +--- + ## 1. Ground Truth (2026-07-02) ### Usage (production DB, supabase.orangecat.ch) diff --git a/src/domain/payments/paymentFlowService.ts b/src/domain/payments/paymentFlowService.ts index b4e93e9a5..064c28987 100644 --- a/src/domain/payments/paymentFlowService.ts +++ b/src/domain/payments/paymentFlowService.ts @@ -34,7 +34,10 @@ import type { import { logger } from '@/utils/logger'; import { sendSellerPaymentNotification } from '@/lib/email/send-seller-notification'; import { NotificationDispatcher } from '@/services/notifications/dispatcher'; -import { notifyFleetCrownEntitlement } from '@/services/fleetcrown/entitlement-notify'; +import { + notifyFleetCrownEntitlement, + notifyFleetCrownProjectFunding, +} from '@/services/fleetcrown/entitlement-notify'; const METHOD_LABELS: Record = { nwc: 'Lightning (NWC)', @@ -463,6 +466,12 @@ async function handlePaymentConfirmed( logger.warn('FleetCrown entitlement notify failed', { err }, 'paymentFlowService') ); + // Funding on a FleetCrown-linked project β†’ activity signal for the fleet. + // Fire-and-forget; the receiver drops events for unlinked entities. + void notifyFleetCrownProjectFunding(paymentIntent).catch(err => + logger.warn('FleetCrown funding notify failed', { err }, 'paymentFlowService') + ); + // Also create in-app notification for the seller const entityTitle = paymentIntent.description?.split(': ')[1] || 'your listing'; const amount = paymentIntent.amount_btc; diff --git a/src/services/fleetcrown/entitlement-notify.ts b/src/services/fleetcrown/entitlement-notify.ts index 6035806e3..e5828ded3 100644 --- a/src/services/fleetcrown/entitlement-notify.ts +++ b/src/services/fleetcrown/entitlement-notify.ts @@ -51,6 +51,53 @@ export function parseFleetCrownPass(tags: unknown): { plan: string; periodDays: return plan && periodDays ? { plan, periodDays } : null; } +const FLEETCROWN_EVENTS_URL = + process.env.FLEETCROWN_EVENTS_URL || 'https://fleetcrown.orangecat.ch/api/orangecat/events'; + +/** + * Settled-payment signal for FleetCrown-linked projects: when money lands on + * an OC project that a FleetCrown project published itself as, tell the fleet β€” + * settled funding is the ground-truth signal the capability layer can't derive + * on its own. FleetCrown drops events for unlinked entities, so we send for + * every settled project payment and let the receiver filter. Same shared + * secret, fire-and-forget, inert until ORANGECAT_WEBHOOK_SECRET is set. + */ +export async function notifyFleetCrownProjectFunding(pi: PaymentIntent): Promise { + const secret = process.env.ORANGECAT_WEBHOOK_SECRET; + if (!secret) { + return; + } + if (pi.entity_type !== 'project' && pi.entity_type !== 'cause') { + return; + } // funding signals only β€” product sales are the entitlement path + + try { + const body = JSON.stringify({ + type: 'payment.settled', + entityType: pi.entity_type, + entityId: pi.entity_id, + title: pi.description ?? undefined, + amountBtc: String(pi.amount_btc ?? ''), + externalId: pi.id, + }); + const signature = 'sha256=' + createHmac('sha256', secret).update(body).digest('hex'); + const res = await fetch(FLEETCROWN_EVENTS_URL, { + method: 'POST', + headers: { 'content-type': 'application/json', 'x-orangecat-signature': signature }, + body, + signal: AbortSignal.timeout(12_000), + }); + if (!res.ok) { + logger.warn('[fc-funding] FleetCrown rejected event', { piId: pi.id, status: res.status }); + } + } catch (err) { + logger.error('[fc-funding] notify failed (non-fatal)', { + piId: pi.id, + error: (err as Error).message, + }); + } +} + export async function notifyFleetCrownEntitlement(pi: PaymentIntent): Promise { const secret = process.env.ORANGECAT_WEBHOOK_SECRET; if (!secret) {