I read this as an independent read-only release audit - I’ll inspect the implementation and fixtures, trace every field and invariant, rerun focused suites, classify legacy failures, and return a severity-first PASS/BLOCK report.


I’ve located the compiler seams and the new contract suite. I’m now comparing the source against its pre-change build copy and tracing parser, model, reconciliation, production, and preview behavior before exercising unsupported variants.


The source changed underneath the audit during verification (the compiler now requires an explicit reference date and has expanded validation). I’m resetting conclusions to the current files and rerunning the matrix serially so the report reflects the actual handoff, not the superseded snapshot.


I read this as a completed independent release audit - I inspected the current handoff, exercised all 22 fields, reran focused and legacy suites, and made no changes.

# Verdict: BLOCK

## Severity-first findings

### HIGH — Invalid sensitive values silently resolve

`conditions`, `medications`, `pregnancy_breastfeeding`, and `eating_disorder_risk` are excluded from normal validation issues regardless of whether the value is a valid sensitive disclosure.

- `conditions={"status":"weird","items":[]}`: no issue, no hold.
- `pregnancy_breastfeeding="maybe"`: no issue, no hold.
- `eating_disorder_risk="maybe"`: no issue, no hold.
- `pregnancy_breastfeeding=1`: accepted through baseline coercion but does not trigger `_sensitive(... is True)`.

Cause:

- `nutrition_onboarding_clarification_policy.py:116-119` narrowly recognizes holds.
- `nutrition_onboarding_clarification_policy.py:179-184` excludes every sensitive-field model error.
- `nutrition_onboarding_clarification_policy.py:232-238` only removes reasons for actual holds, but excluded errors were never added.

Malformed medication data can be worse: `{"status":"provided","items":[]}` becomes a hold despite being structurally invalid.

### HIGH — Provider availability still controls deterministic compilation

The provider is called before the compiler-backed record exists:

- Production: `telegram_nutrition_onboarding_runtime_publication.py:83-102`.
- Preview: `telegram_nutrition_onboarding_preview_completion.py:120-131`.

An unavailable preview provider returns `phase="failed"` without compiling otherwise-valid answers. Production catches provider failure at lines 136-145 and publishes the generic attestation card without creating the required deterministic reconciliation record.

This violates “provider advisory only / no provider control.” The new tests only exercise malicious successful providers, not unavailable providers.

### HIGH — Owner revision binding is not exact

`NutritionOnboardingService.review_as_owner()` checks answer and reconciliation digests, then only checks that `revision_issue_id` has length 64:

- `nutrition_onboarding.py:337-357`.

It never verifies that:

- the issue ID exists in the bound reconciliation,
- the issue belongs to `revision_field`,
- the issue is currently visible/actionable,
- the ID is lowercase hexadecimal.

A caller with valid answer/reconciliation digests can supply an arbitrary 64-character ID and any canonical field. Customer clarification revision has stronger first-issue validation at `nutrition_onboarding.py:589-602`; owner revision does not.

### MEDIUM — Structured ambiguity is silently accepted

`_is_ambiguous()` only examines top-level strings:

- `nutrition_onboarding_clarification_policy.py:112-113`.

All seven structured fields accept:

```python
{"status": "provided", "items": ["달걀 또는 우유"]}
```

without a clarification issue. `conditions` and `medications` become holds, but the five non-sensitive structured fields silently resolve.

Affected fields:

- `allergies`
- `intolerances`
- `religious_ethical_exclusions`
- `disliked_foods`
- `dietary_preferences`
- `conditions`
- `medications`

### MEDIUM — Boundary parsing silently changes contradictory or multi-value answers

At `telegram_nutrition_onboarding_copy.py:122-200`:

- Activity uses substring matching at lines 145-151. `"보통 운동을 자주 합니다"` becomes `moderate`, contrary to the pre-existing exact-boundary contract.
- Any structured sentence containing `"없습니다"` becomes `status=none` at lines 138-143, even if later text discloses an item.
- Boolean text containing `"해당하지 않습니다"` or `"없습니다"` becomes `False` at lines 173-178, even if the rest of the answer contradicts it.
- Date parsing uses the first regex match at lines 124-128 and ignores a second date unless the exact compiler ambiguity marker survives.
- Meal parsing takes the first matching count at lines 192-199.

The activity regression is already caught by `test_telegram_nutrition_onboarding_copy.py:30-43`.

### MEDIUM — Canonical output types are not enforced by the compiler

`_canonical_answers()` only canonicalizes `equation_sex_basis`:

- `nutrition_onboarding_clarification_policy.py:103-109`.

The compiler accepts and preserves alternate coercible types:

- `height_cm=180.0`
- `weight_kg=80`
- `meal_count="3"`
- `pregnancy_breastfeeding=1`
- structured `items=()`

All produce no issue, while `ClarificationPolicyResult.answers` retains the noncanonical type. Parser-originated Golden values are canonical, but service/API-originated values are not.

### MEDIUM — Issue IDs are deterministic per snapshot, not stable across unrelated revisions

Issue IDs include the complete `answers_digest`:

- `nutrition_onboarding_clarification_policy.py:187-202`.

Changing a valid unrelated field rotates every outstanding issue ID. Independent exercise confirmed both unchanged `cooking_access` and `budget_band` issues received new IDs after only `schedule_constraints` changed.

