Skip to content

Release: durable investigations, analytics correctness, and platform hardening - #541

Merged
izadoesdev merged 141 commits into
mainfrom
staging
Jul 23, 2026
Merged

Release: durable investigations, analytics correctness, and platform hardening#541
izadoesdev merged 141 commits into
mainfrom
staging

Conversation

@izadoesdev

@izadoesdev izadoesdev commented Jul 4, 2026

Copy link
Copy Markdown
Member

Release overview

Promotes staging at 96110dda to main.

This is the full release train since main, not only the final investigations hardening slice: 138 commits, 731 files, +41,530 / -35,908. The final net behavior is summarized below; superseded deterministic-investigation and synthetic-eval iterations are intentionally not presented as shipped architecture.

What ships

Durable investigations

  • Replaces insight cards and disconnected digests with one persistent investigation per exact subject: a named error, goal, funnel, page, event, campaign, or measured signal.
  • Gives the agent freedom to inspect analytics, history, site content, named goal/funnel definitions, profiles, revenue, vitals, errors, and read-only GitHub/deploy context before choosing an outcome.
  • Persists a chronological case timeline of observations, agent outcomes, human replies, rechecks, recovery, recurrence, and resolution.
  • Uses one outcome contract: act, ask, watch, or resolve. Actionable/blocked cases notify; watch outcomes recheck quietly; resolved cases close.
  • Dashboard, Slack, MCP, and the general agent share the same list/get/reply lifecycle. Replies resume the same case instead of starting another chat.
  • Simplifies scheduling to one organization-level Off/Daily/Weekly configuration and keeps recurring Slack updates in the original investigation thread.
  • Rebuilds the dashboard as a centered investigation list, detail/timeline view, settings surface, and concise home preview.
  • Removes the old cockpit/card/local-state stack, duplicate evidence and digest tools, deterministic repair/rollup/enrichment/reflection layers, and the synthetic investigations eval harness. The production shadow remains a manual quality tool.

Revenue, billing, and identity

  • Attributes recurring Stripe invoice payments to sessions and identified users, including newer invoice metadata shapes.
  • Normalizes Stripe and Paddle revenue lifecycles, carries attribution through late/sparse updates and refunds, avoids payment-intent/invoice duplication, and handles cents, refund-only periods, and failed-payment invariants consistently.
  • Adds user LTV and deterministic hashed-email lookup with stable identity/profile joins and clearer revenue/currency/attribution UI.
  • Adds a durable Autumn webhook inbox with leasing, retry, replay, dead-letter handling, idempotent effects, and coordinated shutdown.
  • Adds organization ownership to usage alerts and clearer billing, entitlement, attachment, cancellation, and recovery states.

Slack, MCP, agent, and feedback

  • Makes investigations native to the Slack assistant: list open cases, run one now, configure delivery, reply in the existing thread, and continue the same durable investigation.
  • Adds server-rendered chart PNG uploads with bounded upload counts and table fallback, plus required native runtime/font assets.
  • Adds visible interruption and timeout handling, safer shutdown/draining, Slack Connect sender checks, bounded payloads, and clearer delivery failures.
  • Exposes the same investigation lifecycle through MCP and agent tools while preserving organization scope and sanitizing public query errors.
  • Adds source-aware feedback submission from dashboard, agent, Slack, and MCP with website/conversation metadata and preview/receipt UI.
  • Adds profile-trait discovery and centralizes trait-filter validation/resolution for analytics queries.

Analytics and product experience

  • Adds server-side pagination, search, sort, filters, folder filtering, and a virtualized list for Links; preserves redirect destinations exactly and excludes soft-deleted links.
  • Records the page path for custom events and includes hash-route changes when configured.
  • Corrects goal, funnel, profile, revenue, vitals, uptime, status, alert, and attribution edge cases.
  • Adds investigation detail pages, funnel-create deep links, onboarding fixes, and clearer auth, billing, notification, error, and recovery UX.
  • Aligns public docs, pricing, marketing copy, tracker-size claims, API docs, and integration guidance with current behavior.

SDK, tracker, privacy, and flags

  • Isolates flag state and evaluation context across identities, emits evaluation events on cache hits, and exposes delivery/flag failures.
  • Honors opt-out, GPC, and DNT before storage or delivery and aborts in-flight tracking when privacy state changes.
  • Hardens browser/server flag evaluation, tracker transport behavior, request identifiers, and typed error boundaries.
  • Updates SDK, Nuxt, React, Vue, Node, and tracker source behavior; package/CDN publication remains a separate release step.

