Reconciles the scattered requirements onto the owner-job namespaces. - docs/REQUIREMENTS.md: 128 requirements across CAPTURE, METADATA, REVIEW, PUBLISH, SETTINGS, APP, OPS, QUALITY - docs/STRUCTURE.md: Test may be a path, a requirement ID, or - ; a shipped path must exist - tools/check_requirements.py: resolve test refs accordingly - .audit/2026-09-18-reconciliation-decisions.md: full decision record plus 18 deferred rulings for the final cleanup pass Gate: python tools/check_requirements.py -> PASS
14 KiB
Requirements reconciliation — decision record
- Date: 2026-09-18
- Branch:
docs/clean-main - Participants: Ty + opencode
- Status: working record. Gets distilled into
docs/REQUIREMENTS.md, then retired.
This captures the decisions made while consolidating the scattered requirements, so context is not lost between sessions. Loose ends are collected in section 16.
1. Why this exists
Requirements are scattered across layers of docs (old main specs, current branch docs,
archive/), with duplicates and contradictions. Goal: one clean main holding all shipped
code plus one reconciled requirement set, with M3 scoped as a subset of it. Coding is paused
until that exists.
2. Git state established
pre-simplificationtag created at13bfa792bb3ed968b333675c85bcec0c451e2635(old main tip; retains the deleted specs: ARCHITECTURE, IMPL-PLAN, OPENAPI, DATA-MODEL, TEST-SPEC, UX, UX-DESIGN, TECH-SPEC-PIPELINE, DEPLOYMENT, PROMPTS, Build-release-standard).mainfast-forwarded tod754cac(the M1>M2>M3 code line).mainnow holds all shipped code plus current docs.- Local
m3-dscreated, trackingorigin/m3-ds. - Phase 1 scaffold committed as
b42e3cbon branchdocs/clean-main. - Nothing pushed.
origin/mainstill at the old tip until we decide to push.
3. Process rules adopted
- Coding is paused until the requirement set is reconciled.
- Requirements live only in
docs/REQUIREMENTS.md, one row per requirement, with stable IDs. - Milestones are expressed as explicit subsets of requirement IDs (
docs/MILESTONES.md). - Conflict resolution rule: current/later docs plus the code that actually runs beat the tag and
archive/. "Typically later docs prevail." - The structure is enforced by
tools/check_requirements.py(registered docs, unique IDs, traceability). - Verification discipline: no "done" without raw evidence; CI is the only mechanical gate.
4. Namespaces (owner jobs, not tech layers)
CAPTURE— ecommerce-ready pics: intake, pick calibration photo, BG removal, crop, axis, length/width, calculations, inventory PNG, dimension overlay, lineup.METADATA— building the listing: attributes and tags, title, short/deep description, the two inference calls.REVIEW— confirm and correct: confidence display, field gating, tweaks, per-slab price suggestion and override, ready gate.PUBLISH— WooCommerce create, draft/live, duplicate SKU, cleanup.SETTINGS— configured before the session: Woo connection, inference config, species$/bdftrates, brand/geo.APP— foundation: device draft, slab status, resume, server seams, secret handling.OPS— foundation: deploy, hosts, release, UAT.QUALITY— foundation: test obligations, vectors, anti-metrics.
5. Capture decisions
- Mask controls are exactly two:
Sheet(auto/green/black) andSensitivity(0–100). Edge offset and Feather are dropped. The Sheet toggle itself is a candidate for removal (auto-detect stays). - Sensitivity changes are debounced 2000 ms; a Sheet change runs an immediate pass.
- The owner uploads all photos of the slab, then selects one as the calibration photo. That photo drives every downstream step, is duplicated with dimensions added, and becomes the first showcase image.
- Crop is 90% fill, shorter side at least 800 px when the source allows (shipped values beat the old 80%/1600).
- Length and thickness are the only user-set numbers. Widths, sqft, and bdft are computed from the approved mask and are immutable in production (no typed override).
- Widths are sampled every 6 inches along the approved axis, dropping the first and last 6 inches.
- Thickness band uses the round-up model: measured
1"lands in the1–1.5"store band. - Woo image order is locked: clean calibration PNG, then dimension-overlay copy, then remaining batch photos in the order added. Minimum two images per listing; cap five.
6. Ready set (submission-ready)
Required:
- At least two images — clean first pic plus its dimension-overlay copy.
skumatching[A-Z0-9-]{3,24}.length_inandthickness_in.- Widths / sqft / bdft (computed, immutable).
categories— exactly one species leaf.categories— at least one wood-category leaf.attributes[Edge Type]— one term.attributes[Figure]— at least one term.attributes[Grade]— exactly one term.attributes[Thickness]— one term (round-up band).attributes[Moisture]— one term, default kiln-dried.regular_price.name,short_description,description.
Optional: all Woo product tags (figure family and features family).
Woo schema field mapping lives in section 6 of this record and will become the METADATA requirements.
Note: title / short description / description are required on the product. Because deterministic templates are the inference-off fallback, they are guaranteed non-empty at create.
7. Taxonomy and categories
- Categories sync from Woo; the product is assigned to leaf categories. Parents are never assigned but matter in context: the site shows them (breadcrumbs/filters) and Call 2 receives the parent path as context.
- Species is exactly one synced leaf. Wood category is one or more synced leaves.
- In review, the owner designates a primary wood category and any secondary ones. Default primary is the first category selected. How the owner corrects it, and how they stay aware of it, is a UX problem deferred to design.
- Primary drives the title. Primary plus secondary drives the description prose.
- The primary leaf selects a specialized Call 2 prompt. This is the "common pattern."
- Wood-category leaves: Knife blanks, Specialty cuts, Dimensional wood, Live edge wood, Bowl blanks, logs and caps, Pen blanks, Pepper mill blanks. All are settled prose/prompt work items except
Specialty cuts, which is parked. - All Woo product tags are optional. Tags come from the synced Woo set; no hardcoded names or prefixes. Wrinkle: one figure tag is bare
fig(Figured), so afig-*glob would drop it — the filter must be data-driven. - Current tags. Features:
feat-mincr(Crack/Check minor),feat-resin(Resin Ready). Figure:fig(Figured),fig-brdseye,fig-chatoy,fig-crotch,fig-curl,fig-fback,fig-flame,fig-hfig,fig-marbled,fig-quilted,fig-splt.
8. Locked attributes
attributes[Edge Type]— 1 term.attributes[Figure]— at least 1 term; includesplainfor basic slabs; never left empty.attributes[Grade]— exactly 1 term.attributes[Thickness]— 1 term, round-up band.attributes[Moisture]— 1 term, default kiln-dried, never inferred.- Missing attributes or terms are skipped, never created. v1 does not invent taxonomy.
9. Text generation (Call 2)
- Call 2 produces
name,short_description, anddescription. - The template is a consistency/style guide in the prompt; the model has creative license. It is not assembled deterministically in code.
- With inference off, the deterministic template is the fallback that fills those three fields.
- Call 2 receives all metadata confirmed or changed in review — not the raw Call 1 suggestions.
- Dimensions are supplied as a literal fact block (species, figure term, length, thickness, average width, width min–max, board feet). The model uses those exact strings and invents no numbers.
- There is no
L × W × Trectangle assumption; slabs are irregular and width varies. - The dimension-overlay image is not sent to the model.
- A post-check for number drift is parked as
later; if it is ever added it is plain deterministic comparison, not an AI call. - The title separator is a hyphen:
-.
10. Inference transport
- Calls are asynchronous: the request returns
202, the client polls and shows phase feedback. - Phases: uploading, analyzing, still working. Polling is the heartbeat that distinguishes dead connectivity from a working model.
- Tunable defaults: ack 15 s, upload-stall 20 s, overall inference deadline 120 s after ack, poll every 2 s backing off to 10 s.
- On timeout or failure, fail gracefully with a plain message and a Retry action. Retry is offered only if
test-inferencepreviously passed. A retry replaces any in-flight job (idempotent). - Inference input is the clean processed image (masked, cropped), composited on soft white, no alpha, downscaled to at most 1024 px long edge, JPEG quality 80.
- Only the pixel cap is exposed in Settings. JPEG quality 80 lives in a server config file.
- Call 1 and Call 2 both receive the images. Call 2 additionally receives the literal fact block.
- Provider threads are dropped; always full resend to the provider.
- Inference never blocks create and never auto-publishes.
11. Prompt management
- Prompts are stored in SQLite as immutable revisions, seeded from bundled repo defaults on first run.
- Saving appends a revision; an active pointer selects which revision is live.
- Reset to default re-activates the bundled default; reset to a revision re-activates that revision.
- Each inference call records the revision id/hash it used.
- A runtime debug flag gates both the Advanced settings UI and the prompt-write endpoints. Single build, not two.
- This is post-M3 (
later).
12. Pricing
- A flat
$/bdftrate per species is configured in Settings, matched to a synced species leaf. No tiers in v1. - The per-slab suggestion is
bdft × rate; the owner may override and the override sticks. No rule means an empty price the owner types. - The price field and its suggestion live in the review screen.
13. Publish
- Create sequence is the documented one: validate the ready set, look up the SKU (stop if it exists), refresh taxonomy if older than one hour, resolve categories/tags/attributes, upload inventory PNGs,
POST /products, then finalize. - On success the local slab row is deleted. WooCommerce is the only source of truth;
sync_logis retained; photos are purged; the device draft is cleared. SLAB-UAT-*always creates a Woo draft.- A duplicate SKU blocks create; there is no update path.
- A stale value on submit is a field-level error telling the owner to pick from the current list.
14. Settings
- Compose env:
WOO_BASE_URL(https only),SLAB_AES_KEY. - Encrypted in SQLite:
woo_wp_username,woo_app_password,inference_base_url,inference_api_key. - Plain settings:
inference_model,inference_enabled,content_llm_enabled,woo_create_status(default draft),brand_voice,geo_context. - Derived:
taxonomy_last_sync_at,taxonomy_anchor. - Auth is a WordPress application password for a dedicated low-privilege user; not consumer keys, not the login password.
15. Scope mapping
Shipped: M1 Capture, M2 Photos, capture knob prefs.
M3: store-connection and AI-connection settings; taxonomy sync; the five attributes; Call 1; Call 2; title/description generation; review screen with field gating; pricing; publish; local draft cleanup.
Later: prompt-management UI, U2Net, offline capture, OCR/ruler, batch create, number-drift check, Specialty cuts prose, primary-category correction UX.
16. Open / parked items
attributeselement shape onPOST /productsis open — needs a live Whidbey product sample or the official Woo REST fields. Do not invent.- Server persistence approach is open — full OpenAPI slab CRUD vs a thinner FastAPI path.
Specialty cutsleaf prose is parked.- Primary-category correction and awareness is a UX problem, deferred.
- Number-drift check is
later. - Prompt management is post-M3.
- The QUALITY/OPS extraction re-run is still pending (the first pass was truncated).
- "Later docs prevail" is the rule, but the tag/archive still hold material not yet reconciled (e.g. U2Net, browser floor, TES vectors, deploy details).
17. Work items
Re-run the QUALITY/OPS extraction.Done 2026-09-18 (TEST-SPEC ~110 rows; plan/deploy/release ~120 rows).- Write
docs/REQUIREMENTS.mdnamespace by namespace, starting withCAPTURE. - Draft the per-leaf title/prose work once the
METADATAsection is being written. - Migrate capability/contract docs into their homes, make docs branch-neutral, rebuild the structure check, wire CI.
18. Rulings deferred to a final cleanup pass
These surfaced during the QUALITY/OPS extraction. They do not block writing REQUIREMENTS.md; park them and reconcile in the cleanup pass.
- Test directory layout: old TEST-SPEC mandates Vitest and
frontend/src/__tests__/; shipped code uses the Node test runner (node --import tsx --test) andfrontend/tests/. Contradiction. - Test coverage target (~95% for deterministic modules) — adopt or drop.
- "The Test Bar" role and who authors reports versus Ty's sign-off.
docs/checks/check-structure.shgate timing and criteria — now replaced bytools/check_requirements.py.- Deploy ports: proposal
18080/18443versus UAT:10881, andAPP_HOSTNAMEbinding. - Backup retention "7 daily + 4 weekly" has no verification test.
SLAB_AES_KEY_OLDrekey env placement (host vs compose).- Smoke step 5: local
publishedversus Woodraftmismatch — intended or not. - Release process:
Build-release-standardpoints at GitHub while the repo is Gitea. - Taxonomy sync endpoint path divergence across docs.
- Ownership of
deploy/INVENTORY.mdacross Gate B vs Phase 6. - Scope labels: do shipped/M3/later map to phases or to release milestones.
C1/C2(Call 1 / Call 2) namespace ownership ambiguity.- Whether
UAT-PRACTICE.mdchange control is a gate. - Definition of "real fixtures" acceptance for TV-3, TV-6, TV-7, TV-10.
INF-1ownership: SETTINGS toggle versus QUALITY suite obligation.- Server-side API surface (
revision_conflict, validation module, slab CRUD) belongs to the open server-persistence decision. output_px_min"never below 1600" from old DEPLOYMENT/IMPL-PLAN versus shipped 800 floor.