I read this as an advisory architecture review - I’ll trace the fixed-profile onboarding flow end to end, compare minimal deterministic designs, and return a decision-ready recommendation with contracts, migration impact, test seams, risks, and evidence paths without editing files.


## Decision

The `equation_sex_basis` patch is symptom-level and should be removed. Clarification eligibility must be computed from a deterministic assessment of the complete 22-field profile. The provider must not supply, add, remove, prioritize, or resolve clarification questions.

Recommended architecture: one pure field-policy module plus the existing reconciliation persistence module. No generic rules engine, plugin system, event bus, or provider reconciliation layer is warranted.

## Current architecture and defect

### Call graph

```text
external customer adapter (not present in this source tree)
  -> NutritionOnboardingService.start_or_resume()
  -> submit_answer() x22
       -> transient/workflow.json
  -> reconciliation_answers()
  -> external model/provider (caller absent from this tree)
  -> record_reconciliation(advisory, clarifications)
       -> _normalized_reconciliation_inputs()
       -> build_reconciliation()
       -> transient/workflow.json["reconciliation"]
  -> revise_reconciliation_answer()
       -> advance_reconciliation()
  -> attest_baseline()
       -> build_onboarding_baseline()
       -> baseline-candidate.json
       -> safety_hold or owner_review
  -> owner/clinical review
  -> finalize()
       -> restriction KB reconciliation
       -> deterministic calculation
       -> readiness artifacts and registry projection
```

`NutritionOnboardingStore.deliver()` also accepts a `provider`, but that is publication delivery and is unrelated to LLM reconciliation.

### Root defects

- `record_reconciliation()` trusts caller-provided clarification eligibility for every field. `PersistedClarification` validates only shape, field membership, kind, and Korean text.
- `_normalized_reconciliation_inputs()` filters only `equation_sex_basis`, based on an exact-phrase alias table. All other valid fields remain vulnerable to fabricated provider questions.
- `revise_reconciliation_answer()` validates only `equation_sex_basis`. Invalid revisions for the other 21 fields advance the cursor and can make reconciliation appear resolved.
- The maximum of three is rejection, not deterministic capping. Provider output of four questions fails the operation.
- Provider order becomes customer question order.
- A resolved record is not re-evaluated after each revision. Questions beyond the original provider list can never surface.
- Safety is evaluated after attestation, separately from reconciliation. Unknown restriction terms can pass owner review and fail only during finalization.
- `enforce_reconciliation=False` is the service default, but finalization always requires an input reconciliation digest.
- `advisory` is an unconstrained dictionary included in the authoritative digest. Equivalent answers can produce different readiness identities because provider prose changed.
- The `RECONCILING` state exists but is unused.
- There is no production provider caller in this source tree; only the service boundary and tests are visible.

## The 22-field contract

Legend:

- **V**: deterministically valid and canonical.
- **A**: malformed, unsupported, or incomplete; deterministic clarification eligible.
- **C**: deterministic cross-field contradiction; clarification eligible.
- **S**: safety or human review required; never by itself clarification eligible.
- **B**: terminal/business block; do not pressure the customer with a clarification.

All 22 keys are mandatory in `QUESTION_FIELDS`; some values are conditionally nullable.

