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-storieshas 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.examplecarried 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.
| Variable | Purpose |
|---|---|
API_BASE_URL | Where rotation drives the ordinary APIs |
DEMO_MARIA_EMAIL | The persona (falls back to DEMO_PERSONA_EMAIL) |
DEMO_HELPER_EMAIL | Offers help on the ordinary request — must share a community with Maria or rotation refuses |
DEMO_PROVIDER_EMAIL | Submits the provider offer |
DEMO_UNRELATED_EMAIL | Proves an out-of-audience viewer is denied — must share ZERO communities with Maria |
DEMO_PERSONA_PASSWORD | Shared simulation password |
DEMO_ENV_FILE | Absolute path to the compose env file the ids are published into |
DEMO_ENABLE_CMD / DEMO_RESTART_AUTH_CMD | scripts/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 --workspacesets 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 foundon every line..gitattributespins.env*to LF. - Compose reads the process environment, not an env file:
deploy.shdoesset -a; source .env.demo.restart-auth.shreproduces 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.