← back to pin list

7h. Shop-rate entry is an INTERFACE, not a spreadsheet — and the product direction behind it

kerfmaster pin list · UI / workflow decisions

rulingmixedA decision by Aristide or Jordan. True because it was decided; it can be superseded, but it cannot be stale.

Content last changed 2026-08-21 — computed from the item itself, not typed.

Contract — Intake stage

This contract states a decision, not an implementation.

takes shop-data ?

makes rate-table ?

fails if A rate value saved without who supplied it and on what authority; a 'measured' claim with no how; reads left open (this is the shop's pricing).

Contract last changed 2026-08-21 — computed, not typed. Dated separately from the text above, so neither date can speak for the other.

Ruled by Aristide, 2026-08-21 (Telegram, same day as the seed pass): the channel for gathering shop numbers is an interface over the table, not a worksheet handed back and forth — and it is a piece of the PRODUCT: “We are going to have this in place for the smaller shops that want a subscription based solution from us.” The rate table is per-shop by design; a subscribing shop fills in its own machine, costs and policy. This supersedes the worksheet framing of 15e (the OWED rows are unchanged — only the way they arrive changed). Explicitly sanctioned interface work — the 2026-08-02 phase ruling is amended for this piece by the commissioning message itself.

Build state — built and live, 2026-08-21

rates_editor/server.py: the form is GENERATED from the rates.json walk (data → generator, no hand-edited HTML — the phase build rule), one field per row with its _-doc as help text. Saving a row writes the value AND its provenance in one motion — strength (measured / decided / assumed), who, date, how-they-know — straight into the row's source block, the same discipline the tables already carry. Guards, each refused server-side: no who → no save; strength measured requires the how (timed when / which invoice); enum rows (pierce_mode, remnant) reject unknown values; blank never deletes. Saving over a seed replaces the seed provenance wholesale — this IS the override mechanism from the seed pass. Every save commits + pushes (entries between turns survive, pin-editor precedent). Reads AND writes are token-gated — pricing is business data, stricter than the pin editor's open reads. Runs as systemd cadmaster-rates-editor behind mca-host sub cadmaster-rates (:8098); setup_rates_editor.sh is the box-reset recovery, mirroring the pin editor's. Token: rates_editor/.edit_token (gitignored); upload to /srv/secrets as cadmaster-rates-token to survive a wipe.

Multi-shop — anticipated, not built

tables.py already reads per-location table dirs (CADMASTER_TABLES / location=), so a second shop is a second rates.json in its own dir with its own editor instance and token — the structure holds tenancy without assuming it. What subscription tenancy actually needs (shop identity, isolation, billing) is future work, deliberately unspecified here.

Extended by Aristide, 2026-08-21 (later same day): “this table you created, would have to be part of the intake wizard when a new shop starts up” — the rates editor is the seed of a NEW-SHOP INTAKE WIZARD (intent). And he spotted the gap himself (“given the extent of the sheet you gave Jordan … seems too short”): the editor today walks rates.json ONLY (~15 pricing fields). The full per-shop dataset an intake must gather also includes the MACHINE PROFILE (machine.json: bed envelope, drop-through, start_clock, comp_register, arc_center_max, chord_sag_max — 12 fields) and the MATERIAL TABLES (materials.json: kerf, beam_comp, slug-tab brackets, part_tab, per material × thickness — ~54 fields and growing per row). Verified 2026-08-21: none of those are in the form. Extending the editor to walk all three tables with the same provenance scheme was BUILT same day on Aristide's go ('it will not hurt to get started on it, as long as you build it as a lego block piece'). The lego block is rates_editor/tableset.py -- pure data, no HTTP/HTML: it walks the three tables into editable fields, resolves provenance (per-field, node-level rule records, inherited block records), validates saves (who required, measured needs the how, enums, list length, known columns only) and writes value+provenance in one motion. server.py is presentation only; the intake wizard imports the same module and reorders the same fields. materials rows are offered SCHEMA-COMPLETE: every known column (union across rows) appears on every row, absent ones OWED -- filling one is the sanctioned way a ragged row grows a field (the engine still never guesses one); a sub-field save nests precise provenance inside the block and never overwrites the row-level origin story (tables.provenance()/unsourced() keep working -- verified). 125 fields render, 46 owed. The form still edits rows and fills known columns only; ADDING a material row or machine profile is the wizard's opening move and stays intent, as does ordering/onboarding flow and per-shop tenancy.

Ruled by Aristide, 2026-08-21 (same thread): column additions are INTERNAL. “well, it will be just Jordan, not clients. Clients would have to put in a request if this ever happens in the future.” The SCHEMA belongs to us: Jordan (and Aristide) may add columns, delivered to Claude and wired in deliberately — named, documented, one commit, then editable like any other field. A subscriber shop fills VALUES only; the form's refusal to invent column names is therefore a permission boundary, not just a typo guard, and stays in every future skin of the block including the intake wizard. A client wanting a new column files a request.