| # | Field | Current parser/canonicalization | Deterministic disposition |
|---|---|---|---|
| 1 | `date_of_birth` | Must be a string; `date.fromisoformat`; derives age at `date.today()`; age 18-120; DOB omitted from canonical baseline. | V: ISO date and adult. A: malformed or implausible date. B: explicit under-18 result (`customer_not_adult`), not an invitation to change DOB. |
| 2 | `equation_sex_basis` | Exact NFC/whitespace/casefold alias map to `male`, `female`, `decline`. Includes canonical English, Korean forms such as `남성`, `여성입니다`, `선택 안 합니다`, and bounded confirmation phrases. Substrings are rejected. | V: one canonical enum. A: unsupported or multiple-choice prose. B: `decline`, because the only approved calculation currently throws `CalculationDeclinedError`; it must not trigger a coercive re-question. |
| 3 | `height_cm` | Pydantic `Decimal`, 120-250; serialized as canonical decimal string. No submit-time parser. | V: numeric in range. A: nonnumeric, unit-bearing unsupported text, or out of range. |
| 4 | `weight_kg` | `Decimal`, 35-300; canonical decimal serialization. | V/A as above. Used in target-direction and trajectory rules. |
| 5 | `activity_category` | Exact enum: `sedentary`, `light`, `moderate`, `very_active`, `extra_active`. | V: enum. A: anything else. |
| 6 | `activity_rationale` | String or `None`; NFC and whitespace normalization; `None -> ""`; maximum 500 characters. | V: normalized text, including blank under the current contract. A: nontext or too long. No semantic C rule is possible from the current free-text contract. |
| 7 | `goal_type` | Exact enum: `loss`, `maintain`, `gain`. | V/A. Drives target rules. |
| 8 | `target_weight_kg` | Nullable `Decimal`, 35-300. | V: null for maintain, numeric for loss/gain. A: malformed/range. C: forbidden for maintain, required for loss/gain, or wrong direction relative to `weight_kg`. |
| 9 | `target_date` | Nullable Pydantic date. Current model checks only goal/null consistency; calculation later checks date against `starts_on`. | V/A. C: forbidden for maintain, required for loss/gain, not after start, or creates unsafe trajectory. |
| 10 | `allergies` | `StructuredItems`: exact `none`/`provided`, sequence of strings, NFC/trim, casefold dedupe preserving order, 1-200 chars each, max 40, status/items consistency. | V/A. Recognized terms become exclusions. S: KB-unresolved term requires human review; it is not a clarification about medical details. |
| 11 | `intolerances` | Same structured parser. | V/A. Recognized terms follow the approved KB action; unresolved terms are S. |
| 12 | `religious_ethical_exclusions` | Same structured parser. | V/A. Enforced as declared constraints. Under the current source KB, no corresponding source-template terms exist, so provided terms become unresolved and S during finalization. |
| 13 | `disliked_foods` | Same structured parser. | V/A. No current safety or cross-field validator. Safety exclusions should dominate dislikes without another question. |
| 14 | `dietary_preferences` | Same structured parser. | V/A. No current deterministic contradiction validator. Restrictions should dominate preferences; semantic provider suspicions remain advisory-only. |
| 15 | `conditions` | Same structured parser. | V: explicit none or provided list. A: malformed structure. S: any provided condition; current KB wildcard requires human review. Do not request more clinical detail. |
| 16 | `medications` | Same structured parser. | V/A. S: any provided medication; current KB wildcard requires human review. |
| 17 | `pregnancy_breastfeeding` | `bool | None`; no custom strict parser. `False` is the only value avoiding current safety hold. | V: explicit false. A: malformed value. S: true or null/declined. No cross-rule with equation basis or identity. |
| 18 | `eating_disorder_risk` | `bool | None`; no custom parser. Omitted from finalized baseline but used to force safety hold. | V: false. A: malformed. S: true or null/declined. Never ask for diagnosis or details. |
| 19 | `cooking_access` | String/`None`, NFC/whitespace normalization, `None -> ""`, max 500. | V: normalized text, including blank under current schema. A: wrong type or too long. |
| 20 | `budget_band` | Same free-text parser. | V/A. Blank is currently schema-valid even though downstream adaptive registration expects nonblank input. That mismatch needs an explicit business decision, not an LLM judgment. |
| 21 | `meal_count` | Pydantic integer, 2-6, default 3. | V: integer in range. A: malformed or out of range. |
| 22 | `schedule_constraints` | String/`None`, normalized, max 500. | V/A. Blank is currently valid. Semantic interpretation is advisory-only. |

The model is not configured with Pydantic strict mode. Apart from DOB and equation basis, coercion is delegated to Pydantic at attestation. A deterministic clarification contract should explicitly define accepted wire forms rather than inheriting dependency-level coercion behavior.

## Source-backed cross-field rules

Only deterministic, contract-backed contradictions should generate questions:

1. `goal_type=maintain` requires both target fields to be null.
2. `goal_type in {loss,gain}` requires both target fields.
3. Loss requires `target_weight_kg < weight_kg`.
4. Gain requires `target_weight_kg > weight_kg`.
5. `target_date` must be after the bound planned start date.
6. Loss must not exceed 1% body weight per week.
7. Gain must not exceed 0.5% body weight per week.
8. The resulting calculation must stay within the approved 1500-4500 kcal range.
9. `equation_sex_basis=decline` is an explicit unsupported-calculation block, not ambiguity.
10. Restriction and medical fields are reconciled against the approved KB; unknown or human-only terms produce safety routing, not further medical questioning.

Explicitly excluded:

- No contradiction between equation basis and pregnancy.
- No inference of sex, gender, pregnancy, disease, medication effects, or eating-disorder status from another answer.
- No LLM-derived contradiction between activity category and free-text rationale.
- No semantic contradiction from cooking, budget, schedule, dislikes, or preferences unless the schema is later made structured.
- Restriction values override preferences in planning; overlap does not require the customer to restate a safety disclosure.

