Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# zeroforce environment. Copy to .env.local (gitignored) and fill in.

# Provider mode hinge (spec §6.6). One of: fake | dev | prod
# fake = deterministic offline providers (default; no keys needed)
# dev = Groq + Tavily
# prod = Gemini 2.5 Pro (Vertex) + Vertex grounding
ZEROFORCE_MODE=fake

# SQLite path (dev). Use :memory: for ephemeral.
ZEROFORCE_DB=./data/zeroforce.db

# Optional single bearer token; if unset, auth is open (fake/dev convenience).
# ZEROFORCE_API_TOKEN=

# --- dev mode keys ---
# GROQ_API_KEY=
# TAVILY_API_KEY=

# --- prod mode (GCP Application Default Credentials must also be configured) ---
# GCP_PROJECT=
# GCP_REGION=us-central1
88 changes: 88 additions & 0 deletions ARCHITECTURE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
# Architecture

zeroforce is a supply-chain **disruption-propagation** layer. Scorecards (Interos, Resilinc)
say *who* is risky; zeroforce answers *what happens next* — if a supplier fails, how fast the
disruption cascades, and which single failure collapses the network fastest.

Full specification: [`.agents/project_specifications.md`](.agents/project_specifications.md) (v2.2).

## The three layers

1. **Extraction** — turn natural language into a weighted directed dependency graph.
Prod: Gemini 2.5 Pro two-pass (ground → structure). Dev: Tavily + Groq. Fake: deterministic.
2. **Computation** — the RZF engine computes exact Expected Propagation Time (EPT) per node
via Markov-chain DP. This is `packages/zeroforce/` and was built first.
3. **Presentation** — Next.js 14 dashboard: force graph, impact ranking, cascade animation,
what-if, executive report.

## ⚠️ The oracle strategy is the most load-bearing decision in the project

A subtly wrong EPT is a plausible number with **no error signal** — a customer could make a
procurement decision on it. So the engine ships first with math-correctness gating CI, and the
tests are designed to **trust the (unrefereed preprint) paper zero**:

- **Hand-computed** 3-node oracle, EPT derived from first principles in the test docstring.
- **Monte Carlo differential** — an independent simulator vs the DP, agreeing to 3 decimals.
- **Engine parity** — pure-Python reference DP vs the numba JIT, exact.

The §4.6 closed-form formulas are **UNVERIFIED transcriptions** and are kept `xfail`. They
currently xpass (the engine reproduces them), but they stay quarantined until someone reads
the paper body and cites the theorem number. **Never** "fix" an xfail by editing the expected
value to match the engine — that defeats the entire safety mechanism. See
`packages/zeroforce/README.md` and spec §11.1.

