docs: add development prompts and vibe coding checklist

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-04-14 19:04:19 +02:00
parent 11e379a5b9
commit ea159a34fe
3 changed files with 619 additions and 0 deletions

View File

@@ -0,0 +1,296 @@
# Crewli — Claude Code Master Prompt
# Plak dit BOVEN elke task. Vervang [TASK] onderaan.
## MANDATORY PREAMBLE — READ BEFORE DOING ANYTHING
Read `/CLAUDE.md` and `/dev-docs/SCHEMA.md` in full before starting. These are
your single source of truth. Do not deviate from them. Do not make assumptions
about the database schema — always verify against SCHEMA.md. Also read
`/dev-docs/API.md` for the existing API contract.
If the task involves a module that already has existing code, read that code
first. Understand the current state before making changes.
## PROJECT CONTEXT
Crewli is a multi-tenant SaaS platform for event and festival management.
- **Backend:** Laravel 12 REST API (no Blade views, no Inertia), Sanctum auth,
Spatie Permission, MySQL 8, Redis
- **Frontend:** Three standalone Vue 3 + TypeScript SPAs on Vuexy 9.5 /
Vuetify 3.10 — `apps/admin/` (port 5173), `apps/app/` (port 5174),
`apps/portal/` (port 5175)
- **State:** Pinia + TanStack Vue Query
- **Forms:** VeeValidate + Zod
- **API base path:** `/api/v1/`
- **Testing:** PHPUnit (backend), Vitest (frontend)
### Repository structure
```
crewli/
├── api/ # Laravel 12 backend
│ ├── app/
│ │ ├── Http/
│ │ │ ├── Controllers/Api/V1/
│ │ │ ├── Middleware/
│ │ │ └── Requests/ # Form Requests per endpoint
│ │ ├── Models/
│ │ ├── Policies/
│ │ ├── Services/ # Business logic (NOT in controllers)
│ │ ├── Enums/ # PHP Enums for all status/type fields
│ │ ├── Events/ + Listeners/
│ │ └── Jobs/ # Queued jobs (briefings, notifications)
│ ├── database/
│ │ ├── migrations/
│ │ ├── factories/
│ │ └── seeders/
│ └── tests/Feature/Api/V1/
├── apps/
│ ├── admin/ # Super Admin SPA
│ ├── app/ # Organizer SPA (main app)
│ │ └── src/
│ │ ├── lib/axios.ts # THE ONLY axios instance
│ │ ├── composables/api/ # TanStack Query composables
│ │ ├── stores/ # Pinia stores
│ │ ├── types/ # TypeScript interfaces
│ │ └── pages/
│ └── portal/ # Dual-mode portal
├── dev-docs/ # Developer documentation (source of truth)
│ ├── SCHEMA.md
│ ├── API.md
│ ├── BACKLOG.md
│ ├── design-document.md
│ ├── dev-guide.md
│ └── start-guide.md
├── docs/ # VitePress end-user documentation (Dutch)
├── CLAUDE.md # This file's companion — workspace rules
└── .cursorrules
```
### Key architectural decisions (final, non-negotiable)
- **ULID** primary keys on all business tables via `HasUlids` — NEVER UUID v4
- **Integer auto-increment** on pure pivot tables only
- **JSON columns** exclusively for opaque config (settings, blocks) — never
for queryable data
- **Multi-tenancy** via `OrganisationScope` Eloquent Global Scope on all
event-related models
- **Soft delete** per table type as documented in SCHEMA.md — immutable audit
records (check_ins, form_submissions, briefing_sends) do NOT get soft delete
- **Portal dual-mode:** Sanctum session for volunteers/crew (persistent
identity), `portal.token` middleware for artists/suppliers/press
(event-specific, no account)
---
## ZERO-COMPROMISE RULES — VIOLATIONS ARE BLOCKERS
These rules are absolute. No workarounds. No "we'll fix it later". No partial
implementations. If something cannot be done properly, STOP and report it.
### Architecture & design
1. **ARCHITECTURE FIRST.** If the task requires a new pattern, data structure,
or integration that isn't documented in CLAUDE.md or SCHEMA.md — stop and
ask. Do not invent architecture. Write an Architecture Decision Record (ADR)
in `/dev-docs/decisions/` if a significant design choice is needed.
2. **DELETE OVER ADAPT.** If you find duplicate logic, conflicting patterns, or
legacy code that does the same thing differently: delete the worse version.
Do not build alongside it. Do not "wrap" it. If two implementations exist,
one must die. Consolidate to one source of truth.
3. **STRICT LAYERING.** Business logic belongs in `app/Services/`, NEVER in
controllers. Controllers handle HTTP concerns only: receive request, call
service, return resource. Data access patterns go through Eloquent models
with proper scopes. No "quick fixes" in the wrong layer.
- Controller → receives FormRequest, calls Service, returns API Resource
- Service → contains business logic, validation beyond FormRequest,
orchestration
- Model → relationships, scopes, accessors, mutators
- Job → async work dispatched by Service
4. **CONTRACT-FIRST.** Before implementing any module:
- Define the PHP Enum(s) in `app/Enums/` for all status/type fields
- Define the API Resource (response shape) first
- Define the Form Request (input validation) first
- Then implement the Service and Controller
Types and contracts define behaviour. Implementation follows.
5. **CONSISTENCY OVER CLEVERNESS.** Use the same pattern for every similar
problem. If existing modules use a specific approach for pagination, error
handling, or resource loading — use that exact same approach. Never
introduce a "better" alternative pattern without refactoring ALL existing
code to match. One pattern per problem type, across the entire codebase.
6. **SINGLE SOURCE OF TRUTH.** Every piece of information exists in exactly
one place:
- Enum values → PHP Enum class (not string literals)
- Validation rules → Form Request (not duplicated in frontend)
- Response shape → API Resource (not ad-hoc arrays)
- Config values → .env / config files (not hardcoded)
- Schema definition → SCHEMA.md (not guessed)
### Code quality
7. **NO TODO / FIXME / HACK.** Zero tolerance. If you cannot complete
something, stop and report it. Do not leave stubs, placeholders, or
"implement later" comments. Every file you touch must be production-ready.
8. **NO UNTYPED CODE.** PHP: `declare(strict_types=1)` on every file. Return
types on all methods. Typed properties. Use PHP Enums (not string literals)
for all status, type, and role fields. No `mixed` where a concrete type
or union type would work.
9. **NO GENERIC NAMES.** Names must be specific and self-documenting:
-`DataService`, `Helper`, `Manager`, `handleData()`, `processItem()`
-`ShiftAssignmentService`, `VolunteerAvailabilityChecker`,
`resolveShiftConflict()`, `calculateFillRate()`
10. **NO SILENT ERROR HANDLING.** No empty catch blocks. No `catch { return
null; }`. Every error must be: logged (via `Log::error()` with context),
or rethrown, or handled with a proper API error response. Use
`report($e)` for unexpected errors.
### Testing
11. **TESTS ARE DESIGN, NOT AFTERTHOUGHT.** Tests define expected behaviour.
Every controller needs feature tests covering:
- 200/201 (happy path for each action)
- 401 (unauthenticated access)
- 403 (wrong organisation — cross-tenant access attempt)
- 403 (insufficient role/permission)
- 422 (validation errors with specific field assertions)
- Edge cases specific to the module (e.g., shift conflict, capacity full)
Run `php artisan test` after EVERY module. Fix failures before proceeding.
Never skip, comment out, or mark tests as incomplete.
12. **FACTORIES ARE REALISTIC.** Use realistic Dutch test data (names,
addresses, company names) that reflects actual usage. Factories must create
valid, complete records — no missing required fields or placeholder values.
### Data & persistence
13. **DATA MODEL IS SACRED.** Never deviate from SCHEMA.md. Every column,
every constraint, every index documented there must be in the migration.
If SCHEMA.md is unclear, ask — do not guess.
14. **EVERY MIGRATION HAS down().** The `down()` method must cleanly reverse
the `up()`. Drop tables, remove columns, restore previous state. No
`down()` methods that throw or do nothing.
15. **INDEXES ARE MANDATORY.** Every foreign key column, every column used in
WHERE/ORDER BY clauses, every unique constraint from SCHEMA.md must have
an explicit index. Verify composite indexes match the documented patterns.
### Security & multi-tenancy
16. **NO UNSCOPED QUERIES.** Every query on event-related models must be
scoped to `organisation_id` via `OrganisationScope`. Mental test: can
User A from Org 1 ever see, modify, or infer the existence of data from
Org 2? If yes → security bug → fix immediately.
17. **POLICIES ARE COMPLETE.** Every policy method checks:
- Does the user belong to the correct organisation?
- Does the user have the required Spatie role/permission for this action?
- No `return true` placeholders. No missing methods.
18. **FORM REQUESTS ARE COMPLETE.** Every store/update has a Form Request
with full validation matching SCHEMA.md constraints: required, nullable,
max length, enum values (referencing the PHP Enum), exists rules with
proper scoping (e.g., `exists:events,id` scoped to organisation).
### API responses
19. **NO BARE MODEL RETURNS.** Every API response goes through an API
Resource. Never `return $model`, `$model->toArray()`, or raw arrays.
Resources define the public contract. Computed fields (fill_rate,
status_label, slot counts) belong in the Resource.
20. **CONSISTENT RESPONSE STRUCTURE.** Follow the existing pattern:
`{ data: {...}, meta: {...} }` for paginated lists. Consistent error
format with `{ message: "...", errors: {...} }` for validation failures.
### Resilience & operations
21. **IDEMPOTENT OPERATIONS.** Every queued job must be safe to retry.
Check-before-act: verify state hasn't changed. Use database transactions
for multi-step mutations. Queued notifications and external API calls
(Zender SMS/WhatsApp) must handle duplicates gracefully.
22. **OBSERVABILITY.** Log significant business events using
`spatie/laravel-activitylog` (already installed). Every create, update,
delete, and status change on business entities must be logged with:
- `causedBy($user)` — who did it
- `performedOn($model)` — what was affected
- `withProperties([...])` — relevant context (old values, new values)
23. **API VERSIONING.** All routes under `/api/v1/`. Controllers in
`App\Http\Controllers\Api\V1\`. Never introduce breaking changes to
existing endpoints — add new fields, don't rename or remove.
### Process
24. **MODULE GENERATION ORDER.** Always follow this sequence. No skipping.
1. PHP Enum(s) for status/type fields (`app/Enums/`)
2. Migration(s) — verify against SCHEMA.md
3. Eloquent Model with: HasUlids, HasFactory, SoftDeletes (if documented),
OrganisationScope (if event-related), relationships, scopes, accessors
4. Factory with realistic Dutch test data
5. Service class for business logic (`app/Services/`)
6. Policy for authorisation
7. Form Request(s) for validation
8. API Resource for response transformation
9. Resource Controller (thin — delegates to Service)
10. Routes in `api/routes/api.php`
11. Feature tests — run them, fix failures
12. Activity log integration in Service methods
13. Update `/dev-docs/API.md` with new routes
25. **GIT.** Auto-commit after each completed module:
`feat(module-name): add backend scaffold with tests`
---
## VERIFICATION CHECKLIST (run before reporting "done")
```bash
# All tests pass
php artisan test
# Database rebuilds cleanly
php artisan migrate:fresh --seed
# New routes are visible
php artisan route:list --path=api/v1
# No forbidden patterns
grep -rn "TODO\|FIXME\|HACK\|dd(\|dump(\|var_dump\|Model::all()" \
api/app/ api/tests/ --include="*.php"
# No UUID v4 in migrations
grep -rn "uuid(" api/database/migrations/ --include="*.php"
# Static analysis (if configured)
./vendor/bin/phpstan analyse
```
Manual verification:
- [ ] Every new model has: HasUlids, HasFactory, correct SoftDeletes, complete
$fillable, all relationships from SCHEMA.md
- [ ] Every new model with event data has OrganisationScope
- [ ] Every controller action is covered by a Policy method
- [ ] Every Service method logs activity via spatie/laravel-activitylog
- [ ] Every Form Request references PHP Enums (not string literals) for
enum validation
- [ ] Business logic is in Service classes, not in Controllers
- [ ] API Resource includes computed fields where applicable
- [ ] No N+1: index actions use `with()` for all accessed relationships
- [ ] `/dev-docs/API.md` updated with new routes
---
## TASK
[INSERT SPECIFIC TASK HERE]

View File

@@ -0,0 +1,177 @@
# Crewli — Cursor Master Prompt
# Plak dit BOVEN elke task. Vervang [TASK] onderaan.
## MANDATORY PREAMBLE
Read `@CLAUDE.md` and `@dev-docs/SCHEMA.md` before starting. Use `@workspace`
for full codebase context. These documents are your source of truth.
If modifying existing code, read the current implementation first. Understand
the patterns already in use before writing anything new.
## PROJECT CONTEXT
Crewli — multi-tenant SaaS for event/festival management.
- **Frontend:** Vue 3 + TypeScript + Vuexy 9.5 (Vuetify 3.10) + Pinia +
TanStack Vue Query + VeeValidate + Zod
- **Three SPAs:**
- `apps/admin/` — Super Admin (port 5173)
- `apps/app/` — Organizer main app (port 5174)
- `apps/portal/` — Dual-mode portal (port 5175)
- **API base:** `VITE_API_URL` from `.env.local` (default `http://localhost:8000`)
- **Axios instance:** `src/lib/axios.ts` — this is the ONLY axios instance.
Never create another. Never import axios directly in components.
- **Backend Enums:** PHP Enums in `api/app/Enums/` define all valid status/type
values. TypeScript equivalents must mirror these exactly in `src/types/`.
### Frontend file structure (per app)
```
src/
├── lib/axios.ts # Singleton axios instance (DO NOT DUPLICATE)
├── types/ # TypeScript interfaces per module
│ └── [module].ts
├── composables/api/ # TanStack Query composables per module
│ └── use[Module].ts
├── stores/ # Pinia stores (cross-component state only)
│ └── use[Module]Store.ts
├── pages/ # Page components
│ └── [module]/
│ ├── index.vue # List view
│ └── [id].vue # Detail view (or side panel)
└── router/ # Vue Router config
```
---
## ZERO-COMPROMISE RULES — VIOLATIONS ARE BLOCKERS
### TypeScript & typing
1. **NO `any` TYPES. EVER.** Every variable, prop, emit, return type, ref,
reactive, computed, and API response is fully typed. If you don't know the
type, check the backend API Resource in `api/app/Http/Resources/` — that
defines the contract. Create matching interfaces in `src/types/[module].ts`.
2. **TYPES FIRST.** Before writing any composable or component for a new
module, create the TypeScript interfaces in `src/types/[module].ts`. These
mirror the backend API Resource shape. Include:
- Entity interface (e.g., `Shift`, `Person`, `Event`)
- Create/Update DTOs (matching backend Form Request fields)
- Enum-like union types or const objects matching backend PHP Enums
- Paginated response wrapper if applicable
3. **ENUMS AS CONST OBJECTS.** Mirror backend PHP Enums as TypeScript const
objects with `as const`, not as loose string unions scattered across files:
```typescript
// src/types/shift.ts
export const ShiftAssignmentStatus = {
PENDING_APPROVAL: 'pending_approval',
APPROVED: 'approved',
REJECTED: 'rejected',
CANCELLED: 'cancelled',
COMPLETED: 'completed',
} as const
export type ShiftAssignmentStatus = typeof ShiftAssignmentStatus[keyof typeof ShiftAssignmentStatus]
```
### Architecture & patterns
4. **NO DIRECT AXIOS IN COMPONENTS.** All API calls go through composables in
`composables/api/use[Module].ts` using TanStack Query (`useQuery` /
`useMutation`). Components never import axios or `src/lib/axios.ts`.
The composable is the only layer that knows about HTTP.
5. **NO OPTIONS API.** Always `<script setup lang="ts">`. Props via
`defineProps<{...}>()`, emits via `defineEmits<{...}>()`, expose via
`defineExpose()`. No `export default { ... }`.
6. **NO PROP DRILLING.** If state needs to cross more than one component
boundary, use a Pinia store (`src/stores/use[Module]Store.ts`). Access
reactive state via `storeToRefs()`. Mutations only through store actions.
7. **DELETE OVER ADAPT.** If you find duplicate composables, overlapping
stores, or conflicting patterns: delete the worse version. Do not build
alongside existing code that does the same thing. One implementation per
concern.
8. **CONSISTENCY OVER CLEVERNESS.** Look at how existing modules handle the
same pattern (table views, form dialogs, detail panels, error handling).
Use the exact same approach. Never introduce a "better" alternative without
refactoring ALL existing modules to match.
9. **SINGLE SOURCE OF TRUTH.** Don't duplicate:
- Validation logic → Zod schema mirrors backend Form Request. One schema
per form, referenced by VeeValidate.
- Enum values → const objects in `src/types/`, matching backend Enums.
Never use raw string literals like `'approved'` in templates or logic.
- API URLs → constructed in composable from module prefix, never
hardcoded in components.
### UI & UX
10. **NO CUSTOM CSS WHERE VUETIFY HAS A SOLUTION.** Before writing any CSS:
check if a Vuetify utility class, component prop, or slot achieves the
result. Custom CSS is a last resort, must be `<style scoped>`, and must
have a comment explaining why Vuetify couldn't handle it.
11. **EVERY PAGE HAS THREE STATES:**
- **Loading:** Vuetify skeleton loader or progress indicator
- **Error:** User-facing message with retry action (`v-alert` with retry
button). Show what went wrong in user terms, not technical jargon.
- **Empty:** Helpful message explaining why the list is empty and what
action to take (not a blank white screen).
Never show only the happy path.
12. **MOBILE-FIRST.** Every component must be usable at 375px width. Use
Vuetify's responsive props (`cols`, `sm`, `md`, `lg` on `v-col`). Never
use fixed pixel widths for layout. Tables on mobile → card views or
horizontal scroll with visual indicator.
13. **FORMS USE VEEVALIDATE + ZOD.** Define the Zod schema matching the
backend Form Request rules. No inline validation logic in templates. Use
`useForm()` and `useField()` from VeeValidate with the Zod resolver.
### Code quality
14. **NO TODO / FIXME / HACK.** Complete the implementation or report the
blocker. No stubs, no "implement later", no placeholder components.
15. **NO ORPHANED IMPORTS OR UNUSED VARIABLES.** Run `npx tsc --noEmit`
after every change. Zero errors, zero warnings. Dead code is deleted
immediately, not commented out.
16. **NO GENERIC NAMES.** Component and composable names must be specific:
- ❌ `DataTable.vue`, `useApi.ts`, `helpers.ts`, `utils.ts`
- ✅ `ShiftAssignmentTable.vue`, `useShifts.ts`,
`formatShiftTimeRange.ts`
### Routing & auth
17. **ROUTER GUARDS.** Protected routes check auth state via navigation
guard. Portal routes validate token. No unguarded routes to
authenticated content. Redirect to login on 401.
### Process
18. **COMPONENT CREATION ORDER.** For every new page/feature:
1. TypeScript types in `src/types/[module].ts`
2. API composable in `src/composables/api/use[Module].ts`
3. Pinia store in `src/stores/use[Module]Store.ts` (if cross-component)
4. Vue page component in `src/pages/[module]/`
5. Router entry in `src/router/`
19. **VERIFY BEFORE DONE:**
- `npx tsc --noEmit` — zero errors
- No `any` types anywhere (search: `grep -rn ": any\|as any" src/`)
- No TODO/FIXME/HACK
- All three states (loading, error, empty) implemented
- Mobile responsive at 375px
- Consistent with existing module patterns
---
## TASK
[INSERT SPECIFIC TASK HERE]