Exact trajectory evaluation currently depends on `starts_on`, which is supplied only at finalization. The assessment therefore needs a trusted `planned_starts_on` in session context and must bind finalization to that same value. Using `date.today()` or an approximate reference date would leave a deterministic late-failure gap.

## Recommended architecture: deterministic post-collection compiler

### Module ownership

A single pure module, conceptually `nutrition_onboarding_input_policy.py`, owns:

- All 22 field specifications.
- Accepted wire forms and canonicalizers.
- Field-local reason codes.
- Cross-field rules.
- Safety and business-block classification.
- Fixed Korean question templates.
- Deterministic priority and dedupe.
- A versioned policy manifest and digest.

Existing modules retain their responsibilities:

- `nutrition_onboarding_models.py`: canonical value models and final invariants.
- `nutrition_restriction_kb.py`: restriction term resolution.
- `nutrition_onboarding_calculations.py`: approved calculation and trajectory limits.
- `nutrition_onboarding_reconciliation.py`: persisted decision schema, digest validation, and cap.
- `nutrition_onboarding.py`: authority, locking, orchestration, revisions, and state transitions.

Do not create a generic rule DSL. A tuple of 22 immutable `FieldSpec` values and a short list of explicit cross rules is sufficient.

### Assessment contract

Field state should be orthogonal rather than a single enum:

```text
FieldAssessment
  field
  parse_state: valid | ambiguous
  canonical_value
  reason_codes
  conflict_codes
  safety_codes
  blocking_codes
```

Question eligibility is exactly:

```text
eligible =
    parse_state == ambiguous
    or conflict_codes is not empty
```

It is never affected by:

- Model output.
- Safety-only flags.
- Owner-review suggestions.
- Confidence scores.
- Provider availability.

A profile assessment contains:

```text
policy_version
policy_digest
reference_date
planned_starts_on
source_answers_digest
canonical_answers_digest | null
field_assessments[22]
clarifications[0..3]
outstanding_clarification_count
safety_reason_codes
blocking_reason_codes
state: clarifying | resolved | blocked
digest
```

### Service behavior

- `submit_answer()` uses the registry parser for every field. Uniquely canonical values are stored canonically; ambiguous raw values remain transient for correction.
- A service-owned `reconcile_inputs()` reads answers under the store lock and computes the decision. It does not accept clarification input.
- `revise_reconciliation_answer()` validates the selected field through the same registry, updates it under lock, and re-runs the complete assessment.
- It must not call `advance_reconciliation()` blindly.
- `attest_baseline()` requires a resolved record matching the current source/canonical answer digests, policy digest, dates, and KB digest.
- Safety codes then route to `SAFETY_HOLD`; they do not make reconciliation unresolved.
- Blocking codes prevent attestation without manufacturing questions.
- Reconciliation enforcement becomes mandatory; the feature flag should not allow a digestless candidate.

### Reason codes

Generic field-local codes, combined with `field`, avoid 22 duplicated enums:

- `missing_required`
- `invalid_type`
- `invalid_format`
- `unsupported_value`
- `out_of_range`
- `text_too_long`
- `item_text_invalid`
- `too_many_items`
- `status_items_mismatch`

Cross-field codes:

- `target_forbidden_for_maintenance`
- `target_required_for_goal`
- `target_direction_conflict`
- `target_date_not_after_start`
- `goal_trajectory_unsafe`
- `calorie_projection_out_of_bounds`

Safety and restriction codes:

- `allergy_exclusion_required`
- `intolerance_exclusion_required`
- `religious_ethical_exclusion_required`
- `restriction_term_unresolved`
- `condition_requires_clinical_review`
- `medication_requires_clinical_review`
- `pregnancy_breastfeeding_requires_clinical_review`
- `eating_disorder_risk_requires_clinical_review`

Blocking/system codes:

- `customer_not_adult`
- `calculation_basis_declined`
- `reconciliation_policy_unavailable`
- `restriction_kb_unavailable`

Only field-local and cross-field codes produce questions.

### Dedupe, cap, and order

- Evaluate all fields in exact `QUESTION_FIELDS` order.
- Field-local parse failures suppress cross-rules involving that field until it is parseable.
- Deduplicate by field: at most one active question per field.
- Within a field, precedence is missing, type/format, unsupported/range, then contradiction.
- Sort by `QUESTION_FIELDS`; never use provider order.
- Cap at three active questions per assessment wave.
- Persist `outstanding_clarification_count`, including questions beyond the visible cap.
- Re-run the whole assessment after every answer. The next deterministic issue then enters the first-three window.
- Reconciliation is resolved only when the total outstanding count is zero.
- Safety flags do not consume the cap.
- A terminal block suppresses further questions.

This fixes the current false-resolution failure when more than three independent defects exist.

