Skip to content

Repository files navigation

RecallGraph

Trace every recalled component before it becomes a customer incident.

An autonomous recall blast-radius investigator for small aerospace and defence parts distributors, built end to end in Jac.

All company names, part numbers, certificates and recall bulletins in this repository are synthetic, created for this demo. No real company, part or recall is referenced.


The problem

Maria is the quality manager at a 15-person aerospace parts distributor.

A supplier bulletin lands in her inbox: part AB-442, lot A17, serials AB442-17001 through AB442-17999, manufactured in March 2026, may lose tensile strength above 180 °C. Quarantine unused inventory and notify customers.

Now she has to answer one question — who did we ship this to? — from purchase orders, shipment spreadsheets, inventory extracts, as-built assembly records and certificates that live in different files and different formats. Today that takes hours, and it fails in two expensive directions:

  • Miss a customer who received an affected part, and a recalled fastener stays in service.
  • Over-notify a customer who bought the same part number from a different lot, and you have burned a relationship and a week of everyone's time.

The hard part is not search. It is that the answer is a path: the recalled lot is three, five, sometimes eight relationships away from the customer, and one of those hops is often missing from the records entirely.

RecallGraph turns those records into a supply-chain graph and answers the question by traversing it — with the evidence path attached to every verdict.


Why this needs a graph

A part-number search is a WHERE clause. A recall investigation is a reachability query with three complications a table cannot express:

  1. The recalled unit is not always the thing you shipped. Customer B never ordered AB-442. They ordered a landing-gear bracket assembly that has an AB-442 fastener installed inside it. Finding them means walking lot → unit → installed component → assembly → shipment → customer — six hops, four of them across different source files.

  2. Absence of an edge is the finding. Customer D's assembly BOM says "AB-442 at fastener station 1" but the as-built record never captured which physical unit was installed. There is no References edge. That missing edge is precisely why they cannot be cleared — and it is invisible to a join.

  3. The reason has to travel with the answer. "Confirmed affected" is worthless to a quality manager without the chain that proves it. Because the walker is the traversal, the path it took is the audit trail, for free.


How Jac is central

This is not a Jac wrapper over Python logic. Remove Jac and there is no project left.