Data and platform

  • Makes checked-in ClickHouse SQL the schema source of truth, generates Row/Insert types from it, verifies registry drift, and removes duplicate hand-written table types, phantom columns, inline DDL, and the undeployed error-hourly model.
  • Adds persisted investigation observations, replies, effects, active-run uniqueness, Autumn inbox storage, feedback context, usage-alert ownership, and the Links pagination index.
  • Hardens request IDs, valid 4xx preservation, auth/callback boundaries, public flag endpoints, tenant isolation, cache invalidation, process failure handling, and service health checks.
  • Simplifies environment handling: AI uses AI_GATEWAY_API_KEY instead of stale AI_API_KEY; Axiom uses AXIOM_TOKEN; stale per-service env schemas, duplicated env examples, unused helpers, and obsolete runtime knobs are removed.
  • Updates Docker, Railway/self-host guidance, CI health coverage, Slack runtime assets, and local telemetry gating.

Production rollout

1. PostgreSQL requires a coordinated maintenance-window migration

This is a destructive, non-expand/contract schema change. Do not let an automatic application deploy race the database push: current main code reads columns the new schema drops, while new code immediately needs the replacement tables and columns.

Before the production push:

  1. Snapshot PostgreSQL and preserve any historical insight/config data that may still be needed.

  2. Pause/disable Insights and coordinate the API, Insights, Dashboard, and Slack rollout from the same SHA.

  3. Run both preflight queries and reconcile any returned rows:

    SELECT organization_id, count()
    FROM insight_generation_configs
    GROUP BY organization_id
    HAVING count(
    ) > 1;

    SELECT organization_id, count()
    FROM insight_runs
    WHERE status IN ('queued', 'running')
    GROUP BY organization_id
    HAVING count(
    ) > 1;

  4. Run bun run db:push during the deployment window.

  5. Deploy API, Insights, Dashboard, and Slack together, then re-enable and verify scheduling, replies, and delivery.

The push drops persisted per-site/custom investigation controls including website, cron, depth, model/tool/budget/limit/lookback/cooldown, and last-run fields. It also removes legacy insight action/evidence/period/run fields and replaces rollups with observations, effects, and replies. Existing enabled per-site rows need an explicit canonical organization-level decision before migration; new configurations default disabled.

2. Production environment preflight

  • Insights needs the literal INSIGHTS_WORKER_ENABLED=true; missing values and 1 both disable the worker. When enabled, it also requires AI_GATEWAY_API_KEY, database/ClickHouse access, BULLMQ_REDIS_URL, Redis, auth, and encryption configuration.
  • Preserve AXIOM_TOKEN on API, Basket, Insights, Slack, and Uptime when Axiom delivery is expected. AI_API_KEY and AXIOM_API_KEY are stale.
  • Basket requires DATABUDDY_ENCRYPTION_KEY in production. Slack also needs it for installation secrets.
  • FIRECRAWL_API_KEY is optional at startup but required for website/page research; set it on API, Insights, and Slack if investigations should use that context.
  • Verify explicit production public/service URLs and Better Auth configuration before builds and deploys.

3. External and separately published surfaces

  • The committed Slack manifest does not include files:write. Chart uploads will fall back to tables until that scope is added and the workspace is reauthorized.
  • Stripe must deliver invoice.paid for recurring subscription attribution.
  • SDK/Nuxt versions and changesets are unchanged, and the tracker CDN deploy is manual. Decide and perform those releases separately if their source changes are part of this launch.
  • clickhouse:init creates missing objects but is not a live migration runner. No live ClickHouse schema verification ran in CI, and this PR does not automatically drop legacy hourly web-vitals objects.

Verification

Fresh evidence on the release head:

  • Local full Turbo test run: 26/26 tasks passed.
  • GitHub: lint, type checks, tests, Dashboard Playwright, API/Basket/Insights health checks, CodeQL, and Socket checks passed.
  • Staging deploys: API, Basket, Slack, Vector, dashboard, docs, and status passed.
  • Browser check: staging dashboard rendered successfully; staging API health returned status: ok.

Coverage limits are explicit: CI does not call the real AI Gateway or grade investigation usefulness, there is no Railway Insights staging deployment check on this head, and the live ClickHouse verification step had no read-only URL and was skipped.

