Plan Review Archive
Human-review HTML for design plans and specs. Markdown in
docs/superpowers/specs remains the source of truth.
- Approved
Landlord API — Slice 1: Foundation
Slice 1 makes a Better Auth API key a valid identity for the rental API, decides per-key what it may do, and freezes a public /api/v1/rental contract over the ten handlers that already exist. It adds no new business endpoints, no new tables, and keeps Bun authoritative for key identity while Go enforces a true hourly allowance in Redis.
- A key may submit a listing for admin review in the landlord's name (D1); it never publishes directly to live, because the admin review queue still stands between submit and live.
- Key auth mounts in a separate APIKeyAuthMiddleware on an explicit v1 route allowlist (D12); AuthMiddleware is untouched, so a key can never reach a legacy rental route or another module's routes.
- v1 is key-only (D13): a cookie or Authorization header is ignored there, and a raw key presented as a Bearer token or cookie does not authenticate legacy Go routes either.
- Approved
DIY Rental Platform — Slice 6: Conversation Inbox, Tenant Overview, Tenant Offers
Slice 6 adds the two screens a rental user opens when they do not already know which record they want: a conversation inbox for both parties and a tenant Overview that answers "where am I, and what next?". It adds no tables, and it repairs two inherited defects — a settled-in tenant who cannot message anyone, and a landlord Offers page 404-ing in production.
- A thread is a viewing request, not a new entity — the inbox is a projection over rental_viewing_requests and rental_messages, with no rental_threads table and no dual-write.
- Thread writability resolves ONE chain stage (none / offer / agreement / tenancy) rather than combining three independent booleans, because entity.AgreementStatus never leaves 'completed' and an OR would make HasLiveAgreement true forever.
- The grace period is 30 days past ends_on, compared as Asia/Kuala_Lumpur calendar dates with an exclusive cutoff at local midnight of ends_on + 31 days; the stored DATE's Y/M/D are used as-is and never timezone-converted first.
- Approved
Rental Portal App Split — Design
Moves the authenticated rental portal (landlord + tenant) out of the Panel SolidStart app into its own independently deployed app, so the two products can ship on separate schedules. Ten reviewed decisions cover error-handler decoupling, the host/cookie constraint, Vitest project enumeration, sign-in and landing behaviour, a usage-derived translation split, two-app E2E and deploy pipelines, and a scoped rename inventory.
- D1 — error-handlers.ts keeps its existing signatures and drops the redundant routeAIError call; installAIAPIErrorHandler moves to a Panel-owned AI module, so PR2 copies the post-PR1 file to the portal unchanged.
- D2 — the portal is hosted on a sibling host under the same registrable domain as the Better Auth session cookie (landlord.kokweng.net as a working assumption); the gremlin.my migration is deferred and the cross-host spike is an open risk, not a confirmation.
- D3a — Vitest projects are enumerated explicitly (shared-packages and panel in PR1, rental in PR2), with acceptance comparing collected test file identities rather than a fixed count.
- Approved
Rental Frontend — Slices 3, 4, and 5 (Panel)
Design for three sequential OpenSpec changes (rental-slice3-panel, rental-slice4-panel, rental-slice5-panel) that build the offers/agreements, ledger/payments, and move-in handover screens in the SolidStart panel at frontend/solidstart/apps/panel. It resolves demo-versus-backend conflicts, keeps all slice 1 routes untouched, and records four missing backend capabilities plus three blocking backend defects. Release is blocked until rental-slice2-panel is built, PR #109 lands, and the backend prerequisites merge.
- The tenant surface lives at /tenant/* in the SolidStart panel as mobile-first responsive pages rather than in Flutter or Astro.
- Screens use the panel's existing glass design system and @monorepo/solid-pkg-ui, taking only information architecture, flow, and wording from the HTML demo.
- Slice 1 routes are preserved: /owner stays the listing grid and the landlord dashboard gets a new /owner/dashboard route.
- Approved
DIY Rental Platform — Slice 4: Tenancy Establishment, Rent Ledger, and AI Payment Proof Matching
Slice 4 turns a completed tenancy agreement into an operational tenancy with a fixed-term rent ledger, human-controlled payment confirmation, durable reminders, and immutable late notices. Optional platform-funded vision AI extracts receipt details under explicit consent, while deterministic checks and landlord decisions remain authoritative.
- A completed agreement creates exactly one operational Tenancy and the entire fixed-term charge schedule in the same database transaction.
- Charge lateness is derived from Malaysian calendar dates, while payment decisions, reversals, reminders, cancellations, and notices are stored as auditable facts.
- Tenant payments are claims requiring landlord confirmation; landlord-recorded receipts are born confirmed, but both paths pay charges only through active allocations.
- Approved
DIY Rental Platform — Slice 5: Move-in Handover and AI-Assisted Condition Report
Slice 5 creates one immutable, party-authorized move-in condition report per tenancy: the landlord submits a structured baseline, the tenant records disputes and omissions, and the platform freezes both positions into canonical evidence and a deterministic PDF. AI may draft neutral photo descriptions only after uploader-specific consent, but the complete manual workflow ships first and remains fully functional without AI.
- Each tenancy has at most one landlord-led move-in report, backed by structured records that freeze into canonical baseline and final snapshots with SHA-256 hashes.
- Report creation and submission use the tenancy start date in Asia/Kuala_Lumpur, with a hard window from seven calendar days before through three calendar days after and no retrospective or admin override.
- The tenant review window is a platform-set seven calendar days; silence finalizes as no tenant response and never as implicit acceptance.
- Approved 中文版
DIY Rental Platform — Slice 3: Offers, Tenancy Agreements, and E-Signature
Slice 3 is where the Malaysian rental platform stops being a noticeboard and becomes the place the deal closes: a tenant with a confirmed viewing offers terms, the landlord counters or accepts, and the platform issues a lawyer-reviewed tenancy agreement that both parties sign inside the app. A grilling session on 2026-08-26 settled 23 open questions and produced three ADRs — documents frozen and content-addressed at issue, click-to-sign under the Electronic Commerce Act 2006, and one blank-filling template under the Legal Profession Act 1976 s.37.
- The tenancy agreement PDF is frozen at issue rather than rendered on demand: rendered once, hashed, and stored under a content-addressed key that is its own SHA-256, so both parties provably sign the same bytes even if the template later changes (ADR-0058).
- Rendering and upload happen before the database transaction opens, not inside it: a rollback would leave the uploaded object behind anyway, and content-addressing makes a retry overwrite the same key so the orphan heals itself instead of needing a cleanup sweep.
- Signing is click-to-sign under the Electronic Commerce Act 2006 — typed full name and NRIC plus an evidence row carrying user id, timestamp, IP, user-agent, and the document hash — rejecting drawn signatures, DSA 1997 certified signatures, and third-party providers like DocuSign (ADR-0059).
- Approved 中文版
DIY Rental Platform — Public Marketplace (Astro)
The anonymous, SEO-critical public face of the Malaysian rental marketplace: a new Astro 7 app at rent.gremlin.my that server-renders search and listing pages, reads Go directly for anonymous public data, and ships no island framework at all. A 22-question grilling session on 2026-08-26 settled the domain, the indexing rules, image handling, and the Seeker-to-Tenant handoff into the panel — and uncovered three gaps in slice 1's schema that must be fixed before its public API contract freezes.
- The public marketplace is a new independently deployed Astro 7 app at frontend/astro/apps/rental served from rent.gremlin.my, a second exception to the one-platform-app rule after the quiz app (ADR-0056).
- SSR calls Go directly at /api/public/rental/… rather than proxying through Bun, because cookie forwarding and CORS handling do not apply to an anonymous server-to-server read, and a public marketing site should not depend on Bun's uptime.
- RENTAL_API_URL holds an origin only and deliberately carries no PUBLIC_ prefix, which makes 'the browser never calls the API' a mechanical guarantee in Astro rather than a convention.
- Approved 中文版
DIY Rental Platform — Slice 1: Listings and Owner Onboarding
Slice 1 of a Malaysian rental marketplace: landlord signup, property listings, manual admin approval, and public browse — built inside the existing monorepo rather than greenfield. A 2026-08-24 design-review grilling session settled the architecture on the platform's shared Postgres database and Better Auth deployment, resolved a role-token naming collision with the existing org-owner role, and tightened the data model, storage, and state-machine details before implementation starts.
- Rental runs in the shared platform Postgres database and shared Better Auth deployment rather than a separate database, isolated only by the rental_ table prefix (ADR-0055).
- Landlord accounts use a distinct global role token landlord, not owner, to avoid colliding with the existing org-scoped owner role already merged into claims.Roles.
- A property has at most one non-terminal listing (draft/pending_review/live) at a time; a new listing can only be created once the previous one reaches withdrawn, and admins can restore a suspended listing directly back to live.
- Approved 中文版
DIY Rental Platform — Slice 2: Viewings, Messaging, and Reminders
Slice 2 of the Malaysian rental marketplace: tenants propose viewing times, owners confirm or counter inside a per-request message thread, contact details reveal only on confirmation, and a 24-hour reminder fires by email. A same-day grilling session resolved 17 design gaps — a terminology collision with the platform's multi-tenancy vocabulary, the fact that slice 1 is still unbuilt, state-machine reachability, a missing terminal status for lapsed viewings, and per-transition email and reveal rules — before implementation starts.
- Viewing requests use request-then-confirm negotiation, not published availability slots: the tenant proposes up to 3 times, the owner confirms, counters, or declines, with no calendar table or booking lock.
- The viewing request row is the message thread — there is no separate chat feature — and counter/re-propose transitions also insert a rental_messages row so the negotiation history survives even though proposed_times only ever holds the current offer.
- No new tenant role: any signed-in user may book, authorization is row-level against tenant_user_id, and users.role stays single-valued exactly as slice 1 established for landlord.
- Approved
Quiz app — favourites, statistics, exam flags, community stats
Adds four server-rendered extensions to the shipped quiz app: saved questions, idempotent exam flags, newest-answer statistics, and an always-visible community evidence block. The design preserves the v1 security and no-JavaScript constraints while adding only one table and one column.
- Favourites use a composite (user_id, question_id) primary key and explicit idempotent favourite and unfavourite actions keyed by question slug.
- Exam flags live on attempt rows, are restricted to exam attempts by a database constraint, and mutate under the same owned exam-row lock as answer submission and finalisation.
- Statistics count only each user's newest answered attempt per non-retired question across practice and exams, with deterministic tie-breakers.
- Approved 中文版
Quiz App — Design
A standalone, mobile-first Astro app for cloud-certification exam simulation and clue-assisted practice. Server-owned attempts and deadlines make sessions resumable and tamper-resistant, while a git-backed CSV question bank with per-row content hashing gives idempotent imports and immutable attempt history from day one.
- The quiz is an independently deployed Astro server app with its own Postgres database and Better Auth domain, with no calls to the Bun or Go backends.
- Exam answers and the immutable deadline are server-owned; forms use POST/Redirect/GET while the client countdown is presentational only.
- Questions are spreadsheet-authored CSV files in git, one per exam type, with the filename stem supplying exam_type and a hand-written Slug column supplying stable identity.
- Approved 中文版
DW Semantic History Search — Design
Adds local, pgvector-based semantic search over Digital Worker chat history. The current Postgres FTS ('simple' config) cannot segment Chinese or code-switched rojak, leaving 40% Malay + 10-20% Chinese chat effectively unsearchable; local bge-m3 embeddings fix this with no API cost and enable cross-lingual retrieval. Approved after the 2026-07-22 grill session resolved all open questions.
- Run per-domain embedding models; DW stays isolated from the ai/crm stacks with its own dw_message_embeddings table using halfvec(1024) + halfvec_cosine_ops.
- Use local bge-m3 (1024-dim) via Ollama, embedding raw code-switched messages with no LLM/translation normalization; store the model identity and always filter queries by it.
- Produce embeddings from one periodic watermark sweep over CLOSED conversation windows — a single code path that yields async, retry, backfill (empty watermark), and model-swap rebuild; ingest is completely unchanged.
- Approved 中文版
FieldForce Mobile App Design
FieldForce is the first Flutter app in the Gremlin mobile workspace: an offline-first field-worker client that syncs directly with the existing Go/Postgres/NATS backend via PowerSync — no separate mobile backend. The MVP proves offline-first work cycles, realtime updates when connected, and feature-flagged shift-scoped background location, inside a melos monorepo built for multiple apps from day one.
- Flutter + PowerSync in a melos monorepo with Clean Architecture is selected over React Native, Capacitor, and native Kotlin/Swift; ecosystem cohesion and PowerSync collapsing offline+realtime+migration outweigh losing TS type-sharing (ADR-0037, ADR-0038, ADR-0039).
- PowerSync keeps a local SQLite DB in sync with the existing Postgres; the Go backend stays the single source of truth and write authority, so there is no separate mobile backend and no hand-built WebSocket realtime (ADR-0040).
- Heterogeneous data paths: interactive entities flow through PowerSync buckets, but high-volume append-only background location telemetry routes around PowerSync to a dedicated Go endpoint via the engine's own queue.
- Draft
FieldForce Mobile App Design
FieldForce becomes a real offline-first Flutter mobile app backed by PowerSync, with the Go backend retaining all identity, write authority, conflict resolution, feature flags, and business rules. The grill pass tightened the design around Task as the canonical sync entity, mobile JWT/session source, Phase 1 Shift backend work, PowerSync Cloud Free for development, explicit uploadData endpoint mapping, rejected Task upload UX, and Crashlytics debug context.
- Build FieldForce as a Flutter app with a Melos monorepo and layered core packages matching the backend's hexagonal boundaries.
- Use PowerSync for interactive offline entities; route background location telemetry through flutter_background_geolocation's own queue to a dedicated Go endpoint.
- Keep Go as the write authority: uploadData() is a Flutter-side bridge to existing or entity-specific Go API endpoints, not a generic backend write endpoint.
- Approved 中文版
Digital Worker Phase 1.5 — Self-Service Identity Verification Design
A Phase 1.5 self-service identity verification flow: a verified Gremlin user binds their own external chat identity by DMing a short-lived, single-use code to the worker on Telegram or WhatsApp, and the inbound webhook supplies the authoritative external_user_id so no one copies an opaque platform ID. It replaces admin-by-hand ID sourcing while producing the same verified dw_external_identities row, leaving the Phase 1 security gate unchanged.
- Self-service DM-code challenge replaces admin ID-sourcing; the webhook envelope's external_user_id is the authoritative binding key (ADR-0035).
- v1 self-service is Telegram + WhatsApp only (shared webhook path); Discord self-service is deferred (Gateway-only ingest) and stays admin-assert.
- Codes persist a single code_lookup = HMAC-SHA256(token, server pepper); the raw token is never stored or logged (the second hash column was dropped).
- Approved
Digital Worker Phase 3 — Progress & Risk Digest Design
Phase 3 turns what the Digital Worker already observes into a scheduled Progress & Risk Digest: a per-channel-binding summary of progress, blockers, stale commitments, and follow-ups, delivered via the in-app notifications module and read in the SolidStart panel. It is the lowest-risk next step, composed almost entirely of Phase 1/2 primitives, with no new external-posting surface.
- Phase 3 ships a per-channel-binding Progress & Risk Digest delivered on a configurable schedule plus on-demand, via the in-app notifications module and the SolidStart panel — no posting to external chat channels.
- The digest is computed hybrid: Commitment health (overdue + unclear ownership) comes from structured extracted data with no AI, while Progress, Blockers, and Suggested follow-ups come from one bounded, gated LLM pass.
- Generation mirrors the Fieldforce briefings engine — a system_cron loop plus a new dw_digests store, with one GenerateChannelDigest use case shared by the scheduled and on-demand triggers.
- Approved
Morning Routine Dashboard — Design
Persists the agent-generated daily Morning Routine briefing to Postgres and renders it on a frosted-glass dashboard in the SolidStart panel, with a service-token ingest API, per-user card preferences, a browsable date rail, and a 2-tier feature flag. Telegram is reduced to a 3-line teaser deep-linking to the dashboard.
- The agent keeps generating the report and POSTs structured JSON to a service-token ingest endpoint; a new Go morningroutine module stores and serves it per user.
- One mr_reports row per (user_id, report_date) with JSONB section arrays; re-POSTing the same date is a full replace, never a partial merge.
- Card preferences are a pure display filter — the report always carries full top_stats and the UI shows the enabled subset, with unknown keys appended at the end for discoverability.
- Approved
Digital Worker Phase 2 — Role-Agent Memory And Skill Learning Design
Phase 2 gives each Digital Worker role-agent durable, cross-channel memory and approved, versioned skills, governed by a Learning Inbox and per-agent approval. A 2026-06-06 grilling session trimmed scope to memory + skills + a tone-only Agent Soul, deferring Constructive-Critic critique and KPI signals to Phase 3, and reconciled the design with the shipped Phase-1 schema.
- Phase 2 ships memory + approved skills + Learning Inbox + Agent Soul as a tone/posture knob only; Constructive-Critic critique, process-improvement proposals, and KPI-signal capture move to a Phase 3 spec.
- Memory is owned by the role-agent keyed on (org_id, role_agent_id), shared cross-channel, with policy-over-learning precedence: admin-authored org memory outranks machine-learned role-agent memory (ADR-0032).
- dw_role_agents is an ALTER not a CREATE: keep the shipped name/persona columns and status CHECK('active','disabled'); add soul_id (FK ON DELETE SET NULL), auto_memory_enabled, auto_memory_require_verified, skill_learning_enabled.
- Approved
Digital Worker Team Chat Observer Design
Phase 1 of the Digital Worker product: a Team Chat Observer that integrates directly with WhatsApp, Telegram, and Discord to answer @-mentions in-channel and draft follow-up tasks from observed commitments into a review inbox. It enforces verified identity, per-channel capability policy, a two-tier LLM (local triage then gated remote), and human approval only for consequential actions — with Fieldforce as the sole task-promotion target.
- Direct integration with the WhatsApp, Telegram, and Discord bot APIs — no OpenClaw/Hermes middleware and no Bun bridge; the Go core owns one adapter per provider (ADR-0031).
- Mention-response is a direct, ungated, in-channel reply; only consequential actions (creating/assigning Fieldforce tasks, posting unsolicited messages, scheduling) are human-gated — a safe-executable vs consequential action model (ADR-0029).
- Proactive extraction is two-tier: a local Ollama triage model is an unbilled pre-gate filter, and only a positive hit calls the frontier model through AccessGate (digital_worker:chat_task_extract) — ADR-0028.
- Approved
Fieldforce Phase 5 — AI Risk Intervention Inbox Design
Phase 5 ships a manager-facing AI Risk Intervention Inbox that turns risk detection into actionable draft interventions — assignee nudges, supervisor escalations, or reassignment recommendations. A daily cron pipeline deterministically scores open tasks, persists ranked recommendation rows, and calls the LLM once per org to draft text; page loads serve stored rows only and never trigger LLM calls.
- Risk Intervention Inbox selected over briefing follow-up drafts and task-detail copilot — a proactive, filterable command-center surface backed by persisted ff_risk_interventions rows, not dependent on the Phase 4 morning briefing.
- Deterministic risk scoring (7 rule keys with additive weights: overdue 40, escalated_no_progress 30, repeated_rejection 25, approaching_due 20, no_activity 20, high_priority_idle 15, stalled_checklist 10) ranks candidates; the LLM drafts text only and never re-ranks.
- generation_mode follows ADR-0023 lease-then-LLM: INSERT starts as pending, finalizes to llm or heuristic_only; all reads must filter WHERE generation_mode <> 'pending'.
- Approved
Plan HTML Review App Design
Defines a manual workflow that converts canonical markdown specs into clean HTML review pages via an agentic LLM CLI, served by a dedicated Astro app at apps/plans. The generated HTML is a fast-comprehension layer over the source markdown — not a second source of truth.
- Generation is LLM-driven via a pluggable agentic CLI (claude|codex|gemini); the agent writes JSON + HTML files directly into a temp dir which the script validates and atomically promotes.
- Output shape is a metadata JSON file plus a sibling HTML body fragment; the Astro shell wraps the fragment with chrome (hero, TOC, footer).
- Component vocabulary is closed: body_html may only use classes declared in COMPONENT_CATALOG.md; undeclared classes trigger a suggestion entry in JSON, not a lint pass.
- Approved
Fieldforce Phase 4 — Daily AI Briefings + At-Risk Delays Design
Phase 4 ships a daily AI-generated briefing per org, persisted as ff_briefings records and surfaced as a panel dashboard widget for managers and supervisors. A hybrid pipeline (deterministic heuristics shortlist → single LLM call ranks and reasons) keeps costs at one call per org per day, with full heuristic-only fallback when the AI gate denies or the LLM fails. A new ai_prompt_templates table lets platform admins tune LLM prompts in the DB without code PRs.
- Approach A selected: one org-scope LLM briefing per day with supervisor team-filtered at-risk view — 1 LLM call per org per day, avoiding cost-scaling per-team calls.
- ff_briefings UNIQUE(org_id, briefing_date) provides lease-based idempotency — the losing replica on a UNIQUE conflict skips the org with no LLM spend.
- team_ids denormalized onto ff_briefing_at_risk at generation time for cheap supervisor read filtering with correct snapshot semantics (team membership at generation time).
- Approved
Phase 2 Grill Session — Decisions & Clarifications
Grill session between Wayne and Claude resolving eight open design questions for Fieldforce Phase 2 across Go backend, Astro mobile, and SolidStart panel. Decisions lock in atomic attachment linking, immutable ticked checklist items, upload-on-select for both platforms, and role-only approval access control.
- Activity creation and attachment linking are atomic: if any attachment ID is invalid or already-used, the entire transaction rolls back with 422.
- Ticked checklist items (done: true) are immutable — Go returns 422 if client attempts to modify done/ticked_at/ticked_by.
- Client sends full checklist item objects including existing ticked_at/ticked_by; Go stores as-is with no server-side matching.
- Approved
Fieldforce Phase 3 — Task Creation AI + BYOK Foundation
Phase 3 delivers the first user-facing AI feature in Fieldforce — natural-language task creation on the SolidStart Panel — plus the platform-wide AccessGate that all future AI calls must pass through, covering trial/platform/BYOK/disabled modes, plan caps, AES-GCM encrypted BYOK key management, and a global AI kill-switch. Three deferred Phase 2 AI features (NL task parsing, auto-categorization, AI checklist generation) are delivered as one user action via a single combined-schema LLM call.
- Single combined-schema LLM call per parse-task request replaces two sequential calls, halving latency and aligning token debit with user-visible Generate actions.
- AccessGate.Authorize must run before any prompt construction; every AI call routes through the gate before provider interaction.
- Trial rows are lazy-provisioned on first AI call via INSERT ON CONFLICT DO NOTHING; no org-creation hook couples core org creation to AI module availability.
- Approved +1 variant
Fieldforce Management — Design Spec
Fieldforce Management is a multi-tenant SaaS module for dispatching tasks to field teams, tracking execution, and verifying completion with AI — supporting service, sales, and logistics verticals. Two surfaces (Panel desktop + Mobile Web) share one org-scoped backend, with ClawUI routing all AI calls across Anthropic, OpenAI, Google, and Azure providers with per-org cost tracking and audit logging.
- Two surfaces (Panel desktop + Mobile Web) share one backend; role controls WHAT you see, surface controls HOW you see it — both route through /api/organizations/:org_id/fieldforce/*.
- All AI calls route through ClawUI middleware for cost tracking, audit logging, and multi-provider failover — Anthropic, OpenAI, Google, and Azure OpenAI supported.
- Storage abstraction layer (FieldforceStorageAdapter interface) is defined in Phase 1 so components never call IndexedDB directly — Electric SQL swap in Phase 4 is a single implementation change with zero component changes.
- Approved +1 variant
grm CLI — Auth MVP Design
Design for the grm CLI's authentication commands (login, logout, whoami) using OAuth 2.0 Device Authorization Flow with Better Auth's deviceAuthorization and apiKey plugins. The CLI stores a long-lived API key locally after browser-based sign-in.
- OAuth 2.0 Device Authorization Flow chosen for login — enables both human and AI agent authentication without exposing credentials in the terminal
- Long-lived API key stored locally after device flow completes — agents authenticate once and reuse the key for all subsequent operations
- Thin Cobra abstraction layer (CommandDef/FlagDef/Context/Build) eliminates framework boilerplate while preserving completions and man pages