<!-- Modulo on-demand di /new — NON un file installato a sé. Caricato via Read dalla § "Routing" del SKILL.md core. -->

> **Modulo `/new`** — eseguilo, poi torna al core § "Routing" per la fase successiva. I `§ "..."` (Context economy, Context Tracking, Trivial-card fast-lane, Risk-signal detector, Fix Application Log) vivono nel **core SKILL.md**.

## Phase 7 — Production Readiness Checklist

After Phase 6 completes (or after the final summary report if Phase 6 is deferred), present a **Production Readiness Checklist** — a clear list of all manual or infrastructure actions required to launch the implemented changes in production.

### How to detect items

Scan ALL files changed across the batch (use the tracker's completed cards + `git diff` against the base branch) and check for the items below.

**The "Action to report" column shows the command for the deployment target
matching `stack.deployment` in `baldart.config.yml`.** When `stack.deployment`
is empty, infer from `stack.database` + presence of config files
(`vercel.json`, `firebase.json`, `wrangler.toml`, `fly.toml`) and fall back to
asking the user.

| Category | Detection signal | Action to report (per `stack.deployment`) |
|----------|-----------------|-------------------------------------------|
| **Database indexes** | New/modified index config file (`firestore.indexes.json`, SQL migration with `CREATE INDEX`, `prisma migrate` artifact, `db.createIndex` call, CDK/Terraform GSI) | firebase: `firebase deploy --only firestore:indexes`; vercel+postgres: run migrations during deploy; aws+dynamodb: apply CDK/Terraform; generic: run the project's migration command |
| **Persistence-layer access rules** | New/modified `firestore.rules`, Supabase RLS migration, Mongo validator, DynamoDB IAM policy | firebase: `firebase deploy --only firestore:rules`; supabase: `supabase db push`; aws+dynamodb: apply IAM via CDK/Terraform |
| **Storage access rules** | New/modified `storage.rules` or equivalent bucket policy | firebase: `firebase deploy --only storage:rules`; aws: apply S3 bucket policy; gcp: apply GCS IAM |
| **Environment variables** | New `process.env.*` references not in the base branch, new entries in `.env.example` or `.env.local` | 1. Add to the deployment platform (Vercel/Firebase/Cloudflare/AWS Param Store) — list each var + target environments 2. Update `${paths.references_dir}/env-vars.md` Change Log |
| **Feature-flag / remote config** | New flag keys in code, new `remoteConfig` calls, growthbook/launchdarkly keys | Add keys in the project's flag system (Firebase Remote Config / Growthbook / LaunchDarkly / Unleash) |
| **Auth provider configuration** | New auth provider initialization or scope change | Configure in the auth provider console matching `stack.auth_provider` (Firebase Console / Supabase dashboard / Clerk dashboard / Auth0 / Cognito) |
| **Scheduled functions / cron** | New or modified cron/scheduled jobs | firebase: `firebase deploy --only functions`; vercel: deploy includes cron from `vercel.json`; aws: deploy EventBridge rule + Lambda |
| **Database migrations** | New {entities}, field renames, data backfills referenced in code or ADRs | Run the project's migration command — varies by stack (firebase admin script / `supabase db push` / `prisma migrate deploy` / Mongo migration tool / SQL migrator) |
| **New API endpoints** | New route files (`src/app/api/` Next.js, `app/routes/api.*` Remix, `src/routes/api/+server.ts` SvelteKit, etc.) | Verify CORS/auth config; update API docs if public |
| **Third-party services** | New API keys, webhook URLs, or external service integrations | Configure in provider dashboard + add secrets to the deployment platform |
| **DNS / domain changes** | Hosting or redirect config changes | Update DNS records on registrar; update domain settings on the deployment platform |
| **Package upgrades with breaking changes** | Major version bumps in `package.json` / `pyproject.toml` / `Gemfile` | Verify compatibility; check migration guides |

### Output format

Present the checklist as a clearly formatted section in the final report.
The example below assumes `stack.deployment: firebase` + `stack.database: firestore`;
adapt commands and tags to the project's resolved stack.

```
## Production Readiness Checklist

### Required before deploy
1. **[DB Indexes — firestore]** Deploy composite indexes
   - Command: `firebase deploy --only firestore:indexes`
   - Reason: New compound query on `<entity>` (`<field1>` + `<field2>` + `<field3>`)

2. **[Environment Variable]** Add `<VAR_NAME>` to the deployment platform
   - Go to: <Vercel/Firebase/Cloudflare/AWS> > Project Settings > Environment Variables
   - Required for: Production, Preview
   - Value: (obtain from <provider> dashboard)

3. **[DB Access Rules — firestore]** Deploy updated security rules
   - Command: `firebase deploy --only firestore:rules`
   - Reason: New {entity} `<name>` access rules added

# Equivalent commands for other stacks:
#   stack.database: supabase   → `supabase db push` (RLS migration)
#   stack.database: postgres   → `prisma migrate deploy` (or project migration tool)
#   stack.database: mongodb    → run the index/validator bootstrap script
#   stack.database: dynamodb   → `cdk deploy` / `terraform apply` for the table+GSI

### No action needed
- No new scheduled functions
- No database migrations
- No DNS changes

### Notes
- DB index propagation can take time (firestore 5-10 min, postgres CONCURRENTLY minutes-to-hours on large tables, dynamodb GSI minutes); deploy BEFORE releasing the code
- Environment variables must be set on the deployment platform BEFORE the deploy triggers
```

### Auto-execution of agent-doable tasks

Before presenting the checklist, **auto-execute** all items that can be performed by the agent
without manual intervention. Do NOT ask the user for approval — just run them.

**Auto-executable items** (run via Bash tool, no confirmation needed). Pick the
command matching `stack.deployment` + `stack.database`. If the inferred command
is wrong for the project, ask the user — never auto-execute a guess.

| Category | Command per `stack.deployment` | Auto-execute? |
|----------|-------------------------------|--------------|
| Database indexes | firebase → `firebase deploy --only firestore:indexes --project <id>`; supabase → `supabase db push`; vercel+postgres → `prisma migrate deploy` (or project-specific) | YES (when stack-matched) |
| Persistence access rules | firebase → `firebase deploy --only firestore:rules --project <id>`; supabase → `supabase db push` (RLS migration); aws → CDK / Terraform apply | YES (when stack-matched) |
| Storage access rules | firebase → `firebase deploy --only storage:rules --project <id>`; aws → S3 bucket policy apply via IaC | YES (when stack-matched) |
| Scheduled functions | firebase → `firebase deploy --only functions --project <id>`; vercel → deploy step already includes cron from `vercel.json`; aws → CDK / Terraform apply | YES (when stack-matched) |

**Manual-only items** (report to user, do NOT auto-execute):

| Category | Why manual |
|----------|-----------|
| Environment variables | Requires deployment-platform dashboard access or secret values |
| Feature-flag / remote config | Requires flag-system console (Firebase Remote Config / GrowthBook / LaunchDarkly / Unleash) |
| Auth provider configuration | Requires console UI of the provider matching `stack.auth_provider` |
| Database migrations / backfills | Risk of data loss — needs human judgment |
| Third-party service config | Requires external dashboards and secrets |
| DNS / domain changes | Risk of downtime — needs human judgment |

### Schema-deploy-from-trunk-only guard (when `stack.schema_deploy_from_trunk_only: true`)

**Generic, datastore-agnostic safety invariant.** A remote schema mutation — any
RLS/access-rule deploy, index deploy, or migration `*deploy`/`*push`/apply command
(`supabase db push`, `prisma migrate deploy`, `firebase deploy --only firestore:rules|firestore:indexes|storage`,
`cdk deploy` / `terraform apply` of a table/GSI, a SQL migrator against the live DB,
etc.) — pushed from a **non-trunk branch / git worktree** desyncs the shared remote
datastore from every other in-flight branch's migration history, producing
"code-ahead-of-schema" production outages (a real consumer hit a `42703 undefined_column`
outage this way).

When `stack.schema_deploy_from_trunk_only: true` in `baldart.config.yml`, BEFORE
auto-executing ANY such remote schema/RLS/index/migration deploy:

1. Resolve the current branch: `git rev-parse --abbrev-ref HEAD`.
2. **On `git.trunk_branch`** → behave exactly as today (auto-execute when stack-matched).
3. **On any other branch / worktree** → **NEVER auto-execute.** The migration stays
   committed; surface a deferral instead — report it as a `MANUAL` item with the note
   *"migration committed; remote schema deploy happens from `<trunk>` after merge"* and
   record it under the tracker `## Production Readiness` as a deferred
   **`db-migration-deploy`** residual. The deploy is not lost — it is intentionally
   sequenced to run from trunk post-merge.

This guard enforces only the **branch invariant**; the actual command, env loader, and
gate UX stay delegated to the project overlay (`.baldart/overlays/new.md`). When the key
is `false` / unset, this section is a no-op and behavior is unchanged. Env-var / DNS /
secret / feature-flag items are already manual-only and unaffected.

**Auto-execution procedure:**

1. For each auto-executable item detected, run the command immediately
   (subject to the Schema-deploy-from-trunk-only guard above when enabled).
2. Log the result (success/failure) in the tracker under `## Production Readiness`.
3. In the final checklist output, mark auto-executed items with their result:
   ```
   1. **[DB Indexes — <stack.database>]** Deploy indexes
      - Command: <stack-matched command from the Auto-executable table>
      - Result: DEPLOYED (took 45s) | FAILED (error: ...)
   ```
4. If an auto-execution FAILS: log the error, mark it as `MANUAL FALLBACK NEEDED`,
   and include it in the "Required before deploy" section for the user to handle.

### DB Index Verification (MUST — after deploy)

After the deploy command succeeds, verify the indexes are actually live before
reporting success. The deploy command typically returns before index propagation
completes (Firestore: 5–10 minutes; Postgres `CREATE INDEX CONCURRENTLY` on a
large table: minutes to hours; DynamoDB GSI: minutes; Mongo background index:
minutes to hours).

**Skip this section entirely when:**
- the card has no `db_indexes` / `firestore_indexes` block, OR
- `stack.database` is unset / `none`, OR
- the card's index artifact is a SQL migration on a small table (the migration
  itself is synchronous and self-verifying — the deploy step already failed if
  the index didn't build).

**Verification procedure — pick the variant matching `stack.database`:**

#### Variant — Firestore (`stack.database: firestore`)

After `firebase deploy --only firestore:indexes` succeeds:

1. **Extract expected collection groups** from the local `firestore.indexes.json`:
   ```bash
   cat firestore.indexes.json | python3 -c "
   import sys, json
   data = json.load(sys.stdin)
   groups = sorted(set(i['collectionGroup'] for i in data.get('indexes', [])))
   print('\n'.join(groups))
   "
   ```

2. **Check index states** via Firestore REST API for each collection group:
   ```bash
   TOKEN=$(gcloud auth print-access-token) && \
   for CG in <collection_groups>; do
     curl -s "https://firestore.googleapis.com/v1/projects/<your-firebase-project>/databases/(default)/collectionGroups/$CG/indexes" \
       -H "Authorization: Bearer $TOKEN" 2>/dev/null
   done | python3 -c "
   import sys, json, re
   raw = sys.stdin.read()
   creating = []
   for match in re.finditer(r'\{[^{}]*\"indexes\"[^}]*\}', raw, re.DOTALL):
       try:
           data = json.loads(match.group())
           for idx in data.get('indexes', []):
               state = idx.get('state', 'UNKNOWN')
               if state != 'READY':
                   fields = ' + '.join(f.get('fieldPath','?') for f in idx.get('fields',[]))
                   cg = idx.get('name','').split('/collectionGroups/')[1].split('/')[0] if '/collectionGroups/' in idx.get('name','') else '?'
                   creating.append(f'{state}: {cg} ({fields})')
       except: pass
   if creating:
       print(f'NOT_READY ({len(creating)} indexes still building):')
       for c in creating: print(f'  - {c}')
   else:
       print('ALL_READY')
   "
   ```

3. **Poll if NOT_READY** — re-check every 30 seconds, up to 10 retries (5 minutes max).

4. **Final status**:
   - All `READY` → `VERIFIED READY`.
   - Still `CREATING` after 10 retries → `DEPLOYED BUT BUILDING` (warning).
   - Any `NEEDS_REPAIR` / `ERROR` → `INDEX ERROR — manual intervention required`.

#### Variant — Postgres / Supabase (`stack.database` ∈ {postgres, supabase})

After the migration / `supabase db push` succeeds:

```bash
# For each index named in the migration:
psql "$DATABASE_URL" -c "SELECT indexname, indexdef FROM pg_indexes WHERE indexname = '<index_name>';"
# If the migration used CREATE INDEX CONCURRENTLY on a large table, also check:
psql "$DATABASE_URL" -c "SELECT pid, phase, blocks_total, blocks_done FROM pg_stat_progress_create_index;"
```

If `pg_stat_progress_create_index` returns rows → index still building, poll every
30s. If `pg_indexes` returns 0 rows for an expected name → CRITICAL (migration
silently skipped or rolled back).

#### Variant — MongoDB (`stack.database: mongodb`)

After the bootstrap script / migration runs:

```bash
mongosh "$MONGO_URL" --eval "db.<entity>.getIndexes()"
```

Confirm every expected `name`/`key` is present. For background-built indexes, watch
`db.currentOp({'$or':[{op:'command','command.createIndexes':{$exists:true}}]})` until empty.

#### Variant — DynamoDB (`stack.database: dynamodb`)

After `cdk deploy` / `terraform apply`:

```bash
aws dynamodb describe-table --table-name <table> \
  --query 'Table.GlobalSecondaryIndexes[].{Name:IndexName,Status:IndexStatus}'
```

All GSIs must show `ACTIVE`. `CREATING` → poll; `DELETING` / `UPDATING` → manual review.

**Checklist output format with verification:**
```
1. **[DB Indexes — <stack.database>]** Deploy indexes
   - Command: <stack-matched command>
   - Deploy: SUCCESS (took 45s)
   - Verification: ALL READY (5/5) | BUILDING (2 still creating after 5min) | ERROR (details)
```

### Rules

- **Always present this section**, even if the checklist is empty (in that case, state "No infrastructure changes required — deploy is code-only").
- Order items by **deployment sequence** (items that must happen first go first — e.g., indexes before code deploy, env vars before code deploy).
- For each item, include the **reason** (which card/feature requires it) and the **exact command or UI path**.
- If an item is **uncertain** (e.g., you suspect a new index might be needed but aren't sure), mark it with `VERIFY` and explain what to check.
- **MUST — schema deploys from trunk only (when `stack.schema_deploy_from_trunk_only: true`):** never auto-execute a remote schema/RLS/index/migration deploy from a non-trunk branch or worktree. Gate on `git rev-parse --abbrev-ref HEAD == git.trunk_branch`; otherwise defer it as a `db-migration-deploy` residual ("deploy happens from trunk after merge"). Datastore-agnostic — applies to every persistence layer, not just one vendor. See the "Schema-deploy-from-trunk-only guard" above.
- **Update the tracker** with the full checklist under a new `## Production Readiness` section.

---
