critical priority low complexity database pending database specialist Tier 0

Acceptance Criteria

Migration file exists in the Supabase migrations directory with a timestamped filename and applies cleanly via `supabase db push` without errors
Table `periodic_summaries` is created with columns: id (UUID PK default gen_random_uuid()), organisation_id (UUID NOT NULL FK → organisations.id), period_type (TEXT NOT NULL CHECK IN ('weekly','monthly','quarterly')), period_start (TIMESTAMPTZ NOT NULL), period_end (TIMESTAMPTZ NOT NULL), session_count (INTEGER NOT NULL DEFAULT 0), total_hours (NUMERIC(10,2) NOT NULL DEFAULT 0), generated_at (TIMESTAMPTZ NOT NULL DEFAULT now()), payload (JSONB NOT NULL DEFAULT '{}')
A composite UNIQUE constraint exists on (organisation_id, period_type, period_start, period_end) to prevent duplicate period rows
Index `idx_periodic_summaries_org_period` on (organisation_id, period_type, period_start DESC) exists and is confirmed via EXPLAIN ANALYZE on a typical SELECT query
Index `idx_periodic_summaries_generated_at` on (generated_at) exists to support retention/cleanup queries
period_end is always strictly greater than period_start — enforced by CHECK constraint (period_end > period_start)
total_hours CHECK constraint ensures value >= 0; session_count CHECK constraint ensures value >= 0
Migration is idempotent: running it twice does not throw an error (use IF NOT EXISTS guards)
Rollback/down migration drops the table and all associated indexes cleanly

Technical Requirements

frameworks
Supabase CLI migrations
apis
Supabase PostgreSQL
supabase db push
supabase db reset
data models
periodic_summaries
organisations
performance requirements
Composite index on (organisation_id, period_type, period_start DESC) must reduce query time to <10ms for a single-org lookup on 10,000 rows
JSONB payload column uses GIN index only if payload query patterns are confirmed — defer until needed
Table partitioning by period_type is out of scope for MVP but schema should not block future partitioning
security requirements
organisation_id FK with ON DELETE CASCADE to prevent orphaned summary rows
No sensitive PII stored in payload JSONB — only aggregated numeric data
Table owner should be the Supabase service role; app roles granted SELECT only via RLS (defined in task-002)

Execution Context

Execution Tier
Tier 0

Tier 0 - 440 tasks

Implementation Notes

Use `supabase migration new periodic_summaries_schema` to generate the file. Place all DDL inside a single transaction block (`BEGIN; ... COMMIT;`) for atomicity. Use `TIMESTAMPTZ` (not `TIMESTAMP`) for all time columns to ensure UTC storage regardless of server locale — critical for Norwegian locale handling in task-003.

The JSONB payload is intentionally schema-less to accommodate future aggregation dimensions (e.g., activity_type breakdown) without further migrations. Do not add application-level columns (e.g., status, is_published) at this stage — keep the schema minimal and extend via payload. Reference the `organisations` table UUID type to confirm the FK type matches exactly.

Testing Requirements

Write SQL-level integration tests using pgTAP or Supabase's built-in test runner: (1) verify all columns exist with correct types, (2) verify CHECK constraints reject invalid period_type values and negative counts/hours, (3) verify UNIQUE constraint rejects duplicate (org, type, start, end) combinations, (4) verify FK cascade deletes orphaned summaries when an organisation is removed, (5) run EXPLAIN ANALYZE on the composite index to confirm index-only scan. All tests must pass in a clean `supabase db reset` environment.

Component
Summary Period Repository
data low
Epic Risks (3)
high impact medium prob security

Supabase RLS policies for aggregation views are more complex than single-table policies. A misconfigured policy could silently allow a coordinator in one organisation to see data from another, causing a data breach and breaking trust with participating organisations.

Mitigation & Contingency

Mitigation: Write automated RLS integration tests that create two separate organisations with distinct data, then assert that queries authenticated as org-A users return only org-A rows. Run these tests in CI on every PR touching the database layer.

Contingency: If an RLS bypass is discovered post-deployment, immediately disable the periodic summaries feature flag, revoke affected sessions, audit access logs, notify affected organisations, and patch the policy before re-enabling.

medium impact medium prob technical

Activity records may span multiple sessions types, proxy registrations, and coordinator bulk entries. Incorrect JOIN logic or missing filters in the aggregation query could double-count sessions or omit activity types, producing inaccurate summaries that erode user trust.

Mitigation & Contingency

Mitigation: Build a fixture dataset covering all activity registration paths (direct, proxy, bulk) and assert expected aggregated counts in integration tests before any UI consumes the repository.

Contingency: If inaccurate counts are reported post-launch, mark affected summaries as invalidated in the database and re-trigger generation once the query is corrected. Communicate transparently to affected users via an in-app banner.

low impact low prob integration

The local cache must be invalidated when a new summary arrives via push notification. If the push token is stale or the FCM/APNs delivery is delayed, the device may show an outdated summary for an extended period, confusing users who see different numbers online versus offline.

Mitigation & Contingency

Mitigation: Implement a TTL on cached summaries (max 48 hours) so stale data is auto-cleared even without a push notification. Also trigger cache refresh on app foreground if the current period's summary is older than 24 hours.

Contingency: Provide a manual pull-to-refresh on the summary card that bypasses the cache and fetches directly from Supabase when a network connection is available.