Layer Where it lives What Jac does
Supply-chain graph models/*.jac 23 node and 34 typed edge archetypes — including review/audit records — make the domain model the graph schema
Impact reasoning walkers/impact_analysis.jac Multi-hop visit traversal, node-typed abilities, walker inheritance
Replacement qualification walkers/replacements.jac Certificate → requirement traversal decides validity
Evidence & actions walkers/action_packet.jac Materialises Evidence / Action nodes back onto the graph
Agentic orchestration walkers/orchestrator.jac One walker drives eight specialist walkers, timing each stage
Bulletin extraction services/extraction.jac by llm() structured output with sem prompts + deterministic fallback
API services/api.jac def:pub endpoints; Jac generates the typed client RPC stubs
User interface components/*.cl.jac The React UI is also written in Jac and compiled to JS by jac2js

~6,900 lines of Jac plus CSS. No Python, no JavaScript, no TypeScript source files in the repository.

Jac features used deliberately, not incidentally:

  • Typed edges with declared endpoints (edge SentTo: Shipment --> Customer) so every traversal is statically typed end to end.
  • A shared base archetype (SupplyNode) giving walkers a catch-all entry type for generic passes such as workspace purge.
  • Walker inheritancerecall_scope is a lookup base that reads the recall criteria off the graph; the direct and assembly trace walkers subclass it and differ only in which hops they take.
  • Deferred exit abilities — ingestion materialises nodes on Workspace entry and wires the relational edges on Workspace exit, so linking is done by querying the graph it just built.
  • visit ... else get-or-create for the workspace node.
  • disengage to stop the orchestrator after exactly one workspace.
  • One shared type module compiled to two runtimesmodels/views.jac defines the wire contract for the Python server and the browser client, so renaming a field is a compile error on both sides at once.

Architecture

                 ┌───────────────────────────────────────────┐
  recall         │  services/extraction.jac                  │
  bulletin  ───► │  by llm()  ──fallback──►  parser          │──► validated
  (text)         │  + validate_criteria()                    │    RecallCriteria
                 └───────────────────────────────────────────┘         │
                                                                       ▼
  13 CSV files ─►┌──────────────────────────────────────────────────────────┐
                 │  walkers/ingestion.jac                                   │
                 │  materialise nodes  ──►  link edges by graph query       │
                 └──────────────────────────────────────────────────────────┘
                                             │  supply-chain graph
                                             ▼
                 ┌──────────────────────────────────────────────────────────┐
                 │  walkers/orchestrator.jac : run_full_investigation       │
                 │   ├─ find_directly_affected_shipments   (recall_scope)   │
                 │   ├─ find_indirectly_affected_shipments (recall_scope)   │
                 │   ├─ find_missing_evidence                               │
                 │   ├─ classify_customer_impact  ──► Evidence nodes        │
                 │   ├─ POSSIBLY_AFFECTED         ──► ReviewCase            │
                 │   ├─ find_valid_replacements                             │
                 │   └─ generate_action_packet    ──► Action nodes          │
                 └──────────────────────────────────────────────────────────┘
                                             │  InvestigationSummary
                                             ▼
                 services/api.jac  (def:pub)  ──►  components/*.cl.jac  (UI)

Full detail, including the graph schema and every walker's responsibility, is in ARCHITECTURE.md.


Setup

Requirements: Python 3.11+ and Bun (Bun builds the client bundle; jac install uses it).

# 1. install bun if you don't have it
curl -fsSL https://bun.sh/install | bash

# 2. install the Jac toolchain
python3 -m venv .venv
source .venv/bin/activate
pip install jaclang==0.16.7 jac-client byllm

# 3. install project dependencies (npm packages for the client bundle)
jac install

# 4. run it
jac start main.jac

Then open http://localhost:8000.

No database, no API key, no authentication, no cloud services. The graph lives in memory and persists to .jac/data between runs.

Commands

Command What it does
jac start main.jac Start the app (API + UI) on port 8000
jac start main.jac --port 8801 Start on a different port
jac start main.jac --dev Dev mode with hot reload for .cl.jac files
jac test tests/investigation_tests.jac -v Run the 32 core + review tests
jac check main.jac Type-check the project
jac start main.jac --no_client API only, no UI bundle

If a run ever behaves strangely after editing a node archetype, clear the persisted graph: rm -rf .jac/data.


Using it

  1. Open the dashboard.
  2. Load demo scenario — reads the bundled bulletin and all 13 CSV files, and shows the extracted recall criteria as a validated schema.
  3. Run investigation — the eight traversal stages report their real node counts and timings as they complete.
  4. Read the customer table: status, route, shipment, part, lot, evidence completeness, recommended action.
  5. Click any customer row for the evidence chain, the graph path, the units examined, missing data, and the replacement recommendation.
  6. Open the Human review queue, select the unresolved Northpoint case, and inspect its machine result, missing evidence, preserved graph path and history.
  7. Choose Confirm affected, Clear / not affected, or Needs more evidence, enter a rationale, and submit. The resulting audit event remains visible even when a terminal case leaves the pending queue.
  8. Generate action packet — produces the drafted customer notification.

You can also paste your own bulletin into the text area before running; the parser reads dot-leader labelled fields (Part Number ..... AB-442).


Demo data

13 synthetic CSV files in demo-data/, plus recall_bulletin.txt.

File Rows Contents
suppliers.csv 2 AeroBolt Manufacturing, Titanhold Fasteners
parts.csv 5 Part catalogue with temperature ratings
manufacturing_lots.csv 5 Lots A16, A17, A18, B04, C01
customers.csv 4 Four customers with quality contacts
purchase_orders.csv 5 Customer orders against parts
inventory.csv 11 Physical units with lot / serial / date traceability
shipments.csv 5 Outbound shipments
shipment_items.csv 8 Units and assemblies inside each shipment
assemblies.csv 2 ASM-88, ASM-91
assembly_components.csv 4 As-built BOM lines
requirements.csv 3 AS9100D, ≥200 °C, DFARS traceability
certificates.csv 8 Certificates held by parts and replacements
replacement_parts.csv 3 Replacement candidates

Ingested, this becomes 57 nodes and 102 typed edges.

Expected investigation results

Running the bundled scenario produces exactly this, every time:

Customer Status Route Why
Orbital Dynamics Aerospace (CUST-001) 🔴 CONFIRMED AFFECTED Direct Shipment SHP-2101 contained INV-101 and INV-102, both lot A17. Lot, serial and manufacture date all place them inside the recall.
Meridian Flight Systems (CUST-002) 🔴 CONFIRMED AFFECTED Via assembly Never ordered AB-442. Received assembly ASM-88 on SHP-2204, which has unit INV-104 (lot A17) installed at IC-301. Found only by walking the as-built BOM.
Northpoint Defense Integration (CUST-004) 🟠 NEEDS INVESTIGATION Direct + assembly Two independent gaps: INV-105 shipped with no lot, serial or date recorded; and IC-304 in assembly ASM-91 names AB-442 with no as-built unit reference. Cannot be confirmed or cleared. Evidence completeness 0/6.
Cascade Avionics Group (CUST-003) 🟢 SAFE Ordered only Did order AB-442 (PO-4495, PO-4523), which is exactly why a part-number search flags them. But every unit shipped (INV-103, INV-106, INV-110) traces to lots A16 and A18 — outside the recall on lot, serial and date. Evidence completeness 9/9.

Also produced:

  • Quarantine list — INV-108 and INV-111, both lot A17, still in stock at BIN-A-12, never shipped.
  • Replacement qualification — 1 of 3 candidates approved:
    • AB-450 — holds AS9100D and DFARS-7009, rated to 260 °C, approved, not itself recalled.
    • TH-771 — rejected: no certificate on file satisfies AS9100D (it holds ISO9001 and DFARS-7009). Its 320 °C rating is fine.
    • AB-448 — rejected: rated to 150 °C, below the required 200 °C, and it does not clear the bulletin's own 180 °C operating restriction.
  • Metrics — 4 analysed · 2 confirmed · 1 needs investigation · 1 safe · 1 valid replacement.

Classification rules

Deterministic, and made by graph traversal only. The LLM never classifies impact — it reads the bulletin, and may narrate evidence it was handed.

Each unit of the recalled part is tested against three independent signals — lot, serial range, manufacture date — and each returns INCLUDE, EXCLUDE or UNKNOWN (unknown when the field is simply not recorded):

  • INCLUDED — any signal says INCLUDE.
  • EXCLUDED — at least one signal says EXCLUDE and none is UNKNOWN.
  • UNKNOWN — otherwise. Missing evidence never silently clears a unit.

Rolled up per customer:

  • CONFIRMED_AFFECTED — the recalled part matches, lot/serial/date confirms inclusion, and a path exists to the customer directly or through an assembly.
  • POSSIBLY_AFFECTED — the part matches but required lot, serial or date evidence is missing, or an incomplete assembly relationship prevents confirmation.
  • SAFE — the part does not match, or every unit that reached the customer is definitively outside the recall criteria.

Auditable human review

The LLM is used where flexibility is useful, but consequential decisions require evidence. Missing evidence leads to explicit review rather than a confident automated clearance. Human decisions are auditable and do not erase the machine's original reasoning or evidence path.

Exact trigger

A review case is created only when the deterministic customer classifier emits POSSIBLY_AFFECTED: no unit has an INCLUDED verdict, at least one unit/path is UNKNOWN, and the records therefore cannot confirm or clear the customer. Confirmed and fully excluded (SAFE) cases do not enter the queue. A confirmed case may still have unrelated documentation gaps, but it does not need a human override to establish impact.

The review detail exposes the investigation and recall IDs, customer, affected part, original machine status, route, deterministic explanation/recommendation, evidence completeness, named missing records, first-class evidence records and the ordered graph path. No LLM explanation is generated for review.

States and actions

Current state Reviewer action Resulting state
PENDING_REVIEW Confirm affected CONFIRMED_AFFECTED_BY_REVIEW
PENDING_REVIEW Clear / not affected CLEARED_BY_REVIEW
PENDING_REVIEW Needs more evidence MORE_EVIDENCE_REQUIRED
MORE_EVIDENCE_REQUIRED Any of the three actions A terminal state, or another auditable evidence-request revision

CONFIRMED_AFFECTED_BY_REVIEW and CLEARED_BY_REVIEW are terminal. A second decision is rejected with INVALID_TRANSITION; it cannot silently overwrite the first. MORE_EVIDENCE_REQUIRED stays in the pending queue and may be reviewed again, appending another revision. Every action requires a non-blank rationale.

Persistence and audit history

The graph-native audit shape is:

root → ReviewLedger → ReviewCase → ReviewEvent (revision 1..n)
                              ├──→ preserved Evidence nodes
                              └──→ ordered ReviewPathSnapshot nodes

ReviewCase.original_machine_status is immutable in the workflow. Each ReviewEvent records the original machine status, prior and resulting review states, action, rationale, reviewer ID, timestamp, evidence references and path references. The current review state changes, but the machine result does not. The ledger is attached directly to root, outside the operational Workspace, so its evidence/path snapshot survives the next dataset purge and rebuild.

The typed public functions are:

Operation Jac endpoint
List pending (or include history) list_reviews(investigation_id, include_completed)
Retrieve review detail get_review_detail(review_id)
Submit validated decision submit_review_decision(review_id, reviewer_decision, reviewer_rationale, reviewer_id)
Retrieve append-only history get_review_history(review_id)

All are available as POST /function/<name> and use Jac's standard response envelope. Case IDs and decisions are resolved/validated server-side; a supplied status cannot mutate the machine result.

Current limitations

  • This is a local, minimal workflow: there is no authentication, RBAC, reviewer assignment/claim, concurrency lock, notification, or external ticketing.
  • Reviewer identity is a free-text value; blank input is stored as the explicit demo-reviewer placeholder.
  • “Needs more evidence” records the request and keeps the case open, but this demo does not upload documents or automatically re-run classification when evidence arrives.
  • The UI lists unresolved cases for the current investigation. Completed cases remain retrievable through include_completed=True and the history/detail endpoints, but there is no elaborate historical admin dashboard.
  • The included records are deterministic synthetic fixtures, not a human-subject study or a production human-review evaluation.

Replacement validation

A replacement is valid only when all five hold:

  1. a Replaces edge declares it compatible with the recalled part;
  2. every Requirement on the recalled part is satisfied — certifications via HasCertificate → Satisfies, temperature against the candidate's rating;
  3. it clears the operating restriction named in the bulletin itself;
  4. its approval status is APPROVED;
  5. it is not itself under an active recall.

Rejected candidates are returned with the specific rule they failed.


Optional: NVIDIA NIM / LLM extraction

The app is fully functional with no API key. To exercise the by llm() path:

cp .env.example .env
# then set ONE of:
#   NVIDIA_NIM_API_KEY=nvapi-...     (checked first)
#   OPENAI_API_KEY=sk-...
#   ANTHROPIC_API_KEY=sk-ant-...
#   GOOGLE_API_KEY=...
jac start main.jac

The "Extracted recall criteria" panel shows which provider produced the schema. The LLM result is still put through validate_criteria(); if it fails validation or the provider errors, RecallGraph falls back to the deterministic parser and says so in the warnings. The demo cannot fail because an LLM is unavailable.


Tests

jac test tests/investigation_tests.jac -v

32 tests, including the original traversal suite and nine review-workflow tests, covering:

  1. Direct affected shipment is found (and unshipped stock is quarantined)
  2. A recalled part inside a shipped assembly is found via the as-built BOM
  3. A safe lot is not misclassified, and the false-positive customer is cleared
  4. Missing lot data produces POSSIBLY_AFFECTED, with named gaps
  5. A valid replacement is recommended
  6. A replacement missing a certification is rejected — for that reason only
  7. A replacement below the temperature requirement is rejected
  8. Evidence paths contain exactly the expected node refs and kinds
  9. Full investigation returns the expected customer counts
  10. Two runs produce identical results (determinism)
  11. Action packets carry evidence, replacement and a drafted notice
  12. Bulletin extraction and schema validation, including rejection cases
  13. Incomplete evidence enters review while sufficient evidence does not
  14. Rationale validation and persistence of valid decisions
  15. Preservation of the original machine verdict, evidence, and graph path
  16. Explicit transition rejection and append-only audit timestamps/history
  17. Pending/history queue filtering and multi-revision evidence requests

Troubleshooting

Symptom Fix
Error: No jac.toml found Run jac start from the repository root.
Bun is required for client development Install Bun (curl -fsSL https://bun.sh/install | bash), then jac install.
Port 8000 already in use jac start main.jac --port 8801. Kill stale servers with pkill -f "jac start".
NodeAnchor ... is not a valid reference Stale persisted graph after a schema edit: rm -rf .jac/data and restart.
UI shows a blank page after an edit rm -rf .jac/client/compiled and restart so the client bundle is rebuilt.
Extraction says "No LLM API key configured" Expected and harmless — that is the deterministic parser reporting itself.
Tests behave oddly on re-run rm -rf .jac/data first; tests share a persisted root.

Repository layout

main.jac                     entry point: server imports + cl { app }
models/
  supply_chain.jac           operational supply-chain node/edge archetypes
  investigation.jac          Investigation / Evidence / Action nodes
                             plus ReviewLedger / ReviewCase / ReviewEvent snapshots
  views.jac                  wire types, compiled to BOTH server and client
walkers/
  ingestion.jac              ingest_recall, ingest_supply_chain_records, purge
  impact_analysis.jac        recall_scope base + direct/assembly traces,
                             classification, missing evidence, evidence path
  replacements.jac           find_valid_replacements
  action_packet.jac          generate_action_packet
  orchestrator.jac           run_full_investigation
  review.jac                 queue creation, transitions, review persistence/history
services/
  extraction.jac             by llm() + deterministic parser + validation
  dataset.jac                CSV loading and column validation
  graph_view.jac             server-side graph layout
  api.jac                    def:pub endpoints
  runtime_env.jac            offline-safe runtime defaults, .env loading
components/                  the UI, written in Jac (.cl.jac -> React)
  ReviewQueue.cl.jac         review detail, required-rationale form, audit history
demo-data/                   synthetic bulletin + 13 CSV files
tests/investigation_tests.jac
assets/global.css

See DEMO_SCRIPT.md for a four-minute presentation walkthrough.

About

Evidence-grounded recall tracing with LLM-assisted extraction, deterministic graph analysis, and auditable human review.

Topics

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages