Architecture.
This page describes the technical boundaries of the CJI system, in plain language. Three independent surfaces, one documented crossing point between them, and the rule that enforces it at every build. Written for CISO and security-architecture audiences evaluating the service.
The three surfaces.
CJI operates three independent surfaces. They do not share infrastructure, databases, or import paths. The only permitted data crossing between the first two is a single JSON file, documented below.
cjipro.com
GitHub Pages behind Cloudflare. No authentication. No customer data. Static HTML only. The methodology, CJI Chronicle, and the Kestrel Cover sample briefing live here. No server-side code executes on this surface. A visitor who never authenticates sees only this.
Auth: None
Customer data: None
Status: Live
login.cjipro.com
+ briefing surface
WorkOS-backed authentication via magic-link. Partners sign in through
login.cjipro.com, which issues a short-lived JWT. Every
request to the briefing pages passes through a Cloudflare Worker edge
bouncer that validates the JWT before passing the request to the origin.
Invalid or missing tokens are redirected to login. Allowlist-gated:
access requires explicit administrator approval. Audit-logged: every
auth event is recorded in the hash-chained D1 table.
Auth: WorkOS AuthKit, magic-link, JWT
Customer data: Named email address only
Status: Live (alpha)
CJI Pulse
Runs inside a partner firm's environment, on that firm's telemetry only. Never crosses into the MIL pipeline that powers the public briefing. The two systems share no infrastructure, no import paths, and no data stores. CJI Pulse does not currently operate on any firm's live customer data; a DPIA is a prerequisite for activation.
Auth: Partner-governed
Customer data: Partner telemetry (DPIA required)
Status: Not yet live on real data
Zero Entanglement.
The separation between the public-signal pipeline and the private-telemetry product is a load-bearing architectural rule, not a convention.
No file inside the MIL pipeline may import from any internal-data path. No file inside the Pulse codebase may import from MIL directly. The only permitted data crossing in either direction is a single JSON file: mil/outputs/mil_findings.json. That file is the exit point from MIL; it is read-only from the Pulse side. Nothing flows in the other direction.
This rule is enforced at build time by a dedicated static import validator: scripts/validate_mil_import_rule.py. A violation is a hard build failure, not a warning. The CI pipeline cannot produce a deployable artefact if the boundary is crossed.
The practical consequence for security reviewers: it is architecturally impossible for public market signal (app-store reviews, public press, DownDetector data) and private customer telemetry (session data, PII, internal identifiers) to occupy the same process, the same file, or the same database. The validator enforces this at every commit.
Data flow at a glance.
Public signal enters through a documented ingest layer and exits through a single publish step. Nothing flows backward. Each stage transition is a documented contract.
-
Stage 1 — Ingest
Public signal sources — app store reviews (Apple App Store, Google Play), DownDetector outage reports, City A.M. and FT RSS feeds, Reddit community posts, YouTube comments. All sources are publicly accessible with no authentication. Trust weights are configured per source; no source exceeds 0.95.
-
Stage 2 — Enrich
Enrichment pipeline — each ingested record is classified by issue type, customer journey, sentiment score, and severity class. The classifier runs locally or via the Claude API (Haiku model). Records are deduplicated by SHA-256 hash of full content before enrichment. Already- enriched records are skipped; no record is re-classified once complete.
-
Stage 3 — Inference
CAC inference and Chronicle anchoring — the inference engine groups enriched records into findings using a Confidence-Adjusted Concern formula. Every finding must be anchored to an entry in the CJI Chronicle (CHR-001 through CHR-019+) before it enters the production findings set. Findings that cannot be anchored enter a research queue for human review; they do not appear in briefings.
-
Stage 4 — Publish
Sonar briefing publish — the findings set is rendered into an HTML briefing and pushed to GitHub Pages via the PublishAdapter abstraction. All published content is static HTML. No database query executes at request time on the public surface. Briefings on the authenticated surface are served from the same static files, with the edge bouncer enforcing access control before the origin receives any request.
-
Stage 5 — Audit
Audit log and per-tenant export — authentication events generated during briefing access are written to the hash-chained D1 audit table. Per-tenant exports are available on request. No data from the briefing content itself is written back to any pipeline stage. The flow is strictly one-way: ingest → enrich → infer → publish → audit. Nothing re-enters the pipeline from the audit layer.
Hash-chained integrity.
Every authentication event — sign-in, approval, denial, revocation,
admin action, force sign-out, webhook receipt — is appended to an
immutable D1 table. Each row carries a row_hash computed
from the row's own content and a prev_hash referencing
the preceding row's hash. This forms a hash chain: tampering with row
N changes its hash, which invalidates the prev_hash of
row N+1, which invalidates N+2, and so on to the current row. The
entire chain can be verified from any point using the verifier CLI
that ships with the audit package.
PII fields — IP address, user agent, and JWT subject — are not stored
in plaintext. Each is stored as
SHA-256(value ‖ daily_salt), where the daily salt rotates at
midnight UTC and is stored in a separate audit_salts table.
If the event rows are extracted without the salts, the hashed values
cannot be reversed to recover IP addresses, user agents, or account
identifiers. Salts and event rows are kept in the same D1 database but
are queried independently.
Per-tenant audit exports (available via the admin interface) exclude the internal hash columns. Partners receive event-type, timestamp, and outcome fields — the information needed for their own compliance logging — without the chain-integrity fields that are only meaningful for internal verification.
The defaults that matter.
These are not aspirations. They are the production configuration as of April 2026. Each item can be verified from the codebase or from inspection of the running service.
- TLS TLS 1.3 enforced at the Cloudflare edge. Older protocol versions are not accepted. No mixed-content warnings on any page.
-
Cookies
Session cookie name:
__Secure-cjipro-session. Attributes:Secure,HttpOnly,SameSite=Lax, domain-scoped to.cjipro.com. The__Secure-prefix requires a secure context and cannot be set over HTTP. Cookie spec is codified in a TypeScript invariants module with 14 enforcement tests. - JWT WorkOS-issued JWTs carry a short expiry (~10 minutes in the current configuration). The edge bouncer validates the JWT signature against the WorkOS JWKS endpoint on every request. Expired tokens are redirected to login, not passed through.
-
State token
The magic-link OAuth state token is HMAC-SHA256 signed with a randomly generated key and carries a 10-minute TTL. An expired or invalid state token causes the login flow to restart with a clear error. Open-redirect guard: the
return_toparameter is validated against an allowlist of permitted destinations before use. -
Admin
All administrative actions — approval, denial, revocation, audit export, force sign-out — require a valid session for an account in the
admin_userstable. Every action is recorded in the audit log with the acting account's identifier and timestamp. - No passwords No password is stored anywhere in the system. Authentication is magic-link only. There is no password-reset flow because there are no passwords to reset.
- Rate limits WAF rate-limiting rules at the Cloudflare edge: signup form (10 requests per IP per hour), admin API (60 per minute), authorisation entry point (30 per minute), and a global ceiling. An in-Worker rate limiter provides a user-facing first layer; the WAF rules are the harder backstop.
-
No tracking
No third-party scripts are loaded from the public surface. No analytics service receives visitor data. Content-Security-Policy restricts resource loading to same-origin only (plus
'unsafe-inline'for style — no external style sheets).