### Model boundary

The simplest safe boundary is no model in the clarification path.

An optional advisory summary may be generated after deterministic assessment, but:

- It receives only an allowlisted projection such as canonical goal/activity codes, non-sensitive counts, and reason codes.
- It does not receive raw DOB, condition names, medication names, pregnancy status, eating-disorder status, or unsupported raw answers.
- It cannot return clarification fields or questions.
- Its output is ephemeral presentation data.
- It is not persisted in the authoritative reconciliation record.
- It is not included in `input_reconciliation_digest`.
- Timeout, malformed output, or provider unavailability cannot change state or question order.

Fixed Korean templates should produce clarification wording. This also removes provider prose from authoritative digests.

## Persistence and digest semantics

### Current behavior

- `workflow.json` stores raw answers, arbitrary advisory output, questions, cursor, and reconciliation digest.
- The v1 digest covers provider advisory and question wording.
- `baseline-candidate.json["digest"]` covers only the baseline payload, not its `input_reconciliation_digest` or `source_answers`.
- Finalization checks the reconciliation digest only as a 64-character lowercase hash and propagates it.
- The original reconciliation record disappears with the transient workflow, so readiness cannot independently validate what the opaque digest represented.
- `baseline-candidate.json` retains `source_answers`, including DOB and medical answers, after successful finalization.

### Required v2 semantics

- `source_answers_digest`: current exact transient answer map, used for revision CAS.
- `canonical_answers_digest`: present only when all fields parse; covers exactly the canonical 22-field map.
- `policy_digest`: canonical JSON digest of the explicit policy manifest, KB digest, calculation method/version, templates, field order, `reference_date`, and `planned_starts_on`.
- `decision_digest`: canonical digest of deterministic assessment data only; no provider content.
- `input_reconciliation_digest`: the resolved `decision_digest`.
- Candidate envelope must bind the baseline digest, canonical answers digest, reconciliation digest, and policy digest. The current unbound top-level wrapper is insufficient.
- Preserve a compact permanent `input-reconciliation-v2.json` containing no raw answers, so readiness can validate the propagated digest after transient deletion.
- Finalization must verify the permanent decision artifact rather than accepting a syntactically valid hash.
- Identical canonical inputs, context, KB, and policy produce the same digest.
- A rule/KB/start-date change intentionally changes the digest and requires reassessment.
- On successful READY transition, delete the transient workflow and `baseline-candidate.json`; permanent artifacts already contain the approved minimum.

## Privacy constraints

Existing strengths:

- Exact customer/owner route checks.
- Current consent and disabled-customer checks.
- Redacted status objects.
- Owner-only directories and files, symlink rejection, hard-link rejection, atomic private writes.
- DOB excluded from the canonical baseline.
- Eating-disorder risk excluded from the finalized baseline.

Required corrections:

- Do not expose the complete `reconciliation_answers()` dictionary to an advisory provider.
- Never echo condition, medication, pregnancy, eating-disorder, or DOB values into question text.
- Do not infer sensitive attributes across fields.
- Treat `decline` and null safety answers as explicit privacy choices: route safely, do not repeatedly solicit details.
- Remove arbitrary persisted provider advisory content.
- Purge `baseline-candidate.json` after finalization; it currently defeats DOB minimization.
- Keep reason codes non-diagnostic and bounded.
- Treat answer digests as sensitive local metadata; low-entropy fields make unsalted hashes guessable.

## Alternative design: strict validation at each question

Every adapter submits only canonical structured values. `submit_answer()` rejects invalid input immediately and returns a fixed correction for that field. Only cross-field and safety assessment remains after field 22.

Trade-offs:

- **Coupling:** tightly couples external chat/UI adapters to canonical wire forms and response handling.
- **Testability:** excellent; ambiguity never enters persistence.
- **Migration cost:** higher because every customer-facing adapter must change concurrently, and no such adapter exists in this source tree.
- **Failure mode:** natural-language chat answers are rejected more often unless each field gains deterministic compatibility aliases.
- **Benefit:** smallest persisted reconciliation state and no multi-wave parser errors.
- **Limitation:** target and safety cross-rules still require the complete profile and planned start date.

This is preferable for a controlled form UI, but the post-collection compiler is the smaller migration for the current service API.

## Migration impact

