# Progress Tracker: Correct 9 Schema-Level Gaps in Canonical Envelope Spec

**Branch:** `fix/canonical-envelope-schema-gaps`
**Base:** `staging`
**Created:** 2026-03-17
**Last Updated:** 2026-03-18
**LOE:** 7 (L)

---

## Task Context

A gap analysis against the Epic 1 Anti-Corrupt Layer (Canonical Envelope Schema) vs the V10 design authority identified ten discrepancies. One gap (actorId folding into v1) was already implemented. The remaining nine must be resolved before any downstream implementation begins (emitter construction, idempotency, merge orchestration, field sync). This task is **spec and documentation authoring only** — no production application code changes.

## Objectives

1. Correct `schemas/envelope/v1.schema.json` to be fully compliant with the V10 design authority.
2. Update payload schemas to document all five NetSuite custom-field prefixes.
3. Author `gap-resolution-spec.md` as the authoritative prose companion for Gaps 4, 5, 8, and 9.
4. Ensure zero residual `eventType` references remain across all schema artifacts.

---

## Progress Log

| # | Date | Phase | Action | Status |
|---|------|-------|--------|--------|
| 1 | 2026-03-17 | Phase 0 | Created branch `fix/canonical-envelope-schema-gaps` from `staging` | ✅ Done |
| 2 | 2026-03-17 | Phase 0 | Created progress tracker at `docs/refactoring/fix-canonical-envelope-schema-gaps/progress-tracker.md` | ✅ Done |
| 3 | 2026-03-17 | Phase 0.5 | Context scan: located `schemas/envelope/v1.schema.json`, payload schemas, field_metadata migration, V10.md design spec, Epic 1 progress tracker | ✅ Done |
| 4 | 2026-03-17 | Phase 1 | Gap 1: Added `orderingKey` to `required` array in envelope schema | ✅ Done |
| 5 | 2026-03-17 | Phase 1 | Gap 2: Added `reconciliation` to `source` enum (ordered by conflict resolution priority) | ✅ Done |
| 6 | 2026-03-17 | Phase 1 | Gap 3: Renamed `eventType` → `operation` everywhere in envelope schema | ✅ Done |
| 7 | 2026-03-17 | Phase 1 | Gap 4: Added inline documentation distinguishing `source` vs `sourceSystem` | ✅ Done |
| 8 | 2026-03-17 | Phase 1 | Gap 5: Updated payload schema descriptions with all 5 prefix documentation | ✅ Done |
| 9 | 2026-03-17 | Phase 1 | Gap 6: Corrected `baseVersion` type from `string` to `["integer", "null"]` with `minimum: 0` | ✅ Done |
| 10 | 2026-03-17 | Phase 1 | Gap 7: Corrected `fullSnapshotRef` pattern from `s3://` to `^gs://` (GCP) | ✅ Done |
| 11 | 2026-03-17 | Phase 2 | Gap 8: Authored `gap-resolution-spec.md` — field_metadata DDL constraints and placement note | ✅ Done |
| 12 | 2026-03-17 | Phase 2 | Gap 9: Authored Stage 2 outbound validation rule in `gap-resolution-spec.md` | ✅ Done |
| 13 | 2026-03-17 | Phase 4 | Verified zero residual `eventType` references in all schema JSON artifacts | ✅ Done |
| 14 | 2026-03-17 | Phase 4 | Documented downstream PHP migration required: 12 test fixtures + JobEvent model use old field names | ✅ Done |
| 15 | 2026-03-17 | Phase 5 | Updated all gap log entries to ✅ Done | ✅ Done |
| 16 | 2026-03-17 | Fix | [Bug] SQLite not in .gitignore: added `.agent/memory.sqlite` to `.gitignore` + `git rm --cached` to stop tracking binary. Verified via `git check-ignore`. | ✅ Done |
| 17 | 2026-03-17 | Phase B — Test Migration | Migrated all 13 fixtures in `EventSchemaValidationTest.php`: renamed `eventType` → `operation`, updated `schemaVersion` from `v1.1` → `v1`, added `orderingKey` (pattern `recordType:recordId`) to every payload. All 13 tests GREEN (82 assertions). | ✅ Done |
| 18 | 2026-03-17 | Phase B — Audit | `JobEvent.php` audit: `event_type` is a database column on the `job_events` table tracking import-job lifecycle events (started/completed/failed). Entirely unrelated to the canonical envelope `operation` field. **No changes made — Option B confirmed.** | ✅ Done |
| 19 | 2026-03-17 | Phase A — Migration | Gap 8 implementation: Created `database/migrations/2026_03_17_200000_create_field_metadata_root_table.php` — root/landlord migration with corrected DDL (`is_synced DEFAULT FALSE`, `conflict_policy DEFAULT 'manual'`, explicit `NOT NULL` on booleans, composite index `idx_field_metadata_synced`). | ✅ Done |
| 20 | 2026-03-17 | Phase A — Migration | Gap 8 implementation: Created `database/migrations/tenants/2026_03_17_210000_drop_field_metadata_table.php` — drops tenant-scoped `field_metadata` table. `down()` intentionally empty (manual operational restore required). | ✅ Done |
| 21 | 2026-03-17 | Phase B — Model | Gap 8 implementation: Removed `UsesTenantConnection` trait from `src/Domain/Metadata/Models/FieldMetadata.php` (both `use` import and class-body `use` statement). Model now resolves to default (landlord) connection. PHPStan level 5: 0 errors. | ✅ Done |
| 22 | 2026-03-17 | Phase B — Observer | `FieldMetadataCacheObserver` verified: cache key pattern `field_metadata:{account_id}:{record_type}` already includes `account_id` as discriminator. No code change required — cache invalidation logic is unaffected by DB placement change. | ✅ Done |
| 23 | 2026-03-18 | Fix — Data Migration | [Bug] Missing data migration before tenant drop: Created `database/migrations/tenants/2026_03_17_205000_migrate_field_metadata_to_root.php`. | ✅ Done |
| 24 | 2026-03-18 | Fix — Safety | [Bug] Silent return in 205000 could cause data loss in 210000: Converted early return to `LogicException` in `205000` to force-stop execution if root table is missing. | ✅ Done |