Known follow-up

One in-flight Autumn replay can exceed the API's 10-second hard shutdown budget and remain leased until retry. Provider idempotency prevents duplicate email delivery, but persistence before process exit is not guaranteed for that slow in-flight claim.

Merge gate

  • Full staging diff is mergeable with no main-only divergence.
  • Required CI and staging deployment checks are green.
  • Obtain the required human approval.
  • Triage/resolve the remaining review conversations; the full final head has not received a fresh CodeRabbit, Cubic, Vercel Agent, or Greptile review.
  • Complete the populated-database preflight and coordinated migration plan.
  • Confirm production env, Slack scope/reauthorization, Stripe webhook, and SDK/tracker publication decisions.

@vercel

vercel Bot commented Jul 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
dashboard (staging) Ready Ready Preview, Comment Jul 22, 2026 3:11pm
databuddy-status Ready Ready Preview, Comment Jul 22, 2026 3:11pm
documentation (staging) Ready Ready Preview, Comment Jul 22, 2026 3:11pm

@coderabbitai

coderabbitai Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Important

Review skipped

Auto reviews are disabled on this repository. Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Repository UI

Review profile: ASSERTIVE

Plan: Pro

Run ID: 167c5d0c-4d30-4c49-8492-8817ffe97f55

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch staging

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@unkey-deploy

unkey-deploy Bot commented Jul 4, 2026

Copy link
Copy Markdown

The latest updates on your projects. Learn more about Unkey Deploy

Name Status Preview Inspect Updated (UTC)
api (preview) Ready Visit Preview Inspect Jul 22, 2026 3:10pm
api (production) Ready Visit Preview Inspect Jul 21, 2026 9:10am

@greptile-apps

greptile-apps Bot commented Jul 4, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR centralizes AI query trait-filter resolution into a new trait-filters.ts module, adds filter-field validation for MCP batch requests, and removes the authClient.signOut() call in the session guard to prevent spurious sign-outs on transient fetch failures.

  • Trait filter centralization: resolveRequestTraitFilters and invalidFilterFieldError now live in one place and are re-used by executeQuery, executeBatch, and the MCP batch-request builder, replacing per-caller ad-hoc logic.
  • Session guard fix: The guard now redirects directly to /login without calling signOut(); this stops a transient null-session response from permanently revoking a still-valid session on the server.
  • Error surfacing: MCP batch results now return the real error string (r.error) instead of the redacted "Query failed", giving the AI agent actionable context for self-correction.

Confidence Score: 4/5

The core refactor is sound — trait resolution is cleanly centralized, index bookkeeping in executeBatch is correct, and the session guard fix prevents accidental sign-outs. The two flagged items are edge-case inefficiencies that do not break the happy path.

The unknown-type guard in resolveRequestTraitFilters lets an unnecessary identity-service round-trip happen before an unknown query type fails downstream, and the raw ClickHouse error strings now reach the LLM context without any sanitization pass. Neither causes incorrect data or broken flows on valid inputs, but both deserve a follow-up before the code sees heavy use with novel query types or noisy ClickHouse errors.

packages/ai/src/query/trait-filters.ts (unknown-type guard) and packages/ai/src/ai/mcp/agent-tools.ts (raw error forwarding) are worth a second look.

Important Files Changed