- Keep `QUESTION_FIELDS` order unchanged.
- Replace the equation-specific helper and filtering path with the 22-field registry.
- Introduce `nutrition_input_reconciliation_v2`; do not reinterpret v1 digests.
- Existing READY artifacts remain immutable and grandfathered.
- Existing owner-approved/finalizing candidates may finish under v1 only if their current checks pass; they should not be silently recomputed.
- Incomplete v1 transient sessions should be deterministically reassessed into v2 before further clarification. Partially answered provider question cursors cannot be safely translated without re-evaluation.
- Remove caller ownership of `clarifications`; this is an API break and warrants a version bump.
- The external provider caller is not in this repository, so its migration surface cannot be enumerated from this source.
- Bind `planned_starts_on` earlier in the session or retain deterministic late owner revision. Exact current trajectory semantics cannot be moved earlier without that context.
- Readiness validation needs a versioned branch for the new permanent reconciliation artifact.
- `enforce_reconciliation` should cease being optional for new sessions.

## Exact test seams

### Pure field-policy tests

- Registry contains exactly the 22 `QUESTION_FIELDS` in the same order.
- One valid, one malformed, and every closed boundary for each field.
- Canonicalization is idempotent.
- All exact equation aliases remain compatibility-tested; substring and mixed-option text remain ambiguous.
- Numeric parsers reject booleans and unsupported units.
- Structured status/items consistency, NFC normalization, dedupe order, lengths, and count.
- Free-text blank behavior is pinned explicitly rather than inherited accidentally from Pydantic.

### Cross-rule tests

- Maintain with either target populated.
- Loss/gain with either target missing.
- Loss target equal to or above current weight.
- Gain target equal to or below current weight.
- Target date equal to/before start.
- Exact 1% loss and 0.5% gain boundaries accepted; one increment above rejected.
- Calorie floor/ceiling failures map to deterministic reason codes.
- Equation decline produces block and zero questions.
- No equation/pregnancy cross-inference.
- Free-text semantic disagreement never becomes a question.

### Safety tests

- Conditions and medications produce safety codes but no clarification when structurally valid.
- Pregnancy/eating-disorder true and null produce safety codes, not detail requests.
- Known restriction term follows KB action.
- Unknown restriction term produces human-review safety code.
- Missing/expired KB produces a system block, not a customer question.
- Safety-only profile resolves reconciliation and then enters `SAFETY_HOLD`.

### Ordering and persistence tests

- Four or more invalid fields produce the first three in contract order and correct outstanding count.
- Correcting the first issue re-evaluates and surfaces the fourth.
- Multiple issues for one field yield one active question.
- No state becomes resolved while outstanding count is nonzero.
- Stale source-answer digest rejects revision.
- Same inputs/context/policy produce identical decision digest.
- Different model summaries do not change state, questions, or digest.
- Tampered policy, question, safety code, context, or digest is rejected.
- v1 transient reassessment and immutable READY compatibility are pinned.

### Service integration tests

- Every revision field, not only equation basis, is validated before persistence.
- Complete valid profile reaches attestation with zero clarification even if a mock provider proposes all 22.
- Provider timeout, exception, malformed output, or contradictory advice has no behavioral effect.
- Attestation rejects a stale policy/KB/start-date decision.
- Candidate envelope tampering with `input_reconciliation_digest` fails.
- Final readiness validates the permanent reconciliation artifact.
- Successful finalization removes transient answers and candidate source answers.

### Privacy tests

- Advisory projection excludes DOB and medical values.
- Status and questions do not contain answer values.
- Provider output is absent from authoritative private artifacts.
- Final permanent reconciliation contains digests and policy metadata, not raw answers.
- Existing 0700/0600, symlink, hard-link, and route-authority tests remain applicable.

## Main risks

1. **Start-date ownership:** exact trajectory eligibility cannot be decided until `planned_starts_on` is bound.
2. **Wire compatibility:** current Pydantic coercion is broader and less explicit than a strict field registry; tightening it may expose adapter dependencies.
3. **Decline semantics:** the schema permits equation-basis decline while the only calculation cannot proceed. Product policy must choose terminal no-plan or an approved alternate method.
4. **Restriction inconsistency:** finalization reconciles the KB source template, while readiness runtime generation rewrites some actions. One authoritative KB projection must be selected.
5. **Legacy evidence:** old readiness artifacts carry only an opaque reconciliation hash; their decision contents cannot be reconstructed.
6. **External integration unknown:** no production LLM/provider adapter is present in this source, so caller migration must be located outside this tree.

## Evidence paths