---

## Deliverables

| File | Type | Status |
|------|------|--------|
| `schemas/envelope/v1.schema.json` | JSON Schema (corrected) | ✅ Done |
| `schemas/payloads/project/v1.schema.json` | JSON Schema (Gap 5 docs) | ✅ Done |
| `schemas/payloads/project-task/v1.schema.json` | JSON Schema (Gap 5 docs) | ✅ Done |
| `docs/refactoring/fix-canonical-envelope-schema-gaps/gap-resolution-spec.md` | Spec Doc | ✅ Done |
| `docs/refactoring/fix-canonical-envelope-schema-gaps/progress-tracker.md` | Tracker | ✅ Done |
| `database/migrations/2026_03_17_200000_create_field_metadata_root_table.php` | Root migration (corrected DDL) | ✅ Done |
| `database/migrations/tenants/2026_03_17_205000_migrate_field_metadata_to_root.php` | Data migration (tenant → root copy) | ✅ Done |
| `database/migrations/tenants/2026_03_17_210000_drop_field_metadata_table.php` | Tenant drop migration | ✅ Done |
| `src/Domain/Metadata/Models/FieldMetadata.php` | Model (UsesTenantConnection removed) | ✅ Done |

---

## Gap Reference

| Gap | Description | File | Status |
|-----|-------------|------|--------|
| Gap 1 | `orderingKey` missing from `required` array | `schemas/envelope/v1.schema.json` | ✅ Done |
| Gap 2 | `source` enum missing `reconciliation`; wrong priority order | `schemas/envelope/v1.schema.json` | ✅ Done |
| Gap 3 | `eventType` must be renamed to `operation` | `schemas/envelope/v1.schema.json` | ✅ Done |
| Gap 4 | `source` vs `sourceSystem` undocumented | `schemas/envelope/v1.schema.json` + spec doc | ✅ Done |
| Gap 5 | patternProperties description missing 4 of 5 prefixes; no reference table | Payload schemas + spec doc | ✅ Done |
| Gap 6 | `baseVersion` typed as `string` instead of `integer` | `schemas/envelope/v1.schema.json` | ✅ Done |
| Gap 7 | `fullSnapshotRef` URI scheme `s3://` (AWS) instead of `gs://` (GCP) | `schemas/envelope/v1.schema.json` | ✅ Done |
| Gap 8 | `field_metadata` DDL missing constraints, index, placement note | Spec doc | ✅ Done |
| Gap 9 | Stage 2 outbound validation rule (is_readonly strip) missing | Spec doc | ✅ Done |

---

## Definition of Done

- [x] `schemas/envelope/v1.schema.json` passes JSON Schema draft-07 validation (verified via python3 json.load)
- [x] Zero `eventType` references in any schema artifact (verified via rg scan)
- [x] `orderingKey` present in both `required` array and `properties`
- [x] `source` enum has 4 values in conflict-resolution-priority order
- [x] `baseVersion` is `["integer", "null"]` with `minimum: 0`
- [x] `fullSnapshotRef` pattern is `^gs://`
- [x] Payload schema descriptions reference all 5 NetSuite prefix families
- [x] `gap-resolution-spec.md` authored covering Gaps 4, 5, 8, 9
- [x] Progress tracker fully updated to ✅ Done at handoff

## Known Downstream Follow-Up Required

> **These are OUT OF SCOPE for this spec task. Document only — do not implement here.**

| # | Item | File | Action |
|---|------|------|--------|
| 1 | Test fixture migration | `tests/Unit/Domain/Events/Actions/EventSchemaValidationTest.php` | ✅ **Done** — All 13 fixtures migrated: `eventType` → `operation`, `schemaVersion: v1.1` → `v1`, `orderingKey` added. 13/13 tests GREEN (82 assertions). |
| 2 | JobEvent model | `src/App/Models/JobEvent.php` | ✅ **Done (No-op)** — `event_type` is a DB column for import-job lifecycle events (unrelated to envelope). Option B confirmed. |
| 3 | field_metadata placement | `database/migrations/tenants/…_create_field_metadata_table.php` | ✅ **Done** — Root migration created at `database/migrations/2026_03_17_200000_create_field_metadata_root_table.php`. Tenant drop migration at `database/migrations/tenants/2026_03_17_210000_drop_field_metadata_table.php`. `FieldMetadata` model updated (trait removed). |
| 4 | field_metadata partial index | New migration required | ✅ **Done** — Composite index `idx_field_metadata_synced` on `(account_id, record_type, is_synced)` included in root migration (MySQL-compatible alternative to PostgreSQL partial index). |