Filename Overview
packages/ai/src/query/trait-filters.ts New module centralizing trait-filter resolution and field validation; logic is clean, but unknown query types bypass the profile_id guard and trigger an unnecessary identity service call.
packages/ai/src/query/batch-executor.ts Trait resolution moved into executeBatch; index bookkeeping for traitFailures is correct and the fast-path single-query shortcut is properly gated on zero failures.
packages/ai/src/ai/mcp/agent-tools.ts Error surfacing upgraded from a static "Query failed" string to the real error; could leak internal ClickHouse error details to the LLM context.
apps/dashboard/components/providers/session-guard.tsx Removes signOut() before redirect to /login, preventing irreversible session revocation on transient network failures; intentional and correct.
packages/ai/src/ai/tools/get-data.ts Per-item trait resolution removed; now delegates to executeQuery which handles it centrally; error branch now differentiates trait vs. generic failures correctly.
packages/ai/src/ai/mcp/mcp-utils.ts Adds invalidFilterFieldError() call between type resolution and request push, giving the MCP layer early rejection before execution.
packages/ai/src/query/index.ts executeQuery now validates filter fields and resolves trait filters before building; validation runs twice for executeBatch callers (once at MCP layer, once here) but no functional impact.
packages/ai/src/query/trait-filters.test.ts New test file with good coverage of happy path, org-scoped rejection, unknown-type rejection, and field allowlist logic.
packages/ai/src/query/builders/sessions.ts Adds profile_id/anonymous_id to allowedFilters and threads filterConditions/filterParams into custom SQL queries for session builders.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant AI as AI Agent / Tool
    participant MCP as MCP buildBatchQueryRequests
    participant EX as executeBatch
    participant TF as resolveRequestTraitFilters
    participant ID as Identity Service (resolveTraitSegment)
    participant CH as ClickHouse

    AI->>MCP: "queries[] with trait:<key> filters"
    MCP->>MCP: invalidFilterFieldError() per query
    MCP-->>AI: invalid[] (bad field) + requests[] (ok)
    AI->>EX: executeBatch(requests)
    loop per request (parallel)
        EX->>TF: resolveRequestTraitFilters(req)
        alt has trait filters
            TF->>ID: resolveTraitSegment(projectId, traitFilters)
            ID-->>TF: profile_id[]
            TF-->>EX: req with profile_id filter
        else no trait filters
            TF-->>EX: original req (no-op)
        end
    end
    EX->>CH: union query (successful requests)
    CH-->>EX: rows
    EX-->>AI: BatchResult[] (with errors for failures)
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant AI as AI Agent / Tool
    participant MCP as MCP buildBatchQueryRequests
    participant EX as executeBatch
    participant TF as resolveRequestTraitFilters
    participant ID as Identity Service (resolveTraitSegment)
    participant CH as ClickHouse

    AI->>MCP: "queries[] with trait:<key> filters"
    MCP->>MCP: invalidFilterFieldError() per query
    MCP-->>AI: invalid[] (bad field) + requests[] (ok)
    AI->>EX: executeBatch(requests)
    loop per request (parallel)
        EX->>TF: resolveRequestTraitFilters(req)
        alt has trait filters
            TF->>ID: resolveTraitSegment(projectId, traitFilters)
            ID-->>TF: profile_id[]
            TF-->>EX: req with profile_id filter
        else no trait filters
            TF-->>EX: original req (no-op)
        end
    end
    EX->>CH: union query (successful requests)
    CH-->>EX: rows
    EX-->>AI: BatchResult[] (with errors for failures)
Loading

Reviews (1): Last reviewed commit: "chore(staging): merge main" | Re-trigger Greptile

Comment thread packages/ai/src/query/trait-filters.ts
Comment thread packages/ai/src/ai/mcp/agent-tools.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 521 files

Confidence score: 5/5

  • Safe to merge after the addressed issues were fixed.

Note: This PR contains a large number of files. cubic only reviews up to 200 files per PR, so some files may not have been reviewed. cubic prioritizes the most important files to review.
Shadow auto-approve: would not auto-approve because issues were found.

Re-trigger cubic

Comment thread apps/basket/src/hooks/auth.ts Outdated
Comment thread apps/dashboard/app/(auth)/login/magic-sent/page.tsx
Comment thread apps/dashboard/lib/user-facing-error.ts
Comment thread apps/dashboard/app/(main)/websites/[id]/funnels/page.tsx Outdated
Comment thread apps/docs/content/docs/api/mcp.mdx Outdated
Comment thread apps/dashboard/app/(auth)/login/forgot/page.tsx
Comment thread apps/docs/public/pricing.md
Comment thread apps/dashboard/app/(main)/billing/actions/cancel-feedback-action.ts Outdated
Comment thread apps/dashboard/components/feature-gate.tsx Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 7 files (changes from recent commits).

Shadow auto-approve: would not auto-approve. Auto-approval blocked by 51 unresolved issues from previous reviews.

Re-trigger cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

1 issue found across 5 files (changes from recent commits).

Prompt for AI agents (unresolved issues)

Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.


<file name="packages/rpc/src/routers/insights.ts">

