diff --git a/Agents.md b/Agents.md
index b68be1d2..792781ac 100644
--- a/Agents.md
+++ b/Agents.md
@@ -1,5 +1,9 @@
# Markets — Project Coding Standards
+> This file extends the **[petry-projects organization AGENTS.md](https://github.com/petry-projects/.github/blob/main/AGENTS.md)** with Markets-specific conventions. The org standards define TDD, SOLID, CLEAN, DRY, DDD, KISS, YAGNI, pre-commit checks, CI gates, BMAD workflows, and multi-agent isolation. This file does not restate those — it specifies how they apply to this project's Go + React Native/Expo stack. If a rule here conflicts with the org AGENTS.md, this file takes precedence.
+>
+> For detailed Markets-specific application of each principle (bounded contexts, aggregate roots, repository interfaces, domain events, typed IDs, dependency direction, test-by-layer guidance, coverage thresholds, and CI commands), see `_bmad-output/planning-artifacts/coding-standards.md`.
+
## Project Overview
**markets** is a real-time coordination platform for local farmers markets. Mobile-first (iOS/Android) with web via React Native for Web. Three user roles: Customer, Vendor, Market Manager.
@@ -148,14 +152,12 @@ theme: {
## Test-Driven Development
-- IMPORTANT: All development MUST follow test-driven development (TDD) practices
-- Write failing tests BEFORE writing implementation code (Red → Green → Refactor)
-- **Go backend:** Write Go test files (`_test.go`) co-located with source files before implementing resolvers, middleware, or services. Use `go test ./...` to verify.
-- **React Native frontend:** Write tests using Jest + React Native Testing Library before implementing components and hooks. Test files co-located as `*.test.tsx` or in `__tests__/` directories.
-- **GraphQL resolvers:** Write integration tests (tagged `//go:build integration`) that test the full resolver → database → response path
-- **Acceptance criteria drive tests:** Each story's Given/When/Then acceptance criteria should map directly to test cases
-- Every PR must include tests that cover the new or changed functionality
-- Do not merge code without passing tests
+TDD rules are defined in the [org AGENTS.md](https://github.com/petry-projects/.github/blob/main/AGENTS.md). Markets-specific test framework configuration, mocking strategy, coverage thresholds, and per-layer test guidance are in `_bmad-output/planning-artifacts/coding-standards.md`.
+
+- **Go backend:** `*_test.go` co-located with source; `//go:build integration` for integration tests
+- **React Native frontend:** `*.test.tsx` co-located or `__tests__/`; Jest + React Native Testing Library
+- **GraphQL resolvers:** Integration tests for the full resolver → database → response path
+- **Acceptance criteria drive tests:** Each story's Given/When/Then maps directly to test cases
## Event-Driven Architecture
diff --git a/CLAUDE.md b/CLAUDE.md
index 2465c655..cb0a6baa 100644
--- a/CLAUDE.md
+++ b/CLAUDE.md
@@ -1,6 +1,6 @@
# Markets — Claude Code Instructions
-> **Read [Agents.md](Agents.md) first.** It contains the project coding standards, technology stack, component organization, styling rules, naming conventions, accessibility requirements, mobile UX constraints, TDD practices, event-driven architecture rules, and anti-patterns that apply to all work in this repo.
+> **Read the [petry-projects org AGENTS.md](https://github.com/petry-projects/.github/blob/main/AGENTS.md) and then [Agents.md](Agents.md).** The org file defines cross-cutting standards (TDD, SOLID, CLEAN, DRY, DDD, KISS, YAGNI, CI gates, BMAD). The project Agents.md contains Markets-specific technology stack, component organization, styling rules, naming conventions, accessibility requirements, mobile UX constraints, event-driven architecture rules, and anti-patterns. For detailed principle application (bounded contexts, aggregates, typed IDs, test strategy, coverage thresholds), see `_bmad-output/planning-artifacts/coding-standards.md`.
## Figma MCP Integration Rules
diff --git a/_bmad-output/implementation-artifacts/sprint-status.yaml b/_bmad-output/implementation-artifacts/sprint-status.yaml
new file mode 100644
index 00000000..19d04015
--- /dev/null
+++ b/_bmad-output/implementation-artifacts/sprint-status.yaml
@@ -0,0 +1,116 @@
+# generated: 2026-03-28
+# last_updated: 2026-03-28
+# project: markets
+# project_key: NOKEY
+# tracking_system: file-system
+# story_location: {project-root}/_bmad-output/implementation-artifacts
+
+# STATUS DEFINITIONS:
+# ==================
+# Epic Status:
+# - backlog: Epic not yet started
+# - in-progress: Epic actively being worked on
+# - done: All stories in epic completed
+#
+# Epic Status Transitions:
+# - backlog → in-progress: Automatically when first story is created (via create-story)
+# - in-progress → done: Manually when all stories reach 'done' status
+#
+# Story Status:
+# - backlog: Story only exists in epic file
+# - ready-for-dev: Story file created in stories folder
+# - in-progress: Developer actively working on implementation
+# - review: Ready for code review (via Dev's code-review workflow)
+# - done: Story completed
+#
+# Retrospective Status:
+# - optional: Can be completed but not required
+# - done: Retrospective has been completed
+#
+# WORKFLOW NOTES:
+# ===============
+# - Epic transitions to 'in-progress' automatically when first story is created
+# - Stories can be worked in parallel if team capacity allows
+# - SM typically creates next story after previous one is 'done' to incorporate learnings
+# - Dev moves story to 'review', then runs code-review (fresh context, different LLM recommended)
+
+generated: 2026-03-28
+last_updated: 2026-03-28
+project: markets
+project_key: NOKEY
+tracking_system: file-system
+story_location: "{project-root}/_bmad-output/implementation-artifacts"
+
+development_status:
+ # ── Epic 1: Authentication & Role-Based Access ──
+ epic-1: backlog
+ 1-1a-frontend-project-scaffolding: backlog
+ 1-1b-backend-project-scaffolding-audit-infrastructure: backlog
+ 1-2-google-apple-sign-in-authentication: backlog
+ 1-3-role-selection-user-record-creation: backlog
+ 1-4-market-scoped-manager-permissions: backlog
+ 1-5-role-based-access-enforcement-middleware: backlog
+ epic-1-retrospective: optional
+
+ # ── Epic 2: Market Administration ──
+ epic-2: backlog
+ 2-1-create-edit-market-profile: backlog
+ 2-2-market-schedule-hours-management: backlog
+ 2-2a-browse-invite-vendors-to-market: backlog
+ 2-2b-future-market-day-planning: backlog
+ 2-2c-market-rules-expectations: backlog
+ 2-2d-send-vendor-notifications: backlog
+ 2-2e-cancel-or-end-market-early: backlog
+ 2-3-per-date-vendor-roster-management-join-request-approval: backlog
+ epic-2-retrospective: optional
+
+ # ── Epic 3: Vendor Profile & Product Catalog ──
+ epic-3: backlog
+ 3-1-create-edit-vendor-profile: backlog
+ 3-2-product-catalog-management: backlog
+ 3-3a-browse-search-markets-to-join: backlog
+ 3-3-market-association-calendar-date-selection: backlog
+ epic-3-retrospective: optional
+
+ # ── Epic 4: Market-Day Vendor Operations ──
+ epic-4: backlog
+ 4-1-vendor-check-in-conflict-detection: backlog
+ 4-2-exception-status-updates: backlog
+ 4-3-product-availability-updates: backlog
+ epic-4-retrospective: optional
+
+ # ── Epic 5: Manager Dashboard & Market-Day Oversight ──
+ epic-5: backlog
+ 5-1-live-attendance-dashboard: backlog
+ 5-2-request-vendor-confirmation: backlog
+ 5-3-manager-check-in-on-behalf-of-vendor: backlog
+ 5-4-market-level-operational-updates: backlog
+ 5-5-automatic-vendor-checkout-at-market-close: backlog
+ epic-5-retrospective: optional
+
+ # ── Epic 6: Customer Discovery, Search & Follow ──
+ epic-6: backlog
+ 6-0-customer-onboarding-location-preferences-first-follows: backlog
+ 6-1-market-vendor-search-distance-filtering: backlog
+ 6-2-vendor-profile-market-status-view: backlog
+ 6-3-follow-vendors-markets: backlog
+ 6-4-following-feed-live-status: backlog
+ 6-5-alternative-vendor-discovery: backlog
+ epic-6-retrospective: optional
+
+ # ── Epic 7: Notifications, Activity Feeds & Real-Time ──
+ epic-7: backlog
+ 7-1-push-notification-infrastructure: backlog
+ 7-2-vendor-check-in-checkout-notifications: backlog
+ 7-3-exception-market-update-notifications: backlog
+ 7-4-notification-preferences: backlog
+ 7-5-per-role-activity-feeds: backlog
+ epic-7-retrospective: optional
+
+ # ── Epic 8: Audit, Privacy & Account Governance ──
+ epic-8: backlog
+ 8-1-immutable-audit-log-infrastructure: backlog
+ 8-2-activity-log-all-roles: backlog
+ 8-3-account-deletion-soft-delete: backlog
+ 8-4-governance-rules-enforcement: backlog
+ epic-8-retrospective: optional
diff --git a/_bmad-output/planning-artifacts/coding-standards.md b/_bmad-output/planning-artifacts/coding-standards.md
new file mode 100644
index 00000000..9a3c8829
--- /dev/null
+++ b/_bmad-output/planning-artifacts/coding-standards.md
@@ -0,0 +1,697 @@
+# Markets — Project Coding Standards
+
+> Authored by Winston (Architect) during Epic 1 Sprint Planning.
+> This document defines how the Markets project applies the organization-wide engineering standards to its Go + React Native/Expo stack.
+
+---
+
+## Organization Standards (Required Reading)
+
+All development in this repository follows the **[petry-projects organization AGENTS.md](https://github.com/petry-projects/.github/blob/main/AGENTS.md)**. That document is the authoritative source for:
+
+- **TDD** — Red-Green-Refactor workflow, coverage enforcement, no `.skip()`, no coverage-ignore comments
+- **SOLID** — SRP, OCP, LSP, ISP, DIP definitions and general guidance
+- **CLEAN Code** — Meaningful names, small functions, minimal arguments, no side-effect surprises, error handling
+- **DRY** — Knowledge deduplication, premature abstraction warnings
+- **DDD** — Ubiquitous language, bounded contexts, aggregate roots, value objects, repository pattern
+- **KISS / YAGNI** — Simplest solution, no speculative features
+- **Defensive coding** — Validate at boundaries, trust internal code, fail fast
+- **Separation of concerns** — Layered architecture, inward dependencies, no framework bleed
+- **Code organization** — Co-located tests, no barrel files, consistent naming
+- **Pre-commit checks** — Format, lint, type check, tests before every commit
+- **CI quality gates** — CodeQL, SonarCloud, CodeRabbit, Copilot review, tests, coverage
+- **BMAD Method** — Spec-driven development, planning artifacts, story-based implementation
+- **Multi-agent isolation** — Git worktrees, one agent per story, no overlapping file ownership
+- **Security** — No committed secrets, environment variables for credentials
+
+**This document does not restate those principles.** It specifies only how they are applied in the Markets codebase.
+
+If a rule here conflicts with the org AGENTS.md, **this document takes precedence** (per org policy).
+
+---
+
+## 1. TDD — Markets Application
+
+### Testing Framework & Configuration
+
+| Layer | Framework | Location | Environment |
+|-------|-----------|----------|-------------|
+| Go unit tests | `go test` | `*_test.go` co-located with source | Go test runner |
+| Go integration tests | `go test -tags=integration` | `*_test.go` with `//go:build integration` | Real Cloud SQL (test instance) |
+| React Native unit/component | Jest + React Native Testing Library | `*.test.tsx` co-located or `__tests__/` | jsdom via Jest |
+| GraphQL resolver integration | `go test -tags=integration` | `internal/graph/*_test.go` | Full resolver → DB → response |
+| E2E | Maestro or Detox | `e2e/` directory | Real app on simulator |
+
+### What to Test (by Layer)
+
+| Layer | What to Test | How |
+|-------|-------------|-----|
+| **Go resolvers** (`internal/graph/`) | Input validation, authorization, business logic, error codes | Unit test with mocked DB; integration test with real DB |
+| **Go middleware** (`internal/auth/`) | JWT validation, role extraction, context population, rejection of invalid tokens | Unit test with crafted JWTs |
+| **Go event bus** (`internal/events/`) | Event publishing, handler registration, handler dispatch, failure isolation | Unit test with mock handlers |
+| **Go DB queries** (`internal/db/`) | CRUD operations, soft-delete filtering, constraint enforcement | Integration test with test database |
+| **PostgreSQL migrations** (`migrations/`) | Schema creation, audit trigger attachment, rollback safety | Integration test: migrate up, verify schema, migrate down |
+| **React Native components** (`components/`) | User interactions, conditional rendering, accessibility labels | React Native Testing Library with user-event |
+| **React Native hooks** (`hooks/`) | State transitions, Apollo cache interactions, return values | `renderHook()` from React Testing Library |
+| **Apollo Client** (`lib/apollo.ts`) | Cache policies, optimistic responses, error handling | Mock Apollo Provider in tests |
+| **GraphQL codegen output** (`graphql/generated/`) | Type correctness (compile-time only) | TypeScript compiler (`tsc --noEmit`) |
+
+### Mocking Strategy
+
+- **Go backend:** Use interfaces for all dependencies (DB, Firebase, event bus). Create `mock_*` or `fake_*` implementations for unit tests. Use real Cloud SQL test instance for integration tests.
+- **React Native:** Mock Apollo Client with `MockedProvider`. Mock `expo-secure-store`, `expo-notifications`, and Firebase SDK. Never import Go backend code in frontend tests.
+
+### Coverage Thresholds
+
+| Metric | Minimum | Target |
+|--------|---------|--------|
+| Go line coverage | 80% | 90% |
+| Go branch coverage | 75% | 85% |
+| React Native line coverage | 80% | 90% |
+| React Native branch coverage | 75% | 85% |
+
+---
+
+## 2. SOLID — Markets Application
+
+### Single Responsibility
+
+**Go backend:**
+- One resolver file per GraphQL schema domain (`auth.resolvers.go`, `market.resolvers.go`, etc.)
+- Business logic in domain packages, not in resolvers directly
+- Event handlers are separate from event publishers
+
+```
+GOOD: internal/auth/middleware.go (JWT validation), internal/auth/claims.go (claim extraction)
+BAD: internal/auth/auth.go (validates JWT + queries DB + publishes events + sets headers)
+```
+
+**React Native:**
+- One component per file (except small internal helpers)
+- Hooks encapsulate one concern (`useVendorStatus`, `useMarketSearch`)
+- Domain components in `components/{domain}/`, not mixed
+
+### Open/Closed — Extension Points
+
+- Go event bus: add new handlers without modifying the publisher or existing handlers
+- GraphQL schema: new types/fields don't break existing queries
+- React Native: new Gluestack component variants via `tva()` without modifying base components
+- Audit triggers: attach to new tables without modifying the trigger function
+
+### Interface Segregation
+
+**Go:**
+```go
+// GOOD: Focused interfaces
+type SessionStore interface {
+ GetSession(ctx context.Context, id string) (*Session, error)
+ SaveSession(ctx context.Context, s *Session) error
+}
+
+type AuditQuerier interface {
+ QueryAuditLog(ctx context.Context, filter AuditFilter) ([]AuditEntry, error)
+}
+
+// BAD: God interface
+type Database interface {
+ GetSession(...) (*Session, error)
+ SaveSession(...) error
+ QueryAuditLog(...) ([]AuditEntry, error)
+ GetMarket(...) (*Market, error)
+ // ... 30 more methods
+}
+```
+
+**GraphQL schema:** Split by domain (6 schema files), not one monolithic schema.
+
+### Dependency Inversion
+
+**Go:** Constructor injection in `cmd/api/main.go` (composition root). No DI frameworks.
+
+```go
+// cmd/api/main.go — composition root
+db := connectDB(cfg.DatabaseURL)
+authMiddleware := auth.NewMiddleware(firebaseApp)
+eventBus := events.NewBus()
+realtimeHandler := realtime.NewHandler(firebaseDB)
+notifyHandler := notify.NewHandler(fcmClient)
+eventBus.Subscribe(realtimeHandler)
+eventBus.Subscribe(notifyHandler)
+resolver := graph.NewResolver(db, eventBus)
+```
+
+**React Native:** React Context for dependency injection. Apollo Provider, Auth Context, and Theme Provider wrap the app at the root.
+
+---
+
+## 3. DRY — Markets Application
+
+### Go Backend
+
+- **Reusable query helpers** in `internal/db/` for common patterns (soft-delete filtering, pagination, market-scope checks)
+- **Single audit trigger function** attached to all domain tables — never duplicate trigger logic
+- **Shared error formatting** — one function to create GraphQL error responses with proper codes
+- **Auth context extraction** — one helper to get user ID + role from context, used by all resolvers
+
+### React Native
+
+- **Gluestack UI components** from `components/ui/` are the single source for base UI elements — never recreate buttons, inputs, text
+- **Domain components** in `components/{domain}/` are composed from base UI — reuse across screens
+- **Custom hooks** encapsulate reusable logic (e.g., `useOptimisticMutation` wraps Apollo mutation + MMKV queue)
+- **Design tokens** in `config.ts` — never hardcode colors, spacing, or typography values
+- **GraphQL codegen** — never manually type API response shapes
+
+---
+
+## 4. CLEAN Code — Markets Application
+
+### Error Handling
+
+**Go:**
+```go
+// GOOD: Handle every error explicitly
+user, err := r.db.GetUser(ctx, userID)
+if err != nil {
+ return nil, gqlError(ctx, "INTERNAL", "failed to fetch user")
+}
+if user == nil {
+ return nil, gqlError(ctx, "NOT_FOUND", "user not found")
+}
+
+// BAD: Swallowing errors
+user, _ := r.db.GetUser(ctx, userID)
+```
+
+**React Native:**
+- Apollo `onError` for network/GraphQL errors — show user-friendly messages
+- Never show raw error messages, stack traces, or error codes to users
+- Error messages are short and action-oriented: "Check-in failed. Tap to retry."
+
+### No Side Effects in Unexpected Places
+
+- Go resolvers: write to DB, then publish events. Never publish events without a successful write.
+- React components: side effects only in `useEffect` or event handlers, never during render
+- GraphQL queries: never mutate state; mutations: always return updated state
+
+---
+
+## 5. DDD — Markets Application
+
+### Bounded Contexts
+
+Each `internal/` package maps to one bounded context with clear responsibilities and ownership.
+
+| Context | Package | Responsibility | Aggregate Root |
+|---------|---------|---------------|----------------|
+| **Auth** | `internal/auth/` | JWT validation, role extraction, session variable injection | — (stateless middleware) |
+| **Graph** | `internal/graph/` | GraphQL resolver orchestration, input validation, response shaping | — (delegates to domain) |
+| **Market** | `internal/market/` | Market lifecycle, schedule, roster, 2-manager invariant | `Market` |
+| **Vendor** | `internal/vendor/` | Vendor profile, product catalog, check-in lifecycle, exception status | `Vendor`, `CheckIn` |
+| **Customer** | `internal/customer/` | Customer profile, follows, preferences, discovery | `Customer` |
+| **Notification** | `internal/notify/` | FCM dispatch, notification preferences, device token management | — (infrastructure) |
+| **Realtime** | `internal/realtime/` | Firebase Realtime Database writes for status propagation | — (infrastructure) |
+| **Events** | `internal/events/` | Domain event bus: publish/subscribe, handler registration | — (infrastructure) |
+| **Audit** | `internal/audit/` | Audit query helpers (writes are PostgreSQL triggers — not application code) | `AuditEntry` (read-only) |
+| **DB** | `internal/db/` | Connection pool, migration runner, shared query utilities | — (infrastructure) |
+
+### Aggregate Roots
+
+Aggregates enforce business invariants. External code accesses children only through the root.
+
+```go
+// internal/market/market.go
+type Market struct {
+ ID MarketID
+ Name string
+ Location Location
+ Schedule Schedule
+ Managers []ManagerAssignment // invariant: len >= 2
+ deletedAt *time.Time
+}
+
+// Enforces 2-manager minimum (FR41a)
+func (m *Market) RemoveManager(managerID UserID) error {
+ if len(m.Managers) <= 2 {
+ return ErrMinimumManagersRequired
+ }
+ // ... remove logic
+ return nil
+}
+
+// internal/vendor/checkin.go
+type CheckIn struct {
+ ID CheckInID
+ VendorID VendorID
+ MarketID MarketID
+ Status CheckInStatus // checked-in, checked-out
+ CheckedAt time.Time
+ CheckedBy Attribution // self or manager-on-behalf
+}
+
+// Enforces conflict detection (FR14)
+func NewCheckIn(vendorID VendorID, marketID MarketID, existingCheckIns []CheckIn) (*CheckIn, error) {
+ for _, ci := range existingCheckIns {
+ if ci.Status == StatusCheckedIn && ci.MarketID != marketID {
+ return nil, ErrAlreadyCheckedInElsewhere{MarketID: ci.MarketID}
+ }
+ }
+ // ... create logic
+}
+```
+
+**Rules:**
+- Aggregates validate their own invariants — resolvers do not contain business rules
+- Aggregates are created via factory functions (`NewMarket`, `NewCheckIn`) that enforce invariants from birth
+- Aggregates return domain errors (not HTTP/GraphQL errors) — resolvers translate to GraphQL error codes
+
+### Value Objects (Typed IDs)
+
+Distinct types for IDs prevent accidental swapping at compile time.
+
+```go
+// internal/domain/ids.go
+type UserID string
+type MarketID string
+type VendorID string
+type CheckInID string
+type AuditEntryID string
+
+// Constructor functions validate format
+func NewUserID(raw string) (UserID, error) {
+ if raw == "" {
+ return "", ErrEmptyID
+ }
+ return UserID(raw), nil
+}
+```
+
+```go
+// Compiler catches misuse:
+func GetVendor(id VendorID) (*Vendor, error) { ... }
+
+marketID := MarketID("abc")
+GetVendor(marketID) // ← compile error: cannot use MarketID as VendorID
+```
+
+**Other value objects:**
+- `Location` — lat/lng pair, validated range
+- `Schedule` — operating days + hours, validated consistency
+- `Attribution` — actor ID + role + source (self vs manager-on-behalf)
+
+### Repository Interfaces (Ports)
+
+Domain code defines repository interfaces. Infrastructure implements them. Domain code never imports `pgx` or any database driver.
+
+```go
+// internal/market/repository.go (port — lives with domain code)
+type MarketRepository interface {
+ FindByID(ctx context.Context, id MarketID) (*Market, error)
+ Save(ctx context.Context, m *Market) error
+ FindByManager(ctx context.Context, managerID UserID) ([]*Market, error)
+ SearchByLocation(ctx context.Context, lat, lng float64, radiusKm float64) ([]*Market, error)
+}
+
+// internal/vendor/repository.go
+type VendorRepository interface {
+ FindByID(ctx context.Context, id VendorID) (*Vendor, error)
+ Save(ctx context.Context, v *Vendor) error
+ FindActiveCheckIns(ctx context.Context, vendorID VendorID) ([]CheckIn, error)
+}
+
+// internal/db/market_repo.go (adapter — lives in infrastructure layer)
+type PgMarketRepository struct {
+ pool *pgxpool.Pool
+}
+
+func (r *PgMarketRepository) FindByID(ctx context.Context, id MarketID) (*Market, error) {
+ // pgx query with WHERE deleted_at IS NULL
+}
+```
+
+**Rules:**
+- Port interfaces live alongside the domain code they serve (`internal/market/repository.go`)
+- Adapter implementations live in `internal/db/` (infrastructure layer)
+- Unit tests use fake/mock implementations; integration tests use real `Pg*Repository`
+- Repositories always filter `WHERE deleted_at IS NULL` — callers never need to remember this
+
+### Domain Events
+
+Events decouple bounded contexts. Cross-domain communication is via events, not direct imports.
+
+```go
+// internal/events/types.go
+type Event interface {
+ EventType() string
+}
+
+type VendorCheckedIn struct {
+ VendorID VendorID
+ MarketID MarketID
+ Timestamp time.Time
+}
+func (e VendorCheckedIn) EventType() string { return "vendor.checked_in" }
+
+type UserCreated struct {
+ UserID UserID
+ Role string
+}
+func (e UserCreated) EventType() string { return "user.created" }
+
+type ManagerAssigned struct {
+ ManagerID UserID
+ MarketID MarketID
+}
+func (e ManagerAssigned) EventType() string { return "manager.assigned" }
+```
+
+```go
+// internal/events/bus.go
+type Handler interface {
+ Handle(ctx context.Context, event Event) error
+}
+
+type Bus struct {
+ handlers []Handler
+}
+
+func (b *Bus) Publish(ctx context.Context, event Event) {
+ for _, h := range b.handlers {
+ if err := h.Handle(ctx, event); err != nil {
+ slog.Error("event handler failed",
+ "event", event.EventType(),
+ "error", err,
+ )
+ // Handler failure logged but does NOT roll back the DB write
+ }
+ }
+}
+```
+
+**Rules:**
+- Events are published AFTER successful database writes — never before, never speculatively
+- Events are NOT published if the DB write fails
+- Each handler is independently testable and can fail without blocking other handlers
+- Audit logging is NOT a domain event handler — it is guaranteed at the database level via PostgreSQL triggers
+- Handler failure is logged but never rolls back the originating write (NFR7)
+
+### Dependency Direction (Import Rules)
+
+```
+ ┌─────────────────────────┐
+ │ cmd/api/main.go │ ← Composition root (wires everything)
+ └────────────┬────────────┘
+ │ imports
+ ┌────────────▼────────────┐
+ │ internal/graph/ │ ← Resolver layer (orchestration)
+ └────────────┬────────────┘
+ │ imports
+ ┌──────────────────────┼──────────────────────┐
+ │ │ │
+┌─────────▼────────┐ ┌─────────▼────────┐ ┌─────────▼────────┐
+│ internal/market/ │ │ internal/vendor/ │ │ internal/customer/│ ← Domain layer
+│ (aggregates + │ │ (aggregates + │ │ (aggregates + │
+│ repository port) │ │ repository port)│ │ repository port) │
+└──────────────────┘ └──────────────────┘ └──────────────────┘
+ ▲ ▲ ▲
+ │ implements │ implements │ implements
+┌─────────┴────────┐ ┌─────────┴────────┐ ┌─────────┴────────┐
+│ internal/db/ │ │ internal/notify/ │ │ internal/realtime/│ ← Infrastructure
+│ (Pg*Repository) │ │ (FCM handler) │ │ (Firebase handler)│
+└──────────────────┘ └──────────────────┘ └──────────────────┘
+```
+
+**Hard rules:**
+- Domain packages (`market/`, `vendor/`, `customer/`) import NOTHING from infrastructure (`db/`, `notify/`, `realtime/`)
+- Domain packages never import each other — cross-domain communication is via domain events
+- `internal/graph/` (resolvers) may import domain packages but not infrastructure directly
+- Only `cmd/api/main.go` knows about all packages — it is the only place where concrete types are wired to interfaces
+- Only `internal/db/` imports `pgx`
+- Only `internal/auth/` imports `firebase-admin-go` for JWT validation
+- Only `internal/notify/` imports `firebase-admin-go` for FCM dispatch
+- Only `internal/realtime/` imports Firebase Realtime Database client
+
+### Anti-Corruption Layer
+
+External SDK types (Firebase, pgx, gqlgen models) must not leak into domain code. Map external types to domain types at the boundary.
+
+```go
+// internal/auth/middleware.go — maps Firebase JWT claims → domain context
+func (m *Middleware) extractUser(token *auth.Token) (UserID, string, error) {
+ uid, err := NewUserID(token.UID) // Firebase string → domain UserID
+ role := token.Claims["role"].(string) // Firebase claim → plain string
+ return uid, role, err
+}
+
+// internal/db/market_repo.go — maps pgx rows → domain Market
+func (r *PgMarketRepository) FindByID(ctx context.Context, id MarketID) (*Market, error) {
+ row := r.pool.QueryRow(ctx, "SELECT ...", string(id))
+ // Map SQL columns → domain Market struct
+ // This is the ONLY place that knows about the DB schema for markets
+}
+```
+
+**Rules:**
+- gqlgen-generated model types (`internal/graph/model/`) are API types, not domain types — map between them in resolvers
+- Domain types own validation and invariants; API types are DTOs
+- If the mapping is trivial (same fields), still keep the types separate — they change for different reasons
+
+---
+
+## 6. Pre-Commit Quality Checks — Markets Configuration
+
+### Go Backend
+
+```bash
+# .githooks/pre-commit (Go)
+golangci-lint run ./...
+go test ./... -short -count=1
+```
+
+### React Native Frontend
+
+```bash
+# .husky/pre-commit (TypeScript)
+npx lint-staged && npx tsc --noEmit
+```
+
+**lint-staged configuration** (in `package.json`):
+
+```json
+{
+ "lint-staged": {
+ "*.{ts,tsx}": [
+ "eslint --fix --max-warnings 0",
+ "prettier --write"
+ ],
+ "*.{css,json,md}": [
+ "prettier --write"
+ ]
+ }
+}
+```
+
+### Per-Commit Checks
+
+| Check | Tool | Target | Purpose |
+|-------|------|--------|---------|
+| Go lint | golangci-lint | Backend | Catch bugs, style issues, performance problems |
+| Go tests (short) | go test -short | Backend | Fast unit tests only |
+| TS lint + autofix | ESLint | Frontend | Catch floating promises, unused code |
+| TS format | Prettier | Frontend | Consistent formatting |
+| TS type check | tsc --noEmit | Frontend | Catch type errors |
+
+---
+
+## 7. CI Quality Gates — Markets Configuration
+
+### Backend CI
+
+| Gate | Command | Blocks PR? | Purpose |
+|------|---------|-----------|---------|
+| Lint | `golangci-lint run ./...` | Yes | Code quality |
+| Unit tests | `go test ./... -short` | Yes | Regressions |
+| Integration tests | `go test -tags=integration ./...` | Yes | Full-path validation |
+| Coverage | `go test -coverprofile=coverage.out` | Yes (80% line) | Adequate test coverage |
+| Build | `go build ./cmd/api/` | Yes | Compilation |
+| Schema validation | `go run github.com/99designs/gqlgen validate` | Yes | GraphQL schema consistency |
+
+### Frontend CI
+
+| Gate | Command | Blocks PR? | Purpose |
+|------|---------|-----------|---------|
+| Type check | `tsc --noEmit` | Yes | Type errors are bugs |
+| Lint | `eslint . --max-warnings 0` | Yes | Code quality |
+| Format | `prettier --check .` | Yes | Formatting consistency |
+| Unit + component tests | `jest --ci` | Yes | Regressions |
+| Coverage | `jest --coverage` | Yes (80% branch/line) | Adequate test coverage |
+| Codegen | `graphql-codegen --check` | Yes | Types match schema |
+
+These are in addition to the org-level gates (CodeQL, SonarCloud, CodeRabbit, Copilot review).
+
+---
+
+## 8. Go-Specific Standards
+
+### Error Types
+
+Use structured GraphQL error codes consistently:
+
+| Code | HTTP Analog | When |
+|------|------------|------|
+| `UNAUTHENTICATED` | 401 | No or invalid JWT |
+| `FORBIDDEN` | 403 | Valid JWT but insufficient role/scope |
+| `NOT_FOUND` | 404 | Resource doesn't exist or is soft-deleted |
+| `VALIDATION_ERROR` | 400 | Input fails validation |
+| `CONFLICT` | 409 | Duplicate check-in, 2-manager minimum violation |
+| `INTERNAL` | 500 | Unexpected server error |
+
+### Package Layout Rules
+
+- `cmd/api/main.go` is the composition root — wires dependencies, starts server
+- `internal/` packages are never imported by external code
+- Each `internal/` package exposes an interface and a constructor
+- No circular imports — dependency direction: `graph → {auth, db, events}`, never reverse
+
+### Structured Logging
+
+```go
+// Use slog everywhere
+slog.Info("vendor checked in",
+ "vendor_id", vendorID,
+ "market_id", marketID,
+ "duration_ms", elapsed.Milliseconds(),
+)
+
+// NEVER use fmt.Println or log.Printf in production code
+```
+
+### Database Patterns
+
+- All queries use `pgx` directly — no ORM
+- All queries include `WHERE deleted_at IS NULL` for user-facing data
+- All new domain tables MUST have the audit trigger attached in the migration
+- Use `pgx.Pool` for connection management — never open individual connections
+- Use `pgxpool.QueryRow` / `pgxpool.Query` with context for cancellation support
+
+---
+
+## 9. React Native / TypeScript Standards
+
+### ESLint Rules (Enforced)
+
+| Rule | Why |
+|------|-----|
+| `@typescript-eslint/no-floating-promises` | Forgotten `await` is the #1 async bug |
+| `@typescript-eslint/strict-boolean-expressions` | Prevents truthy/falsy surprises |
+| `@typescript-eslint/no-unused-vars` | Dead code removal |
+| React hooks rules | Enforce rules of hooks and exhaustive deps |
+| No `any` type | Use proper types from `@graphql-codegen` |
+
+### TypeScript Configuration
+
+- `strict: true` — enables all strict checks
+- `noUncheckedIndexedAccess: true` — forces null checks on array/object indexing
+
+### Component Patterns
+
+```typescript
+// GOOD: Gluestack components with NativeWind
+import { Box, Text, Button, ButtonText } from '@/components/ui';
+
+export function VendorCard({ vendor }: VendorCardProps) {
+ return (
+
+ {vendor.name}
+
+
+ );
+}
+
+// BAD: Raw React Native components with inline styles
+import { View, Text, TouchableOpacity } from 'react-native';
+
+export function VendorCard({ vendor }) {
+ return (
+
+ {vendor.name}
+
+ );
+}
+```
+
+---
+
+## 10. Story Implementation Protocol
+
+When implementing any story, follow this exact sequence:
+
+1. **Read the story** from epics document
+2. **Read referenced FRs** from the PRD for full context
+3. **Read architecture decisions** relevant to the story
+4. **Read UX design specs** for any UI components
+5. **Write failing tests** for each acceptance criterion (Red)
+6. **Implement minimum code** to pass each test (Green)
+7. **Refactor** while keeping tests green
+8. **Verify pre-commit passes** before committing
+9. **Run full test suite** to catch regressions
+
+### Story Completion Checklist
+
+Before marking a story as done:
+
+- [ ] All acceptance criteria have corresponding tests
+- [ ] All tests pass (unit + integration)
+- [ ] Pre-commit hook passes (lint + format + type check + tests)
+- [ ] Coverage meets minimum thresholds
+- [ ] No `any` types introduced (TypeScript)
+- [ ] No swallowed errors (Go)
+- [ ] Audit trigger attached to any new domain tables
+- [ ] Domain events published for any new mutations
+- [ ] Soft-delete filtering applied to any new queries
+- [ ] Accessibility labels on all interactive elements
+- [ ] Error messages are user-friendly, not raw
+- [ ] Design tokens used for all colors/spacing (no hardcoded values)
+
+---
+
+## 11. Naming Conventions
+
+### Go Backend
+
+| Category | Convention | Example |
+|----------|-----------|---------|
+| Packages | lowercase, single word | `auth`, `events`, `notify` |
+| Files | snake_case | `auth_middleware.go`, `event_bus.go` |
+| Types/interfaces | PascalCase | `EventHandler`, `MarketResolver` |
+| Functions | PascalCase (exported), camelCase (unexported) | `NewResolver()`, `validateInput()` |
+| Constants | PascalCase (exported) | `MaxRetryCount`, `DefaultTimeout` |
+| Test files | `*_test.go` co-located | `middleware_test.go` |
+| Integration tests | `//go:build integration` tag | Tagged build constraint |
+| GraphQL schema | snake_case fields, PascalCase types | `vendor_status`, `MarketProfile` |
+| DB tables | snake_case, plural | `market_vendors`, `audit_log` |
+| DB columns | snake_case | `created_at`, `vendor_id` |
+| Migrations | `NNNNNN_description.up.sql` / `.down.sql` | `000001_create_users.up.sql` |
+
+### React Native Frontend
+
+| Category | Convention | Example |
+|----------|-----------|---------|
+| Components | PascalCase file + export | `VendorCard.tsx`, `MarketList.tsx` |
+| Hooks | camelCase with `use` prefix | `useVendorStatus.ts` |
+| Utilities/lib | camelCase files | `apollo.ts`, `firebase.ts` |
+| GraphQL files | camelCase.graphql | `getMarket.graphql`, `checkIn.graphql` |
+| Constants | UPPER_SNAKE_CASE | `MAX_RETRY_COUNT`, `API_URL` |
+| Test files | `*.test.tsx` co-located | `VendorCard.test.tsx` |
+| Design tokens | `--color-{name}-{scale}` | `--color-primary-500` |
+
+---
+
+## References
+
+- **[petry-projects Org AGENTS.md](https://github.com/petry-projects/.github/blob/main/AGENTS.md)** — Organization-wide engineering standards (TDD, SOLID, CLEAN, DRY, DDD, KISS, YAGNI, CI gates, BMAD)
+- **Agents.md** — Project-level coding standards (component organization, styling, accessibility, anti-patterns)
+- **Architecture** — `_bmad-output/planning-artifacts/architecture.md`
+- **PRD** — `_bmad-output/planning-artifacts/prd.md`
+- **UX Design** — `_bmad-output/planning-artifacts/ux-design-specification.md`
diff --git a/_bmad-output/test-artifacts/epic-1-test-strategy.md b/_bmad-output/test-artifacts/epic-1-test-strategy.md
new file mode 100644
index 00000000..9acf6e5b
--- /dev/null
+++ b/_bmad-output/test-artifacts/epic-1-test-strategy.md
@@ -0,0 +1,259 @@
+# Epic 1: Authentication & Role-Based Access — Test Strategy
+
+> Authored by Murat (TEA - Test Architect) during Epic 1 Sprint Planning.
+> Defines the test approach, test cases, and quality gates for all 6 stories in Epic 1.
+
+---
+
+## 1. Epic 1 Test Scope
+
+Epic 1 establishes the foundation: project scaffolding, authentication, role assignment, market-scoped permissions, and role-based access enforcement. Every subsequent epic depends on this one working correctly.
+
+**Stories:**
+- 1.1a: Frontend Project Scaffolding
+- 1.1b: Backend Project Scaffolding & Audit Infrastructure
+- 1.2: Google & Apple Sign-In Authentication
+- 1.3: Role Selection & User Record Creation
+- 1.4: Market-Scoped Manager Permissions
+- 1.5: Role-Based Access Enforcement Middleware
+
+**Risk Assessment:** HIGH — Foundation errors cascade to all subsequent epics. Auth and RBAC bugs are security vulnerabilities.
+
+---
+
+## 2. Test Framework Setup (Stories 1.1a & 1.1b)
+
+### Go Backend Test Infrastructure
+
+| Tool | Purpose | Configuration |
+|------|---------|---------------|
+| `go test` | Unit + integration test runner | Standard Go tooling |
+| `//go:build integration` | Tag for integration tests | Separate from unit tests in CI |
+| `testcontainers-go` or local Cloud SQL | Test database | Real PostgreSQL for integration tests |
+| `httptest` | HTTP handler testing | Test middleware and GraphQL endpoint |
+| `golangci-lint` | Static analysis | Pre-commit + CI |
+
+**Test Database Strategy:**
+- Unit tests: mock DB interfaces (no real database)
+- Integration tests: dedicated test PostgreSQL instance with migrations applied fresh per test suite
+- Each integration test runs in a transaction that is rolled back after the test
+
+### React Native Test Infrastructure
+
+| Tool | Purpose | Configuration |
+|------|---------|---------------|
+| Jest | Test runner | Configured via `jest.config.js` |
+| React Native Testing Library | Component testing | `@testing-library/react-native` |
+| `@testing-library/jest-dom` | DOM assertions | Extended matchers |
+| `MockedProvider` | Apollo Client mocking | From `@apollo/client/testing` |
+
+**Test Verification for Scaffolding:**
+
+```
+Story 1.1a — Frontend scaffolding is DONE when:
+ [ ] Jest runs and finds test files
+ [ ] A smoke test renders the root layout without crashing
+ [ ] TypeScript compiles with zero errors (tsc --noEmit)
+ [ ] ESLint passes with zero warnings
+ [ ] GraphQL codegen generates types successfully
+
+Story 1.1b — Backend scaffolding is DONE when:
+ [ ] go test ./... passes with at least one test
+ [ ] A smoke test starts the HTTP server and responds to health check
+ [ ] gqlgen validate passes (schema is valid)
+ [ ] golangci-lint passes
+ [ ] Migrations run up and down cleanly
+ [ ] Audit trigger function exists and fires on test table insert
+ [ ] Event bus publishes and receives a test event
+```
+
+---
+
+## 3. Story 1.2: Google & Apple Sign-In Authentication
+
+### Test Cases — Go Backend (Auth Middleware)
+
+| ID | Test Case | Type | Given | When | Then |
+|----|-----------|------|-------|------|------|
+| 1.2.1 | Valid Google JWT accepted | Unit | Valid Firebase JWT with Google provider | Request hits auth middleware | Context populated with uid and role; request proceeds |
+| 1.2.2 | Valid Apple JWT accepted | Unit | Valid Firebase JWT with Apple provider | Request hits auth middleware | Context populated with uid and role; request proceeds |
+| 1.2.3 | Expired JWT rejected | Unit | Expired Firebase JWT | Request hits auth middleware | UNAUTHENTICATED error returned; request blocked |
+| 1.2.4 | Malformed JWT rejected | Unit | Random string as Authorization header | Request hits auth middleware | UNAUTHENTICATED error returned |
+| 1.2.5 | Missing Authorization header | Unit | No Authorization header | Request hits auth middleware | UNAUTHENTICATED error returned |
+| 1.2.6 | JWT with wrong issuer | Unit | Valid JWT but not from Firebase | Request hits auth middleware | UNAUTHENTICATED error returned |
+| 1.2.7 | Session variables set | Integration | Valid JWT with uid="user1", role="vendor" | Request processed by middleware | PostgreSQL session vars `app.actor_id`="user1", `app.actor_role`="vendor" set |
+
+### Test Cases — React Native (Auth Flow)
+
+| ID | Test Case | Type | Given | When | Then |
+|----|-----------|------|-------|------|------|
+| 1.2.8 | Google sign-in button renders | Component | Login screen mounted | Screen renders | "Sign in with Google" button visible with correct accessibility label |
+| 1.2.9 | Apple sign-in button renders | Component | Login screen mounted | Screen renders | "Sign in with Apple" button visible with correct accessibility label |
+| 1.2.10 | Successful Google sign-in stores token | Unit (hook) | Firebase returns valid JWT | Sign-in completes | JWT stored in expo-secure-store |
+| 1.2.11 | Successful sign-in configures Apollo | Unit (hook) | JWT stored in secure store | Apollo Client initialized | Authorization Bearer header set on all requests |
+| 1.2.12 | Auth error shows user-friendly message | Component | Firebase returns auth error | Sign-in fails | Error message displayed (not raw error) |
+| 1.2.13 | Loading state during sign-in | Component | User taps sign-in | Auth flow in progress | Loading indicator shown, button disabled |
+
+---
+
+## 4. Story 1.3: Role Selection & User Record Creation
+
+### Test Cases — Go Backend
+
+| ID | Test Case | Type | Given | When | Then |
+|----|-----------|------|-------|------|------|
+| 1.3.1 | Create user with customer role | Integration | Authenticated user, no existing record | `createUser` mutation with role="customer" | User record created in DB; Firebase custom claim set; role returned |
+| 1.3.2 | Create user with vendor role | Integration | Authenticated user, no existing record | `createUser` mutation with role="vendor" | User record created; custom claim set |
+| 1.3.3 | Create user with manager role | Integration | Authenticated user, no existing record | `createUser` mutation with role="manager" | User record created; custom claim set; recovery contact required |
+| 1.3.4 | Reject duplicate user creation | Integration | User record already exists | `createUser` mutation | CONFLICT error returned |
+| 1.3.5 | Reject invalid role | Unit | Authenticated user | `createUser` with role="admin" | VALIDATION_ERROR returned |
+| 1.3.6 | Audit log entry created | Integration | User created successfully | Check audit_log table | Entry exists with action="user_created", actor=uid |
+| 1.3.7 | User record includes required fields | Integration | User created | Query users table | id, firebase_uid, role, name, email, created_at all populated; deleted_at is NULL |
+
+### Test Cases — React Native
+
+| ID | Test Case | Type | Given | When | Then |
+|----|-----------|------|-------|------|------|
+| 1.3.8 | Role selection screen renders three options | Component | New user (no role claim) | Screen mounts | Customer, Vendor, Manager options visible with descriptions |
+| 1.3.9 | Selecting customer role navigates to customer tabs | Component | User selects Customer | Role mutation succeeds | App navigates to `(customer)/` tab layout |
+| 1.3.10 | Selecting vendor role navigates to vendor tabs | Component | User selects Vendor | Role mutation succeeds | App navigates to `(vendor)/` tab layout |
+| 1.3.11 | Selecting manager role navigates to manager tabs | Component | User selects Manager | Role mutation succeeds | App navigates to `(manager)/` tab layout |
+| 1.3.12 | Returning user bypasses role selection | Unit (hook) | Existing user with role claim in JWT | App opens | Routed directly to role-specific tabs |
+| 1.3.13 | Role selection error shows retry | Component | Network error on mutation | Role selection fails | Error message with retry option |
+
+---
+
+## 5. Story 1.4: Market-Scoped Manager Permissions
+
+### Test Cases — Go Backend
+
+| ID | Test Case | Type | Given | When | Then |
+|----|-----------|------|-------|------|------|
+| 1.4.1 | Assigned manager can access market | Integration | Manager assigned to Market A | Query Market A data | Data returned successfully |
+| 1.4.2 | Unassigned manager blocked | Integration | Manager NOT assigned to Market B | Query Market B data | FORBIDDEN error returned |
+| 1.4.3 | Two managers see same shared state | Integration | Manager1 and Manager2 both assigned to Market A | Both query dashboard | Same vendor statuses and counts returned |
+| 1.4.4 | Cannot reduce below 2 managers | Integration | Market A has exactly 2 managers | Remove one manager | CONFLICT error: "Market requires minimum 2 managers" |
+| 1.4.5 | Can remove manager if 3+ remain | Integration | Market A has 3 managers | Remove one manager | Success; 2 managers remain |
+| 1.4.6 | market_managers junction table integrity | Integration | Manager assigned | Query junction table | Correct manager_id, market_id, created_at |
+| 1.4.7 | Scope check queries market_managers | Unit | Manager with role in context | Resolver checks scope | SQL query hits market_managers table with correct manager_id |
+
+---
+
+## 6. Story 1.5: Role-Based Access Enforcement Middleware
+
+### Test Cases — Go Backend
+
+| ID | Test Case | Type | Given | When | Then |
+|----|-----------|------|-------|------|------|
+| 1.5.1 | Customer blocked from vendor mutation | Unit | JWT with role="customer" | `checkInVendor` mutation | FORBIDDEN |
+| 1.5.2 | Customer blocked from manager query | Unit | JWT with role="customer" | `marketDashboard` query | FORBIDDEN |
+| 1.5.3 | Vendor blocked from manager mutation | Unit | JWT with role="vendor" | `approveJoinRequest` mutation | FORBIDDEN |
+| 1.5.4 | Vendor can access own vendor queries | Unit | JWT with role="vendor" | `myVendorProfile` query | Data returned |
+| 1.5.5 | Manager can access dashboard | Unit | JWT with role="manager" | `marketDashboard` query | Data returned (with scope check) |
+| 1.5.6 | Customer can access customer queries | Unit | JWT with role="customer" | `searchMarkets` query | Data returned |
+| 1.5.7 | Soft-deleted records excluded | Integration | User record with deleted_at set | Any query returning users | Soft-deleted user excluded from results |
+| 1.5.8 | Role enforcement at resolver level | Unit | Each resolver | Inspect role check | Every mutation/query resolver checks role before proceeding |
+
+### Security Edge Cases
+
+| ID | Test Case | Type | Given | When | Then |
+|----|-----------|------|-------|------|------|
+| 1.5.9 | Tampered role claim rejected | Unit | JWT with role modified after signing | Request hits middleware | UNAUTHENTICATED (signature invalid) |
+| 1.5.10 | Role=empty string rejected | Unit | JWT with role="" | Request hits middleware | UNAUTHENTICATED or FORBIDDEN |
+| 1.5.11 | Multiple roles not supported | Unit | JWT with role="customer,vendor" | Request hits middleware | VALIDATION_ERROR or FORBIDDEN |
+| 1.5.12 | SQL injection in user ID blocked | Integration | JWT uid contains SQL injection | Query executed | Parameterized query prevents injection; no data leak |
+
+---
+
+## 7. Cross-Cutting Test Requirements for Epic 1
+
+### Audit Logging Verification
+
+Every write operation in Epic 1 must generate an audit log entry. Verify for each mutation:
+
+| Mutation | Expected Audit Entry |
+|----------|---------------------|
+| `createUser` | action_type="user_created", actor_id=uid, target_type="user", target_id=new_user_id |
+| `assignManager` | action_type="manager_assigned", target_type="market_managers" |
+| `removeManager` | action_type="manager_removed", target_type="market_managers" |
+
+### PostgreSQL Session Variables
+
+For every authenticated request, verify:
+- `app.actor_id` is set to the Firebase UID
+- `app.actor_role` is set to the user's role
+- These values are available to the audit trigger function
+
+### Domain Event Publishing
+
+| Mutation | Expected Event |
+|----------|---------------|
+| `createUser` | `UserCreated{userId, role}` |
+| `assignManager` | `ManagerAssigned{managerId, marketId}` |
+| `removeManager` | `ManagerRemoved{managerId, marketId}` |
+
+Each event must be tested for:
+- Event is published after successful DB write
+- Event is NOT published if DB write fails
+- Event handlers receive the event with correct data
+- Handler failure does not roll back the DB write
+
+---
+
+## 8. Quality Gates for Epic 1
+
+### Per-Story Gates
+
+Before a story moves from `in-progress` to `review`:
+
+- [ ] All test cases from this document are implemented and passing
+- [ ] Go coverage >= 80% for changed files
+- [ ] React Native coverage >= 80% for changed files
+- [ ] Zero ESLint warnings, zero golangci-lint issues
+- [ ] TypeScript compiles cleanly (`tsc --noEmit`)
+- [ ] Pre-commit hooks pass
+
+### Epic 1 Completion Gate
+
+Before Epic 1 moves to `done`:
+
+- [ ] All 6 stories are `done`
+- [ ] Integration tests cover the full auth flow: sign-in → role selection → permission check → scope enforcement
+- [ ] Security edge cases (1.5.9 - 1.5.12) all pass
+- [ ] Audit logging verified for every write mutation
+- [ ] Domain events verified for every write mutation
+- [ ] No `any` types in TypeScript codebase
+- [ ] All accessibility labels present on auth/role screens
+- [ ] Skeleton screens (not spinners) for loading states
+
+---
+
+## 9. Test Execution Order
+
+Stories should be implemented and tested in this order (dependencies flow downward):
+
+```
+1.1a (Frontend Scaffolding) ──┐
+ ├── 1.2 (Authentication) ── 1.3 (Role Selection) ──┐
+1.1b (Backend Scaffolding) ──┘ │
+ ├── 1.5 (RBAC Middleware)
+ 1.4 (Manager Scope) ──┘
+```
+
+- **1.1a and 1.1b** can be developed in parallel (no dependencies)
+- **1.2** requires both scaffolding stories complete
+- **1.3** requires 1.2 (authentication must work for role assignment)
+- **1.4** can start after 1.3 (needs user records to exist)
+- **1.5** requires 1.3 and 1.4 (enforces roles and scopes)
+
+---
+
+## 10. Recommended Test Tooling Additions
+
+| Tool | Purpose | When to Add |
+|------|---------|-------------|
+| `testcontainers-go` | Spin up PostgreSQL in Docker for integration tests | Story 1.1b |
+| `go-sqlmock` | Mock pgx for unit tests (if interfaces aren't sufficient) | Story 1.2 if needed |
+| `firebase-admin-go` test helpers | Create test JWTs for middleware testing | Story 1.2 |
+| `msw` (Mock Service Worker) | Mock GraphQL responses in React Native tests | Story 1.2 frontend |
+| Coverage reporting | `go tool cover` + Jest `--coverage` with CI thresholds | Story 1.1a/1.1b |