Docs·8777c5dd·Updated Aug 7, 2026·95 ADRs
User Guides

Understanding the Demo

The live platform at karmyq.com runs a continuous simulation of a mutual aid network based in Portland, Oregon. This simulation exists so you can see what Karmyq looks like when it is actually being u

Understanding the Demo

The live platform at karmyq.com runs a continuous simulation of a mutual aid network based in Portland, Oregon. This simulation exists so you can see what Karmyq looks like when it is actually being used — not a wireframe, but a living community.

What You're Seeing

The platform currently shows a simulated network of neighbors helping neighbors across several Portland communities: the Portland Mutual Aid Network, Southeast PDX Helpers, PDX Parents Co-op, Portland Tool Library & Share, and several professional service networks.

All accounts with @test.karmyq.com email addresses are synthetic. Their activity — requests for help, offers, completed matches, trust connections — is generated by a simulation engine running continuously in the background.

How Activity Is Generated

The simulation engine runs 10 concurrent workers, each independently acting as a simulated community member. Workers create help requests, offer assistance, complete matches, call dibs on requests, and participate in community governance — all through the same APIs a real user would call. They do not currently submit interaction feedback, which is why quality signals read as neutral (see Social Karma below).

This means the trust graph, karma scores, and match history you see are the result of real platform behavior, not seeded test data.

What Real Users Would Look Like

In a real deployment, each of these interactions would be a person. A neighbor without a car asking for a ride to a medical appointment. A parent needing a school pickup covered. Someone with tools to lend finding someone who needs them. The simulation reflects these real patterns so evaluators can see the platform as it would actually be used.

Trust Graph

The trust network shows how trust has accumulated between simulated users through repeated positive interactions. Every completed match strengthens the trust edge between the helper and the person they helped. This is how real trust networks form — through doing things together over time.

Social Karma

Standing is derived from stored exchange history, not from pre-loaded scores. Every completed match projects karma to both participants — the helper and the person helped — through the same rules a new exchange uses today, at the time the exchange actually completed. Nothing is written that a live match would not have written.

Two-sided ratings (helpfulness, responsiveness, clarity) are a real part of the platform and feed the Social Karma system, which surfaces patterns of participation without reducing people to a single number. The demo currently contains no ratings at all. Quality signals are therefore neutral rather than synthesized: rather than invent feedback nobody gave, the demo leaves that input empty and lets standing rest on what demonstrably happened. Where you see a rich profile, it is rich because that account genuinely completed many exchanges — you can trace any score back to the matches behind it.

Tester Account (for evaluators)

To explore the platform with a rich, realistic profile already wired up, sign in with the primary tester account:

maria.reyes@test.karmyq.com / password123

This account is the most complete state in the demo: 15 active communities, 28 trust edges, 33 connections, 19 created requests, hundreds of helper and requester matches, and an active provider profile. It exercises the dashboard, the community pages, trust surfaces, dibs/matching, and provider offers without any setup.

If you want a plainer, member-only perspective (no provider profile, fewer communities), use the fallback:

aisha.white6964@test.karmyq.com / password123

All simulated accounts share the password password123.

Auditing Demo Data Quality

The demo data is regenerated continuously, so its quality is checked with a repeatable read-only script rather than by hand: scripts/audit-demo-data.sql. It reports membership-count drift, pulse helpers who are not members of the community being rendered, open requests with no active community link, and a ranking of the richest tester accounts.

Run it against the demo database (read-only):

scp scripts/audit-demo-data.sql ubuntu@karmyq.com:/tmp/audit-demo-data.sql
ssh ubuntu@karmyq.com "docker cp /tmp/audit-demo-data.sql karmyq-postgres:/tmp/audit-demo-data.sql \
  && docker exec karmyq-postgres sh -c 'PGPASSWORD=\$POSTGRES_PASSWORD psql -U \"\$POSTGRES_USER\" -d \"\$POSTGRES_DB\" -f /tmp/audit-demo-data.sql'"

Trust truth audit (Sprint 98)

A second read-only script, scripts/audit-trust-truth.sql, checks that trust relationships describe the same truth across layers: trust-edge endpoints that are still active members of the edge community, exchange connections backed by a completed match, cached social_distances rows with valid community context, provider shared-communities active on both sides, and dibs candidates that share an active community. Run it the same way (swap the filename). Findings and dispositions live in docs/bugs/sprint-98-trust-truth-audit.md.

Maria's two guided stories (Sprint 116)

The demo rehearses two contrasting relationship stories for Maria through ordinary APIs only:

  • Ordinary story — a richly connected, cross-community helper (a short trust path, several shared people) so the reciprocal lens reads as a real neighbourhood, not an empty ring.
  • Provider story — a low-overlap provider, shown as a deliberate contrast.

Rehearse with npm --workspace @karmyq/simulation-service run rehearse:maria-relationship (dry-run by default; add -- --apply to mutate). It refuses to apply a story that falls below the rich-overlap floor, seeds no trust edges, and prints the verified request/match/offer IDs used to configure the read-only demo session.

The rehearsal consumes the privacy-safe neighborhood API, whose nodes use user_id (not the internal graph field id). It normalizes both shapes before measuring overlap; a dry run that reports an unachievable floor must never be followed by --apply.

An apply run is resumable: always rerun dry-run after an error before retrying mutations. In the Sprint 116 live rehearsal, all four story records committed before a post-insert provider-notification lookup returned a false 500; reconciliation recovered the authoritative IDs without duplicating data.

DEMO_PERSONA_EMAIL also excludes Maria from random simulation workflows. This keeps the surrounding synthetic community alive while preventing the simulator from accepting a competing proposal and invalidating the stable guided story.

The read-only demo session (PR C)

