kerfmaster pin list · Preflight — how files come in
intentWhat we want to build. Never claimed the present, so it cannot be stale — and must never be read as a description of the code.
Content last changed 2026-08-23 — computed from the item itself, not typed.
Contract — Intake stage
This contract describes a stage we intend to build — it is NOT a description of the code.
takes customer-file
makes identified-file
fails if A report card that does not end in one clear question or a release.
Contract last changed 2026-08-06 — computed, not typed. Dated separately from the text above, so neither date can speak for the other.
1 open question blocks this pin
Requested by Aristide, 2026-08-01. The intake automation that runs the 14c checks and enforces the 14 gate.
⚑ Build state, 2026-08-21: steps 3–5 of this flow (identify, preflight, report data with the three outcomes and the one question) exist as preflight.run() — and the fast estimate rides the same pass (15d). The FORM, ingest/versioning, and the notification channel remain intent, gated by the phase ruling. “Internal upload uses the identical gate” is now real in the useful sense: the CLI and any future uploader call the same function.
Commissioned by Aristide's customer-surface rulings (7g, 2026-08-21/22) and 'continue on client facing interface.' Built as uploader/intake.py (flow logic — accounts, tenancy, submit→preflight→estimate→roll-up, job store; a lego block, no HTML) + uploader/server.py (presentation only), the tables-editor split. Live at cadmaster-upload.aristide.mycloudally.com behind a pre-go-live dev token (the seed-price ruling leans on that gate), systemd cadmaster-uploader (:8096). What runs: minimal-info account (email + name — outgoing mail unwired, recorded to outbox.json); tenant resolved from the Host header via shops.json (tenant zero = a stand-in demo shop, provenance says never to present it as real); upload → immutable hashed original → preflight's three outcomes → report card with gray source preview (DXF only), customer-language notes DERIVED from the note categories (the raw preflight texts are shop-sheet wording and never reach the page), and the Q29-composed indicative price where the shop's tables allow — today the live table correctly blocks on the OWED operator rate, so the page says 'price to follow.' Verified end-to-end on BNM76282 (PASS WITH NOTES, 14.35×50.14 in read back) and EAGLES.ai (BLOCKED, the one scale question, no price shown). Known gaps, said not hidden: vector uploads get no preview (a wrong picture beats none only in reverse); no per-quote emails.
Approval round-trip BUILT (record, 2026-08-22): intake.approve() closes the loop — only the job's own customer, never on a BLOCKED file, only when a price was actually SHOWN (an approval of a number the customer never saw is worthless as evidence), idempotent. What is approved is the high-side estimate as displayed, recorded as approved_estimate_usd; the page states the consequence at the moment of approval: if the exact price comes out higher, the shop asks again before cutting — so the standing 'exact price for your approval before anything is cut' survives. Approved jobs land in the shop's data/<shop>/queue.json (role-neutral, staged — this file IS what the shop works from until a shop-side page exists) and the confirmation email intent is recorded in outbox.json. Verified end-to-end with a /tmp rates overlay (test $50/hr, never a shop number): composed $150.04, wrong-customer/no-price/BLOCKED all refused, idempotent re-approve, all three page states rendered through server.job_page. The live demo tenant still shows 'price to follow' + 'approval opens the moment your price is ready' — correct OWED behavior until the real operator rate row is filled.
Sales-vs-operator flow (ruling + record, 2026-08-23): Aristide: 'small shop sales = operator / large shop sales is separate. both have to flow' and 'for small shops we wire it together with a click. for larger shops, it is 2 steps.' An approval is a SALES event first — the price re-ask promise is sales work, not operator work. Built same day: each shop in shops.json carries flow = combined (the small-shop default — the customer's approval click lands the queue entry already 'released to production', sales and operator wired together) or two_step (the entry lands 'with sales'; intake.release() is sales' click, idempotent, moving it to 'released to production'). Unknown flow values raise rather than land a job on the wrong desk. Verified both flows end-to-end with the /tmp rates overlay ($50/hr test figure): combined → released in one step, two_step → with sales then released, idempotent re-release, unknown job refused. Known gap, said not hidden: the re-ask flow itself (exact price above the approved estimate goes back to the customer before release) is not built; release() is the plain confirm.
Per-shop tables threaded through the price (record, 2026-08-23): the gap above is closed — fast_estimate.rollup_items() and every knob and provenance read in rollup.compose() now take the shop's tables location (shops.json, Q29), so the line items and the composition quote the SAME shop's layer: base rates.json → $CADMASTER_TABLES → $CADMASTER_TABLES/<location>/, deep-merged — a shop layer holds only the rows it overrides. price_fast() threads too. Verified with a test overlay (throwaway figures, never shop numbers): same file priced $150.04 on default tables and $139.38 on a shop layer overriding hourly + margin; default path unchanged (regression), CLI callers (quote.py, approval.py) untouched — location defaults to None. Recorded behaviour, said not hidden: a `tables` value pointing at a dir with no rates.json silently prices from the merged base — an absent layer file legitimately means 'no overrides', so a typo'd location is not caught here; the registry entry is ours to type carefully.