{ "verdict": "CONDITIONALLY FEASIBLE WITHOUT A MIGRATION. The existing V5 mapping path already has the required single write boundary—authenticated dashboard POST to a private action inbox, followed by independent worker validation and transactional database mutation—and the committed schema contains enough tables for individual reviewed mappings, provenance, idempotency, composite review, and personal tolerance. The smallest safe Sprint 7C-D extension is therefore an additive V5/API/action-v2 change that groups existing queue identities, previews an exact target-set digest, confirms that same digest, and applies deterministic per-queue child actions through the existing worker. Do not create another mapping engine, review-status table, or write service, and do not modify V4. A durable relational record of every previewed item identity is not representable by the current schema; the migrations-free ceiling is a cryptographic target-set commitment in the existing private action/receipt artifacts plus existing action-log hashes and per-day provenance.", "tables": [ "histamine_food_rules — canonical histamine classification catalog: unique canonical_food, SIGHi score constrained to 0–3, free-text category/tags/notes, confidence, source, updated_at", "histamine_food_aliases — alias primary key to histamine_food_rules.canonical_food; this is the reusable classifier input", "nutrition_items — imported observations with item_hash unique identity, source identifiers, day/meal, display fields, macros, and unchanged raw import JSON", "nutrition_item_nutrients — per-item nutrient observations; Sprint 6H-A adds raw/canonical units, conversion factor, value status, and contract version", "nutrition_histamine_scores — one classification row per nutrition item, including canonical food, score, traffic/status field, tags, confidence, reason, and scored_at", "nutrition_review_queue — unique normalized_name, one example_name, occurrence count, first/last seen, suggestion, reason, and status", "nutrition_meal_summary — derived meal aggregates; histamine score must remain NULL while any item is unknown", "nutrition_daily_summary_v2 — derived day aggregates, maximum score, unknown count, coverage label, and nullable histamine score", "nutrition_mapping_action_log — idempotency/audit commitment keyed by action_id with action_hash and the accepted decision/provenance fields", "nutrition_mapping_provenance — decision provenance by action_id, normalized identity, and affected day; UNIQUE(action_id, affected_day)", "nutrition_composite_product_review — existing composite-review state keyed by normalized_name", "personal_food_tolerance — separate personal observation state keyed by canonical_food", "food_enrichment_sources — separate nutrient-enrichment source catalog created by Sprint 6F-B runtime DDL", "food_nutrient_enrichment — BLV nutrient enrichment mappings and review_status; separate in purpose from histamine mapping and not a substitute review-status truth", "sync_state — import fingerprint state used by the YAZIO synchronization path" ], "states": [ "nutrition_review_queue.status: open, mapped, ignored", "Mapping decisions: assign, composite, ignore", "nutrition_composite_product_review.status: needs_ingredient_review, reviewed", "personal_food_tolerance.personal_status accepted by the active workflow: unknown, documented_tolerated, documented_not_tolerated, unclear", "Legacy personal tolerance values retained by schema compatibility: safe, problematic", "Mapping confidence: low, medium, high", "Mapping methods: sighi_reference, ingredient_label, manual_review, local_alias", "SIGHi source classification: integer 0, 1, 2, or 3; missing remains unknown", "nutrition_histamine_scores traffic/status values encountered by current code: unknown, classified, green, yellow, orange, red", "Derived meal/day mapping status: classified only when every documented item has a trusted classification; otherwise unknown/incomplete with no complete index", "food_nutrient_enrichment.review_status: auto_applied, needs_review, approved, rejected", "Nutrient value status: documented_zero, documented_value, estimated, not_reported, unknown, not_applicable", "Source-status projection for nutrition review: none or review_open; this is a projection of nutrition_review_queue, not an independent workflow state" ], "files": [ "database/schema.sql:158-174 — histamine rules and aliases", "database/schema.sql:306-409 — nutrition summaries, item classifications, observations, and review queue", "database/schema.sql:424-479 — personal tolerance, mapping audit/provenance, and composite review", "scripts/health/yazio_nutrition_sync.py:112-117 — importer normalization", "scripts/health/yazio_nutrition_sync.py:252-271 — alias/rule classifier", "scripts/health/yazio_nutrition_sync.py:292-380 — fixed production DB synchronization and item replacement", "scripts/health/yazio_nutrition_sync.py:386-495 — meal/day recomputation", "scripts/health/food_nutrient_enrichment.py:63-69 — separate BLV/YAZIO normalization and simple-name eligibility", "scripts/health/food_nutrient_enrichment.py:112-177 — enrichment writer and report generation", "scripts/health/dashboard_v5/sprint6f_b_schema.py:61-103 — food enrichment runtime tables and statuses", "scripts/health/dashboard_v5/sprint6h_a_schema.py:6-28 — nutrient provenance columns absent from the canonical schema snapshot", "scripts/health/dashboard_v5/contracts.py:18-21 — opaque nutrition queue key", "scripts/health/dashboard_v5/data_provider.py:258-294 — static nutrition review bundle capped at 25 with page-length open count", "scripts/health/dashboard_v5/data_provider.py:405-406 — mapping work projected as system data preparation", "scripts/health/dashboard_v5/read_api.py:1982-1997 — histamine definition and missingness semantics", "scripts/health/dashboard_v5/read_api.py:2181-2277 — day-item mapping, tolerance, composite, confidence, and exact open-queue deep link", "scripts/health/dashboard_v5/read_api.py:2375-2411 — bounded open-only mapping queue API", "scripts/health/dashboard_v5/read_api.py:4563-4573 — nutrition route dispatch", "scripts/health/health_dashboard_server.py:848-930 — exact v1 mapping form validation", "scripts/health/health_dashboard_server.py:1189-1355 — authenticated, same-origin, one-time-CSRF action enqueue and queued receipt", "scripts/health/health_dashboard_action_worker.py:167-173 — mapping enums and definition version", "scripts/health/health_dashboard_action_worker.py:228-310 — independent worker payload validation", "scripts/health/health_dashboard_action_worker.py:492-511 — duplicated queue-key and normalization implementation", "scripts/health/health_dashboard_action_worker.py:628-748 — runtime mapping schema bootstrap/compatibility mutation", "scripts/health/health_dashboard_action_worker.py:751-829 — fail-closed classification and affected-day recomputation", "scripts/health/health_dashboard_action_worker.py:832-998 — mapping conflict handling, mutation, provenance, and action log", "scripts/health/health_dashboard_action_worker.py:2021-2096 — single worker action dispatcher and V5-only regeneration path", "scripts/health/health_dashboard_action_worker.py:2099-2203 — private receipts, action deletion, and failure quarantine", "scripts/health/assets/health-assets/dashboard-v5.js:669-727 — mapping URL state, client-only filters, statuses, and sorting", "scripts/health/assets/health-assets/dashboard-v5.js:730-796 — master-detail review UI and v1 action form", "scripts/health/assets/health-assets/dashboard-v5-nutrition.js:18-22 — nutrition workspace routing", "scripts/health/assets/health-assets/dashboard-v5-nutrition.js:37-39 — item detail and mapping deep link", "scripts/health/dashboard_v5/source_status.py:173-177 — true SQL open-review count", "scripts/health/dashboard_v5/source_status.py:211-216 — nutrition source review projection", "tests/test_dashboard_v5_sprint6g_nutrition.py — principal mapping API, worker, schema, conflict, provenance, and missingness tests", "tests/test_dashboard_v5_sprint6h_a.py — nutrient unit/status and raw-row preservation tests", "tests/test_dashboard_v5_sprint7b.py — source/import/review/regeneration state-separation tests", "tests/browser/dashboard_v5_sprint6g_a1.spec.js — mapping workspace browser coverage", "tests/browser/dashboard_v5_sprint6h_a.spec.js — nutrition day/master-detail usability coverage", "docs/sprint6g-a3-day-content-document-access.md:27-39 — opaque mapping deep-link and private authentication contract", "docs/sprint7c-b-nutrition-comparison-inventory.md:34-74 — current Sprint 7C histamine/missingness and no-second-truth contract", "scripts/health/health_dashboard_v4.py — legacy V4 reader; no Sprint 7C-D change is required", "tests/test_health_dashboard_v4.py — frozen V4 regression surface" ], "security_contract": [ "Keep the HTTP process read-only for the health database; all mutations continue through the existing private action inbox and network-isolated one-shot worker.", "Retain Host allowlisting, authenticated V5/bootstrap session, exact same-origin validation, application/x-www-form-urlencoded enforcement, bounded request size, exact single-value fields, and one-time CSRF.", "Never expose numeric database IDs, YAZIO product IDs, item hashes, raw import JSON, file paths, source URLs, tokens, or unbounded provenance through grouped-review or preview responses.", "Use the existing 32-hex opaque queue keys as browser identities; derive them only from exact normalized queue identities.", "Preview must be computed from a read-only connection and return only bounded counts, affected-day count/range, queue keys, catalog descriptors, and an opaque target-set digest/revision.", "Confirm must submit the preview digest/revision; the server must re-resolve it before enqueue, and the worker must independently re-resolve and compare it again before mutation.", "The worker must reject changed membership, changed queue status/revision, normalization collisions, catalog changes, existing-classification conflicts, alias conflicts, target-count overflow, and affected-day overflow.", "Do not accept canonical score, category, confidence, source label, or source version as browser authority. Resolve them server-side and worker-side from histamine_food_rules for catalog-backed assignments.", "Keep source classification and personal tolerance separate. Composite decisions remain unclassified and cannot write personal tolerance.", "Preserve raw nutrition tables unchanged and recompute only deterministically affected meals/days.", "Retain atomic 0600 action and receipt files, 0700 directories, no-follow/owner/mode/size checks, duplicate-JSON rejection, idempotency, and failed-action quarantine.", "Do not add a second review/status table, mapping engine, HTTP writer, or V4 mutation/regeneration path.", "A private receipt may retain the confirmed target-set digest/count as audit evidence, but it must remain a receipt for the existing worker—not a competing mapping or review-status source of truth." ], "test_contract": [ "Existing static contract: tests/test_dashboard_v5_sprint6g_nutrition.py verifies bounded allowlisted nutrition APIs, unknown-not-zero behavior, exact queue keys, transactional/idempotent mapping, affected-day-only recomputation, provenance, composite/ignore semantics, exact form shape, one-time CSRF, personal-tolerance separation, normalization-collision rejection, trusted local-rule provenance, schema idempotency, and restore behavior.", "Add API tests for true total independent of page length, keyset pagination before LIMIT, bounded grouped results, exact catalog allowlist, duplicate query rejection, and no internal identifier leakage.", "Add normalization parity tests proving importer, API preview, server confirm, and worker resolve the same Unicode/case/punctuation identity.", "Add preview tests for deterministic target digest, stable ordering, target/item/day bounds, zero-target rejection, queue collision rejection, and catalog revision capture.", "Add confirm tests for fresh one-time CSRF, exact shape, digest mismatch, stale queue updated_at, changed target membership, duplicate queue keys, oversized groups, and replay.", "Add worker tests for deterministic per-queue child action IDs, one transaction for the whole group, full rollback on any child conflict, idempotent replay, action-hash conflict, and partial failure after DB commit but before receipt publication.", "Add alias persistence tests showing a catalog-backed reviewed assignment survives a forced re-import and classifies future exact normalized occurrences; conflicting aliases must fail closed.", "Add immutable-target tests that insert, remove, or rename a target after preview and prove the worker rejects the entire group without changing scores, queue statuses, summaries, or tolerance.", "Add provenance tests for multiple selected queue identities affecting the same day, using deterministic child action IDs so UNIQUE(action_id, affected_day) is not violated.", "Add browser tests for group buckets, explicit per-row selection, preview counts, immutable confirmation summary, cancel/edit behavior, queued versus applied wording, desktop/mobile focus restoration, reload/Back/Forward, and no horizontal overflow at 390px and 200% text.", "Retain V4 checksum/diff and tests/test_health_dashboard_v4.py as frozen regression gates.", "Test execution blocker during this audit: pytest is not installed in either the active Hermes Python environment or /usr/bin/python3, so the existing pytest suite could not be executed. Python syntax compilation succeeded, and the tracked Git tree remained clean." ], "extension_path": [ "1. Freeze scope to V5 histamine mapping only. Leave food_nutrient_enrichment review semantics, V4, correlation engines, and medical interpretation unchanged.", "2. Extract one shared normalize_food_identity helper and one shared nutrition_queue_key helper; make YAZIO classification, read API, server preview/confirm, and worker use them. Preserve the current normalization output for existing rows.", "3. Extend GET /api/v1/nutrition/mapping-queue rather than adding a second queue: add bounded keyset pagination, true SQL total, server-side status/kind/search/sort filters, and honest group descriptors. Grouping is presentation only; every selectable member remains an exact existing queue identity.", "4. Add an authenticated bounded read endpoint for canonical choices sourced directly from histamine_food_rules and histamine_food_aliases. Return an opaque catalog key plus allowlisted canonical label, score, category, confidence, source/version, and alias-conflict availability. Do not expose IDs or let the browser author score/provenance.", "5. Add a preview operation that accepts a bounded ordered set of exact queue keys and intended decisions, resolves current open rows and exact nutrition_items through a read-only connection, and computes per-member target_count, affected-day count, queue updated_at, catalog revision, and SHA-256 digest over sorted item_hash commitments. Return a group preview revision/digest and neutral summary.", "6. Add a dedicated action contract nutrition_mapping_action_v2 to the existing /health-actions/nutrition-mapping boundary. Confirm submits the preview revision and selected decisions; the server re-resolves the target set and queues one immutable 0600 group envelope only if it still matches.", "7. Extend the existing worker validator for v2. Re-resolve queue rows, catalog entries, exact names, counts, revisions, and item-hash digests. Reject the whole envelope on any stale or conflicting member.", "8. Refactor apply_mapping_action into a connection-scoped mutation primitive and process the v2 group in one BEGIN IMMEDIATE transaction. Derive a deterministic child action_id from the envelope action ID and queue key, then write existing nutrition_mapping_action_log and nutrition_mapping_provenance rows per child. This avoids the existing UNIQUE(action_id, affected_day) collision without changing schema.", "9. For assign, permit only an existing catalog rule whose worker-loaded score matches. Insert the reviewed exact alias into histamine_food_aliases with explicit conflict checks so forced re-imports and future matching items retain the decision. Do not auto-create or overwrite histamine_food_rules in this sprint.", "10. For composite, write only nutrition_composite_product_review and provenance; keep score unknown. For ignore, change only the existing queue status and provenance. Preserve personal tolerance as a separately explicit, per-canonical decision.", "11. Recompute only unique affected days after all child mutations succeed. Preserve NULL/unknown for incomplete meals and days.", "12. Extend the existing private processed receipt with envelope action ID, child action IDs, confirmed group digest, per-child target digest/count, and applied status. Existing action-log hashes commit each child payload; database mapping and queue tables remain the only authoritative mapping/review state.", "13. Update the V5 master-detail UI to fetch the API rather than relying on the static 25-row bundle, show truthful server groups and totals, require explicit member selection, display the immutable preview, and expose a separate Confirm action. Keep queued and applied wording distinct.", "14. Keep the existing v1 action path temporarily readable by the worker for already queued files, but make new UI submissions v2. Do not introduce another engine or status projection.", "15. Verify focused unit/API/worker/browser gates, then confirm git diff excludes scripts/health/health_dashboard_v4.py, tests/test_health_dashboard_v4.py, schema migrations, and database/schema.sql." ], "blockers": [ "No committed production writer for nutrition_review_queue was found. YAZIO import classifies items but does not create/update unknown review rows; current queue population therefore depends on an untraced external/manual process or pre-existing data.", "Reviewed mappings are not reusable today. apply_mapping_action updates nutrition_histamine_scores for existing items but never inserts histamine_food_aliases; sync_date deletes and recreates YAZIO item/classification rows, so a forced re-import can erase the reviewed result and future occurrences remain dependent on the old alias catalog.", "The current V5 workspace renders bundle.nutrition.mapping_review, which is capped at 25, and data_provider.py reports mapping_open_count as len(review). This can undercount the real queue even though source_status.py separately computes a true SQL count.", "The dedicated mapping API is open-only, has no pagination or separate total, and raises row_limit_exceeded above its bound. The current client polls this API after submit but does not use it as the primary review list.", "The UI advertises all/done/mapped/queued/composite/product-kind filters, but the static provider supplies only open rows and mappingPresentation assigns kind unknown. Those filters are currently presentation fiction rather than server-backed states.", "There is no canonical-food/catalog endpoint. The browser currently submits free-text canonical_food and SIGHi score; only mapping_method=sighi_reference is checked against histamine_food_rules. manual_review, ingredient_label, and local_alias can create per-item arbitrary canonical/score combinations.", "There is no optimistic concurrency field or immutable target-set commitment in action v1. The worker resolves all currently open queue rows and all current exact-name nutrition items at apply time, creating a preview-to-apply TOCTOU gap.", "Grouped mutation cannot safely reuse one action_id unchanged because nutrition_mapping_provenance has UNIQUE(action_id, affected_day); two group members affecting the same day would collapse/conflict. Deterministic child action IDs are required.", "nutrition_mapping_action_log stores only action_hash, not target digest/count or canonical payload, and processed action files are deleted. Therefore durable reconstruction of every previewed item identity is impossible with database tables alone.", "If the requirement means a durable relational per-item preview snapshot that can be enumerated later, a migration is a hard blocker. The migrations-free alternative can prove equality at apply time and retain cryptographic commitments, but cannot provide a queryable historical item-membership table.", "Canonical schema ownership is inconsistent: database/schema.sql includes mapping tables but omits food_enrichment_sources and food_nutrient_enrichment, while Sprint 6F-B creates them at runtime; Sprint 6H-A also adds nutrition_item_nutrients columns outside the snapshot. ensure_nutrition_mapping_schema itself performs runtime DDL and legacy table rewrites from the action worker.", "The YAZIO importer uses a fixed production DB_PATH rather than a mandatory explicit --db target, which weakens deterministic testability and makes safe alternate-target verification harder.", "Normalization is duplicated across yazio_nutrition_sync.py, food_nutrient_enrichment.py, read_api.py, and health_dashboard_action_worker.py. The implementations are similar but not one authoritative contract.", "Histamine rule category, tags, notes, confidence, source, and updated_at are mostly unconstrained text; aliases have no normalization column or uniqueness on normalized identity. Catalog-backed grouping must therefore detect normalized alias collisions explicitly.", "Existing conflict handling is fail-closed for normalized-name collisions, existing contradictory item classifications, stale/closed queue keys, and mismatched local SIGHi rules, but conflict reasons are not exposed as a bounded review contract and no retry/re-preview state exists.", "The enrichment script writes unmatched/review names into a private JSON report. It is a separate nutrient-enrichment workflow and must not be repurposed as the V5 histamine review queue or exposed to the browser.", "The audit was read-only at clean HEAD af91e14c21e25d2a4245757ab435c49240830113; no tracked files were modified and V4 had no diff. Runtime pytest verification was blocked because pytest is not installed." ] }