- Field order and examples: `checkin_cli/nutrition_onboarding_contract.py::QUESTION_FIELDS`
- Current aliases and schema parsing: `checkin_cli/nutrition_onboarding_models.py`
- Equation-only patch and service call graph: `checkin_cli/nutrition_onboarding.py`
- Provider-owned v1 clarification schema/cap/digest: `checkin_cli/nutrition_onboarding_reconciliation.py`
- Current safety hold: `checkin_cli/nutrition_onboarding_authority.py::requires_safety_hold`
- Goal direction/rate/calorie rules: `checkin_cli/nutrition_onboarding_calculations.py`
- Restriction resolution and human-only categories: `checkin_cli/nutrition_restriction_kb.py`
- Approved KB terms: `checkin_cli/policies/nutrition-restriction-kb-template-v1.json`
- Candidate and readiness digest propagation: `checkin_cli/nutrition_onboarding_finalization.py`
- Private persistence controls: `checkin_cli/nutrition_onboarding_fs.py`
- Current symptom-level regression tests: `tests/test_nutrition_onboarding.py`
- Parser contracts: `tests/test_nutrition_onboarding_models.py`
- Calculation safety boundaries: `tests/test_nutrition_onboarding_calculations.py`


I read this as a strengthened advisory review - I’ll extend the deterministic onboarding design into one exact automated golden-path rehearsal, trace the activation/check-in/approval/delivery/cleanup surfaces implicated by the red-team block, and define the minimum evidence bundle that must pass before the single final live run.


I read this as architecture finalization - I’ll convert the red-team BLOCKs into one recommended design and a pass/fail rehearsal evidence gate.

## Verdict: NO-GO / BLOCKED

Candidate `573d19e...d5730d7` is not eligible for another live run:

- Observer v7 writes a `SUBSCRIBED` declaration, then sleeps until timeout; it observes no lifecycle events.
- Launch evidence terminates at `READY_CUSTOMER_CLAIM` after sending the invite.
- Preview uses isolated in-memory state and cannot prove profile persistence.
- Reconciliation publication can use the transient provider result instead of the committed filtered record.
- `op_rev` binds to `QUESTION_FIELDS[0]`, not the clarification being revised.
- Approval edits Telegram before the approved-card projection is safely persisted.
- Cleanup v6 has `execution_permission: false`; its inherited cleanup/rollback paths can compete or reverse each other.

Any correction changes the candidate digest, so all current dry-run receipts become predecessor evidence only.

## Architecture decision

### Recommended: one root lifecycle aggregate with immutable blobs and transactional outboxes

Create one private, candidate-bound lifecycle aggregate per rehearsal:

```text
data/lifecycles/<lifecycle_id>/
  aggregate.json
  aggregate.lock
  blobs/sha256/<digest>
```

`aggregate.json` is the sole phase authority and contains:

- Candidate, customer, session, actor and route bindings.
- Monotonic aggregate revision.
- Current phase.
- Digests of immutable onboarding, reconciliation, baseline, check-in, draft, approval, delivery and cleanup blobs.
- Pending external side-effect intents and their attempts/receipts.
- Transition history and hash-chain head.
- Holds and exact deterministic reason codes.

Each transition follows:

1. Write an immutable content-addressed blob.
2. Lock and CAS the expected aggregate revision.
3. Atomically reference the blob, change phase, and enqueue any side-effect intent.
4. `fsync` file and directory.
5. Notify observers after the commit.

An unreferenced blob is inert and collectible. Existing domain files become recoverable projections of the aggregate, not independent phase authorities.

### Rejected alternative: patch the six individual paths

Locally fixing publication, `op_rev`, projection recovery, observer and cleanup would be smaller, but would preserve independent ledgers and cross-file split-brain windows. It cannot strongly prove one revision-bound lifecycle from invite to cleanup.

## Canonical lifecycle

```text
PREPARED
-> OBSERVER_READY
-> INVITE_INTENT
-> INVITE_RECEIPTED | INVITE_UNKNOWN
-> CUSTOMER_INGRESS_COMMITTED
-> ONBOARDING_COLLECTING
-> RECONCILIATION_COMMITTED
-> CUSTOMER_ATTESTED
-> OWNER_APPROVED
-> READY
-> ACTIVATION_INTENT
-> ACTIVE
-> CHECKIN_COMMITTED_AND_GENERATION_QUEUED
-> GENERATING
-> DRAFT_READY
-> APPROVED_CARD_PENDING
-> APPROVED_CARD_PROJECTED
-> DELIVERY_CAPABILITY_ISSUED
-> DELIVERY_ATTEMPTED
-> DELIVERY_RECEIPTED | DELIVERY_UNKNOWN
-> SENT_AUDITED
-> CLEANUP_PREPARED
-> ARCHIVED
-> DISABLED
-> QUIESCED
-> CLEANUP_COMPLETE
-> SEALED
```

Unknown invite or delivery outcomes prohibit blind retry.

## Deterministic clarification compiler

Keep the previously recommended 22-entry `FIELD_SPECS` registry. The compiler exclusively owns:

