CANCRIDOCS · v0.4
DOCS --:--:-- ET

Maintaining

How cancri is put together, and the invariants a maintainer protects. Every load-bearing decision is an smADR; this page is the map, not a substitute for them.

Three runtime classes

cancri splits cleanly into three execution classes (ADR-0002), each with a different trust and lifetime profile.

classruns asresponsibility
clientbrowser · Firebase Hostingvanilla-TS terminal, single rAF loop. Subscribes to normalised ticks only — no source internals, no keys.
request/responseCloud Functions (2nd gen)Gemini normalise/confirm + logo. Vertex via IAM, no API key.
always-on / scheduledCloud Run service + JobThe feed-engine (sole RTDB writer) and the self-heal capture Job.

The data layer

Built from scratch behind a source-agnostic adapter interface that delivers normalised ticks (ADR-0006). The one shared seam is packages/data-contractsnothing source-specific may cross it (ADR-0009).

primary L&S (ls-tc.de)

Real-time, cent-accurate, an undocumented Lightstreamer-6 protocol. Isolated in packages/ls-protocol as the break surface — a versioned config that the self-heal targets.

fallback Yahoo

Delayed ~15 min, always marked freshness: delayed. Doubles as the runtime sanity oracle — the cross-check that primary prices are plausible.

The feed-engine is a single-process singleton (ADR-0003) and the sole writer to the RTDB tick bus (ADR-0004 / ADR-0005). The client reads /quotes/{isin} and /feed/status read-only.

Degradation FSM — honesty over blackout

When the primary feed is lost, the dashboard does not blank. A finite-state machine drives a visible, honest transition:

primary → fallback
LIVE ──(primary lost)──▶ RECONNECT ──(give up after n/5)──▶ DEGRADED
  ▲                                                          │
  └──────────────────(primary recovers)─────────────────────┘

on transition: header flips LIVE→DELAYED · master dot recolours + switches to the
slower off-beat pulse · degraded banner slides in · every row → delayed · latency climbs.
the dashboard stays warm the whole time.

Self-heal governance

The primary source is undocumented, so it will drift. A scheduled probe watches L&S for liveness and price sanity. On a sustained break, the self-heal Job (ADR-0010):

  1. Captures ground truth

    A real browser (Playwright) captures raw protocol frames alongside the simultaneously-rendered price — the ground truth.

  2. Diffs against the known protocol

    Deterministic replay searches for the fix that makes the captured frames decode to the captured price.

  3. Opens a reviewable PR

    With frame/price fixtures attached. No auto-merge — a human merges. Fixtures accrete append-only as protocol documentation and a regression corpus.

Never wire up auto-merge for self-heal PRs. The whole governance model is "the machine proposes, a human disposes." The ls-replay-regression check + ≥1 human review are the gate; branch-protect main to require both.

Secrets & isolation

  • All source access and Gemini calls run server-side inside Firebase. The client never holds a key.
  • Gemini runs via Vertex AI + IAM (ADR-0008) — there is no Gemini API key to leak.
  • Per-user isolation is enforced by the datastore's security rules (config/firestore.rules, config/database.rules.json), tested on the emulator — not just by the UI.
  • L&S handshake config lives in Secret Manager, never in the repo.
  • Keep .env gitignored; document placeholders in .env.example.

CI & gates

workflowtriggerwhat it guards
ci.ymlpush to main · PRstypecheck (all) + web build + unit tests; rules/persistence/tick-bus on the emulators.
adr-validate.ymlchanges under docs/decisions/smADR schema validity.
ls-replay-regression.ymlselfheal/* PRsthe self-heal merge gate — deterministic replay + bounded-surface check.
security-review.ymlsame-repo PRsOAuth-authenticated review of the diff (deterministic FP-filtering).
issue-triage.ymlnew issues / PRsauto-labels; flags ADR-breaking requests; opens PRs only on a maintainer's @claude implement.
deploy.ymlpush to main gatedkeyless WIF deploy — Firebase surfaces + Cloud Run feed-engine. See Deploy.
pages.ymlpush touching site/publishes this docs site to GitHub Pages.

All GitHub Actions are SHA-pinned (supply-chain-conscious); pnpm dependency build scripts are explicitly approved via allowBuilds in pnpm-workspace.yaml. Claude tooling authenticates with CLAUDE_CODE_OAUTH_TOKEN, never ANTHROPIC_API_KEY — see SETUP.md.

Design tokens

The design/ handover is the source of truth for everything visual (ADR-0011). UI tokens — colour + motion — are generated from design/cancri.handover.json by tools/tokens:

regenerate tokens
pnpm tokens   # writes apps/web/src/generated/{tokens.css,tokens.ts}

Never hand-edit the generated files — change the handover and re-run. This docs site mirrors the same tokens in site/assets/cancri.css so its optics stay in lockstep with the terminal.

Architecture decision record

Eleven accepted smADRs pin the load-bearing decisions. Read them at docs/decisions/.

#decision
0001Pin region europe-west1
0002Three runtime classes (execution model)
0003Feed-engine single-process singleton
0004Datastore split — Firestore (the book) / RTDB (the wire)
0005Realtime transport — RTDB tick bus
0006Tick schema & SourceAdapter contract
0007ISIN resolution — LLM proposes, resolver disposes
0008Gemini via Vertex + IAM callable
0009ls-protocol break-surface isolation
0010Self-heal governance — PR + deterministic gate
0011Frontend — vanilla TS + Vite, single rAF

Maintainer runbook

  • A self-heal PR landed. Review the captured frame/price fixtures, confirm the replay-regression check is green, and merge by hand. Never auto-merge.
  • The dashboard is stuck DELAYED. Expected when primary is down — the FSM degraded honestly. Check the feed-engine logs and the L&S liveness probe; recovery flips it back to LIVE automatically.
  • Onboarding mis-parses. Confidence < 0.7 rows are flagged for the user, not silently trusted — by design. Tune the prompt/resolver in functions/, and add a fixture.
  • A protocol capture is needed. Run the self-heal Job during trading hours to fill packages/ls-protocol/protocol.config.v1 — exactly what the replay gate verifies.
  • Shipping the docs. Edit under site/, push to main; pages.yml publishes it.