v0.1 wave 1 (steps 2+3+4): SQLite ledger + FastAPI skeleton + async job runner

- db.py: migrations + DAOs for tokens / projects / jobs / findings (SQLite WAL)
- auth.py: SHA-256 bearer hashing + LAN-CIDR allowlist + admin/app token tiers
- models.py: Pydantic shapes (Project, Subproject, Schedule, Notify, Job, CreateJobRequest)
- server.py: FastAPI on port 8810; /healthz, /admin/tokens/*, /projects/*, /jobs, /jobs/{id}, /jobs/{id}/log, /jobs/{id}/findings
- runner.py: bounded asyncio pool, per-job timeout with process-group SIGTERM→SIGKILL escalation, orphaned-job recovery on boot
- workspace.py: bare-clone + worktree materialization, gc
- config.py: env-driven
- 62 tests across db / auth / projects / jobs / runner / e2e — all green

Cross-token project access returns 404 (not 403) — existence-leak guard.
Bearer tokens hashed at rest; admin token bootstrapped on first boot.
Recipe subprocess uses start_new_session=True so killpg targets the
whole process tree on timeout — child processes can't escape SIGKILL.
Pump task guarded with wait_for(2s) + cancel fallback against any
orphan that survives the group kill.

Wave 2 (parsers + findings extraction + MCP + email digest) pending.

Spec: memory/spec-crafting-table.md
This commit is contained in:
Kayos 2026-04-29 08:17:41 -07:00
parent 4e668a79e1
commit 0ec3a04676
20 changed files with 3328 additions and 0 deletions

33
.env.example Normal file
View file

@ -0,0 +1,33 @@
# crafting-table runtime config — every key is optional; defaults shown.
#
# Copy to `.env` and edit, or pass each key explicitly to `docker compose`.
# SQLite ledger location (created on first boot)
CRAFTING_DB=/data/crafting.db
# Where workspaces (bare clones + per-job worktrees) live
CRAFTING_WORKSPACE=/workspace
# Per-job log files: /data/jobs/<job_id>.log
CRAFTING_LOG_DIR=/data/jobs
# Admin bearer plaintext is written here on first boot, chmod 600
CRAFTING_ADMIN_BEARER=/data/admin-bearer.txt
# Bounded asyncio pool size — how many recipes can run concurrently
CRAFTING_MAX_CONCURRENT=4
# HTTP listen socket
CRAFTING_PORT=8810
CRAFTING_BIND=0.0.0.0
# Default per-job timeout in seconds (recipes can override via timeout_secs)
CRAFTING_DEFAULT_JOB_TIMEOUT=1800
# Override the default LAN allowlist if you want stricter scoping.
# Default: 10/8, 172.16/12, 192.168/16, 127/8, ::1/128
# CRAFTING_LAN_CIDRS=192.168.0.0/16,127.0.0.0/8
# Workspace gc — how often to sweep for stale worktrees, and the age cutoff.
CRAFTING_GC_INTERVAL=3600
CRAFTING_GC_AGE=86400