design+state: Convex-Signal spike full design (40h/3-phase plan, 6 modular seams, Repository interface, auth seam decision) [Pi]

This commit is contained in:
Kisa 2026-06-24 13:47:57 -04:00
parent bd0009ea10
commit 04d204e965
3 changed files with 539 additions and 3 deletions

View file

@ -2,12 +2,13 @@ v:1
# current-state
ACTIVE: Claude SHIPPED + verified the Claude-side delegation trigger (caught + fixed Pi's $1->stdin bug and the Finding-4 dispatch-rule conflict; verified firing live). Diagnosed the NocoDB leads failure: the Signal base's data source is corrupted and creates PK-less tables (capture still works, delete/update don't). Ran a full Convex platform evaluation (Professional $25/dev/mo includes the BAA + HIPAA/SOC2 + daily backups, NOT Enterprise-gated; leads are PII-not-PHI so they stay outside the PHI boundary). Wrote the Convex-Signal spike brief and created a committed ship-status ledger (docs/ship-status-ledger.md).
ACTIVE: Pi designed the modular Convex-Signal spike from the brief (docs/convex-signal-spike-design-2026-06-24.md). Design covers module map with swappable seams, data-access interface (Repository portability hedge), Convex schema matching the readiness model, auth seam (Convex default, Clerk swappable), phased 40h spike plan (Phase 1 proves core readiness loop). Claude builds Phase 1 once repo exists.
NEXT (in order): (1) Kisa: dedup decision (same patient + device + DOS under two order numbers -> one worklist line or two?) -> unblocks Claude's readiness-model build. (2) Pi: design the modular Convex-Signal spike from docs/convex-signal-instance-brief-2026-06-24.md (separate repo signal-convex; swappable auth + data layer; reuse the readiness model). (3) Kisa: send the Convex BAA email + /pilot-prep labABLE Fri 2026-06-26 1:30 PM. (4) Claude: build the readiness model once dedup is decided. (5) Claude: research-versioning Layer-1 + add signal/docs/ to its scope (Audit Finding 5 — the reason design docs need manual commits). (6) After Pi's Convex design + Kisa approval: create signal-convex (Claude=GitHub, Forgejo needs Kisa/token), Claude builds the spike. FULL ship-status + reasons/dependencies: docs/ship-status-ledger.md.
NEXT (in order): (1) Kisa: dedup decision (same patient + device + DOS under two order numbers -> one worklist line or two?) -> unblocks Claude's readiness-model build. (2) Kisa: send the Convex BAA email + /pilot-prep labABLE Fri 2026-06-26 1:30 PM. (3) Claude: build the readiness model once dedup is decided from its brief (rename coverage->readiness, rules-to-config, patient/line/shipment grouping+verdict, device-keyed override, require-plan-type, frontend nesting). (4) Claude: create signal-convex repo (GitHub via `gh`, Forgejo needs Kisa's token) + build Phase 1 of the Convex-Signal spike from Pi's design (docs/convex-signal-spike-design-2026-06-24.md). (5) Claude: research-versioning Layer-1 + add signal/docs/ to its scope (Audit Finding 5 — the reason design docs need manual commits).
## Decisions (last 8)
- 2026-06-24 [Pi]: Convex-Signal spike full design persisted to docs/convex-signal-spike-design-2026-06-24.md. 12 sections covering module map with 6 swappable seams (auth, data, readiness, storage, automations, notifications), Repository interface for backend portability, Convex schema (8 tables matching the readiness model), auth seam decision (Convex first, Clerk swappable), phased 40h/5-day spike plan (Phase 1 proves core readiness loop on Convex). Claude builds Phase 1 from this.
- 2026-06-24 [CC]: SHIPPED the Claude-side delegation trigger — dispatch-trigger.sh reads stdin JSON (fixed Pi's $1 bug), settings.json UserPromptSubmit hook wired, dispatch SKILL.md thresholds added, Finding-4 clarifying line so old+new dispatch rules don't conflict. Verified live. Audit Findings 1/2/4 (this build) closed; 3/8 Pi-side, 5/6/7/9 research-versioning/phase-2 and non-blocking.
- 2026-06-24 [CC]: NocoDB leads diagnosed — Signal-base data source bdmhi02nks1pg6g is corrupted, creates PK-less tables (old leads + a fresh test table both no-PK; content base healthy). SQLite can't add a PK to a populated table (Route 1 failed). Capture still works (n8n webhook signal-cgm-lead inserts ok); 2 test rows un-deletable. Folded into the platform decision.
- 2026-06-24 [CC]: Convex evaluation (verified). Professional $25/dev/mo includes BAA + HIPAA/SOC2 reports + daily backups (NOT Enterprise). Leads = PII-not-PHI -> outside the PHI/Validated-Cloud boundary. AWS (BAA + RDS/S3 provisioned, parked) = proven fallback; Neon = cheaper managed Postgres. Decision: build a SEPARATE 'Signal on Convex' spike (repo signal-convex), modular + backend-swappable to limit lock-in. Brief: docs/convex-signal-instance-brief-2026-06-24.md.

View file

@ -0,0 +1,533 @@
# Convex-Signal Spike — Build Design
**Date:** 2026-06-24
**Design by:** Pi
**Builds by:** Claude
**Approves:** Kisa
**Source:** docs/convex-signal-instance-brief-2026-06-24.md
**Status:** Design input. This is a SEPARATE, parallel spike in repo `signal-convex`. The current Signal build keeps running.
---
## 1. What This Spike Proves
One question answered with a build, not a whiteboard:
> Can Convex's reactive platform serve as Signal's data + backend layer (at $25/mo with a BAA) without losing the readiness model's integrity, the auth flexibility we need, or the ability to walk away if Convex doesn't work out?
The spike proves three things:
1. **Core readiness loop** — import CSV → grade → display worklist — on Convex
2. **Auth seam works** — the spike runs on Convex auth; swapping to Clerk is a config change, not a rewrite
3. **Data-access interface holds** — the readiness engine doesn't call Convex directly; it calls an interface
---
## 2. Module Map (The Seams)
Six modular seams per the brief, but Koaned to ensure they're actually buildable, not over-granular.
```
convex/ ← Convex server-functions directory
├── schema.ts ← Convex schema (tables + indexes)
├── auth/
│ ├── AuthProvider.ts ← Interface: login, getUser, hasPermission
│ └── convexAuth.ts ← Default: Convex auth
│ └── clerkAuth.ts ← Seam: Clerk JWT validation (future)
├── data/ ← DATA-ACCESS LAYER (the keystone)
│ ├── Repository.ts ← Generic interface: get, list, create, update, delete
│ ├── PatientRepository.ts ← Patient + CoverageLine + Shipment queries
│ ├── DocItemRepository.ts ← DocItem CRUD
│ ├── OverrideRepository.ts ← Override CRUD
│ └── README.md ← How to swap the backend
├── readiness/ ← PORTABLE READINESS ENGINE
│ ├── engine.ts ← gradeLine(), rollupPatient(), computeVerdict()
│ ├── rules.ts ← load payer_rules.json, resolve rule by ID
│ ├── citations.ts ← build CascadeEntry records
│ ├── overrides.ts ← apply override, recompute, re-rollup
│ ├── payer_rules.json ← COPIED from signal/python-backend/config/
│ └── README.md ← This is portable; review before every copy
├── storage/
│ ├── FileStorage.ts ← Interface: upload, getUrl, delete
│ └── convexStorage.ts ← Default: Convex file storage
│ └── s3Storage.ts ← Seam: S3 (future)
├── automations/
│ ├── ScheduledChecks.ts ← Cron jobs: resupply reminders, expiry alerts
│ └── crons.ts ← Convex cron definitions
└── notifications/
├── NotificationService.ts ← Interface: sendAlert, notifyWorklistChange
└── navBotNotify.ts ← Default: NavBot pattern
└── emailNotify.ts ← Seam: SES/SendGrid (future)
src/ ← React app
├── App.tsx ← Auth provider + routing
├── convexClient.ts ← Convex client config
├── components/
│ ├── worklist/
│ │ ├── WorklistTable.tsx ← Reimagined on Convex reactive queries
│ │ └── PatientDetail.tsx ← Nested patient → device → doc view
│ ├── import/
│ │ └── CSVImport.tsx ← CSV upload → Convex mutation
│ └── auth/
│ └── AuthGate.tsx ← Auth seam adapter
└── lib/
└── readinessQueries.ts ← Convex-specific reactive query hooks
```
### Analogy
Think of it like a **modular synthesizer**:
- **Convex** = the powered Eurorack case (provides power, bus, physical space)
- **Readiness engine** = the oscillator module (does the core work, same module in any case)
- **Auth** = the MIDI interface (different models, same CV/gate output)
- **Data layer** = the patch cables (the interface is the jack standard; swap the module behind it)
- **Storage/notifications/automations** = utility modules (envelope generators, mixers)
- **`convexClient.ts` + reactive queries** = the speakers (Convex-specific output; swap the whole signal chain)
The readiness engine (oscillator) is the valuable module. Everything else exists to make it produce sound. Designing for swappable patch cables means you can move the oscillator to a different case without rewiring the whole rig.
---
## 3. Data-Access Interface (The Lock-In Hedge)
This is the real design tension. Convex's core value is **reactivity**`useQuery()` auto-re-fetches when data changes. A naive DAO interface that returns plain data loses that.
**Compromise:** Two-layer design.
### Layer A — Convex-specific reactive queries (UI-facing)
These ARE Convex queries — they subscribe to data, auto-update, and ARE NOT portable. This is the Cost Of Doing Business with Convex.
```typescript
// convex/readiness/queries.ts — Convex-specific, reactive
export const getWorklist = query({
args: { orgId: v.id("organizations") },
handler: async (ctx, args) => {
const patients = await ctx.db
.query("patients")
.withIndex("by_org", (q) => q.eq("orgId", args.orgId))
.collect();
const readinessEngine = new ReadinessEngine(ctx); // ctx.db → repository
return patients.map((p) => readinessEngine.rollupPatient(p));
},
});
```
### Layer B — Repository interface (readiness-engine-facing)
The readiness engine never calls `ctx.db` directly. It calls a repository interface, which is implemented by a ConcreteRepository that wraps `ctx.db`. If the backend changes, implement a PostgresRepository that implements the same interface.
```typescript
// convex/data/Repository.ts — portable interface
export interface Repository {
getPatient(patientId: Id<"patients">): Promise<Patient | null>;
listPatientsByOrg(orgId: Id<"organizations">): Promise<Patient[]>;
getCoverageLines(patientId: Id<"patients">): Promise<CoverageLine[]>;
createCoverageLine(line: NewCoverageLine): Promise<Id<"coverageLines">>;
updateCoverageLine(id: Id<"coverageLines">, fields: Partial<CoverageLine>): Promise<void>;
listDocItems(lineId: Id<"coverageLines">): Promise<DocItem[]>;
upsertDocItem(item: NewDocItem): Promise<Id<"docItems">>;
getOverride(patientHash: string, deviceType: string, docType: string): Promise<Override | null>;
setOverride(override: NewOverride): Promise<void>;
// ... etc
}
```
```typescript
// convex/data/convexRepository.ts — Convex-specific implementation
export class ConvexRepository implements Repository {
constructor(private ctx: QueryCtx | MutationCtx) {}
async getPatient(patientId: Id<"patients">) {
return this.ctx.db.get(patientId);
}
async listPatientsByOrg(orgId: Id<"organizations">) {
return this.ctx.db
.query("patients")
.withIndex("by_org", (q) => q.eq("orgId", orgId))
.collect();
}
// ...
}
```
The readiness engine:
```typescript
// convex/readiness/engine.ts — portable, takes a Repository
export class ReadinessEngine {
constructor(private repo: Repository) {}
async gradeLine(patientId: Id<"patients">, deviceType: string, planId: string) {
const lines = await this.repo.getCoverageLines(patientId);
// ... grading logic, calls this.repo for all data access
}
}
```
**What portability costs:** The reactive queries (`getWorklist`, `getPatientDetail`) ARE Convex-specific and would be rewritten if we move off Convex. The grading logic in `engine.ts` is fully portable. The cost of swappability is roughly 20% more code (the interface + implementation split) in exchange for 100% portable business logic. Worth it for a data-critical compliance engine.
**BYOD Postgres note:** Convex supports connecting an external Postgres database (bring-your-own-database). If we eventually want to own the data layer separately from Convex's runtime, this preserves that path without changing the repository interface at all — just swap which database the interface talks to.
---
## 4. Convex Schema + Readiness Model
### Schema
```typescript
// convex/schema.ts
import { defineSchema, defineTable } from "convex/server";
import { v } from "convex/values";
export default defineSchema({
organizations: defineTable({
name: v.string(),
clerkOrgId: v.optional(v.string()), // seam: Clerk org ID, null for Convex auth
createdAt: v.number(),
}),
patients: defineTable({
orgId: v.id("organizations"),
patientId: v.string(), // client's UUID
status: v.string(), // rolled-up status (derived)
}).index("by_org", ["orgId"]),
coverageLines: defineTable({
patientId: v.id("patients"),
deviceType: v.string(), // "dexcom_g7"
planType: v.string(), // REQUIRED: medicare | medicare_advantage | medicaid | commercial | unknown
planId: v.string(), // plan identifier from CSV mapping
gradeable: v.boolean(), // true only for CGM this phase
lineStatus: v.optional(v.string()), // derived
timingFlag: v.optional(v.string()),
}).index("by_patient", ["patientId"]),
shipments: defineTable({
lineId: v.id("coverageLines"),
dateOfService: v.string(), // YYYY-MM-DD
quantity: v.number(),
components: v.array(v.string()),
orderNumber: v.string(),
hcpcs: v.string(),
isCurrent: v.boolean(),
}).index("by_line", ["lineId"])
.index("by_line_dos", ["lineId", "dateOfService"]),
docItems: defineTable({
lineId: v.id("coverageLines"),
docType: v.string(), // swo | visit | pecos | pa | diagnosis
requiredState: v.string(), // REQUIRED | NOT_REQUIRED | NOT_EVALUATED
inputState: v.string(), // SUPPLIED | ABSENT
value: v.optional(v.string()),
source: v.string(), // csv | staff_override | computed
citation: v.optional(v.string()),
}).index("by_line", ["lineId"]),
overrides: defineTable({
orgId: v.id("organizations"),
patientId: v.id("patients"),
deviceType: v.string(),
docType: v.string(),
status: v.string(),
statusDate: v.optional(v.number()),
expiryDate: v.optional(v.number()),
confirmedBy: v.optional(v.string()),
confirmedAt: v.optional(v.number()),
reason: v.optional(v.string()),
}).index("by_override_key", ["orgId", "patientId", "deviceType", "docType"]),
cascadeEntries: defineTable({
patientId: v.id("patients"),
lineId: v.optional(v.id("coverageLines")),
docType: v.string(),
severity: v.string(), // blocker | alert
reason: v.string(),
ruleId: v.string(), // points to payer_rules.json key
clientInput: v.string(),
requirement: v.string(),
basis: v.string(),
rulesVersion: v.string(),
createdAt: v.number(),
}).index("by_patient", ["patientId"]),
uploads: defineTable({
orgId: v.id("organizations"),
fileName: v.string(),
uploadedAt: v.number(),
recordCount: v.number(),
status: v.string(), // processing | done | error
storageId: v.optional(v.id("_storage")),
}).index("by_org", ["orgId"]),
});
```
### Function Structure
```
convex/
├── auth.ts ← Auth helpers (login, register, session)
├── crons.ts ← Cron job definitions
├── patient/
│ ├── queries.ts ← getWorklist, getPatientDetail
│ └── mutations.ts ← importCSV, deduplicate
├── coverageLine/
│ ├── queries.ts ← getLinesByPatient
│ └── mutations.ts ← updatePlanType, recomputeLine
├── docItem/
│ ├── queries.ts ← getDocItemsByLine
│ └── mutations.ts ← setDocItemOverride
└── readiness/
├── queries.ts ← gradeLine (read-only grade), rollupPatient
└── mutations.ts ← recomputeAll, applyOverride
```
---
## 5. Auth Seam
### Recommended Default: Convex auth for the spike
Rationale:
- **One less moving part.** The spike is proving Convex on Signal. Adding Clerk adds auth complexity without proving anything new.
- **Convex auth supports the same flows:** email/password, Google OAuth, magic link.
- **The seam stays clean** — the spike's components call `AuthProvider.getUser()` and `AuthProvider.hasPermission(orgId)`. The default implementation uses Convex auth. A Clerk implementation would validate Clerk JWT tokens.
- **Swappable later.** When/if the spike graduates, the auth backend can change without touching the readiness engine, worklist display, or any business logic.
### Seam interface
```typescript
// convex/auth/AuthProvider.ts
export interface AuthProvider {
getUserId(ctx: AuthCtx): Promise<string | null>;
getOrgId(ctx: AuthCtx): Promise<string | null>;
hasPermission(ctx: AuthCtx, orgId: string, permission: string): Promise<boolean>;
}
```
### Migration path to Clerk
1. Clerk issues JWT tokens → Convex validates Clerk's JWKS endpoint (already at `clerk.sttilsolutions.com/.well-known/jwks.json`)
2. Convex's `auth` middleware checks the Clerk JWT and extracts user + org
3. `AuthProvider.getUserId()` reads from Clerk's session, not Convex's
4. UI gate swaps from `<ConvexAuthGate>` to `<ClerkAuthGate>` — same props, same behavior
---
## 6. Readiness Model Reuse Strategy
### Copy, not share (for the spike)
The readiness engine + `payer_rules.json` are **copied** into `signal-convex/convex/readiness/`. Rationale:
- **Isolation.** The spike is parallel. A shared package creates coupling between a production app and an experiment.
- **Speed.** Copying is faster than extracting a package. The spike needs to ship fast.
- **The readiness model is stable.** It was locked on 2026-06-23 and won't change during the spike.
### If both instances graduate
Extract a shared npm package (`@sttil/readiness-engine`) that both repos import. The engine is already designed to be portable (Repository interface, no I/O dependency) — this extraction is a mechanical step, not a redesign.
### What changes for Convex
The _logic_ is identical. What changes is how it's called:
- **Python → TypeScript.** The engine is rewritten from Python to TypeScript (Convex is TS-native). The logic is the same.
- **Convex types for IDs.** Instead of string patient_ids, Convex uses typed `Id<"patients">`.
- **Repository implementation.** Instead of Supabase SQL queries, the ConvexRepository uses `ctx.db.get/query/patch/insert/delete`.
- **No SQL migrations.** Convex schema changes are instant (no explicit migrations).
---
## 7. Repo Structure + Workflow Integration
### Repository
```
signal-convex/ ← NEW repo, separate from signal/
├── convex/ ← Convex server-functions directory
│ ├── schema.ts
│ ├── auth.ts
│ ├── crons.ts
│ ├── data/
│ ├── readiness/
│ ├── storage/
│ ├── patient/
│ ├── coverageLine/
│ ├── docItem/
│ └── notifications/
├── src/ ← React app (Vite + TypeScript)
│ ├── main.tsx
│ ├── convexClient.ts
│ ├── App.tsx
│ └── components/
├── package.json
├── convex.json ← Convex project config
├── tsconfig.json
├── vite.config.ts
├── CLAUDE.md ← Project instructions (like signal/CLAUDE.md)
├── context/
│ └── current-state.md ← Optional: state-sync for the spike
├── docs/
│ └── convex-signal-design-2026-06-24.md ← This doc
├── .github/
│ └── workflows/ ← CI: typecheck, lint, test on push
│ └── ci.yml
└── README.md
```
### Git workflow
- **Source of truth:** Forgejo (`git.forgejo.sttilsolutions.com:sttil/signal-convex`)
- **Mirror:** GitHub (via `git-push` skill convention)
- **Branch strategy:** `main` is the active spike. Feature branches for each phase.
- **Commit discipline:** research-versioning patterns apply. The readiness engine and payer_rules.json have an explicit COPY-FROM marker in commit messages.
### Operating model
- **Pi designs → Claude builds** (same as the current workflow)
- **state-sync** applies: `signal-convex/context/current-state.md` for spike state
- **ship-status-ledger** extended: add spike items alongside current-build items
- **research-versioning** skill: covers the new repo once the Stop hook is added
---
## 8. Compliance Touchpoints + BAA Gate
### PHI boundary (copied from the existing model, same rules)
| Data | Status | Reason |
|------|--------|--------|
| patient_id (client UUID) | PII-level identifier | Same as current build — hashed in logs, only crosswalk key |
| Patient names, SSNs, DOBs, contacts | NEVER INGESTED | Architecture contract, same as current build |
| Device type, DOS, quantity | Non-PHI | Standard supply chain data |
| Payer name, plan type | Non-PHI | Payer policy data |
### Gate: BAA required BEFORE real PHI
The spike currently runs on mock/synthetic data. Before ANY real patient data touches it:
1. **Convex BAA must be signed** by Kisa (email drafted, costs known: $25/mo Professional)
2. **sttil-compliance-check** gate: run the compliance check on the Convex setup
3. **Attorney review** of the Convex BAA terms vs AWS BAA terms
4. **Data-handling doc update:** add Convex to the compliance docs in `signal/docs/compliance/`
### Built-in audit trail
Convex's Professional plan includes an audit log. No extra build needed for basic audit. The cascade entries (citations table) serve as the business-level audit trail.
---
## 9. Phased Spike Plan (Timeboxed)
Total estimate: **~40 engineer-hours** (Claude with Kisa review). Timeboxed to **5 calendar days** of focused work — can be parallelized with the readiness-model build on the current Signal repo.
### Phase 1 — Core Readiness Loop (prove the thesis, ~20h)
**What ships:** CSV in → grade → worklist display. Running on Convex auth.
| Step | What | Hours |
|------|------|-------|
| 1 | Create `signal-convex` repo, scaffold Convex + Vite project | 1 |
| 2 | Define schema (organizations, patients, coverageLines, shipments, docItems) | 2 |
| 3 | Implement Repository interface + ConvexRepository | 3 |
| 4 | Port readiness engine from Python to TypeScript (module 1: gradeLine + rollupPatient) | 6 |
| 5 | Copy `payer_rules.json`, build rules loader | 1 |
| 6 | Implement CSV upload mutation (parse → store → grade → cascade) | 3 |
| 7 | Build worklist display with reactive queries (patient list + status) | 3 |
| 8 | Implement auth: Convex auth gate, org-aware queries | 1 |
**Gate:** Kisa reviews Phase 1 before Phase 2 starts.
### Phase 2 — Auth Seam + Device-Keyed Override (~12h)
**What ships:** Override + recompute + re-rollup. Auth seam proven swappable.
| Step | What | Hours |
|------|------|-------|
| 1 | Implement Override schema + repository | 1 |
| 2 | Add override mutation + recomputeLine + re-rollupPatient | 3 |
| 3 | Build override UI in patient detail view | 2 |
| 4 | Implement AuthProvider interface + ClerkAuthProvider stub | 2 |
| 5 | Write tests: gradeLine with known inputs, override+recompute, dedup | 3 |
| 6 | Verification: swap to ClerkAuthProvider in config, confirm everything still works | 1 |
### Phase 3 — Hardening + Remaining Seams (~8h)
**What ships:** File storage (Convex), automations (cron reminders), notifications (NavBot), documentation.
| Step | What | Hours |
|------|------|-------|
| 1 | File storage seam: upload documents, attach to patients | 2 |
| 2 | Automation: schedule resupply-reminder checks | 2 |
| 3 | Notification: NavBot-compatible alert on new-lead / override-action-needed | 2 |
| 4 | README, CLAUDE.md, compliance notes, spike retrospective | 2 |
---
## 10. What Claude Builds First
### Claude builds Phase 1, Steps 1-8, in order
The priority is proving the core thesis: CSV-in → grade → worklist-display on Convex. Nothing else matters if this doesn't work.
**Explicitly NOT in Phase 1:**
- File storage (Phase 3)
- Automations (Phase 3)
- Notifications (Phase 3)
- Override + recompute (Phase 2 — need the core loop first to verify baseline correctness)
- Clerk auth (Phase 2 — prove the spike on Convex auth first, swap later)
- Export, multi-device display, or non-CGM grading
### Before Phase 1 starts
1. Kisa: send the Convex BAA email (item 8 in ship-status ledger)
2. Claude + Kisa: verify Convex Professional plan includes the BAA (already verified 2026-06-24 — reconfirm at sign-up)
3. Create `signal-convex` repo on Forgejo + GitHub
---
## 11. Open Questions Resolved
### Convex auth vs Clerk for the spike
**Decision:** Convex auth as the default. The auth seam means Clerk is a config swap, not a rewrite. Reduces spike complexity without compromising future flexibility.
### Dedup key (carried from readiness-model decision)
**Pending Kisa's decision** — same patient + device + DOS under two order numbers → one worklist line or two? The spike design supports both. The `shipments` table has `orderNumber` as a displayed field (not a dedup key), and the dedup logic lives in `readiness/engine.ts`. When Kisa decides, one line changes in the engine.
### Data-access interface boundary
**Decision:** Repository interface between the readiness engine and the database. The reactive query layer (Convex-specific `convex/patient/queries.ts`) is the cost of Convex and is NOT portable. The tradeoff is documented in Section 3.
---
## 12. Effort Estimate Summary
| Phase | Hours | Builds | Key Risk |
|-------|-------|--------|----------|
| Phase 1 | ~20h | Claude | Porting readiness logic from Python to TS |
| Phase 2 | ~12h | Claude | Override + recompute complexity |
| Phase 3 | ~8h | Claude | Notification integration |
| **Total** | **~40h** | Claude | Timeboxed to 5 calendar days |
**Key risk:** The readiness engine rewrite from Python to TypeScript. The logic is stable and documented (readiness-model-brief-2026-06-23.md), but porting requires attention to edge cases (enum mapping, date handling, the dedup decision). Mitigation: code-auditor review after each Phase-1 step.

View file

@ -15,7 +15,7 @@ Canonical, git-tracked record of **what shipped, what didn't, and the reason or
| 3 | Audit Finding 2 — `$1` bug → read stdin JSON | Claude | **SHIPPED** | caught + fixed during build | done |
| 4 | Audit Finding 4 — old+new dispatch rules conflict | Claude | **SHIPPED** | added clarifying line (kept judgment fallback, marker = evaluate not auto-dispatch) | done |
| 5 | Convex-Signal spike **brief** | Claude | **SHIPPED** (committed this wrap) | — | Pi designs from it |
| 6 | Convex-Signal spike **design** | Pi | **NOT STARTED** | Dependency: brief (item 5, ready). Safe to start now. | hand brief to Pi |
| 6 | Convex-Signal spike **design** | Pi | **SHIPPED** | docs/convex-signal-spike-design-2026-06-24.md. 40h/3-phase plan; Module map + Repository interface + Convex schema + Auth seam (Convex default, Clerk swappable) + Phase 1 (core readiness loop) = what Claude builds first. | Claude builds Phase 1 from it |
| 7 | `signal-convex` repo creation | Claude (GitHub) + Kisa/token (Forgejo) | **PENDING** | Dependency: Pi design + Kisa approval (create once, correctly). Claude can do GitHub via `gh`; Forgejo has no API token here. | after design approved |
| 8 | Convex BAA email (plan/cost/signing) | Kisa | **IN PROGRESS** | Kisa handling | Kisa sends |
| 9 | Readiness-model build (rename coverage→readiness, rules-to-config, grouping+verdict, device override, require-plan-type, frontend nesting) | Claude | **BLOCKED** | Dependency: Kisa's dedup decision (same patient + device + DOS under two order numbers → one worklist line or two?). Design ready: `docs/readiness-model-brief-2026-06-23.md` | Kisa decides → Claude builds |
@ -42,10 +42,12 @@ Canonical, git-tracked record of **what shipped, what didn't, and the reason or
5. **Fixed date:** labABLE `/pilot-prep` Fri 2026-06-26 1:30 PM.
## Dependencies summary
- Readiness build ⟸ Kisa dedup decision.
- Convex spike build ⟸ Pi design ⟸ committed brief (ready) ; and ⟸ Kisa approval + repo creation.
- NocoDB/leads fix ⟸ data-platform decision ⟸ Convex spike evidence + BAA terms.
- Design-doc durability ⟸ `signal/docs/` auto-versioning (until built, commit docs manually).
## Maintenance
Update this whenever an item ships or a blocker changes. Move shipped items to a `## Shipped (archive)` section when the table grows. Both agents reason from this; Kisa validates and redirects.