Two clarifications the build surfaced over the spec pseudocode (both faithful to the locked
decisions, documented in the engine README):
- the row-stochastic invariant is asserted on the **full** graph, not on induced subgraphs
(unreachable suppliers legitimately leave subgraph rows summing < 1);
- the recurrence uses the **unconditional** transition probability divided once by
`(1 − p_stay)` (the prose's conditional phrasing would double-divide).

## Service topology

Six logical services (spec §5.1). Each has a pure-logic `core.py` and a thin FastAPI wrapper:

| Service | Core | Responsibility |
|---|---|---|
| gateway | `gateway/app.py` | Auth, routing, all `/api/v1` endpoints. Embeds the orchestrator. |
| extractor | `extractor/core.py` | Prompt 1 + grounding → `Graph`. |
| validator | `validator/core.py` | §7.2 weight-normalization invariant. |
| engine | `engine/core.py` | Wraps `packages/zeroforce`: analyze / simulate / what-if. |
| reporter | `reporter/core.py` | Prompt 3 executive report. |
| orchestrator | `orchestrator/core.py` | Async job lifecycle + 5-min timeout. Co-located in gateway. |

The orchestrator reaches the workers through a **`ServiceClient`** abstraction:
- `InProcessServiceClient` (default) calls cores directly — single-process, offline, testable.
- `HTTPServiceClient` calls the per-service FastAPI apps (`worker_apps.py`) — used by
docker-compose (six containers) and Cloud Run (one service each).

```
Frontend ─▶ Gateway ─▶ Orchestrator ─▶ ServiceClient ─▶ {extractor, validator, engine, reporter}
▲ poll /analyses/{id} │
└────────────────────────────┘ (SQLite/Firestore jobs + analyses; Redis/dict extraction cache)
```

## The dev/prod/fake hinge

One env var, `ZEROFORCE_MODE`:
- `fake` (default) — deterministic offline providers; no keys, no network. Powers tests/CI.
- `dev` — Groq (`GROQ_API_KEY`) + Tavily (`TAVILY_API_KEY`).
- `prod` — Gemini 2.5 Pro (Vertex, ADC) + Vertex grounding.

Identical I/O contract across modes; only source-attribution metadata and call count differ.
Application code imports only the `LLMProvider` / `SearchProvider` protocols, never a vendor SDK.

## Repository layout

```
packages/zeroforce RZF engine (built first; gating oracle suite)
packages/schemas shared Pydantic API models (engine types re-exported)
packages/providers LLM + search abstraction (fake/groq/tavily/gemini/vertex) + factory
services/backend 6 service cores + FastAPI apps + orchestrator + storage + observability
apps/web Next.js 14 dashboard
infra/ Dockerfiles, docker-compose.dev, Terraform (Cloud Run, Firestore, …)
data/ BEA 15-sector bundle
tests/parity engine-golden + extraction-consistency
```

See [`docs/RUNBOOK.md`](docs/RUNBOOK.md) to run it.
68 changes: 68 additions & 0 deletions PROGRESS.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
# Build progress — autonomous full-project build

Branch: `phase-0-engine` (continuing here; will push + open one PR at end).
Started autonomous full build 2026-05-22. Operator asleep; no questions allowed.

## Operating rules (from operator)
1. Providers: operator intended to paste Groq/Tavily keys but none arrived. → Build real
Groq/Tavily/Gemini providers + a **deterministic FakeProvider as the default** for
tests/CI/local-no-keys. Keys plug in via `.env` later. Live LLM stays UNVERIFIED.
2. Priority if time short: **backend depth first**; frontend = functional dashboard.
3. Delivery: local commits per phase, **push + one PR at end**.
4. Ambiguity: **decide + document here**, keep going.
5. No cloud deploy, no spending money, no pushing secrets. Infra-as-code written, not applied.

## Phase checklist
- [x] Phase 0 — RZF engine `packages/zeroforce/` + gating tests + CI. (commit b5ac81e)
- [x] Phase 1 — backend (d9fce07) + functional Next.js dashboard (force graph, ranking table,
report/citations/notes tabs, async polling, node-select unreachable disclosure).
tsc clean, vitest 3/3, next build clean, live uvicorn smoke OK.
- [x] Phase 2 — per-service worker apps (extractor/validator/engine/reporter) + HTTPServiceClient
wiring; FirestoreRepository + Redis cache (lazy, prod, untested); Dockerfile.backend +
Dockerfile.web; docker-compose.dev (6 services + redis + web); Terraform (Cloud Run x6,
Firestore, Memorystore, Artifact Registry, Secret Manager, IAM) — written, not applied;
engine-golden + extraction-consistency suites (tests/parity). 52 tests pass.
NOTE: orchestrator co-located in the gateway service (documented consolidation).
- [x] Phase 3 — cascade animation (Simulate from a node → play/pause/scrub timeline, nodes
recolor by per-round disruption probability), what-if panel (edge weight slider → delta
table + narration), citations panel (Phase 1), responsive stacked layout for <1024px.
`/simulate` + `/whatif` endpoints were built in Phase 1. tsc + next build clean.
DECISION: executive report is rendered non-streaming (fake/Groq reporter is single-shot;
true token streaming via Vercel AI SDK deferred — documented, low value at this stage).
- [x] Phase 4 — observability (timing middleware + structured access logs + opt-in OTel);
chaos/auth/PII hardening tests (caught + fixed a real upload-redaction bug); k6 load
script (10 RPS sustained + 100 RPS burst on the async path); SECURITY.md threat model
with implementation status; a11y (prefers-reduced-motion guard, aria-live cascade,
table semantics). 55 tests pass. Cost-regression: documented approach, not CI-wired.
- [x] Phase 5 — docs: ARCHITECTURE.md (with the oracle-strategy callout), README rewrite,
docs/RUNBOOK.md (local / compose / Terraform deploy steps / observability / load / beta
onboarding process). DECISION: skipped a separate `apps/docs` Next site; the math
explainer lives in the engine README + ARCHITECTURE.md (lower value, avoids a second
frontend build). Cloud deploy steps written but NOT applied (no GCP creds).

## Architecture decisions made autonomously
- **Microservices as modules + thin HTTP wrappers.** Each of the 6 services has pure-logic
`core.py` + a thin FastAPI `app.py` + Dockerfile (prod parity, docker-compose.dev wires
all six). Orchestrator talks to them via a `ServiceClient` abstraction with two impls:
`InProcessServiceClient` (default; calls core directly — offline + testable) and
`HTTPServiceClient` (compose/prod). Rationale: real microservice deployability without
forcing 6 live HTTP processes for every test/CI run.
- **Provider modes:** `ZEROFORCE_MODE = fake | dev | prod`. `fake` = deterministic offline
(default for tests/CI). `dev` = Groq+Tavily (needs keys). `prod` = Gemini+Vertex (needs GCP).
- **Storage abstraction:** `Repository` protocol; `SQLiteRepository` (default/dev),
`FirestoreRepository` (prod, untested). Cache: `dict` (default) / `Redis` (prod).
- **uv workspace** at repo root; members = `packages/*` + `services/*`.

## DONE — all phases built (2026-05-22, autonomous)

Branch `phase-0-engine` (name is historical; now holds the whole build), 8 commits, pushed.
`gh` CLI not installed (the `gh` shell alias is unrelated), so open the PR manually:
**https://github.com/justin06lee/zeroforce/compare/master...phase-0-engine?expand=1**

Verification at hand-off: 55 Python tests pass (engine pristine under `-W error`); frontend
tsc clean, vitest 3/3, `next build` clean; live uvicorn + worker-chain smoke OK in fake mode.

## Open / unverified
- Live Groq/Tavily/Gemini calls untested (no keys). Fake provider proves pipeline shape.
- Cloud deploy untested (no GCP). Terraform/Cloud Run config written, not applied.
- Frontend e2e (Playwright) may not run if browser download blocked; unit/component tests do.
48 changes: 47 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
@@ -1 +1,47 @@
# Zero Force AI
# zeroforce

**Supply-chain disruption intelligence.** If a supplier fails today, how fast does the
disruption cascade through the dependency network — and which single failure collapses it
fastest? zeroforce answers the *propagation* question that supplier scorecards don't, using a
custom **Randomized Zero Forcing (RZF)** engine that computes exact Expected Propagation Time
(EPT) from every node.

- Math core: `packages/zeroforce/` — built first, math-correctness gates CI.
- Spec: [`.agents/project_specifications.md`](.agents/project_specifications.md) (v2.2).
- Design: [`ARCHITECTURE.md`](ARCHITECTURE.md) · Run it: [`docs/RUNBOOK.md`](docs/RUNBOOK.md) ·
Security: [`SECURITY.md`](SECURITY.md).

## Quickstart (offline, no keys)

```bash
# Python backend (uv workspace)
uv sync --all-packages
ZEROFORCE_MODE=fake ZEROFORCE_DB=:memory: \
uv run uvicorn zeroforce_backend.asgi:app --port 8080

# Frontend (separate terminal)
cd apps/web && npm install && API_BASE=http://localhost:8080 npm run dev
# open http://localhost:3000
```

`ZEROFORCE_MODE=fake` runs the entire pipeline with deterministic offline providers — no API
keys, no network. Set `dev` (Groq + Tavily) or `prod` (Gemini + Vertex) with the relevant keys
from [`.env.example`](.env.example) to use live providers.

## Tests

```bash
uv run pytest -q # engine oracles + backend + parity (55 tests)
cd apps/web && npm test # frontend unit (vitest)
```

The engine's gating oracles (hand-computed graph, Monte Carlo differential, py/numba parity)
trust the source paper zero. The §4.6 closed-form formulas are kept `xfail` until verified
against the paper body — see [`ARCHITECTURE.md`](ARCHITECTURE.md) for why that discipline is
the project's most important safety mechanism.

## Status

Built through Phase 4 (engine → backend pipeline → infra-as-code → frontend interactivity →
hardening). Cloud deployment is written as Terraform/Docker but **not applied**. See
[`PROGRESS.md`](PROGRESS.md) for the phase-by-phase state and autonomous decisions.
25 changes: 25 additions & 0 deletions SECURITY.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
# Security

Threat model and mitigations (spec §12), with implementation status in this repo.

| Threat | Surface | Mitigation | Status |
|---|---|---|---|
| Prompt injection | User input → LLM | Prod structuring (Pass B) uses schema-constrained output; all extractor output is validated against the `Graph` Pydantic schema before the engine runs. Untrusted text never triggers tool calls. | Implemented (schema validation in extractor/validator) |
| LLM cost exhaustion | `/analyze` | `max_nodes` hard cap 18 (Pydantic bound + orchestrator check); 5-minute job timeout; per-request token caps in providers. Per-IP/user rate limiting is scaffolded (gateway) — wire a limiter (e.g. slowapi) before public exposure. | Partial |
| PII in uploads | `/upload` | 20 MB cap; SSN / card-number regex redaction before any text leaves the gateway. | Implemented + tested (`test_upload_redacts_pii`) |
| Credential exposure | Provider keys | Keys only via env / Secret Manager; never in the client bundle (all LLM calls are server-side); `.env*` gitignored; `.env.example` ships placeholders only. | Implemented |
| Citation forgery | LLM hallucinated sources | Prod citations come from Vertex grounding metadata (Pass A), not free text. Unverified citations should be badged in the UI. | Partial (grounding metadata captured; UI badge TODO) |
| Cross-tenant data leak | Firestore | Prod: Firestore security rules enforce `userId` on reads; analyses user-scoped, sharing opt-in. | TODO (rules not written; single-user dev assumed) |
| AuthN/Z | All endpoints | Optional bearer token (`ZEROFORCE_API_TOKEN`); when set, all protected routes require it. Prod target: Firebase Auth + IAP. | Implemented (dev token) + tested (`test_auth_required_when_token_configured`) |

## Operational notes

- **No secrets are committed.** Verify with `git grep -nE "(GROQ|TAVILY|API_KEY)=.+"` returning nothing.
- **Chaos**: a provider outage mid-pipeline fails the job cleanly with `llm_provider_error`
(tested in `test_provider_outage_fails_job_gracefully`) rather than hanging or crashing.
- **Before public deployment**: enable rate limiting, write Firestore security rules, add the
unverified-citation UI badge, and run a dependency audit (`uv pip audit`, `npm audit`).

## Reporting

This is a pre-production build. Report security issues to the repository owner.
6 changes: 6 additions & 0 deletions apps/web/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
node_modules/
.next/
next-env.d.ts
out/
*.tsbuildinfo
.vercel
Loading
Loading