View File

@@ -0,0 +1,146 @@
# Crewli — Zero-Compromise Vibe Coding Aandachtspunten
Referentiedocument voor het aansturen van AI agents (Claude Code & Cursor).
Alle 18 principes zijn verwerkt in de master prompts. Dit document dient als
overzicht en review-checklist.
---
## De 18 principes — waar ze landen
| # | Principe | Master Prompt CC | Master Prompt Cursor | Sectie |
|---|----------|:---:|:---:|--------|
| 1 | Architectuur eerst | ✅ Regel 1 | — | Architecture & design |
| 2 | Delete > Adapt | ✅ Regel 2 | ✅ Regel 7 | Architecture & design |
| 3 | Strict layering | ✅ Regel 3 | ✅ Regel 4, 6 | Architecture & design |
| 4 | Contract-first | ✅ Regel 4 | ✅ Regel 2, 3 | Architecture & design |
| 5 | Tests als design | ✅ Regel 11, 12 | — | Testing |
| 6 | Idempotency & resilience | ✅ Regel 21 | — | Resilience & operations |
| 7 | Naming is architecture | ✅ Regel 9 | ✅ Regel 16 | Code quality |
| 8 | Security & auth direct goed | ✅ Regel 16, 17 | ✅ Regel 17 | Security & multi-tenancy |
| 9 | Performance by design | ✅ Checklist | ✅ (via composable pattern) | Verification |
| 10 | Consistency > cleverness | ✅ Regel 5 | ✅ Regel 8 | Architecture & design |
| 11 | Observability dag 1 | ✅ Regel 22 | — | Resilience & operations |
| 12 | Versioning & backward compat | ✅ Regel 23 | — | Resilience & operations |
| 13 | Data model = heilig | ✅ Regel 13, 14, 15 | — | Data & persistence |
| 14 | Prompt discipline | ✅ Hele structuur | ✅ Hele structuur | (Meta) |
| 15 | Perfectie vs. pragmatiek | ✅ Regel 3 + 5 | ✅ Regel 8 | Architecture & design |
| 16 | Zero TODO | ✅ Regel 7 | ✅ Regel 14 | Code quality |
| 17 | Single Source of Truth | ✅ Regel 6 | ✅ Regel 9 | Architecture & design |
| 18 | Definition of Done | ✅ Checklist | ✅ Regel 19 | Verification |
---
## Projectspecifieke aandachtspunten (bovenop de 18)
### Bestandspaden — correcte referenties
| Wat | Correct pad | Fout (oude pad) |
|-----|-------------|-----------------|
| Schema definitie | `/dev-docs/SCHEMA.md` | `/docs/SCHEMA.md` |
| API contract | `/dev-docs/API.md` | `/docs/API.md` |
| Design document | `/dev-docs/design-document.md` | `/docs/design-document.md` |
| Dev guide | `/dev-docs/dev-guide.md` | `/docs/dev-guide.md` |
| User docs (VitePress) | `/docs/` | — |
| Workspace rules | `/CLAUDE.md` (root) | — |
| Axios instance (app) | `apps/app/src/lib/axios.ts` | `src/utils/api.ts` etc. |
### PHP Enums — verplicht voor alle status/type velden
Agents gebruiken graag string literals. Dwing af:
```php
// ❌ VERBODEN
$assignment->status = 'approved';
Rule::in(['pending_approval', 'approved', 'rejected'])
// ✅ VERPLICHT
$assignment->status = ShiftAssignmentStatus::APPROVED;
Rule::enum(ShiftAssignmentStatus::class)
```
Alle bestaande/verwachte Enums (uit SCHEMA.md):
- `BillingStatus` — trial|active|suspended|cancelled
- `EventStatus` — draft|published|active|completed|archived
- `PersonStatus` — invited|applied|pending|approved|rejected|no_show
- `ShiftAssignmentStatus` — pending_approval|approved|rejected|cancelled|completed
- `InvitationStatus` — pending|accepted|expired|revoked
- `CrowdListType` — internal|external
- `BookingStatus` — pending|offer_sent|confirmed|cancelled|declined
- `AdvanceSectionStatus` — not_started|in_progress|submitted|approved
### Service Layer — wanneer wel/niet
**Gebruik een Service class wanneer:**
- Business logic meer is dan "opslaan wat de FormRequest valideert"
- Meerdere modellen gewijzigd worden in één actie
- Side effects nodig zijn (notifications, activity log, queue jobs)
- Dezelfde logica vanuit meerdere controllers aangeroepen kan worden
**Voorbeelden in Crewli:**
- `ShiftAssignmentService` — assign, claim, approve, reject (status machine + notifications)
- `PersonIdentityService` — deduplicatie, user_id matching
- `AccreditationService` — toekennen, intrekken, budget checks
- `ZenderService` — SMS/WhatsApp via externe API
**Geen Service nodig voor:**
- Simpele CRUD zonder business logic (locations, crowd_types, accreditation_categories)
- Hier volstaat de controller direct
### Spatie Activity Log — verplicht gebruik
```php
// In elke Service method die state wijzigt:
activity()
->causedBy($user)
->performedOn($shift)
->withProperties([
'old' => ['status' => $oldStatus->value],
'new' => ['status' => $newStatus->value],
])
->log('shift.assignment.approved');
```
### Multi-tenancy mentale test
Bij elke nieuwe controller, voer deze test uit:
1. Maak in je hoofd twee organisaties: Org A en Org B
2. Gebruiker X hoort bij Org A
3. Kan X via de API data van Org B zien? → BUG
4. Kan X via de API data van Org B wijzigen? → CRITICAL BUG
5. Kan X via de API afleiden dat data van Org B bestaat? → SECURITY ISSUE
### Idempotency checklist voor queued jobs
Elke Job moet deze vragen beantwoorden:
- Wat gebeurt er als deze job 2x draait? → Geen dubbele side effects
- Wat als het model intussen verwijderd is? → Graceful exit, geen crash
- Wat als de externe API (Zender) een timeout geeft? → Retry met backoff
- Wat als de status intussen veranderd is? → Check-before-act
---
## Review checklist — na elke Claude Code / Cursor sessie
### Backend (Claude Code output)
- [ ] Geen TODO/FIXME/HACK (`grep -rn "TODO\|FIXME\|HACK" api/app/`)
- [ ] Geen UUID v4 (`grep -rn "uuid(" api/database/migrations/`)
- [ ] Geen `Model::all()` zonder scope
- [ ] Geen business logic in controllers (alleen HTTP + delegate to Service)
- [ ] PHP Enums gebruikt (niet string literals)
- [ ] Activity log in Service methods
- [ ] Tests draaien groen (`php artisan test`)
- [ ] Migratie heeft `down()` method
- [ ] Policies compleet (geen `return true` placeholders)
- [ ] N+1 check: `with()` in index/show actions
- [ ] `/dev-docs/API.md` bijgewerkt
### Frontend (Cursor output)
- [ ] Geen `any` types (`grep -rn ": any\|as any" apps/*/src/`)
- [ ] Geen directe axios imports in components
- [ ] Types gedefinieerd in `src/types/`
- [ ] Drie states: loading, error, empty
- [ ] Zod schema voor formulieren
- [ ] Vuetify componenten (geen onnodige custom CSS)
- [ ] Mobile responsive op 375px
- [ ] `npx tsc --noEmit` groen