Commit Graph

285 Commits

Author SHA1 Message Date
grokbot_ty e61303e2d8 Merge pull request 'fix(review): #88 thickness band derived; #90 MultiSelect names + commit' (#97) from work/ux-bugs-fixes into main 2026-09-21 17:01:49 +00:00
Ty bdd03afce4 fix(review): thickness band derived from thickness_in (#88); MultiSelect shows selected names + commits on toggle settle (#90)
#88 — thickness band is now display-only, derived deterministically from
thickness_in against the synced Woo thickness terms (TV-9 / QUALITY-95).
Replaces the <select> with a read-only div. New pure function
deriveThicknessTermId + 6 unit tests. Docs (UAT-PRACTICE, contracts/UX)
updated to reflect the band is not an owner control.

#90 — MultiSelect closed state now shows the selected option names
(truncated to 2 + +N) instead of just a count. onCommit prop wired so
multi-select changes persist + re-review immediately (250ms debounce,
flush on Done, cleanup on destroy). Single-select binds use string vars
(speciesSel/edgeSel/gradeSel) to fix the number/string mismatch with
<option value={id}>; a tick()-deferred syncSelects() keeps the DOM
option in sync after programmatic writes (AI prefill, reload hydrate)
that Svelte's own select_option would otherwise reset.
2026-09-21 09:56:44 -07:00
Ty e5acef4c9b chore(deploy): record final #90 UAT state (band omitted) 2026-09-21 02:50:06 -07:00
Ty 204bc5e960 fix(review): #90 thickness band omitted entirely (not read-only) 2026-09-21 02:49:47 -07:00
Ty 191b507934 chore(deploy): record #90 UAT deploy marker 2026-09-21 02:43:26 -07:00
Ty 0329052d37 feat(review): #90 wood primary category + derived thickness band 2026-09-21 02:41:20 -07:00
Ty e1b928ccbd fix(review): #90 clearer mandatory-photo indicator (padlock + REQUIRED tag)
The mandatory clean-cal and dimension-overlay photos now show a padlock glyph
in their keep box and an amber REQUIRED tag at the top of the card, so the
owner can see at a glance which photos cannot be removed (vs. optional batch
photos, which keep a plain checkmark).
2026-09-21 02:18:01 -07:00
Ty 76fcb36a2b fix(review): #90 clean cal + dimension-overlay are mandatory (checked + disabled)
REVIEW-5 requires both the clean calibration photo and its dimension-overlay
copy to ship. Their keep checkbox is now checked AND disabled (aria-disabled,
a lock glyph, cursor:default, tap is a no-op) so the owner can't remove them.
Only batch photos are removable. The submission minimum can no longer be
broken by the owner, so publish is never lineup-blocked by a missing required
photo.
2026-09-21 02:15:00 -07:00
Ty 5a1800df91 fix(review): #90 persist the dimension-overlay copy with its role
The Capture→Review upload filtered out the overlay (role !== 'overlay') and
sent no role field, so the dimension-overlay copy was never stored and every
photo defaulted to role=batch. Review then fell back to a stored /processed
URL that had no dimensions drawn — the owner saw the overlay thumbnail
without its measurements. Now upload the full lineup with the correct role,
keeping the overlay's dimension-drawn blob as both original and processed.
2026-09-21 01:59:27 -07:00
Ty cd326d1ec9 test(review): #90 assert Primary photo src is valid + checkerboard on strip
Locks in the two live-bug fixes: the Primary (leftmost kept) photo's <img src>
must be a real URL (blob: for an in-memory capture blob) and never empty/
"object Blob"; and each strip card must carry the gray checkerboard
background matching the Capture lineup.
2026-09-21 01:54:21 -07:00
Ty 502314b338 Update deploy/INVENTORY.md 2026-09-21 08:50:13 +00:00
Ty 0612ce66a6 fix(review): #90 photo strip — blob: URL for Primary photo, checkerboard thumbs
Bug #1: the first/Primary (calibration) photo rendered broken because its
in-memory capture Blob was assigned to <img src> directly (String(Blob)=
"object Blob"). resolvePhotoUrl() now mints a blob: object URL (revoked on
unload) and falls back to the stored photo endpoint when there is no blob.
Bug #2: strip thumbnails used a flat dark bg; now carry the gray checkerboard
from the Capture lineup (MeasurePanel) so transparent inventory PNGs read the
same on both screens.
Also harden missingMinimums to filter kept internally (pre-existing test 7
failed on the clean base because it passed un-kept items).
2026-09-21 01:48:43 -07:00
Ty 7b7adbdb34 fix(deploy): nginx client_max_body_size 25m for photo uploads
Capture→Review uploads the calibrated lineup PNGs (clean cal + batch) as
multipart to /api/v1/slabs/{id}/photos. nginx's default 1MB
client_max_body_size 413s on real multi-MB photos, blocking the
Capture→Review transition. Allow 25m.
2026-09-21 01:41:31 -07:00
Ty d9af8420bc feat(review): #90 photo strip — keep/remove, drag-reorder, leftmost=primary, persist
REVIEW-5 / PUBLISH-45: owner edits the stored photo lineup in Review — tap a
card to keep/remove, drag to reorder, leftmost kept photo = primary (Woo
cover). The kept set must contain a clean calibration + its dimension-overlay
copy; publish is blocked until it does.

- backend: role column on slab_photos (schema + idempotent _migrate ALTER),
  role in photo CRUD + new DELETE endpoint, Woo image order by role-rank
  (clean→overlay→batch) per PUBLISH-45
- frontend: Review photo strip (render from listPhotos, keep/remove,
  HTML5 drag-reorder, dynamic PRIMARY badge from .primary class),
  persistLineup() re-seq kept + drop removed before entering Publish
- tests: lineup.ts unit, e2e review-photo-strip.spec, backend role + order

Refs: #90, REVIEW-5, PUBLISH-45
2026-09-21 01:34:22 -07:00
Ty fe82133ca4 fix(backend): #84 real woo_reachable, #83 reject unknown fields, #82 live taxonomy in stale_field, #80 persist review overrides
#84: /api/health woo_reachable is now a real check (GET /wp-json/ with a
3s timeout) instead of a hardcoded False stub. Version bumped to 0.5.0-m3-s1.

#83: POST /slabs rejects unknown (non-ALLOWED_FIELDS) keys with a 400
'unknown fields' error, instead of silently dropping them. The INSERT
binding count is now provably correct (no more assert needed).

#82: stale_field 422 error now returns the LIVE taxonomy ids
(SELECT id FROM table) as current_options, plus the field name and
submitted value, instead of echoing the submitted value back.

#80: regular review (no lock) persists owner overrides (price/sku/title/
short_description) from the payload to the DB via an UPDATE, so the
review endpoint is self-contained. server_rev is incremented on persist.