karmyq.com/demo walks these two stories with no account and no writes. POST /auth/demo-session issues a 30-minute token that carries sessionMode: 'demo_read_only'; the shared auth middleware rejects any mutating HTTP method server-side, and the /demo page shows no Accept/Decline/Submit controls. No refresh token is issued — the tour simply expires.

To enable it, set the four IDs printed by the --apply run plus the persona into the demo environment (see .env.demo.example): DEMO_SESSION_ENABLED=true, DEMO_PERSONA_EMAIL, DEMO_ORDINARY_REQUEST_ID, DEMO_ORDINARY_MATCH_ID, DEMO_PROVIDER_REQUEST_ID, DEMO_PROVIDER_OFFER_ID. The persona must be an active, non-admin @test.karmyq.com account, and both stories must be coherent (Maria owns each request; the match/offer hang off the correct request) — any mismatch returns one opaque 503 DEMO_UNAVAILABLE.

How the Demo Is Built (Sprint 117)

The demo now begins from a deterministic, age-aware synthetic baseline rather than pure open-ended simulation. A single guarded reset establishes a compact, curated history — a set of Portland communities and neighbours whose completed exchanges, trust connections, and karma are derived from the same rules the live platform uses, aged relative to one reset moment (some exchanges days old, some months old). After the baseline is in place, ambient synthetic activity continues to evolve the wider population.

A small protected core of people — including the narrative persona and her closest connections — is held stable so the guided story stays coherent and is never altered by ongoing simulation. The persona is an ordinary active member (never an administrator).

Because the platform models real time, demo content behaves like the real product:

  • Fresh vs. aging: recent requests are open; older ones expire on the normal 60-day schedule.
  • Designed to forget: content past the retention window is redacted to a [forgotten] sentinel, exactly as it would be for real users — the demo demonstrates the forgetting behaviour, it does not hide it.
  • Finite live stories: the guided persona's live decisions are real, finite, and must be rotated explicitly before they age out — see Rotating the stories below. This is a standing operational obligation, not a background process.

Everything you see is illustrative synthetic data — real numbers and names would belong to real people. It is not a frozen screenshot; it is the actual product running on curated, truthful history.


Rotating the stories (Sprint 129)

⚠️ The demo dies on a timer if nobody rotates it. This is not hypothetical: it is BUG-039, which left karmyq.com/demo dead from roughly 2026-09-09 to 2026-09-12.

cleanup-service marks an open request expired once expires_at passes (expirationJob.ts:18-22, hourly) and hard-deletes it seven days after that marking (:84-88, daily at 02:00). The DEMO_* variables hold four ids pointing at those rows, so when they are deleted the config silently points at nothing and every POST /auth/demo-session returns 503.

Historical note. rotate:demo-stories has existed since Sprint 117 and this guide has always said the stories are rotated before they age out. It was never actually operational on the demo host: simulation-service is not deployed there, and .env.demo.example carried none of the five variables rotation requires. The documented safety mechanism could not run, which is why the stories aged out silently instead of being rotated. Sprint 129 wired it.

Running a rotation

On the demo host, from the repo root:

cd ~/karmyq
set -a && . ./.env.demo.rotation && set +a

# Dry run first — reports the steps and changes nothing.
npm --workspace @karmyq/simulation-service run rotate:demo-stories

# Then apply.
npm --workspace @karmyq/simulation-service run rotate:demo-stories -- --apply --publish-config

Rotation creates replacement stories through ordinary APIs (so the demo stays the real product on real data, not hand-inserted rows), verifies them, backs up and rewrites only the five allowlisted keys in .env.demo, re-enables the demo, recreates auth-service, and re-verifies a live demo session. It is fail-closed at every step: if verification fails, no config is published.

The wiring, and why it is a separate file

.env.demo.rotation (template: .env.demo.rotation.example, chmod 600, never committed) holds the five variables rotation needs plus two host commands. It is deliberately not part of .env.demo, because that file is injected into every service container and must never carry DEMO_PERSONA_PASSWORD.

VariablePurpose
API_BASE_URLWhere rotation drives the ordinary APIs
DEMO_MARIA_EMAILThe persona (falls back to DEMO_PERSONA_EMAIL)
DEMO_HELPER_EMAILOffers help on the ordinary request — must share a community with Maria or rotation refuses
DEMO_PROVIDER_EMAILSubmits the provider offer
DEMO_UNRELATED_EMAILProves an out-of-audience viewer is denied — must share ZERO communities with Maria
DEMO_PERSONA_PASSWORDShared simulation password
DEMO_ENV_FILEAbsolute path to the compose env file the ids are published into
DEMO_ENABLE_CMD / DEMO_RESTART_AUTH_CMDscripts/demo/enable-demo.sh and scripts/demo/restart-auth.sh; both fail-closed if unset

⚠️ Re-derive the account emails; do not trust a stored list. Demo accounts do not outlive a re-seed, and an account that was unrelated at seed time can be joined into Maria's communities by ambient simulation — which had already happened to the previously-recorded unrelated account by 2026-09-12, quietly weakening the verifier's negative check.

Three traps, all hit while wiring this:

  • npm --workspace sets cwd to the workspace directory, not the repo root, so relative paths in those command variables misresolve. Use absolute paths (the scripts also re-anchor themselves).
  • The file is shell-sourced, so a value containing spaces must be quoted, and CRLF fails as $'\r': command not found on every line. .gitattributes pins .env* to LF.
  • Compose reads the process environment, not an env file: deploy.sh does set -a; source .env.demo. restart-auth.sh reproduces that, across both compose files.

You will be warned before it breaks

.github/workflows/demo-health.yml runs daily and asserts both that a demo session can be issued and that the story rows are more than 14 days from deletion, filing a labelled issue otherwise. It is read-only — it never rotates for you. When that issue appears, run the rotation above.