Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 19 additions & 0 deletions docs/AUDIT_REPORT.md
Original file line number Diff line number Diff line change
Expand Up @@ -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**.
Expand All @@ -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).
Expand Down
28 changes: 28 additions & 0 deletions docs/business/executive/master-plan-2026-07.md
Original file line number Diff line number Diff line change
Expand Up @@ -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=<id>`) 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)
Expand Down
11 changes: 10 additions & 1 deletion src/domain/payments/paymentFlowService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<string, string> = {
nwc: 'Lightning (NWC)',
Expand Down Expand Up @@ -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;
Expand Down
47 changes: 47 additions & 0 deletions src/services/fleetcrown/entitlement-notify.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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<void> {
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<void> {
const secret = process.env.ORANGECAT_WEBHOOK_SECRET;
if (!secret) {
Expand Down
Loading