---
phase: 12
plan: 02
type: execute
wave: 2
depends_on: [1]
files_modified: []
autonomous: false
requirements: [OFFER-11, OFFER-15, OFFER-16, OFFER-17, OFFER-18]

must_haves:
  truths:
    - "Migration 0008 (offer_macros/offer_micros additive columns + offer_tier_services table) has been applied to the production database"
    - "Plan 03's query layer and Plan 04/05's UI can read/write tier_letter, public_price, offer_macros category/ticket/transformation-promise fields, and offer_tier_services against real production data"
  artifacts: []
  key_links:
    - from: "scripts/push-12-offer-tier-schema.ts"
      to: "production Postgres (178.104.27.55:54321, via SSH+docker exec)"
      via: "manual operator run of the idempotent push script against prod DATABASE_URL"
      pattern: "tier_letter|public_price|offer_tier_services"
---

<objective>
BLOCKING checkpoint: apply migration 0008 (`src/db/migrations/0008_offer_tier_schema.sql`,
created in Plan 01) to the production database. Per CLAUDE.md Data Safety and project memory
(`project_clienthub_deploy_db.md`: Gitea→Coolify, prod Postgres reachable only via SSH+docker
exec, port 54321 firewalled from outside), this migration MUST be applied to prod BEFORE
Plans 03-05 (query layer, server actions, and UI that read/write `tier_letter`, `public_price`,
the new `offer_macros` columns, and `offer_tier_services`) are exercised against production
data.

Purpose: Unblock Wave 2/3 schema-dependent code per the project's locked migration convention
(migrations applied to prod before schema-dependent code ships).
Output: Migration 0008 applied to production; confirmation that `offer_tier_services` exists
and the new columns are queryable on the live DB.
</objective>

<execution_context>
@$HOME/.claude/get-shit-done/workflows/execute-plan.md
@$HOME/.claude/get-shit-done/templates/summary.md
</execution_context>

<context>
@.planning/PROJECT.md
@.planning/STATE.md
@scripts/push-12-offer-tier-schema.ts
@src/db/migrations/0008_offer_tier_schema.sql
</context>

<tasks>

<task type="checkpoint:human-action" gate="blocking">
  <name>Task 1: Apply migration 0008 to production database</name>
  <what-built>
    Plan 01 created `src/db/migrations/0008_offer_tier_schema.sql` (additive-only: 11
    `ALTER TABLE ... ADD COLUMN IF NOT EXISTS` statements on `offer_macros`/`offer_micros`, a
    guarded `CHECK` constraint on `offer_micros.tier_letter`, and
    `CREATE TABLE IF NOT EXISTS offer_tier_services` + index) and the idempotent push script
    `scripts/push-12-offer-tier-schema.ts` that applies it.

    This script CANNOT be run from this environment: the production DATABASE_URL
    (`178.104.27.55:54321`) is firewalled and reachable only via SSH to the production host
    (per `project_clienthub_deploy_db.md`). Opening a root SSH session to the shared
    production host is outside this agent's authorization.
  </what-built>
  <how-to-verify>
    On your machine (or via your existing SSH access to the production host), run the
    migration push script against the production database, then confirm the new schema
    objects exist:

    1. SSH to the production host and exec into the Postgres container (same pattern used
       for the Phase 11 migration apply):
       ```bash
       ssh root@178.104.27.55
       docker exec -it <postgres-container-name> psql -U clienthub -d clienthub
       ```
       OR, if you prefer running the idempotent TypeScript push script with
       `DATABASE_URL` pointed at prod (via SSH port-forward / tunnel, same as Phase 11):
       ```bash
       npx tsx scripts/push-12-offer-tier-schema.ts
       ```

    2. If using `psql` directly, run the contents of
       `src/db/migrations/0008_offer_tier_schema.sql` (copy/paste — it is additive-only and
       idempotent, safe to run even if partially applied already).

    3. Confirm the new columns and table exist:
       ```sql
       \d offer_macros
       \d offer_micros
       \d offer_tier_services
       ```
       Expect: `offer_macros` has `description`, `category`, `ticket`, `is_archived`,
       `cliente_ideale`, `risultato`, `tempo`, `pain`, `metodo`; `offer_micros` has
       `tier_letter` (with a CHECK constraint `offer_micros_tier_letter_check`) and
       `public_price`; `offer_tier_services` table exists with columns `tier_id`,
       `service_id`, `created_at` and a composite primary key on (`tier_id`, `service_id`).

    4. Confirm no existing data was affected:
       ```sql
       SELECT count(*) FROM offer_macros;
       SELECT count(*) FROM offer_micros;
       SELECT count(*) FROM clients;
       SELECT count(*) FROM projects;
       ```
       Row counts should match pre-migration counts (migration is additive — no rows
       deleted).
  </how-to-verify>
  <resume-signal>Type "applied" once migration 0008 has been run against production and you've confirmed the new columns/table exist (or describe any errors encountered).</resume-signal>
</task>

</tasks>

<threat_model>
## Trust Boundaries

| Boundary | Description |
|----------|-------------|
| Operator (human) -> Production Postgres | Manual SSH+docker exec session per project convention; only additive, idempotent SQL from migration 0008 is executed |

## STRIDE Threat Register

| Threat ID | Category | Component | Disposition | Mitigation Plan |
|-----------|----------|-----------|-------------|-----------------|
| T-12-05 | Denial of Service / Tampering | Manual prod migration apply | accept | Operator runs only the additive, idempotent migration 0008 (already grep-gated in Plan 01 against DROP/TRUNCATE/RENAME); per project convention this is a manual, out-of-band step not reachable via any HTTP endpoint |
</threat_model>

<verification>
1. Operator confirms (via `\d offer_macros`, `\d offer_micros`, `\d offer_tier_services` on prod) that all Plan 01 additive columns and the new junction table exist.
2. Operator confirms row counts for `offer_macros`, `offer_micros`, `clients`, `projects` are unchanged (additive migration, no data loss).
3. Resume-signal "applied" received before Wave 3 (Plans 04/05) begins relying on these columns/table against production data.
</verification>

<success_criteria>
- Migration 0008 is live on production.
- `offer_tier_services` table and all new `offer_macros`/`offer_micros` columns are queryable on prod.
- No row in `clients`, `projects`, `payments`, `phases`, `offer_macros`, or `offer_micros` was deleted or modified destructively.
</success_criteria>

<output>
After completion, create `.planning/phases/12-offer-composition-drag-drop-csv-import/12-02-SUMMARY.md`
</output>