- Parsing and canonicalization.
- Field and cross-field validation.
- Clarification eligibility and reason codes.
- Dedupe, ordering and display cap.
- Fixed Korean question templates.
- Holds and state transitions.

The model may return advisory analysis, but its questions and prose are never persisted as mandatory clarifications or published.

The authoritative reconciliation v2 digest binds:

```text
canonical answers
policy/schema digest
restriction-KB digest
reference date
planned_starts_on
all deterministic issues
visible issue IDs
hold decisions
```

It excludes provider prose and advisory output. `planned_starts_on` must be fixed before reconciliation and attestation.

After every revised answer, recompute the complete issue set. The three-question cap limits presentation only; it never discards unresolved issues.

## Exact 22-answer golden fixture

Fixed dates:

- `reference_date`: `2026-08-18`
- `planned_starts_on`: `2026-08-24`

| Field | Exact customer answer | Canonical value |
|---|---|---|
| `date_of_birth` | `1990년 1월 15일입니다.` | `1990-01-15`, age 36 |
| `equation_sex_basis` | `네, 남성입니다.` | `male` |
| `height_cm` | `키는 180cm입니다.` | `"180"` |
| `weight_kg` | `현재 체중은 80kg입니다.` | `"80"` |
| `activity_category` | `보통 활동량입니다.` | `moderate` |
| `activity_rationale` | `주 3회 60분 근력 운동을 하고 평일에는 하루 8천 보 정도 걷습니다.` | normalized text |
| `goal_type` | `현재 체중을 유지하고 싶습니다.` | `maintain` |
| `target_weight_kg` | `유지가 목표라 목표 체중은 정하지 않겠습니다.` | `null` |
| `target_date` | `유지가 목표라 목표 날짜도 정하지 않겠습니다.` | `null` |
| `allergies` | `음식 알레르기는 없습니다.` | `none, []` |
| `intolerances` | `음식 불내증은 없습니다.` | `none, []` |
| `religious_ethical_exclusions` | `종교적 또는 윤리적으로 제외하는 음식은 없습니다.` | `none, []` |
| `disliked_foods` | `싫어해서 피하는 음식은 없습니다.` | `none, []` |
| `dietary_preferences` | `특별한 식단 선호는 없습니다.` | `none, []` |
| `conditions` | `진단받은 질환은 없습니다.` | `none, []` |
| `medications` | `복용 중인 약이나 보충제는 없습니다.` | `none, []` |
| `pregnancy_breastfeeding` | `임신 또는 수유에 해당하지 않습니다.` | `false` |
| `eating_disorder_risk` | `섭식장애 위험이나 과거력은 없습니다.` | `false` |
| `cooking_access` | `가스레인지, 전자레인지, 냉장고를 사용할 수 있고 기본 조리가 가능합니다.` | normalized text |
| `budget_band` | `식비 예산은 보통입니다.` | `보통` |
| `meal_count` | `하루 세 끼를 먹습니다.` | `3` |
| `schedule_constraints` | `평일 점심은 12시, 저녁은 운동 후 8시쯤이며 그 외 제약은 없습니다.` | normalized text |

Expected compiler result:

```text
clarification issues: 0
visible questions: 0
safety holds: 0
human-review requirements: 0
```

## Six red-team corrections

1. **Observer**
   - Install an actual inotify/application-event subscription before reading the initial aggregate revision.
   - Persist watcher identity, starting revision and cursor in readiness evidence.
   - Block on the event descriptor with a bounded deadline; no sleeps or polling.
   - Replay committed aggregate revisions to close notification races.

2. **Launcher**
   - Invite receipt means only `INVITE_RECEIPTED`.
   - Golden-path PASS requires a Telegram update entering the production adapter and committing `CUSTOMER_INGRESS_COMMITTED`.
   - Preview or direct service calls cannot satisfy this transition.

3. **Reconciliation publication**
   - Commit reconciliation and publication intent in one aggregate transition.
   - Publisher accepts only a reconciliation digest, reloads the committed blob, and renders that record.
   - The transient provider object is discarded after advisory storage.

4. **Revision binding**
   - Bind revision operations to:
     `lifecycle_id + issue_id + exact field + answers_digest_before + reconciliation_digest`.
   - Remove every use of `QUESTION_FIELDS[0]`.
   - CAS the current issue and recompute the compiler after the revision.

5. **Approved-card projection**
   - Approval atomically creates `APPROVED_CARD_PENDING`.
   - The projector edits the exact Telegram message.
   - Success or Telegram’s exact “not modified” response commits `APPROVED_CARD_PROJECTED`.
   - Edit failure remains recoverable pending/unknown; no capability can be issued.
   - Restart retries the same idempotent edit, never sends a replacement customer message.

