Architecture

Analysis Date: 2026-05-05

System Overview

β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    Data Layer (JSON Files)                    β”‚
β”‚  `frames/*.crumbframe.json`  β”‚  `partners/partners.json`     β”‚
β”‚  `schema/crumbframe.v1.json` β”‚  `.doktor_results.json`       β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”΄β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚                          β”‚
               β–Ό                          β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”     β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚  Validation Layer    β”‚     β”‚      Web Renderer Layer         β”‚
β”‚  `scripts/doktor.sh` β”‚     β”‚  `web/index.html`              β”‚
β”‚  (Bash + jq +        β”‚     β”‚  `web/renderer/exploded2d.js`  β”‚
β”‚   python3/jsonschema)β”‚     β”‚  `web/renderer/connections.js` β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”¬β”€β”€β”€β”€β”€β”€β”€β”˜     β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
               β”‚
               β–Ό
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                    External APIs                              β”‚
β”‚  github.com/betaflight  β”‚  github.com/ExpressLRS             β”‚
β”‚  shop.rc-hangar15.de                                         β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜

Component Responsibilities

Component Responsibility File
CrumbFrame Schema Defines valid structure for all frame files, enforces Passkante schema/crumbframe.v1.json
Frame Data File Single drone build: components, connections, agent checks, partners frames/whoop_1s_65mm.crumbframe.json
Partner Database Global partner registry (shops, tools, knowledge, community) partners/partners.json
Doktor Agent Validates frames, runs agent checks, writes results scripts/doktor.sh
Exploded2D Renderer Draws SVG component map from frame position_2d data web/renderer/exploded2d.js
Connections Renderer Draws typed bezier curves between components on SVG web/renderer/connections.js
Web Entry Point Fetches frame JSON, orchestrates renderer classes web/index.html
Agent Results Cache Persists last check status and result per agent check .doktor_results.json

Pattern Overview

Overall: Data-centric, schema-first, no-framework

Key Characteristics:
- All domain knowledge lives in JSON files β€” code is thin orchestration over data
- Schema (crumbframe.v1.json) is the single source of truth for what a valid frame is
- Validation and rendering are independent consumers of the same frame JSON
- No build step, no package manager, no server β€” intentionally minimal
- Agent checks are declared in the frame file itself (self-describing data)

Layers

Data Layer:
- Purpose: Stores all drone knowledge as structured JSON
- Location: frames/, schema/, partners/, .doktor_results.json
- Contains: Frame builds, JSON Schema definitions, partner records, check results
- Depends on: Nothing (pure files)
- Used by: Doktor validation layer, web renderer layer

Validation Layer:
- Purpose: Check frame correctness, run agent checks against live external sources
- Location: scripts/doktor.sh
- Contains: Bash orchestration of jq, python3/jsonschema, curl calls
- Depends on: jq (CLI), python3 + jsonschema (pip), curl, frame JSON files
- Used by: Developers and contributors (manual pre-commit), future CI

Web Renderer Layer:
- Purpose: Visualize a frame's component layout and connections as interactive SVG
- Location: web/
- Contains: HTML shell, ES Module renderer classes, CSS
- Depends on: Browser fetch API, frame JSON (loaded via relative path)
- Used by: End users viewing drone exploded diagrams in a browser

Data Flow

Validation Path (doktor.sh)

  1. Receive frame file path as argument (scripts/doktor.sh frames/whoop_1s_65mm.crumbframe.json)
  2. Validate JSON syntax with jq empty
  3. Check use_context[] against forbidden list (military, surveillance, weaponized, dual_use) β€” Passkante gate
  4. Verify passkante: true field present
  5. Log meta.id, meta.class, meta.version, meta.contributors
  6. Verify all connections[].from and connections[].to keys exist in components
  7. For each entry in agent_checks: execute bash field via eval, store result
  8. Run python3 -c jsonschema.validate(...) against schema/crumbframe.v1.json
  9. Write results to .doktor_results.json (JSON: {frame, checked_at, checks})
  10. Print summary: green / yellow / red

Web Renderer Path

  1. Browser loads web/index.html
  2. Inline <script type="module"> fetches ../frames/whoop_1s_65mm.crumbframe.json via fetch()
  3. Constructs Exploded2D(container, frameData) β€” resolves $ref component references, calculates SVG bounds from position_2d
  4. renderer.initSVG() β€” creates <svg> element with computed viewBox
  5. Connections(renderer.svg, frameData).draw() β€” draws bezier <path> elements per connection, styled by type CSS class
  6. renderer.drawComponents() β€” draws <g> nodes (circles for motor/prop, rects for others), binds mouseenter for inspector panel

State Management:
- No application state beyond the DOM. Inspector panel updated via direct innerHTML writes on mouseenter.

Key Abstractions