<violation number="1" location="packages/rpc/src/routers/insights.ts:337">
P2: The `.catch(() => false)` wrapper around `withWorkspace` in `getById` and `related` swallows every rejection, including database and infrastructure errors. After reviewing `resolveWorkspace` in `packages/rpc/src/procedures/with-workspace.ts`, it calls `getMemberRole` and `context.getBilling()` which can throw on DB/connectivity failures. When that happens the endpoint will return `{ success: true, insight: null }` (or an empty array) instead of a real error, masking outages and bypassing telemetry.

Preserve the anti-enumeration behavior for actual permission denials, but rethrow unexpected/infrastructure errors so they surface properly. A simple approach is to inspect the rejection and only swallow the expected `rpcError.forbidden` / `rpcError.unauthorized` kinds.</violation>
</file>

Shadow auto-approve: would not auto-approve because issues were found.
Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/rpc/src/routers/insights.ts Outdated

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 1 file (changes from recent commits).

Shadow auto-approve: would not auto-approve. Auto-approval blocked by 46 unresolved issues from previous reviews.

Re-trigger cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 22 files (changes from recent commits).

Shadow auto-approve: would not auto-approve because issues were found.
Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread apps/dashboard/lib/safe-callback.ts

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

0 issues found across 1 file (changes from recent commits).

Shadow auto-approve: would not auto-approve. Auto-approval blocked by 22 unresolved issues from previous reviews.

Re-trigger cubic

@cubic-dev-ai cubic-dev-ai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

All reported issues were addressed across 91 files (changes from recent commits).

Shadow auto-approve: would not auto-approve because issues were found.
Tip: Review your code locally with the cubic CLI to iterate faster.

Re-trigger cubic

Comment thread packages/ai/src/ai/insights/validate.ts Outdated
Comment thread apps/insights/src/generation.ts Outdated
Comment thread apps/insights/src/resolution.ts Outdated
Comment thread apps/insights/src/detection.ts Outdated
Comment thread apps/insights/src/delivery.ts Outdated
Comment thread apps/insights/src/index.ts
Comment thread apps/insights/src/funnel-detection.test.ts
Comment thread packages/ai/src/query/builders/vitals.ts Outdated
Comment thread packages/ai/src/ai/insights/product-context.ts Outdated
Comment thread packages/ai/package.json Outdated
* feat(insights): restore bounded investigation agent

* fix(insights): keep investigations time-scoped

* docs(insights): define investigation product contract

* fix(ai): correct page and session query semantics

* refactor(ai): consolidate investigation tools

* refactor(insights): simplify investigation persistence model

* feat(insights): run durable agent investigations

* feat(rpc): add investigation timelines and replies

* feat(dashboard): replace insight cards with investigations

* fix(ai): add Terra usage pricing

* test(evals): replace synthetic insight evals with production shadow

* chore(deps): prune obsolete investigation package surface

* feat(insights): simplify durable investigations

* refactor(ai): expose native investigation tools

* feat(slack): continue delivered investigations

* feat(dashboard): surface investigations

* feat(investigations): add business and deploy context

* chore(repo): remove obsolete investigation evals

* docs(investigations): align product language

* docs(agent): update investigation guidance

* fix(ai): keep dry-run tools read-only

* fix(docs): build tracker before copy tests

* style(repo): fix baseline lint

* fix(ai): make shared mocks order-safe
* fix(tracker): reject unsafe URLs and align docs

* fix(ai): isolate ClickHouse test doubles

* fix(platform): harden external and client trust boundaries

* docs(repo): fix API test command

* fix(billing): make Autumn ingestion replay-safe

* feat(revenue): normalize payment events and reporting

* fix(analytics): correct identity funnels links and uptime

* fix(db): enforce ClickHouse schema parity

* fix(revenue): scope payment diagnostics to filters

* test(revenue): stabilize payment scope integration

* test(revenue): stabilize invoice reconciliation integration

* fix(notifications): bound Slack payload delivery

* fix(insights): preserve concurrent schedule edits

* refactor(config): simplify runtime and deployment setup

* fix(dashboard): clear insight caches on organization change

* fix(analytics): resolve session identities by event time

* fix(basket): bound graceful shutdown

* fix(dashboard): preserve revenue detail

* fix(revenue): retain sparse refund attribution

* fix(api): drain webhook replay before shutdown

* fix(env): respect explicit production URLs

* fix(api): stop replay claims during shutdown

* fix(dashboard): polish investigations layout
@greptile-apps

greptile-apps Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Too many files changed for review. (731 files found, 500 file limit)

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant