# Epic 2 — Shadow Event Emitter

## 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`