6. **Cleanup**
   - One lock, one monotonic cleanup journal and one controller.
   - Archive first; then freeze ingress, disable customer and delivery, reconcile outboxes, stop owned processes, seal, and prune only permitted transient data.
   - Failure resumes forward from its committed phase. It never re-enables the customer or restores delivery.
   - A concurrent cleanup returns the current phase or existing receipt.

To remove the current circular seal dependency:

1. Observer produces pre-cleanup lifecycle and membership seals.
2. Cleanup archives, disables and quiesces.
3. Observer records `CLEANUP_COMPLETE` and produces final seals.
4. Cleanup verifies immutable publication, prunes ephemeral state, and writes its terminal receipt.

## One-pass automated rehearsal

Run against the newly sealed installed wheels and an ephemeral profile. Use the production Telegram/runtime handlers and real profile persistence. Fake only external Telegram/provider I/O, preserving their receipt and unknown-outcome contracts.

Sequence:

1. Subscribe observer and await its readiness event.
2. Persist invite intent and fake Telegram invite receipt.
3. Feed a serialized Start update through the actual adapter.
4. Feed consent plus the exact 22 messages above through adapter ingress.
5. Return adversarial provider advisory containing fabricated DOB and sex-basis questions.
6. Assert committed reconciliation has zero issues and publication contains none of that text.
7. Attest, owner-approve, finalize, and explicitly activate.
8. Submit one complete daily nutrition check-in through the real wizard:
   `80 kg`, `2200 kcal`, `C220/P150/F80`, three meals, `2.5 L`, `7.5 h`, sleep/condition `4`, normal digestion, low stress, 60-minute strength session, no note.
9. Prove check-in and unique generation job commit together.
10. Generate one draft, approve it and project the approved card.
11. Issue one capability, consume it before transport, record one delivery receipt, and reach `SENT_AUDITED`.
12. Replay the send callback and prove transport count remains one.
13. Execute cleanup to final immutable seals and verify no active residual state.

Tests subscribe to exact commit signals before triggering operations and await them with bounded timeouts. Fixed sleeps are forbidden.

## Mandatory adversarial branches

The same test invocation must also prove:

- Provider-invented questions never become authoritative or visible.
- Ambiguous height such as `180 또는 185cm` produces only the schema-owned fixed issue.
- Goal-direction conflict is deterministic.
- Conditions, medication, pregnancy or eating-disorder risk produce a hold, not a sensitive clarification.
- Stale answer/reconciliation digests mutate nothing.
- A later-field revision records that exact field, never `date_of_birth`.
- Crash after reconciliation commit recovers publication from the committed record.
- Provider generation failure creates no draft.
- Telegram card-edit failure blocks capability issuance and recovers after restart.
- Unknown delivery consumes capability and prohibits another send.
- Cleanup failure remains truthfully incomplete and resumes forward.
- Two cleanup callers create one archive and one receipt.
- Preview output cannot satisfy any acceptance transition.

## Minimum evidence bundle

1. **`candidate-seal.json`**
   New candidate digest, source/wheel/inventory/config/harness digests and installed-byte proof.

2. **`fixture-manifest.private.json`**
   Exact 22 inputs, dates, canonical map and expected digests; mode `0600`. Public evidence exposes only its digest.

3. **`observer-subscription.json`**
   Watcher identity, subscribed directory/source, start revision and readiness commit preceding invite intent.

4. **`lifecycle-events.jsonl` plus final seal**
   Hash-chained actual revisions from invite through cleanup. Each row carries artifact and receipt digests but no raw health text.

5. **`transport-receipts.json`**
   Invite receipt, card-edit receipt, capability issuance/consumption, exactly one customer delivery receipt and replay count.

6. **`adversarial-matrix.json`**
   One independently verifiable result for every branch above, including before/after aggregate revisions.

7. **`cleanup-receipt.json` and archive manifest**
   Exact retained, archived, deleted, still-present and not-delivered sets; disabled registry proof; empty pending jobs/outboxes; owned-process exit; final lifecycle and membership seals; immutable-retention publication digest.

8. **`independent-verification.json`**
   Recomputes every digest and transition. It must reject declarations such as `PASS_DRY_RUN` unless backed by the corresponding committed artifact and receipt.

## Final-live-run gate

A final live request is permitted only when:

- The complete automated rehearsal passes once.
- Related tests, full build and independent verifier pass against the same candidate digest.
- Cleanup finishes as `SEALED`, with no unexplained residue.
- The evidence bundle proves zero provider-controlled clarification fields and exactly one delivery.

No live run should be requested now. No files, state, Telegram operations or tests were changed or executed during this architecture review.