Tests: +3 (test_create_rejects_unknown_fields,
test_update_stale_field_returns_live_options,
test_review_persists_owner_overrides). 87/87 passing.
2026-09-20 23:38:29 -07:00
Ty 693719e85f fix(review): #89 explicit metadata lock — deterministic copy on lock, not on review
The Review screen now has an explicit 'Lock & Build Title' step. Until the
owner locks, title/short_description stay empty (no 'ready' copy from an
unlocked AI draft). On lock the server validates the taxonomy, sets
metadata_locked=1, builds + persists the deterministic title/short_description
(owner prose wins), and returns the ready state.

- schema.sql: add metadata_locked INTEGER DEFAULT 0 to slabs
- db/__init__.py: bump _SCHEMA_SENTINELS for metadata_locked
- slabs.py: expose metadata_locked in GET; reject it in PUT (server-managed)
- review.py: POST /{id}/review action=lock path; regular review reports
  stored (empty) copy until locked
- test_review.py: 8 tests covering lock gate, owner-prose-wins, stale taxonomy
- frontend/api/slabs.ts: Slab.metadata_locked, ReviewResult.locked,
  ReviewInput.action, lockSlab() helper
- Review.svelte: lockMetadata() with correct commit-token ordering (bump AFTER
  inner commit so the inner PUT isn't superseded), Lock button, locked banner
- e2e/m3-create.spec.ts: pare to single golden path (capture→review→lock→
  ready→publish→done); mock respects optimistic concurrency (client_rev) so
  stale PUTs 409 like the real backend
2026-09-20 23:36:31 -07:00
Ty 41401a7266 fix(inference): #87 redact image bytes from inference_log
Log full text + image_ref (photo_id/seq/bytes) only; wire path still
sends data-URLs. Call 1/2 SELECT id+seq for refs. Cap is safety net
after redaction (METADATA-66).
2026-09-20 23:01:38 -07:00
Ty 7287a55707 fix(inference): #86 tighten Call 1 prompt and closed lists
Ship locked expert Call 1 seed: order cats before edge, species=best
confidence, mill-count edge rules, Wild for cookies/burl, multi figure,
feature_tag_ids only (no free-text bags, no grade). LISTS keys match the
seed; feat- prefix stripped for the model. Gate and review UI apply
feature_tag_ids. Seed auto-activates on deploy when still seed-authored.
2026-09-20 22:54:36 -07:00
Ty 81be17d5cd fix(inference): METADATA-35 fit Call 1/2 images to 1024 long edge
Downscale processed PNGs only on the inference send path (keep alpha, never
upscale). Stored inventory/Woo blobs unchanged. Adds Pillow + unit/e2e-style
tests; cite tests on METADATA-35.
2026-09-20 22:32:14 -07:00
Ty cec03c5bd3 fix(m3): AI-assist scope + confidence gate + grouped wood categories
- inference: _taxonomy_lists hands the model the synced species leaves + wood
  leaves + case-insensitive attribute terms (was furniture roots / empty)
- inference: CALL1_SEED pins exact flat schema + *_confidence keys, drops grade
- inference: _gate maps field->confidence key correctly (was species_id_confidence),
  reads nested shape as safety net, omits grade (METADATA-40)
- review.svelte: figure gets AI assist, grade dropped from AI (owner judgment)
- MultiSelect: grouped mode (parents non-selectable headers, selectable leaves)
- METADATA-40 updated: grade not suggested by Call 1
2026-09-20 21:47:36 -07:00
Ty e36979617b fix(m3): uuid secure-context fallback + middle-tier SKU business rules
- frontend: uuid() helper (crypto.randomUUID -> getRandomValues) for plain-HTTP LAN UAT
- slabs.py: empty SKU at create -> NULL (no-SKU-yet is transient, filled in Review)
- review.py: ready-gate enforces REVIEW-45 (SKU 3-24 alnum+hyphen) in the middle tier
- DB CHECK stays as backstop only (referential/structural), per layering principle
2026-09-20 21:15:29 -07:00
Ty c8d6d5eda0 fix(backend): create accepts capture-only payload; fix INSERT binding count
create_slab required ALL ALLOWED_FIELDS present, but the Review screen fills
taxonomy/content after create (CREATE.md §1). Now requires only the capture
fields (id, sku, dims, widths); the rest stay nullable for Review.

Also fixed a pre-existing off-by-one: vals re-filtered cols by 'in data',
dropping the 6 server-managed columns and under-counting bindings (19 vs 18).
Split allowed_cols/server_cols so vals always matches cols.

Verified: 9/9 slabs tests pass; live POST /slabs with the app's capture-math
payload returns 201.
2026-09-20 14:45:32 -07:00
Ty 1133201f30 M3 Review→Publish UI (#77): one-column flow, searchable pickers, fig/feat tags, e2e
- Review screen: slug-resolved species/wcat groupers, case-insensitive attrs,
  fig-*/feat-* tag pickers (METADATA-30/40), AI-assist labels + AI bubbles.
- App orchestrator: single writable flow store, capture→review→publish, Resume boot.
- API client (slabs.ts) mirroring live backend routes; Blob name fix.
- Playwright e2e (7 tests, hermetic mock): canvas PNG seed, client-UUID mock,
  SKU blur commit, ready-state reactive fix; expect timeout 4s, 2 workers.
- e2e green 7/7 in 6s; check_requirements + check_coverage PASS.
2026-09-20 14:34:24 -07:00
Ty 2e2be48154 Merge pull request 'SETTINGS-35: species $/bdft in Settings + taxonomy parent fix (#76)' (#85) from issue-76-settings-species-pricing into main 2026-09-20 19:39:23 +00:00
Ty 18c262b5b4 SETTINGS-35: species $/bdft in Settings + taxonomy parent fix (#76)
Add Settings species pricing UI (children of Species grouper by slug,
never hardcoded ids). Map Woo REST parent→parent_id on taxonomy sync;
upsert categories so slab FKs survive re-sync. Tests: parent map, FK-safe
sync, TV-8 0.96×12.50→12.00, Playwright mock e2e. UAT verified on 5060.
2026-09-20 12:37:52 -07:00
Ty b99cbdf194 Fix 403 on test-woo: add User-Agent header, classify 403 as WooForbiddenError
WordPress security plugins (Wordfence, WAF) block the default
Python-urllib User-Agent with 403. Send a descriptive User-Agent on
all Woo requests. Also add WooForbiddenError so 403 is classified
distinctly from 401 (auth) per SETTINGS-80, and surface a clear
message in test-woo.
2026-09-20 07:59:00 -07:00
Ty d1e862770d Settings: split Test Connection into WooCommerce and Inference sections 2026-09-20 07:42:37 -07:00
Ty d614123503 Settings: render UAT secrets as plain text inputs (Svelte forbids dynamic type on bound input) 2026-09-20 07:39:33 -07:00
Ty 34ee7eff60 Settings: temporary UAT secret-unmask gate (SLAB_UAT_UNMASK_SECRETS)
Owner-directed, LAN/HTTP dev-test only. When the env flag is set,
GET /api/v1/settings returns decrypted secret values (string) instead of
{configured: bool}; the UI prefills and shows them in clear text. Default
off -> SETTINGS-75 masking and the existing tests stay intact. Revert
before any non-UAT deployment.
2026-09-20 07:38:38 -07:00
Ty e51753b6d0 Fix Settings black-on-black contrast (Gitea #75)
Scope .settings styles to the panel: light #eee text on the dark
#121212 surface for labels, and white inputs/selects/options with dark
text. Both pairs exceed WCAG 4.5:1. Add settings-contrast e2e as the
regression guard for the Settings form plus the sibling dev panels.
2026-09-20 01:10:07 -07:00
Ty 7575471397 test(exit): M3 exit wave — inference-off suite + Woo-mock integration + Compose E2E + UAT walk
- test_exit_inference_off.py (QUALITY-20/120): full create->photo->review->publish
  happy path with inference OFF, asserts zero inference_log rows and no raw codes/
  tracebacks reach the owner; not-ready publish 422 with plain field-level message.
- test_integration_woo_mock.py (QUALITY-35): Woo mock covering success, error,
  duplicate SKU, stale field (no raw code), and taxonomy anchor bump on change.
- m3-create.spec.ts (QUALITY-50): Playwright E2E — all M3 dev screens render,
  Review/Publish controls load, Generate control visible only when enabled+reachable.
- deploy/uat_walk.sh (QUALITY-110): ready-to-run human walk for the real SLAB-UAT-*
  draft (inference off) against the live store — NOT a mock.

Verified: pytest 61 passed; frontend unit 88 passed; e2e 4 passed; gates exit 0.
2026-09-19 17:12:14 -07:00
Ty 8a8a89ed3e feat(inference): S7 Call 1/Call 2 + prompt revision REST + test-inference + Generate text UI
- backend/inference.py: prompt seed (rev 0) + append-only revision REST
  (POST /prompts/{call}/revisions, /activate, GET; METADATA-105/106). Call 1 vision
  classification (1024px processed photos + synced taxonomy, 0.7 confidence gate,
  METADATA-35/40/45), Call 2 long description (user-triggered, METADATA-55/60/65),
  inference_log per call (METADATA-66), inference-off server_description (METADATA-70),
  test-inference probe (SETTINGS-45). Stdlib urllib, no new deps.
- frontend: create/inference/GenerateText.svelte (control visible only when content
  enabled + endpoint reachable, METADATA-110; Call 2 + Call 1 buttons) + dev toggle.
- tests: 6 inference tests (seed+revision, call1 gate, call2 stores desc, off->409,
  server desc, test-inference unconfigured).

Verified: pytest 54 passed; gates exit 0; npm build ok.
2026-09-19 17:04:01 -07:00
Ty 5e03f45660 feat(publish): S6 Woo create sequence + UAT draft + dup/stale/partial failure + Publish UI
- backend/publish.py: POST /slabs/{id}/publish. Sequence (PUBLISH-5): validate ready
  set -> SKU lookup (409 on dup, PUBLISH-15) -> refresh taxonomy if >1h (SETTINGS-60)
  -> resolve cats/tags/5 attributes -> upload processed PNGs -> POST /products ->
  finalize (set woo_product_id, purge photos, PUBLISH-25). SLAB-UAT-* always draft
  (PUBLISH-10). Failure: mark failed, retain photos, sync_log, plain recoverable/
  unrecoverable message (PUBLISH-30/36/37/38). Partial (media ok, POST failed):
  orphan media logged + manual cleanup note (PUBLISH-31). Payload: only non-null,
  slug = name+sku, sold_individually true (PUBLISH-55/60).
- frontend: create/publish/Publish.svelte (slab id, publish, result + recoverable/
  orphan tags) + dev toggle.
- tests: 5 publish tests (success+purge, UAT draft, not-ready 422, dup 409,
  partial-media 502). Woo transport mocked.

Verified: pytest 48 passed; gates exit 0; npm build ok.
2026-09-19 17:00:26 -07:00
Ty 732310fdbe feat(review): S5 pricing rules + deterministic title/short desc + server-derived ready + Review UI
- backend/pricing.py: /admin/pricing CRUD (one flat $/bdft per species leaf, SETTINGS-35).
- backend/review.py: POST /slabs/{id}/review -> deterministic title + short description
  (7 leaf patterns, length ft/in split, thickness 1/8-in precision, METADATA-80/85/90),
  price suggestion (bdft x species rate, REVIEW-25), server-derived ready + missing set
  (REVIEW-5).
- frontend: create/review/Review.svelte (slab id, primary category, price override,
  ready state + generated copy) + dev toggle.
- tests: 3 pricing + 4 review tests (title no-dims, ready set, price suggestion,
  knife-blank dims).

Verified: pytest 43 passed; gates exit 0; npm build ok.
2026-09-19 16:52:33 -07:00
Ty 861c5e0dd1 fix(deps): add python-multipart for S3 multipart photo upload (runtime dep missing from image) 2026-09-19 16:47:52 -07:00
Ty 714a227e57 feat(photos): S3 multipart BLOB storage + original/processed GET + 410 after purge + attach UI
- backend/photos.py: POST /slabs/{id}/photos (multipart original+processed ->
  slab_photos BLOBs, re-upload replaces), GET original/processed (image/png, 410
  once purged), GET list. Cites CAPTURE-95/105/130, PUBLISH-40.
- frontend: create/photos/PhotoAttach.svelte (slab id, original/processed files,
  calibration flag, lineup) + dev toggle.
- tests: 5 photos tests (upload+get both variants, 410 after purge, 404 slab,
  reupload replaces, list).

Verified: pytest 36 passed; gates exit 0; npm build ok; live QA upload->200,
get original/processed->200, purge->410.
2026-09-19 16:47:07 -07:00
Ty d83c9d5c21 feat(woo): S4 Woo client + taxonomy sync + test-woo + admin taxonomy
- backend/woo.py: stdlib urllib Woo v3 client. Basic-auth, HTTPS-only, X-WP-TotalPages
  pagination, typed errors (CredentialsMissing/Auth/NotFound/Server/Connection),
  injectable transport for tests, WP core media upload.
- backend/taxonomy.py: sync pulls categories/tags/attributes/terms into cache in one
  commit (rollback keeps prior cache on failure); fig-*/feat-* tag scoping; stable
  taxonomy_anchor hash; get_cached grouped admin view.
- backend/app.py: POST /settings/test-woo (fail-closed, resync on success),
  GET /admin/taxonomy, POST /admin/taxonomy/sync (502 on Woo failure).
- frontend: Settings Test Woo button + result display.
- tests: 8 woo/taxonomy tests (auth header, non-https refusal, pagination, missing
  creds, 401, scoped sync, failure-keeps-cache, anchor stability).

Verified: pytest 31 passed; check_requirements + check_coverage exit 0; npm build ok;
live QA test-woo fail-closed + sync 502.
2026-09-19 16:40:37 -07:00
Ty 38eebbc8bb fix(db): generalize schema-drift detection to all M3 tables (slabs, slab_photos, sync_log)
Live host had stale M2 slabs table (no deleted_at/server_rev) -> GET /slabs 500.
Drift check now verifies sentinel (table,column) pairs across settings/slabs/
slab_photos/sync_log; on drift drops all app-owned M3 tables and rebuilds, stamps
schema_version. Drift test now seeds M2 settings + slabs and asserts both rebuilt.
pytest 23 passed; gates exit 0.
2026-09-19 16:30:43 -07:00
Ty cc5241f7da feat(slabs): S2 CRUD + optimistic concurrency + soft delete + slab list UI
- backend/slabs.py: create (calibrated, client-derived stored as sent), list (excl
  soft-deleted, most recent), get, update (single compare-and-set on server_rev,
  409 revision_conflict / 409 duplicate_sku / 422 stale_field, status server-derived),
  delete (soft, published rejected). Explicit column allowlists, parameterized SQL,
  JSON array fields, commits.
- backend/app.py: wire slabs router.
- frontend: src/api/slabs.ts client + create/slabs/SlabList.svelte + dev toggle.
- tests: 9 slabs tests (create/dup/list/get/update/rev-conflict/dup-sku/soft-delete/
  published-delete) with full taxonomy seeding.

Verified: pytest 23 passed; check_requirements + check_coverage exit 0; npm build ok.
2026-09-19 16:29:06 -07:00
Ty a0032ddd2d fix(db): schema_version drift detection + rebuild for pre-M3 volumes
- Add schema_version table; init_db detects a pre-M3/unknown DB (no version
  row, or settings table missing the value column) and rebuilds to the clean
  M3 schema, then stamps version 1
- _has_schema_version checks for a real row (empty table != versioned)
- _reset_for_tests checkpoints WAL before close (Windows handle release)
- TDD: test_schema_drift seeds the M2 settings shape, asserts rebuild + version

Fixes live 500 'no such column: value' on the UAT host (stale M2 slab_db volume).
Gates: check_requirements + check_coverage exit 0; pytest 14 passed.
2026-09-19 16:05:40 -07:00
Ty 60bc2fb9d0 feat: add schema drift handling and rebuild 2026-09-19 16:02:04 -07:00
Ty 7074b82ced fix(deploy): add resolver 127.0.0.11 for fastapi upstream in nginx-m1.conf 2026-09-19 15:57:14 -07:00
Ty e63c964844 fix(deploy): use variable for fastapi upstream in nginx-m1.conf to fix host not found at startup 2026-09-19 15:56:20 -07:00
Ty 6aaad81583 fix(deploy): restore context: .. in compose (relative to deploy/ file); remove invalid || from backend/Dockerfile; prune backend from .dockerignore so COPY succeeds 2026-09-19 15:53:49 -07:00
Ty 39ee82bdf8 fix(deploy): correct build context to . in docker-compose.yml (was .. breaking M3 build from repo root) 2026-09-19 15:52:11 -07:00
Ty 3fb35e8957 fix(deploy): proxy /api/ to fastapi in nginx-m1.conf for M3 smoke checks 2026-09-19 15:50:40 -07:00
Ty b3380f3848 feat(settings): S1 AES-GCM crypto + Settings API/UI (masked write-only secrets)
- backend/crypto.py: AES-GCM (cryptography.AESGCM); key=base64(SLAB_AES_KEY)
  must be exactly 32 bytes; value_enc=base64(nonce(12)||ct); DecryptError on tamper
- backend/settings.py: get_settings (secrets->{configured:bool}, woo_base_url env
  read-only, flags default false), update_settings (rejects woo_base_url SETTINGS-85,
  encrypts secrets, partial plain writes)
- backend/app.py: wire GET/PUT /api/v1/settings
- frontend/src/create/settings/Settings.svelte: masked form + partial PUT
- tests: crypto roundtrip/tamper, masked GET, encrypted PUT, reject woo_base_url,
  flags default, decrypt-fail->configured:false (TDD)

Verified live: secrets round-trip configured:true with valid key; woo_base_url write
rejected 400; no secret leak in GET. Gates: check_requirements + check_coverage exit 0;
pytest 13 passed. Deploy+build on ubudual5060 (never Windows).
2026-09-19 15:48:42 -07:00
Ty 0eb2b0f697 feat(backend): S0 foundation (schema + stdlib sqlite3 db factory + health/meta + JSON logs)
- backend/db/schema.sql: all 12 tables from DATA-MODEL.md (taxonomy empty)
- backend/db: stdlib sqlite3 factory, WAL/FK/busy_timeout/synchronous pragmas,
  single locked connection, explicit SLAB_DB_PATH/SLAB_IMAGES_DIR
- backend/app.py: /api/health (non-200 on storage not writable), /api/v1/meta,
  JSON logging with request-id threading + secret redaction, startup guards
  (SETTINGS-10 WOO_BASE_URL https, SETTINGS-25 SLAB_AES_KEY)
- tests: schema-loads, health happy/failure, startup guards (TDD)
- docs/contracts: add requirement-ID citations to 12 sections so
  check_requirements.py passes (pre-existing gate debt)

Gates: check_requirements.py + check_coverage.py exit 0; pytest 5 passed
2026-09-19 15:34:15 -07:00
Ty 9b181a937b docs(agents): focus AGENTS.md on agent-operational rules
The file restated facts owned by other docs and carried personal
references.

- Drop the status section, the agent-context block, the M3 scope prose, and
  the 14-bullet invariants list; each restated docs/README.md,
  docs/MILESTONES.md, or a docs/REQUIREMENTS.md row.
- Replace the ports and hosts paragraph with a pointer to docs/DEPLOYMENT.md
  and deploy/INVENTORY.md.
- Correct the subagent rule to the real constraint: one at a time on the
  single GPU. Subagents do coding work.
- Remove the name references.
- Keep the structure contract, the change-a-requirement procedure, and the
  gate commands.
2026-09-19 13:24:41 -07:00
Ty b166e796ad tools(docs): require citations in citing docs
Two checks were added to catch drift between the point of record and the
docs that cite it.

- Check 11: a capability or contract doc must declare its IDs in a
  Requirements impact table, and may cite only those.
- Check 12: a section that makes a behavior claim must cite an ID in that
  section. External-shape sections are exempt.

CAPTURE.md and CREATE.md gained the impact-table rows the first check
requires. check_coverage is unchanged.
2026-09-19 13:17:37 -07:00