CrumbFrame (JSON object):
- Purpose: Complete description of a drone build β€” hardware, spatial layout, connections, validation rules
- Examples: frames/whoop_1s_65mm.crumbframe.json
- Pattern: Self-describing JSON document with embedded agent checks and partner refs

Component Node (within components object):
- Purpose: Single hardware part with type, specs, 2D/3D position, and traceability (partner, contributor, donated_by)
- Pattern: Keyed object map; supports $ref to another component key for spec inheritance

Agent Check (within agent_checks object):
- Purpose: Declared automation task β€” firmware version watch, product availability, schema validation
- Pattern: Self-describing check with type, interval, action, optional bash command, last_checked/last_result fields

Connection (within connections array):
- Purpose: Typed directed edge between two component keys
- Pattern: {from, to, type} where type is one of power | signal | control | mechanical | video | data

Passkante:
- Purpose: Hard ethical constraint β€” zero dual-use/military/surveillance contexts permitted
- Enforcement: JSON Schema not.contains rule in use_context, explicit bash check in doktor.sh, passkante: true boolean field

Entry Points

Doktor CLI:
- Location: scripts/doktor.sh
- Triggers: Manual call bash scripts/doktor.sh frames/DATEI.crumbframe.json; intended as pre-commit hook
- Responsibilities: Full validation pipeline β€” syntax, Passkante, component integrity, agent checks, schema

Web App:
- Location: web/index.html
- Triggers: Browser open (requires local web server or file:// with CORS disabled)
- Responsibilities: Load hardcoded frame whoop_1s_65mm.crumbframe.json, render interactive SVG diagram

Architectural Constraints

  • No server: All logic runs either in the browser (renderer) or on developer's local shell (doktor). No backend.
  • Hardcoded frame path: web/index.html fetches ../frames/whoop_1s_65mm.crumbframe.json β€” only one frame displayable without code change.
  • Global state: None. Each doktor.sh run is stateless except for the .doktor_results.json output file. Web renderer holds state only in ES class instance variables.
  • $ref is custom, not JSON-Pointer: Component $ref values (e.g., "$ref": "prop_fl") are resolved by Exploded2D constructor using object key lookup β€” not standard JSON Schema $ref.
  • agent_checks bash field uses eval: scripts/doktor.sh line 88 runs eval "$BASH" β€” arbitrary shell execution of whatever is in the frame's bash field. Frame files must be trusted.
  • No CI enforcement: Passkante and schema checks are only enforced if doktor.sh is run manually. No GitHub Actions workflow exists.

Anti-Patterns

Hardcoded frame in web renderer

What happens: web/index.html fetches '../frames/whoop_1s_65mm.crumbframe.json' directly as a string literal.
Why it's wrong: Adding a second frame requires editing index.html β€” renderer cannot display arbitrary frames.
Do this instead: Accept a ?frame= URL query parameter or build a frame picker UI that lists frames/.

eval in agent check runner

What happens: scripts/doktor.sh runs eval "$BASH" on the bash field from the frame JSON.
Why it's wrong: Any contributor-submitted frame with a malicious bash field executes arbitrary shell commands on the validator's machine.
Do this instead: Whitelist allowed command patterns or run checks in a sandboxed subprocess with restricted PATH.

$ref is non-standard

What happens: Component spec inheritance uses "$ref": "motor_fl" as a sibling component key (e.g., motor_fr.$ref = "motor_fl"), resolved only in Exploded2D.js.
Why it's wrong: doktor.sh and the JSON Schema validator do not resolve these refs β€” schema validation treats $ref-only components as valid by passing additionalProperties on the parent object. Consistency is fragile.
Do this instead: Use a dedicated spec_ref field name (not $ref) to avoid confusion with JSON Schema semantics.

Error Handling

Strategy: Bash set -euo pipefail β€” hard exit on unhandled errors in doktor.sh. Errors in agent checks are caught and logged to .doktor_results.json as status: "error" without aborting.

Patterns:
- JSON syntax error in frame β†’ jq empty fails β†’ exit 1 immediately
- Passkante violation β†’ ERRORS counter incremented β†’ non-zero exit at end
- Missing jq β†’ exit 1 with install hint before any processing
- Missing python3 or jsonschema β†’ warning only, validation step skipped
- Agent check eval failure β†’ status: "error" in results, WARNINGS incremented, continues
- Web renderer fetch error β†’ caught in try/catch, error message injected into #frame-meta element

Cross-Cutting Concerns

Logging: doktor.sh uses colored ANSI output (ok/fail/info/warn helpers). Results persisted to .doktor_results.json.
Validation: JSON Schema draft-07 (schema/crumbframe.v1.json) is the authoritative validator. Bash pre-checks in doktor.sh supplement it for Passkante.
Passkante / Ethics: Enforced at schema level (JSON Schema not.contains on use_context), at script level (bash loop in doktor.sh), and at data level (passkante: true required field, forbidden_contexts array in frame files).


Architecture analysis: 2026-05-05