This repository has been archived on 2026-09-12. You can view files and clone it. You cannot open issues or pull requests or push a commit.
Files
praxis/.ciagent/PROJECT.md
T
Praxis CI 813bd586d6 docs(milestone): merge v0.3-mastery-scoring → main
v0.3 milestone merged to main. Mastery scoring + competency rubrics +
verifiable credentials (formative-tier) shipped. 13/13 REQ-IDs covered.
Next milestone: v0.4 (operator tier — cohort dashboard + auth + Postgres).

---ci---
project: praxis
phase: 2
milestone: v0.3
status: complete
milestone_complete: true
milestone_merged_to_main: true
---/ci---
2026-08-04 00:14:59 +00:00

191 lines
30 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Praxis — Voice-first AI Apprenticeship Platform
**Milestone:** v0.3 (Mastery scoring + competency rubrics)
**Status:** phase 0 — specify (active milestone)
**Autonomy:** full
**Previous milestone:** v0.2 (Proxmox LXC deployment) — complete, tagged v0.1.2, release #377
## Vision
Praxis is a voice-first, AI-tutored skill platform for learners in resource-constrained environments. Instead of courses, videos, and quizzes, learners practice real job scenarios through real-time spoken conversation with AI tutors. The platform treats every learner as an apprentice to a master craftsperson — open the app, talk, do the job, get better at it.
**One-line pitch:** Praxis turns every smartphone into a master craftsperson that talks to you, challenges you, and helps you get good at your job.
## Objective
Build a voice-first AI apprenticeship platform where learners engage in spoken role-play scenarios with AI tutors, receive coaching debriefs, and progress via mastery gates — working on low-cost phones over constrained bandwidth.
## v0.3 Scope (Mastery Scoring + Competency Rubrics)
v0.3 activates the mastery/assessment layer deferred from v0.1/v0.2 (per D-021, ROADMAP line 53). Learners progress via **mastery gates** — they move on only when they can do the thing across varied scenarios, scored against a competency rubric. v0.3 also introduces the multi-tenant + auth foundation required for the cohort dashboard, and a verifiable-credential issuer so mastery is portable.
**v0.3 in scope (activated REQ groups — post-grill):**
- **Mastery core (REQ-MAST-01, REQ-MAST-02):** competency rubric per skill; Mastery Score updated after each session, requiring varied-scenario success before a mastery gate opens
- **Verifiable credentials (REQ-MAST-03):** portable, tamper-evident credentials issued on week-final mastery gate (W3C VC Data Model 2.0, Ed25519, **formative-tier**, SQLite-backed issuer keys, public verification endpoint)
- **Dynamic difficulty (REQ-SCEN-02):** scenario difficulty adjusts to learner performance (item-response-theory-informed)
- **Scenario library (REQ-SCEN-03, REQ-SCEN-04):** library tagged by skill/difficulty/failure_mode; expert-authored format extended with rubric mappings + AI-generated variation hooks
- **Path structure (REQ-PATH-02):** path-as-job 6-week structure (PRD §6.4) — the progression container mastery gates live in
**v0.3 out of scope (deferred to v0.4 per GRILL-v0.3.md Axis 2):**
- **REQ-DASH-01 (cohort dashboard) + REQ-AUTH-01 (operator auth) + REQ-MT-01/02 (operator Postgres + aggregation) + 4 NFRs** — the operator tier was originally v0.8 on the ROADMAP; pulling it into v0.3 created a 2-milestone program. The grill's binding verdict splits it to v0.4. D-031 (override D-007) is deferred with the operator tier.
- REQ-PATH-01 (full multi-path launch) — v0.3 ships the Customer Service path only
- REQ-DASH-02 (full operator-suite dashboard) — later milestone
- REQ-ASSIST-01..03 (Live Assist) — later milestone
- REQ-LOWBW-01..03 (WhatsApp/USSD/offline) — later milestone
- REQ-VOICE-05/06 (multi-language, persona switching) — later milestone
- Active failure injection (D-009) — D-049 confirms stays off in v0.3
- Dynamic rubric weight re-weighting on branch outcome — static in v0.3 (grill Axis 9)
- Traefik proxy / public TLS — deferred from v0.2 (R-AUTH-01 deferred to v0.4 with the operator surface)
**Carries forward from v0.2 (already in production):**
- Docker-in-LXC deployment (`lxc-deploy.sh`, `praxis.service`, `/health` :8789)
- Voice loop (Deepgram Nova-3 + Cartesia + Pipecat + Ollama Cloud)
- v0.1 scenario (`cs_refund_ca_v01.yaml`) + guardrails + debrief
## v0.2 Scope (Proxmox LXC Deployment — complete)
v0.2 deploys praxis into a Proxmox LXC container, reusing and adapting the battle-tested deployment toolkit from `~/coreci/scripts/proxmox/`. The v0.1 voice loop becomes deployable infrastructure — a Docker image runs the Python/Pipecat server (serving the React client as static files) inside an LXC container on the operator's Proxmox cluster.
**v0.2 in scope:**
- Docker image (multi-stage: Node builds `client/dist`, Python runs `server` + serves dist via FastAPI StaticFiles)
- `scripts/proxmox/` adapted from coreci (api.sh, lxc-deploy, lxc-clone, lxc-config, lxc-start, health-check, rollback, stage-snippet, firstboot-hook, timing)
- `scripts/install-service.sh` (systemd unit for `docker compose up`)
- Secret wiring: PROXMOX_* sourced from coreci's `.env.secrets`; GITEA_TOKEN + DEEPGRAM_API_KEY from praxis's secrets
- Health-check adapted for `/health` :8789 (praxis's endpoint, not coreci's `/healthz` :18080)
- E2E deploy verification against the live Proxmox cluster
**v0.2 out of scope (deferred):**
- Mastery scoring, competency rubrics (deferred to v0.3)
- CARTESIA_API_KEY / OLLAMA_API_KEY provisioning (infrastructure-only; server degrades gracefully per v0.1 design)
- Traefik proxy / public TLS (pilot = direct bridge IP access)
- Multi-environment (dev/staging/prod) — single pilot CT
- vmbr1 private network (pilot uses vmbr0 DHCP)
## Product Principles (non-negotiable)
1. **Voice is the primary interface.** Text is fallback, not default.
2. **Doing > Knowing.** Every session produces observable action, not passive consumption.
3. **One skill, one outcome.** Each path is a job someone can get.
4. **Works on a cheap phone, on 2G.** Engineering constraints are product features.
5. **The AI is a master, not a chatbot.** Personality, standards, opinions.
6. **Mastery gates progression.** Move on when you can do the thing.
7. **Failure is the curriculum.** AI provokes mistakes, then coaches recovery.
## Requirements (summary — see REQUIREMENTS.md for formal REQ-IDs)
- Voice conversation engine: real-time ASR + streaming TTS, <600ms round-trip, interruptible, persona switching
- Scenario engine: branching role-plays with failure-injection and dynamic difficulty (v0.1: one scenario)
- Learner state: progress, session history, mastery accumulation (v0.3: mastery scoring + competency rubrics + verifiable credentials)
- Scenario engine: branching role-plays with failure-injection and dynamic difficulty (v0.3: dynamic difficulty + scenario library + AI variations)
- Skill paths: path-as-job 6-week structure (v0.3: Customer Service path structured + mastery gates)
- Cohort dashboard: anonymized cohort view for training operators (v0.3: multi-tenant + auth + cohort view)
- LLM foundation: Ollama-hosted open-weights models `gemma4:cloud` and `deepseek-v4-flash:cloud`
- Low-bandwidth surfaces (later milestones)
## Constraints
- C-1 Voice is primary interface; text is fallback only
- C-2 Must work on $100 Android phone over 2G/3G
- C-3 Cost ≤ $3/active learner/month (target markets; v0.1 is Canada launch — relaxed for pilot)
- C-4 Audio-only in v1 (no large video assets)
- C-5 Open-weights LLM via Ollama catalog — `gemma4:cloud` + `deepseek-v4-flash:cloud`
- C-6 Domain safety guardrails + human-in-the-loop + disclaimers for safety-sensitive domains
- C-7 Scenarios authored by domain experts + learning designers; AI generates variations only
- C-8 Latency budget < 600ms end-to-end (ASR → LLM → TTS)
## Key Decisions
| ID | Decision | Rationale | Confidence | Alternatives |
|----|----------|-----------|------------|--------------|
| D-001 | Launch market = **Canada** (path: Customer Service) | User-directed; Canada as initial market for v0.1 pilot. PRD named Kenya — overridden. | 0.70 | Kenya + Customer Service (PRD default) |
| D-002 | Milestone = **v0.1 foundation** (v1.0 reserved for working/tested product) | User-directed; v0.1 is the foundation slice (Phase 0 + Phase 1 minimal voice loop). v1.0 is a future milestone. | 0.90 | v1.0 = Phase 0 + Phase 1 (too ambitious for first milestone) |
| D-003 | LLM foundation = **Ollama catalog**`gemma4:cloud` + `deepseek-v4-flash:cloud` | User-directed; open-weights via Ollama, two base models for edge/cloud split. Research phase to verify exact catalog IDs. | 0.75 | Llama-family, Mistral-family, Qwen-family |
| D-004 | Defer monetization model decision to Phase 1 | PRD §11.5 explicitly lists this as a Phase 1 decision (B2C paid, B2B per-seat, donor-funded, government). | 0.85 | Decide now (insufficient data) |
| D-005 | Single-project mode | Fresh repo with one project; no multi-project need. | 1.00 | Multi-project mode |
| D-006 | "One persona" = one voice persona; scenario role-play uses the same TTS voice as mentor (no distinct character voice in v0.1) | Minimizes v0.1 surface area; PRD's full persona-switching (REQ-VOICE-06) is deferred. Same voice avoids a second TTS configuration to validate. | 0.70 | Two voices (mentor + character) — adds TTS config risk |
| D-007 | "Single learner state" = local single hardcoded profile, no auth, no multi-tenant; persisted via SQLite on-device (or local file fallback) | v0.1 is a pilot harness, not a production multi-user system. Auth/multi-tenant is a later-milestone concern. SQLite chosen as the default local store; research phase may refine. | 0.80 | In-memory only (no persistence), server-side Postgres (premature) |
| D-008 | Interruptibility = abort-and-yield (learner speech cuts AI TTS immediately, AI yields the floor, no pause/resume state machine in v0.1) | Matches real-conversation semantics per PRD §6.1; pause/resume adds state-machine complexity inappropriate for v0.1. | 0.75 | Pause/resume state machine |
| D-009 | Failure-injection hook = architecturally present (scenario declares a `failure_mode` field) but NOT actively provoked in v0.1 sessions | v0.1 validates the data model and one scenario's success criteria; provoking failures is a coaching-debrief feature tied to mastery (deferred). Hook present so Phase 2+ can activate it without schema change. | 0.70 | Active failure injection in v0.1 (couples to deferred mastery engine) |
| D-010 | v0.1 Canada Customer Service scenario = "Angry customer requesting refund on a damaged product" (retail context, single branch point) | Concrete, universally recognizable, low safety-risk (non-medical/non-electrical). One branch point (customer escalates vs accepts resolution) keeps scenario runtime minimal while exercising branching. | 0.65 | "Customer with wrong booking" (hospitality — less universal for Canada pilot) |
| D-011 | Coaching debrief = included in v0.1 as a single end-of-session text+voice summary (not the full PRD §5.1 multi-moment replay) | The debrief is part of the core daily loop and cheap to include at a basic level. Full replay/multi-moment coaching is tied to mastery (deferred). | 0.70 | Exclude debrief entirely (loses core loop identity), full replay (over-scoped) |
| D-012 | v0.1 cost ceiling = no enforced ceiling (pilot); architecture must not bake in assumptions that would prevent meeting ≤$3/learner/month post-pilot | C-3 is a target-market constraint. Canada pilot is a foundation/tech-validation milestone, not a unit-economics milestone. Logging actual cost per session is a v0.1 NFR to inform later milestones. | 0.85 | Enforce $3 ceiling in v0.1 (premature optimization, wrong market) |
| D-013 | ASR = **Deepgram Nova-3** streaming (cloud, WebSocket) | Research-verified: streaming-native, ~200-300ms first partial, accent-robust for Canadian English, first-class Pipecat integration, Canada data-residency available. Fallback: Groq-hosted Whisper. | 0.85 | whisper.cpp (breaks <600ms budget), OpenAI Whisper API (batch) |
| D-014 | TTS = **Cartesia Sonic** (cloud, ~120ms first audio) primary; **Piper** (self-hosted, ~80ms) fallback behind interface | Research-verified: Cartesia #1 on Speech Arena; Piper is open-weights post-pilot ≤$3/learner path. R4 risk: all-cloud path ~670ms — Piper local may be required for production v0.1 latency. | 0.80 | ElevenLabs (quality but higher latency/cost), Amazon Polly |
| D-015 | Client = **React + WebRTC** via Pipecat client SDK | Research-verified: Pipecat ships React/RN/Swift/Kotlin SDKs; web client = fastest v0.1 iteration, no app-store distribution, upgrades to React Native for Android later. | 0.85 | Python CLI harness (dev-integration only), native Android Kotlin (premature) |
| D-016 | Transport = **WebRTC** (UDP, sub-50ms audio); WebSocket dev fallback | Research-verified: WebRTC is Pipecat's production transport; adaptive bitrate, UDP. SSE/HTTP rejected (unidirectional/high overhead). | 0.85 | WebSocket-only (higher audio latency), custom raw HTTP/2 |
| D-017 | Orchestration = **Pipecat** (not custom, not Vocode) | Research-verified: 13.8k★, active, integrates Deepgram+Cartesia+Piper+Ollama natively, has VAD/interrupt/Flows for branching. Vocode stale since Nov 2024. Custom orchestration rebuilds solved problems. | 0.85 | Vocode (stale), custom from scratch |
| D-018 | Scenario format = **YAML DSL → Pydantic → Pipecat Flows** | Research-verified: YAML is human-authorable + diffable + supports comments (critical for learning-designer rationale per C-7); Pydantic gives typed runtime; Pipecat Flows consumes the schema for branching. JSON is wire format only. | 0.85 | JSON DSL (no comments), code-authored (couples authoring to engineering) |
| D-019 | v0.1 guardrail layer = **pluggable interface** with Customer Service ruleset implementation | Research: v0.1 is low-risk (Customer Service) but architecture must support pluggable guardrails for later high-risk domains (health/electrical). Ruleset: no legal/financial/medical advice, no real-company employee impersonation, stay-in-role, session-start disclaimer audio, no PII beyond hardcoded profile. | 0.80 | No guardrails (violates C-6), hardcoded non-pluggable rules (blocks future domains) |
| D-020 | LLM access = **Ollama Cloud direct API** (`https://ollama.com/api/chat` + `OLLAMA_API_KEY`) — no local daemon | Research-verified: `:cloud` tags are real Ollama hosted-inference on NVIDIA cloud partners. Direct API eliminates local-daemon deployment dependency. `gemma4:cloud` (256K ctx) → role-play fast path; `deepseek-v4-flash:cloud` (1M ctx, no-think mode) → debrief. Self-host `gemma4:e4b` is the post-pilot cost-reduction path. | 0.85 | Local Ollama daemon proxy mode (adds deployment dependency) |
| D-021 | v0.2 scope = **Proxmox LXC deployment** (replaces roadmap's mastery-scoring v0.2) | User-directed: deploy praxis into an LXC container hosted on Proxmox, reusing `~/coreci/scripts/proxmox/` methods. Mastery scoring deferred to v0.3. | 0.95 | v0.2 = mastery scoring (original roadmap), v0.2 = LXC deploy + mastery (too large) |
| D-022 | Artifact = **Docker image in LXC** (nesting=1) | User-directed. Isolates Python/Pipecat deps; coreci's clone script already sets `features=nesting=1`. Avoids venv/pip first-boot fragility (Pipecat has many native deps). Multi-stage build: Node stage produces `client/dist`, Python stage runs the server. | 0.85 | Clone repo + venv + pip (fragile first-boot), sdist tarball (needs build/release step) |
| D-023 | Client serving = **FastAPI serves `client/dist` as StaticFiles** | User-directed. Single port (8789), simplest pilot — no nginx/caddy. The Docker image bundles the pre-built dist. | 0.90 | Separate static server (nginx/caddy — more moving parts), client out of scope |
| D-024 | Voice-service keys = **infrastructure-only** for v0.2 | User-directed. Server starts and `/health` passes even without CARTESIA/OLLAMA keys (v0.1 graceful degradation). Keys provisioned in a later milestone. Only GITEA_TOKEN + DEEPGRAM_API_KEY are in `.env.secrets`. | 0.90 | Provision all keys in v0.2 (premature — deploy infra first) |
| D-025 | Image distribution = **host-build → `pct push` tarball** (research decision, see RESEARCH.md) | The LXC CT may not route to the internet (coreci pattern: host-fetch → pct push). Build the Docker image on the PVE host (Docker available on Proxmox host) and `docker save | pct exec -- docker load`, or `pct push` a tarball. Avoids needing a container registry. | 0.75 | Gitea container registry (requires registry setup), Docker Hub (external dependency) |
| D-026 | Proxmox secrets sourced from **`~/coreci/.ciagent/.env.secrets`** | Same Proxmox cluster, same operator. PROXMOX_API_URL/TOKEN/NODE/STORAGE/TEMPLATE_VOLID already provisioned there. Praxis's `.env.secrets` adds GITEA_TOKEN + DEEPGRAM_API_KEY. The deploy script sources both. | 0.90 | Duplicate proxmox secrets in praxis (drift risk) |
| D-027 | VMID = **`auto`** (fresh allocation via `pve_nextid`) | CLARIFY auto-decide (full autonomy). Don't reuse coreci's fixed PROXMOX_LXC_VMID — praxis gets its own CT on the same cluster. | 0.95 | Reuse coreci's VMID (collision), hardcode a new fixed VMID (manual allocation) |
| D-028 | Docker installed **inside the CT** via apt (CT has network via vmbr0 DHCP) | CLARIFY auto-decide. Avoids needing Docker on the PVE host. The debian-12 template + nesting=1 supports Docker-in-LXC. firstboot hook runs `pct exec` to install `docker.io` + `docker-compose-v2`. | 0.90 | Docker on PVE host (extra host dependency), pre-baked template (custom template maintenance) |
| D-029 | Image built **inside the CT** (clone repo from Gitea, `docker build`, `docker compose up`) | CLARIFY auto-decide. Self-contained — CT fetches its own source + builds. No image transfer needed. Slower first-boot (~3-5 min for build) but simpler and reproducible. | 0.80 | Build on PVE host + pct push tarball (host Docker dependency), pre-built image from registry (external dependency) |
| D-030 | CT network = **vmbr0 DHCP only** (pilot, no vmbr1, no Traefik proxy) | CLARIFY auto-decide. v0.2 is infrastructure-only pilot. Direct bridge IP access for health-check. Proxy/TLS deferred to a later milestone. | 0.90 | vmbr1 + Traefik proxy (over-scoped for pilot) |
| D-031 | v0.3 introduces **multi-tenant + auth****overrides D-007** for the cohort-dashboard surface | REQ-DASH-01 (anonymized cohort view for training operators) requires multi-tenant data. D-007's single-learner/no-auth stance was correct for v0.1/v0.2 pilot but blocks v0.3's cohort dashboard. Resolution: **hybrid** — learner-local state stays SQLite-on-device (D-007 preserved for learner surface); a new **operator-tier Postgres** stores cohort aggregations + operator accounts + issued credentials. Learner auth deferred (single-learner-per-device still valid for pilot). Operator auth = session-based, single operator role in v0.3. Research phase to validate Postgres-in-LXC + migration path. | 0.75 | Full Postgres migration (abandons SQLite pilot work), defer DASH-01 again (scope creep), no auth (insecure) |
| D-032 | Mastery gate = **N-of-M varied-scenario success + rubric score ≥ threshold** | Operationalizes PRD principle 6 ("move on when you can do the thing"). N=3 distinct scenarios, rubric mean ≥ 3.5/5.0 (configurable per path). Research phase to validate rubric model + threshold against competency-based-assessment literature. | 0.70 | Single-scenario pass (gaming risk), pure rubric score (no variety), pure time-on-task (invalid) |
| D-033 | Verifiable credentials = **W3C VC Data Model 2.0, platform-issued** (operator key), Ed25519 signatures | Research-anticipated: W3C VC 2.0 is the current standard; platform-issued is simplest viable issuer model (no DID method proliferation); Ed25519 is compact + widely supported. Self-issued (learner-side key) rejected — no tamper-evidence authority. Third-party issuer (university/agency) deferred to v0.9 credentialing milestone. Revocation = simple status list (VC Status List v2025). | 0.70 | Self-issued (no authority), third-party issuer (v0.9 scope), JWT-VC (less mature tooling) |
| D-034 | Cohort anonymization = **k-anonymity ≥ 10** + aggregation window ≥ 7 days | REQ-DASH-01 operator view must not expose individual learners. k=10 is the conventional minimum for anonymized analytics; 7-day aggregation prevents re-identification via sparse windows. Research phase to validate against differential-privacy literature. Operator sees aggregate progression/failure-patterns only. | 0.70 | No anonymization (privacy violation), differential privacy (over-engineered for v0.3 scale), k=5 (too weak) |
| D-035 | Dynamic difficulty = **IRT-informed (1-parameter Rasch)**, updated per session | REQ-SCEN-02. Item Response Theory (1PL/Rasch) is the simplest well-grounded model: learner ability θ, scenario difficulty b, P(success)=logistic(θ−b). Bayesian update of θ after each session. Avoids 2PL/3PL complexity (discrimination/guessing params — needs more data than v0.3 has). Research phase to validate. | 0.70 | ELO-like (less theoretically grounded), fixed difficulty steps (no adaptation), 2PL/3PL (data-hungry) |
| D-036 | Scenario library structure = **YAML directory + index manifest**, tagged by skill/difficulty/failure_mode/rubric | Extends D-018's YAML DSL. Library = `scenarios/<path>/<scenario>.yaml` + `scenarios/index.yaml` manifest (tagged, versioned). Expert-authored scenarios ship as YAML; AI-generated variations use the same schema with a `generated_from` backref. Rubric mapping added to scenario schema (each scenario declares which rubric criteria it exercises). | 0.80 | Database-backed library (premature — YAML is diffable + authorable per C-7), JSON (no comments per D-018), inline in code (couples authoring to engineering) |
| D-037 | Path structure = **6-week job-structured path**, JSON + YAML, mastery gates between weeks | REQ-PATH-02 (PRD §6.4). Path = `paths/<slug>.yaml` defining 6 weeks, each week = a set of scenarios + a mastery gate. Gate opens when D-032 mastery condition met. v0.3 ships the Customer Service path fully (6 weeks) with ≥1 scenario per week (library REQ-SCEN-03 fills the rest). | 0.75 | Free-form progression (no structure), 12-week (too long for pilot), week-as-fixed-time (relax to mastery-paced) |
| D-038 | Rubric scoring path = **rule-based final score, LLM-assisted criterion extraction only** (REQ-NFR-MAST-01) | Final score must be deterministic. LLM (deepseek-v4-flash:cloud no_think) extracts criterion evidence from session turns (which utterance maps to which rubric criterion); a rule function computes the 1-5 score per criterion from the extracted evidence + branch outcome. No LLM in the numeric scoring step. Preserves REQ-NFR-MAST-01 determinism + keeps latency off the voice path. | 0.80 | Pure-LLM scoring (non-deterministic, violates NFR-MAST-01), pure-rule extraction (rigid — can't handle free-form speech) |
| D-039 | Rubric YAML format = **`rubrics/<skill>.yaml`** with criteria, 5-level anchors, per-skill weights | Extends D-018's YAML-everywhere stance. One rubric file per skill (v0.3: `rubrics/customer_service.yaml`). Each criterion has id, name, 5 anchored levels (1=fail … 5=mastery), weight. Scenario YAML maps to rubric criteria via `rubric_criteria` field (D-036). | 0.80 | JSON (no comments per D-018), inline in scenario (couples rubric to scenario — rubric is per-skill not per-scenario), DB-backed (premature) |
| D-040 | Operator Postgres deployment = **second Docker service in the existing LXC CT** (`docker-compose.yml` adds `postgres` service) | REQ-NFR-MT-01. Reuses v0.2's LXC + Docker-in-LXC. No new CT, no host Postgres. Postgres 16, persistent volume, internal Docker network only (not exposed to bridge). Operator auth + cohort API + VC issuer connect to it. | 0.80 | Separate CT (over-provisioned for v0.3 scale), host Postgres (PVE host dependency), SQLite for operator (cohort aggregation needs relational + k-anonymity queries — SQLite workable but Postgres is the safer default) |
| D-041 | Operator auth = **session-cookie, argon2id passwords, single `operator` role, login rate-limited (5 attempts/min)** | REQ-NFR-AUTH-01. Simplest viable auth for v0.3's single operator role. No OAuth/JWT complexity for one role. Cookie: httpOnly, secure, SameSite=Strict, 8h expiry. Rate limit via in-memory counter (single-instance). RBAC deferred (one role). | 0.75 | JWT (over-engineered for server-side session), OAuth (no IdP yet), basic-auth (insecure), no rate-limit (brute-force risk) |
| D-042 | VC issuer key = **Ed25519 keypair in operator-tier secrets (`PRAXIS_VC_ISSUER_KEY`), generated on first issuer init, not committed** | REQ-NFR-VC-01. Key generated at first boot if absent, stored in Postgres `issuer_keys` table encrypted at rest with a root key from secrets. Verification endpoint serves the public key. Rotation = new key + old key marked superseded (not revoked — old VCs still verify against archived public key). | 0.70 | RSA (larger, slower), KMS-managed (no KMS in LXC), self-signed cert chain (X.509 complexity unjustified for one issuer) |
| D-043 | VC verification endpoint = **public, unauthenticated, GET `/vc/verify/<credential_id>`** | Third parties (employers/agencies) verify credentials without an account. Returns `{valid: bool, status: "active"\|"revoked", issuer: "praxis-v0.3", mastery: {...}}`. No PII in the verification response beyond what the credential itself asserts. | 0.80 | Authenticated verification (friction for employers), no public endpoint (credentials not portable), returns full learner PII (privacy violation) |
| D-044 | Cohort dashboard UI = **React route under `/operator/*`, served by the same FastAPI server (new prefix), reuses v0.2 StaticFiles** | REQ-DASH-01. Frontend-engineer reactivates (PERSONAS.md). Adds `/operator` React route + `/api/operator/*` FastAPI endpoints. Auth gate in React + server-side session check. No separate SPA build — same `client/dist`. | 0.75 | Separate operator SPA (extra build pipeline), server-rendered HTML (abandons React investment), no UI (operator reads JSON — not a product) |
| D-045 | Cohort aggregation trigger = **on-session-end hook + nightly reconciliation job** | REQ-MT-02. Hook fires after `end_session()` → writes k-anonymized aggregate to Postgres (incremental). Nightly job (cron in the praxis service) reconciles + recomputes 7-day windows. Hybrid: low-latency updates + correctness guarantee. | 0.70 | Pure real-time (race-prone), pure nightly (stale, violates NFR-DASH-02 if job lags), CDC/streaming (over-engineered) |
| D-046 | IRT θ persistence = **in learner-local SQLite** (`learner_ability` table: learner_id, path, theta, updated_at) | REQ-NFR-IRT-01. θ is per-learner-per-path, computed in-process on session end, no LLM call. Stays in SQLite with the rest of learner state (D-007 preserved). Cohort dashboard sees only k-anonymized aggregates of θ, never raw θ. | 0.80 | Postgres (couples learner state to operator tier — violates D-031 hybrid), in-memory (lost on restart), file-based JSON (no queryability) |
| D-047 | Scenario library minimum for v0.3 = **≥6 expert-authored Customer Service scenarios** (one per path week) + **AI-generated variations gated by expert review** | REQ-SCEN-03/04. 6 scenarios give the mastery gate's N=3 varied-scenario condition room (D-032) without being so few that mastery is gameable. AI variations: LLM generates a variation from an expert scenario's schema with `generated_from` backref; expert reviews + approves before it enters the library. | 0.70 | 3 scenarios (mastery gate N=3 = exactly the minimum — no room for failure-retry variety), 12 scenarios (over-scoped for one milestone), no AI variations (loses REQ-SCEN-04) |
| D-048 | Mastery gate open action = **advance learner to next path week + issue VC if week-final gate** | When D-032 condition met for a week's scenarios: learner `progress.current_week` advances. If the gate is the final week's gate, a VC is issued (REQ-MAST-03) asserting mastery of the path. Mid-path gates: no VC, just advancement. VCs are path-level, not week-level. | 0.75 | VC per week (credential spam — devalues the credential), no advancement (mastery gate is decorative), manual advancement (violates autonomy) |
| D-049 | v0.3 activation of D-009 failure-injection = **NO** — failure-injection stays architecturally present but not provoked in v0.3 | D-009 hook stays in the schema. v0.3 mastery scoring scores *recovery* from naturally-occurring failure branches (the `escalate` branch in cs_refund_ca_v01), not AI-provoked failures. Active failure injection couples to a "failure-recovery coaching" feature that's a later milestone. v0.3 RESEARCH confirms this — no new failure-injection scenarios authored. | 0.80 | Activate failure injection in v0.3 (couples mastery scoring to a new feature — scope creep), remove the hook (breaks forward compat) |
### Confidence updates from research
| ID | Before | After | Reason |
|----|--------|-------|--------|
| D-003 | 0.75 | **0.95** | Both Ollama model IDs verified in catalog as real, current, cloud-hosted tags |
| D-007 | 0.80 | **0.90** | SQLite confirmed appropriate for v0.1 single-learner scale; no evidence favors alternatives |
## Target Users (v0.3: Canada pilot — Customer Service path)
| Persona | Description | Pain |
|---------|-------------|------|
| Aspiring Adebayo → "Aspiring Alex" | 1928, Canada. Recent secondary school grad. Smartphone, limited data. Wants a service job. | Can't afford vocational school. Needs to actually do the job. |
| Upskilling Ursula → "Upskilling Uma" | 2540, Canada. Retail, hospitality, healthcare. Wants promotion/new role. | No time for courses. Learns on the job. |
| Frontline Felix | Customer service / sales / field tech agent, hired recently. | Manager has no time to coach. Wants quick on-shift practice. |
## Success Metrics (Year-1 targets, post-v0.1)
| Metric | Target | Why |
|--------|--------|-----|
| Active weekly learners | 100k | Engagement, not downloads |
| Sessions per learner / week | ≥5 | Habit formation |
| Mastery rate per path | ≥40% completion | Real learning |
| Median session length | 610 min | On-the-go use |
| Cost / active learner / month | ≤$3 | Sustainable |
| Reported job/promotion outcome | ≥25% | North star |
| NPS (learner) | ≥50 | Word-of-mouth growth |
## Open Questions (for research/clarify phases)
1. Will learners talk to their phone in public? (earbuds + "no one will know" framing)
2. How to certify mastery credibly? (employer/agency recognition)
3. Domain safety minimum HITL for health/electrical scenarios
4. Voice cloning / impersonation disclosure
5. Monetization model (deferred to Phase 1)
6. Skills that should remain out of scope
## References
- PRD v0.1 (this document's source)
- ARCHITECTURE.md — system architecture
- ROADMAP.md — phase breakdown
- REQUIREMENTS.md — formal requirements with REQ-IDs