Because the revision binding already carries `answers_digest` and `reconciliation_digest`, coupling issue identity to the whole answer set is unnecessary and conflicts with stable issue identity across recomputation.

### LOW — Preview reference-date behavior is not production-parity exact

Production persists one `reference_date` and reuses it during recomputation:

- `nutrition_onboarding_reconciliation.py:142-153`.

Preview calls `date.today()` on every completion:

- `telegram_nutrition_onboarding_preview_completion.py:132-136`.

A clarification crossing midnight can compile age-dependent DOB validity against a different date than its first pass or production.

`allow_clarifications` is also accepted but ignored at lines 114-142. The preview flow deliberately passes `False` after clarification at `telegram_nutrition_onboarding_preview_flow.py:148-165`, but unresolved answers can restart clarification anyway.

## Independent 22-field matrix

`Fixture` means the exact Golden Korean phrase parsed to its asserted value. All 22 passed that narrow check.

| Field | Golden canonical value/type | Invalid / ambiguity / hold audit | Result |
|---|---|---|---|
| date_of_birth | `"1990-01-15"` string | Invalid DOB now issues correctly; multiple-date parser can silently choose first | Partial |
| equation_sex_basis | `"male"` string | Invalid and marker ambiguity issue correctly | PASS |
| height_cm | `"180"` string | Range/ambiguity issue; numeric float accepted noncanonically | Partial |
| weight_kg | `"80"` string | Range/ambiguity issue; integer accepted noncanonically | Partial |
| activity_category | `"moderate"` string | Invalid issues; broad substring false-positive | BLOCK |
| activity_rationale | string | Typed invalid and marker ambiguity issue correctly | PASS |
| goal_type | `"maintain"` string | Invalid issues; cross-field ownership corrected | PASS |
| target_weight_kg | `None` | Required/forbidden/direction rules work; numeric alternate types remain | Partial |
| target_date | `None` | Invalid, required, and forbidden cases issue correctly | PASS |
| allergies | none-structure | Invalid structure issues; structured ambiguity and tuple canonicalization fail | BLOCK |
| intolerances | none-structure | Same | BLOCK |
| religious_ethical_exclusions | none-structure | Same; exact fixture itself contains “또는” but parser erases it via broad none match | BLOCK |
| disliked_foods | none-structure | Invalid structure issues; structured ambiguity fails | BLOCK |
| dietary_preferences | none-structure | Invalid structure issues; structured ambiguity fails | BLOCK |
| conditions | none-structure | Valid disclosure holds; malformed values silently resolve | BLOCK |
| medications | none-structure | Valid disclosure holds; malformed provided value can hold despite invalid shape | BLOCK |
| pregnancy_breastfeeding | `False` bool | `True` holds; malformed/coercible values silently resolve | BLOCK |
| eating_disorder_risk | `False` bool | `True` holds; malformed/coercible values silently resolve | BLOCK |
| cooking_access | string | Typed invalid and marker ambiguity issue correctly | PASS |
| budget_band | `"보통"` string | Typed invalid works; parser uses broad substring | Partial |
| meal_count | `3` int | Range/marker ambiguity works; numeric string accepted and multi-count sentence may choose first | Partial |
| schedule_constraints | string | Typed invalid and marker ambiguity issue correctly | PASS |

## Cross-field rules

The current implementation correctly improved field ownership:

- Maintain with target weight/date issues the target fields.
- Loss/gain missing targets issues `target_weight_kg` and/or `target_date`.
- Loss target at/above current and gain target at/below current issue `target_weight_kg`.

See `nutrition_onboarding_clarification_policy.py:137-159`.

## Privacy and provider-copy checks

PASS:

- Fixed question copy exists for every `QUESTION_FIELDS` entry.
- Issue payloads contain only ID, field, reason, and fixed copy.
- No raw sensitive text appears directly in issue IDs or message evidence.
- Provider-authored clarification questions are removed from authoritative issues.
- Persisted issue ordering follows `QUESTION_FIELDS`; visible IDs are the first three.
- No remaining production `QUESTION_FIELDS[0]` path was found.

Caveat: provider advisory content is persisted mostly intact at `nutrition_onboarding_reconciliation.py:92-105`. It is not published by the new authoritative rendering, but a provider can echo sensitive source data into the persisted advisory section.

## Focused verification

Current source, run serially with the profile source first on `PYTHONPATH`:

- New deterministic suite: **11 passed**
- Profile onboarding/model/store suites: **61 passed**
- Legacy reconciliation/copy/preview selection: **36 passed, 12 failed**

## Classification of the 12 legacy failures

### Obsolete expectations: 11

- Four measurement cases expect floats; the Golden contract now requires canonical decimal strings.
- Production provider summary publication expectation.
- Production provider-authored ambiguity question expectation.
- Clarification reply without issue/reconciliation binding.
- Restart from legacy v1 clarification cursor.
- Preview provider-authored ambiguity.
- Owner QA provider-authored clarification.
- Final preview provider summary publication.

These conflict with deterministic fixed-copy, v2 records, or exact binding.

### Real regression: 1

`tests/gateway/test_telegram_nutrition_onboarding_copy.py:30-43` correctly expects `"보통 운동을 자주 합니다"` to remain unrecognized. The new substring matcher converts it to `moderate`, broadening the parser beyond an exact bounded activity answer.

No files, Git state, plans, todos, services, or evidence were changed.