# Epic 4: SuiteX Shadow Event Emitter — Design Spec

## Business Justification & Problem Statement

Currently, when a user creates or updates a Project in SuiteX, the system triggers the legacy outbound sync directly to the NetSuite API. To transition to the new architecture without causing a production outage, we must begin **capturing** these data changes and routing them into our new Event-Driven Architecture (Google Cloud Pub/Sub) — without turning off the legacy sync. We need a way to generate **Shadow Events** to test our new pipeline with real production data safely.

## Proposed Architectural Solution

We will implement an **Observer/Hook pattern** within the SuiteX Laravel backend. Whenever a Project or Project Task is saved, this hook will asynchronously intercept the data, transform it into the **Canonical JSON Envelope**, and publish it to the **events.raw** Pub/Sub topic. This code will be wrapped in a global **Feature Flag (Shadow Mode)**, allowing us to emit events to the new queue while the legacy sync continues to handle actual NetSuite updates.

## Functional Requirements

| # | Requirement | Details |
|---|-------------|---------|
| 1 | **Data Interception** | Implement Laravel Observers on the **Project** and **ProjectTask** models to detect `created`, `updated`, and `deleted` lifecycle events. |
| 2 | **Payload Formatting** | Transform raw model data into the Canonical JSON Envelope (Epic 1). `sourceSystem` must be `suitex`; a unique `writeId` (UUID) must be generated per event for idempotency. |
| 3 | **Abstract Event Publisher** | Define a **SyncEventPublisherInterface**. Implement a temporary local log driver as the initial destination, allowing the formatting logic to be tested before GCP Pub/Sub (Epic 2) is provisioned. |
| 4 | **Shadow Mode Switch** | Wrap the publisher in the **shadow_sync** feature flag, stored in the core **feature_flags** database table. If the flag is enabled for the current tenant, publish the event and let the legacy sync run normally. If not, do not publish. |

## Architectural Boundaries & Technical Constraints

- **Performance:** The Pub/Sub publishing mechanism **must** be completely asynchronous (queued via Laravel Horizon/Redis). A failure to publish must not roll back the user's save action or block the HTTP response.
- **Scope:** Only **Project** and **ProjectTask** records are in scope for this Epic.

## Acceptance Criteria

### Scenario 1 — Shadow Emission

- **Given** the **shadow_sync** feature flag is enabled for the current tenant.
- **When** a user updates a Project in SuiteX.
- **Then** the legacy sync executes normally **and** a structurally valid Canonical JSON event is successfully published to the **events.raw** Pub/Sub topic.

### Scenario 2 — Asynchronous Safety

- **Given** the GCP Pub/Sub service is artificially unreachable.
- **When** a user updates a Project.
- **Then** the SuiteX UI still saves successfully without returning a **500** error, proving the emitter is properly decoupled.

## Deliverables

- **Laravel Observers** for **Project** and **ProjectTask** extended with shadow dispatch logic.
- **CanonicalEventMapper** service that maps SuiteX model data to the Epic 1 JSON schema.
- **SyncEventPublisherInterface** contract and a **LocalLogPublisher** local driver.
- **ShadowSyncFeatureFlag** service that checks the **shadow_sync** record in the core **feature_flags** table.
- **Migration** to seed the **shadow_sync** row into **feature_flags**.
