From f78655debced0fe832d8386b7f512895733f59cd Mon Sep 17 00:00:00 2001 From: sayan Date: Sat, 11 Apr 2026 19:35:45 +0530 Subject: [PATCH] feat: Built the Oracle Tab1 (#14) #13 Built the complete Oracle Tab with all the functionalities. Co-authored-by: Sayan Datta Reviewed-on: https://git.desineuron.in/sagnik/Project_Velocity/pulls/14 --- .../Bibels/Project Velocity - The Oracle.md | 2319 +++++++++++++++++ .../Bibels/oracle_development_status.md | 94 + app/dist/index.html | 4 +- .../.tmp/tsconfig.app.tsbuildinfo | 2 +- .../.vite/deps/@radix-ui_react-avatar.js | 6 +- .../.vite/deps/@react-three_drei.js | 16 +- .../.vite/deps/@react-three_fiber.js | 6 +- app/node_modules/.vite/deps/_metadata.json | 108 +- app/src/App.tsx | 12 +- app/src/app/oracle/page.tsx | 1080 +++----- app/src/components/modules/Catalyst.tsx | 195 +- .../components/modules/GroundTruthPicker.tsx | 219 ++ app/src/components/modules/Inventory.tsx | 20 +- app/src/components/modules/Settings.tsx | 28 +- app/src/lib/utils.ts | 11 +- app/src/oracle/components/BranchBar.tsx | 201 ++ app/src/oracle/components/CanvasViewport.tsx | 184 ++ .../oracle/components/ComponentRegistry.tsx | 163 ++ app/src/oracle/components/PromptRail.tsx | 205 ++ .../components/RollbackConfirmModal.tsx | 208 ++ app/src/oracle/components/ShareModal.tsx | 261 ++ .../renderers/ActivityStreamRenderer.tsx | 135 + .../components/renderers/BarChartRenderer.tsx | 71 + .../renderers/ErrorNoticeRenderer.tsx | 61 + .../components/renderers/GeoMapRenderer.tsx | 115 + .../components/renderers/KpiTileRenderer.tsx | 69 + .../renderers/LineChartRenderer.tsx | 54 + .../renderers/PipelineBoardRenderer.tsx | 121 + .../components/renderers/RendererWrapper.tsx | 184 ++ .../components/renderers/TableRenderer.tsx | 97 + .../components/renderers/TimelineRenderer.tsx | 75 + .../components/review/MergeReviewDrawer.tsx | 386 +++ app/src/oracle/hooks/useOracleExecution.ts | 130 + app/src/oracle/hooks/useOraclePage.ts | 120 + app/src/oracle/lib/oracleApiClient.ts | 199 ++ app/src/oracle/lib/oracleDemoData.ts | 455 ++++ app/src/oracle/types/canvas.ts | 488 ++++ app/src/store/useCurrencyStore.ts | 158 ++ backend/main.py | 2 + backend/oracle/__init__.py | 1 + backend/oracle/canvas_service.py | 596 +++++ backend/oracle/collaboration_service.py | 369 +++ backend/oracle/data_access_gateway.py | 242 ++ backend/oracle/policy_service.py | 225 ++ backend/oracle/prompt_orchestrator.py | 576 ++++ backend/oracle/router_v1.py | 364 +++ backend/oracle/schema_oracle.sql | 206 ++ backend/requirements.txt | 1 + backend/tests/__init__.py | 1 + backend/tests/oracle/__init__.py | 1 + backend/tests/oracle/test_canvas_service.py | 133 + .../oracle/test_collaboration_service.py | 207 ++ backend/tests/oracle/test_policy_service.py | 142 + .../tests/oracle/test_prompt_orchestrator.py | 143 + 54 files changed, 10651 insertions(+), 818 deletions(-) create mode 100644 .Agent Context/Bibels/Project Velocity - The Oracle.md create mode 100644 .Agent Context/Bibels/oracle_development_status.md create mode 100644 app/src/components/modules/GroundTruthPicker.tsx create mode 100644 app/src/oracle/components/BranchBar.tsx create mode 100644 app/src/oracle/components/CanvasViewport.tsx create mode 100644 app/src/oracle/components/ComponentRegistry.tsx create mode 100644 app/src/oracle/components/PromptRail.tsx create mode 100644 app/src/oracle/components/RollbackConfirmModal.tsx create mode 100644 app/src/oracle/components/ShareModal.tsx create mode 100644 app/src/oracle/components/renderers/ActivityStreamRenderer.tsx create mode 100644 app/src/oracle/components/renderers/BarChartRenderer.tsx create mode 100644 app/src/oracle/components/renderers/ErrorNoticeRenderer.tsx create mode 100644 app/src/oracle/components/renderers/GeoMapRenderer.tsx create mode 100644 app/src/oracle/components/renderers/KpiTileRenderer.tsx create mode 100644 app/src/oracle/components/renderers/LineChartRenderer.tsx create mode 100644 app/src/oracle/components/renderers/PipelineBoardRenderer.tsx create mode 100644 app/src/oracle/components/renderers/RendererWrapper.tsx create mode 100644 app/src/oracle/components/renderers/TableRenderer.tsx create mode 100644 app/src/oracle/components/renderers/TimelineRenderer.tsx create mode 100644 app/src/oracle/components/review/MergeReviewDrawer.tsx create mode 100644 app/src/oracle/hooks/useOracleExecution.ts create mode 100644 app/src/oracle/hooks/useOraclePage.ts create mode 100644 app/src/oracle/lib/oracleApiClient.ts create mode 100644 app/src/oracle/lib/oracleDemoData.ts create mode 100644 app/src/oracle/types/canvas.ts create mode 100644 app/src/store/useCurrencyStore.ts create mode 100644 backend/oracle/__init__.py create mode 100644 backend/oracle/canvas_service.py create mode 100644 backend/oracle/collaboration_service.py create mode 100644 backend/oracle/data_access_gateway.py create mode 100644 backend/oracle/policy_service.py create mode 100644 backend/oracle/prompt_orchestrator.py create mode 100644 backend/oracle/router_v1.py create mode 100644 backend/oracle/schema_oracle.sql create mode 100644 backend/tests/__init__.py create mode 100644 backend/tests/oracle/__init__.py create mode 100644 backend/tests/oracle/test_canvas_service.py create mode 100644 backend/tests/oracle/test_collaboration_service.py create mode 100644 backend/tests/oracle/test_policy_service.py create mode 100644 backend/tests/oracle/test_prompt_orchestrator.py diff --git a/.Agent Context/Bibels/Project Velocity - The Oracle.md b/.Agent Context/Bibels/Project Velocity - The Oracle.md new file mode 100644 index 00000000..c7771dc1 --- /dev/null +++ b/.Agent Context/Bibels/Project Velocity - The Oracle.md @@ -0,0 +1,2319 @@ +# Project Velocity - The Oracle + +## Master Architecture and Implementation Artifact + +Document version: 1.0 +Prepared for: Sprint 1 execution baseline +Artifact status: Authoritative product and engineering specification +Document date: 2026-04-08 +Repository baseline: `Project_Velocity` at workspace root + +## 0. Topic Index + +| Section | Topic | What the section answers | Primary audience | +| --- | --- | --- | --- | +| 1 | Charter and Baseline Reality | What Oracle is, what exists in the repo today, and what this artifact formally replaces | Product, engineering, founders | +| 2 | System Overview and Goals | What success looks like, who uses Oracle, and how the vertical JSON canvas should behave | Product, design, leadership | +| 3 | Functional Requirements | What users, AI, components, canvases, and collaboration flows must actually do | Product, frontend, backend | +| 4 | Nonfunctional Requirements | How fast, safe, scalable, reliable, and observable Oracle must be in production | Platform, backend, security | +| 5 | System Architecture | How the system is layered, which services exist, and how the data flows end to end | Backend, platform, architecture | +| 6 | Data Model and JSON Canvas Schema | What canonical contracts, entities, schemas, and examples define Oracle state | Backend, data, frontend | +| 7 | Premade Components Catalog and AI Synthesis | How templates are organized, learned, synthesized, validated, cached, and promoted | Frontend, AI, product | +| 8 | Nemoclaw Integration and Data Access | How prompts become plans, plans become safe queries, and AI stewardship is constrained | Backend, AI, data governance | +| 9 | Security, Privacy, and Compliance | How identity, authorization, encryption, retention, redaction, and compliance are enforced | Security, compliance, platform | +| 10 | Testing Strategy and Quality Assurance | How Oracle is validated through unit, integration, replay, performance, and security tests | QA, backend, frontend | +| 11 | Deployment, Operations, and Observability | How Oracle is deployed, monitored, traced, rolled back, and operated in production | Platform, SRE, backend | +| 12 | Data Governance, Provenance, and Lineage | How Oracle proves source-to-visualization lineage and preserves accountability | Compliance, data, leadership | +| 13 | API Contracts and Integration Points | What the public and internal interfaces look like, including errors, websocket messages, and flows | Backend, frontend, integrations | +| 14 | Roadmap, Milestones, and Rollout Plan | How Oracle should move from MVP to enterprise-grade rollout with controlled risk | Leadership, product, program owners | +| 15 | Appendices | Reference glossary, dictionary, samples, and transcript evidence for implementation and review | All stakeholders | +| 16 | Detailed Execution Blueprint | How the current codebase should be transformed into the production Oracle implementation | Frontend, backend, platform | +| 17 | Collaboration and Merge Algorithm Specification | How revisions, forks, order indices, conflicts, and merge resolutions behave deterministically | Backend, frontend, QA | +| 18 | CRM Operating Model and Product Behavior | How Oracle should function as a world-class real-estate CRM grounded in first principles | Product, founders, GTM | +| 19 | Delivery Work Packages and Sequencing | How the work should be partitioned, staffed, ordered, and piloted | Program leads, engineering managers | +| 20 | Production Readiness Exit Criteria | What must be true before Oracle is considered production-ready rather than merely demo-ready | Leadership, platform, QA | +| 21 | Repository Cutover and File Mapping | Which existing files are temporary, which new modules should exist, and how to map the artifact into repo work | Engineering leads, implementers | +| 22 | Success Metrics and Adoption Governance | Which product, operational, and business metrics prove Oracle is valuable after launch | Leadership, product, operations | + +## 1. Charter and Baseline Reality + +The Oracle is the AI operating surface for the Velocity Suite CRM. It is not a chat widget bolted onto a dashboard. It is the product surface where a broker, sales director, marketing operator, or compliance reviewer expresses business intent in natural language and receives executable, auditable, shareable data views on a persistent canvas. The core design objective is to give non-technical operators the speed of AI while preserving the control, provenance, and safety expected from a system that owns customer and revenue-critical data. + +The current repository contains a visual shell for Oracle but not a production Oracle system. The frontend implementation in `app/src/app/oracle/page.tsx` and `app/src/lib/oracleQueryClient.ts` presents a polished UI that renders mock responses across a fixed set of views. That UI is valuable as a style and interaction reference, but its data contract is intentionally temporary and must be retired. The active contract today is centered on `OracleQueryResult`, which returns a single synthetic result payload rather than a persistent branchable canvas. This artifact replaces that contract with a canvas-native architecture. + +The active FastAPI runtime in `backend/main.py` does not mount an Oracle router or CRM router for production Oracle flows. Empty placeholder files exist in `backend/api/routes_oracle.py` and `backend/api/routes_crm.py`, and they should be treated as placeholders rather than compatibility commitments. The backend stack that is real today is FastAPI, asyncpg, PostgreSQL, JWT-based auth, and a Nemoclaw client abstraction already used by Sentinel. That Nemoclaw abstraction currently supports a hosted NVIDIA-compatible path as the primary runtime and a compatible endpoint or local fallback by policy. Oracle should reuse that runtime abstraction instead of binding itself to one model vendor or one inference location. + +This specification assumes the canonical operating model named `Hybrid Sovereign`. Under this model, tenant data, regulated execution, and high-sensitivity query operations remain inside a tenant-controlled VPC or on-premise boundary whenever policy demands it. The reasoning layer remains pluggable. A tenant may run Nemoclaw through the current hosted model path, a private compatible endpoint, or a local model runtime, but Oracle contracts, audit behavior, safety rules, and data access controls remain invariant across those runtime choices. + +The product premise is deliberately real-estate CRM first. Oracle is designed for brokerages, developers, sales galleries, and investor-facing teams that want the discipline of Salesforce, the ease and automation feel of HubSpot, and the branch-review rigor of Jira, without forcing brokers into an IT-admin workflow. The database is AI-operated in the sense that ingestion, enrichment, retrieval planning, and routine record maintenance are AI-managed, while structural schema changes remain human-governed through reviewable proposals and migrations. + +## 2. System Overview and Goals + +### 2.1 Product Vision + +The Oracle is the data intelligence layer of Velocity Suite. It receives natural-language questions such as "show me this week's whale leads by source, highlight the brokers with the highest QD-weighted pipeline, and map investor concentration in Palm Jumeirah," converts that request into a verified retrieval plan, executes the plan against authorized tenant data, chooses or synthesizes suitable visualization components, and persists those components to a vertically scrollable JSON canvas. The output is not a transient answer. It is a durable page revision that becomes part of the user's operating workspace and collaboration history. + +The vertical scrollable JSON canvas is a semantic canvas rather than a freeform pixel board. The canonical structure is an ordered array of component objects enriched with layout hints, data bindings, provenance, access controls, and rendering metadata. The visual renderer materializes that JSON into responsive UI sections that stack vertically and support infinite growth over time. Users do not drag arbitrary rectangles on a blank plane. They append, insert, group, review, fork, and merge structured analytical components backed by typed data contracts. + +Every user receives a unique main Oracle canvas when the account is provisioned. That canvas functions as the user's primary analytical branch. Sharing does not expose the live main branch for direct mutation. Sharing creates an isolated fork for the recipient, preserving the contributor's ability to explore, comment, reorder, and extend the page without silently modifying the origin. Any incorporation of fork changes into the main branch occurs through a merge request workflow with provenance, diff review, and explicit merge records. + +### 2.2 Product Principles + +| Source product | Strongest capability | Oracle translation | +| --- | --- | --- | +| Salesforce | Object model discipline, pipeline rigor, role-aware analytics | Oracle preserves a canonical tenant data model, strict permissions, and operational revenue views. | +| HubSpot | Low-friction operator UX, fast setup, automation feel | Oracle turns natural language into guided actions and appendable canvases without forcing users through report builders. | +| Jira | Branching, review, merge history, accountable change management | Oracle treats canvases as revisioned analytical artifacts with forks, merge requests, and immutable audit trails. | + +The result is a CRM that keeps brokers in flow. It reduces menu depth, centralizes the prompt surface, makes data views durable and shareable, and lets AI do the tedious work of query planning, binding, styling, and data enrichment while preserving human review where it matters. + +### 2.3 Intended Users and Roles + +| Role | Core goal inside Oracle | Authority boundary | +| --- | --- | --- | +| Junior Broker | Ask questions, review personal leads, build personal working canvases, request follow-ups | May read assigned datasets and create private forks but cannot approve merges into team templates or review schema proposals. | +| Senior Broker | Operate team canvases, compare lead pools, request synthesized components, review subordinate work | May approve intra-team merges and tenant-private component promotions. | +| Sales Director | Own pipeline visibility, performance reviews, branch governance, approval queues | May approve merge requests into official canvases and approve tenant-level catalog items. | +| Marketing Operator | Analyze campaign-to-lead flow, audience quality, conversion by source | May access campaign, audience, and attribution datasets allowed by tenant policy. | +| Data Steward | Review AI stewardship proposals, data quality anomalies, lineage evidence | May approve schema proposals, derived fields, connector mappings, and retention exceptions. | +| Compliance Reviewer | Audit prompt history, sensitive queries, deletions, and merge provenance | May access immutable audit and lineage views but not change business data. | +| Platform Admin | Operate infrastructure, tenant policy, connector setup, runtime routing | May manage service configuration but not bypass tenant-scoped data access policy. | + +### 2.4 Integration with Velocity Suite + +Oracle is not isolated from the rest of Velocity Suite. Oracle consumes CRM objects, activity logs, campaign signals, vault engagement, inventory snapshots, and Sentinel scores. Dashboard consumes Oracle aggregates for high-level KPIs. Sentinel enriches Oracle leads with QD scores, reactions, visit evidence, and behavioral tags. Inventory provides structured unit, project, and availability datasets to power pricing, absorption, and buyer-fit views. Catalyst contributes campaign, ad, and audience performance. Settings defines policy, identity, connector, and tenant-level defaults. Oracle becomes the analytical and operational convergence layer where those signals are composed. + +### 2.5 Core Interaction Narrative + +The golden path begins when the user submits a prompt from the Oracle page. The prompt and visible page context are packaged as a `PromptExecution` request. Nemoclaw receives the prompt, the semantic model, user role and tenant policy, page context, and allowed data sources. Nemoclaw returns a typed intent classification, a structured retrieval plan, and a visualization plan. The policy engine validates that plan, ensuring all requested entities, joins, row-level scopes, redaction rules, and connectors are authorized. The Data Access Gateway compiles the validated plan into executable queries against the tenant data store. Results are profiled for shape, density, grain, and statistical suitability. The visualization engine resolves the best component template or synthesizes a new one, binds the result data to the component contract, writes the component into a new page revision, and broadcasts the update over the page WebSocket. The renderer appends the component to the vertically scrollable canvas and preserves the revision in the audit trail. + +If the user shares the page, Oracle creates a fork for the recipient at a specific source revision. If the recipient edits the fork, the original page remains unchanged. If the recipient later opens a merge request, Oracle computes a component-aware diff between the fork head, the fork base, and the target branch head. Conflicts are resolved deterministically when changes do not overlap and escalated for manual review when they do. Every accepted merge creates a new target revision and preserves the source branch, merge reasoning, review comments, and lineage. + +### 2.6 Error Handling, Rollback, and Auditability + +Oracle must never silently fail, silently broaden permissions, or silently overwrite user work. If Nemoclaw produces an invalid plan, the user receives a structured failure artifact that explains whether the failure was caused by ambiguity, authorization, query compilation, data unavailability, or model runtime error. If the runtime cannot meet confidence or policy thresholds, Oracle may request clarification or create a "diagnostic notice" component rather than returning fabricated analytics. + +Rollback is revision-based rather than destructive. Every successful write to a canvas creates an immutable page revision. A rollback action restores a prior state by creating a new revision that points to a previously valid component tree. This preserves full auditability while giving users a familiar undo or restore experience. Each prompt, query, component creation, share, fork, merge, deletion, and rollback event receives an immutable audit record with `tenantId`, `actorId`, `entityType`, `entityId`, `correlationId`, `executionId`, and timestamp. + +## 3. Functional Requirements + +### 3.1 Prompt Authoring and User Experience + +The Oracle prompt surface must support analytical prompts, operational prompts, and mixed prompts. Analytical prompts request facts, comparisons, trends, maps, tables, forecasts, or outlier views. Operational prompts request workflow actions such as creating follow-ups, assigning owners, or annotating a lead segment. Mixed prompts combine both, for example asking for a chart and the creation of tasks for the top three leads shown in that chart. + +Prompt input must preserve the existing premium UI style while evolving its contract. The current Oracle page shell should remain visually recognizable, but the canvas area becomes a persistent vertical page rather than a single view swapper. The right-side conversation rail remains the interaction log. The center canvas becomes scrollable, revisioned, and branch-aware. The top insight banner becomes a page status bar that shows branch state, execution status, and the highest priority insight extracted from the latest successful execution. + +Oracle must support prompt context from four sources: the current conversation transcript, explicit filters chosen by the user, the current canvas component selection, and system context such as the active tenant, role, and time zone. Oracle must also support prompt presets for common brokerage flows such as pipeline health, lead-source quality, project absorption, broker performance, investor geography, follow-up gaps, and QD-weighted opportunity ranking. + +### 3.2 Prompt Interpretation and Retrieval Planning + +Nemoclaw must transform each prompt into a deterministic typed plan. The plan includes the intent class, target business entities, required metrics, required dimensions, requested filters, permissible aggregation grain, requested temporal window, required joins, privacy tier, and preferred visualization semantics. Nemoclaw must return structured JSON only. Freeform model prose is not considered executable output. + +The retrieval plan must be compiled against a tenant semantic model that maps natural-language business terms to canonical datasets and fields. Terms such as "hot lead," "broker performance," "whale," "Palm Jumeirah inventory," or "QD-weighted pipeline" are resolved through a semantic dictionary rather than ad hoc prompting alone. This keeps the system stable when underlying schemas evolve and lets Oracle explain exactly which fields and tables were used. + +If a prompt is ambiguous but still recoverable, Oracle must adopt a default assumption strategy that surfaces the assumption explicitly in the response metadata. If the ambiguity would materially change the answer or cross a policy boundary, Oracle must stop execution and ask for clarification. A prompt that references multiple tenants, forbidden data classes, or undeclared external data sources must fail closed. + +### 3.3 Data Access Orchestration + +The Data Access Gateway must be the sole execution path to tenant data. Nemoclaw does not receive raw connector credentials and does not execute arbitrary SQL directly. Nemoclaw produces an abstract retrieval plan. The policy engine validates the plan. The gateway compiles that plan into parameterized SQL, stored procedures, or connector-specific requests under least-privilege service identities. + +In MVP, the first-class datasets are internal Velocity PostgreSQL datasets for leads, users, inventory, activities, campaigns, vault engagement, audit events, and Sentinel signals. External CRM or data warehouse connectors may exist later, but MVP assumes Oracle is the primary CRM rather than an analytics overlay on another CRM. This keeps the system simple, secure, and aligned with the "walled garden" sales thesis. + +### 3.4 Visualization Generation and Canvas Growth + +Oracle must bind data results to modular component objects. A single prompt may produce zero, one, or many components. Zero-component completion is valid when the prompt performs only an operational action and records a task or note. One-component completion is common for single-metric or focused analytical prompts. Multi-component completion is preferred when the prompt requests both overview and supporting detail, such as a KPI tile followed by a bar chart and a detail table. + +The canvas grows vertically. The default placement mode is `append_after_last_visible_component`, which preserves a chronological narrative of inquiry. Oracle also supports `insert_after_component`, `replace_component`, and `group_under_section`, but those modes must be explicit. Every component records `layout.orderIndex`. The component array order and the persisted `orderIndex` must agree. Gaps in order indices are allowed to simplify concurrent insertions and three-way merges. + +### 3.5 Component Lifecycle + +| Lifecycle state | Meaning | Exit rule | +| --- | --- | --- | +| draft | Component exists only in an in-flight execution and is not visible on a committed page | Exits on validation pass or execution failure | +| active | Component is visible on a committed page revision | Exits on supersede, archive, or deletion | +| superseded | Component remains part of history but is no longer the latest operative version | Exits only through retention expiry | +| archived | Component is hidden from default view but recoverable for audits and replay | Exits only through explicit restore or retention deletion | +| revoked | Component failed policy or security review after creation and must not render again | Exits only through replacement or legal deletion workflow | + +Component editing must preserve lineage. If a user changes a title, threshold, color token, aggregation mode, or filter, Oracle creates a new component version rather than mutating history in place. If a component is cloned from the catalog, its provenance records the original `templateId` and version. If it is synthesized from a prompt, provenance records the `PromptExecution` that created it, the style exemplar set, the model runtime, and all source lineage records. + +### 3.6 Premade Catalog and On-Demand Synthesis + +Oracle ships with a tenant-visible catalog of premade components organized by semantics rather than by chart library name. A broker should think in terms such as "pipeline board," "lead heatmap," "investor map," "follow-up queue," or "project absorption trend," not "bar chart variant 17." Each template carries a semantic profile describing the data shapes it accepts, the audience it serves, the interaction rules it supports, accessibility guarantees, and its style signature. + +When no premade template fits the requested data shape or storytelling need, Oracle may synthesize a new component. Synthesis is constrained by the same schema, security, accessibility, and performance contracts as premade templates. The output of synthesis is a first-class `ComponentTemplate` plus a rendered `CanvasComponent` instance. Auto-promotion is allowed only within the originating tenant and only after automated validation passes. The promotion path does not create a global shared component. Cross-tenant sharing of synthesized components is explicitly disabled in this artifact. + +### 3.7 Sharing, Forking, and Merging + +Oracle uses page-branch collaboration. Every page has one main branch and zero or more fork branches. Sharing a page creates a fork for the recipient based on a concrete source revision. The recipient can annotate, insert, reorder, filter, and synthesize components on the fork without modifying the origin. The system records the fork source page, source branch, source revision, recipient, created time, and sharing scope. + +When the recipient wants to merge changes back, Oracle creates a `MergeRequest`. The merge engine performs a three-way comparison across the target head, source head, and fork base. Adds, deletes, and reorders to disjoint components merge automatically. Concurrent edits to the same component identity, the same query descriptor, or the same layout slot become explicit conflicts. The reviewer sees a component-aware diff rather than raw JSON only. The merge UI must show changes in titles, filters, visualization parameters, redaction settings, and order indices. + +Oracle must preserve provenance across merges. A merged component retains its original source branch, source component id, original prompt execution, and merge request id. Silent overwrite is forbidden. If a conflict is unresolved, the merge request cannot complete. If a merge is abandoned, the fork remains intact and auditable. + +### 3.8 Privacy and Cross-Dataset Enforcement + +Cross-tenant exploration is not enabled in MVP. Oracle may combine multiple datasets only when they belong to the same tenant and the requesting role is authorized to see the resulting joined output. If a prompt attempts to blend sensitive lead data with a dataset outside the actor's scope, the plan validator must reject the request before any data fetch occurs. + +Sensitive prompts must carry privacy tier annotations. For example, a prompt involving phone numbers, medical references, passport identifiers, or financial documents triggers enhanced redaction and narrower logging rules. Data at rest and in transit must remain protected at every stage. Prompt payloads, model outputs, data result sets, component objects, and page revisions are all covered by encryption, access control, and audit logging. + +### 3.9 Failure Behavior + +Oracle must degrade gracefully. If the query returns no rows, the system should render a no-result component with assumptions, applied filters, and suggested follow-up prompts. If the query runtime exceeds latency SLOs but the request is still viable, Oracle may return a pending execution state and stream the component when ready. If a component template fails at render time, Oracle must preserve the page revision but replace the failed renderer with a policy-safe fallback notice and emit an alert for operator review. + +## 4. Nonfunctional Requirements + +### 4.1 Service Level Objectives and Error Budgets + +Oracle SLOs are measured on rolling 30-day windows and exclude planned maintenance windows communicated to tenants at least 72 hours in advance. + +| Capability | Availability target | Monthly error budget | Latency or integrity target | +| --- | --- | --- | --- | +| Authentication and page fetch | 99.95% | 21.9 minutes | `GET` page retrieval p95 <= 400 ms for cached metadata | +| Prompt-to-plan path | 99.9% | 43.8 minutes | Prompt intake validation p95 <= 300 ms and Nemoclaw plan generation p95 <= 2.5 s | +| Analytical query execution | 99.9% | 43.8 minutes | Warm query p95 <= 4 s and cold query p95 <= 8 s | +| Component synthesis | 99.5% | 3 hours 39 minutes | Synthesis completion p95 <= 6 s excluding long-running custom ML jobs | +| Component render after response arrival | 99.9% | 43.8 minutes | First component paint p95 <= 1.5 s | +| Canvas persistence | 99.95% | 21.9 minutes | Append or revision create p95 <= 500 ms | +| Fork creation | 99.95% | 21.9 minutes | Fork create p95 <= 1 s | +| Merge diff generation | 99.9% | 43.8 minutes | Merge diff compute p95 <= 2 s | +| Merge integrity | 99.99% | 4.4 minutes | Zero silent overwrite and deterministic merge replay for every accepted merge | + +### 4.2 Reliability and Data Integrity + +Oracle must provide at-least-once event delivery for internal orchestration and exactly-once visible page revision commits. A user may see retries in the execution timeline, but the committed page history must never show duplicate component insertions for a single successful execution. The write path therefore uses idempotency keys such as `clientRequestId`, `executionId`, and `revisionCommitId`. + +Fork integrity is a first-class reliability requirement. A fork must reference an immutable source revision. The source revision may later receive new commits, but the fork base cannot drift. Merge requests must remain reproducible long after creation by storing both source base and target base commit references. Audit trails must remain queryable for at least the retention period configured by the tenant and longer when legal hold is active. + +### 4.3 Scalability and Frontend Performance + +The frontend must support canvases containing up to 5,000 components while preserving 60 fps scrolling on modern desktop hardware and 45 fps on supported tablets. The renderer must use windowed virtualization, retained measurement caches, incremental hydration for expensive components, and compiled template caching keyed by template id and version. Data-heavy components such as tables and maps must independently virtualize rows or markers rather than forcing the entire canvas to absorb their cost. + +The default implementation choice is a dedicated virtualization adapter in the Oracle frontend backed by a React 19 compatible virtual list engine. Because the current repo does not include a virtualization library, the implementation should introduce one under Oracle ownership rather than forcing generic virtualization behavior into shared layout code. The UI must also preserve stable scroll anchors during component insertion, merge replay, and live collaboration updates. + +### 4.4 Security, Privacy, and Compliance Targets + +Oracle must follow zero-trust principles. Every request is authenticated. Every data fetch is authorized. Every service-to-service hop is mutually authenticated. Every data object is encrypted at rest and in transit. Every secret is stored outside source control. Every model runtime receives least-privilege context only. Every sensitive action is logged immutably. + +Multi-tenant isolation is mandatory. Every persisted entity carries `tenantId`, and every query predicate includes tenant scope. JWTs or external identity assertions are never sufficient on their own; the authorization layer must also resolve the effective tenant, role, page access scope, and data policy. Oracle must be capable of operating in GDPR-, CCPA-, and HIPAA-ready environments, even if healthcare mode is a tenant-specific option rather than the default. + +### 4.5 Observability Targets + +All critical paths must emit structured logs, metrics, and distributed traces. Every prompt execution, data access request, component synthesis job, page revision commit, share event, fork event, merge request, and merge completion must be traceable through a shared `correlationId`. Production dashboards must surface request rates, p50 and p95 latencies, error rates, cache hit ratios, component render times, merge conflict rates, and policy rejection counts. Alerts must fire on SLO burn, merge integrity anomalies, repeated model malformed output, data access denials above baseline, and page render crash spikes. + +### 4.6 Documentation, Training, and Developer Experience + +Oracle is successful only if it is operable by engineers and understandable to commercial teams. Developer setup time for a new engineer should be less than 30 minutes using a documented local profile. Every external contract must be versioned and example-backed. Every service must publish OpenAPI or schema registry entries. Every merge-sensitive algorithm must have deterministic replay fixtures. Operator training material should let a sales director understand private pages, sharing, forks, merges, and audit evidence in under 45 minutes. + +## 5. System Architecture + +### 5.1 Current State in the Repository + +The frontend Oracle experience exists today as a styled page with hard-coded view switching and synthetic payloads. The backend Oracle service does not exist as an active mounted runtime. The system does already have a real FastAPI service, asyncpg-backed PostgreSQL integration, authentication dependencies, audit-friendly Sentinel flows, and a Nemoclaw client abstraction. The correct architectural move is therefore not a greenfield platform rewrite. It is the addition of Oracle-specific services and contracts on top of the current backend and frontend stack. + +### 5.2 Reference Architecture + +```text +Browser / Tablet + -> Oracle Prompt Surface + -> Virtualized Canvas Renderer + -> Branch Banner, Merge Review, Presence Rail + -> Oracle WebSocket Client + +Oracle API Layer + -> Canvas Service + -> Prompt Orchestrator + -> Collaboration Service + -> Component Catalog Service + -> Visualization Renderer Service + -> Identity and Access Gateway + +Execution and Data Layer + -> Policy Engine + -> Data Access Gateway + -> PostgreSQL Core Store + -> Object Storage for exports, snapshots, rendered assets + -> Audit and Event Store + +AI and Runtime Layer + -> Nemoclaw Prompt Planner + -> Style Signature Extractor + -> Component Synthesis Engine + -> Validation and Safety Pipeline + +Integration Layer + -> External IdP + -> Notification channels + -> Future external data connectors + -> Model runtime adapters +``` + +### 5.3 Service Responsibilities + +| Service | Responsibility | Repo alignment | +| --- | --- | --- | +| Canvas Service | Owns page creation, revision commits, component ordering, rollback, and page retrieval | New FastAPI router and service layer on top of current backend | +| Prompt Orchestrator | Accepts prompt requests, builds execution context, calls Nemoclaw, and coordinates validations | Extends current Nemoclaw-backed backend patterns | +| Collaboration Service | Owns sharing, fork creation, merge requests, diff generation, review state, and merge commits | New service and websocket namespace | +| Component Catalog Service | Stores premade templates, synthesized templates, style signatures, validation results, and promotion states | New service backed by PostgreSQL and object storage | +| Data Access Gateway | Compiles validated plans into data queries with tenant isolation and redaction | New service boundary, even if colocated in MVP | +| Visualization Renderer | Converts template plus dataset plus style signature into renderable component JSON and UI metadata | New backend module with typed contracts | +| Identity and Access Gateway | Resolves authenticated actor, tenant scope, role, and page permissions | Reuses existing auth foundation and external IdP integration | + +### 5.4 Layer Details + +The presentation layer extends the existing Oracle UI. The right rail conversation pattern, premium glass styling, and current compositional language remain valuable and should become the default style signature seed set. The central area becomes a virtualized vertical canvas. Each component renders from JSON rather than from a global `view` switch. Real-time collaboration indicators show active viewers, branch status, pending merge requests, and execution progress. + +The application layer is responsible for orchestration rather than ad hoc business logic scattered across controllers. The Prompt Orchestrator assembles context. The Canvas Service persists revisions. The Collaboration Service owns branch semantics. The Component Catalog Service owns reusable visual intelligence. The Data Access Gateway and Visualization Renderer are separate bounded contexts because data authorization and UI rendering safety are different concerns even when implemented in the same runtime. + +The data layer uses raw PostgreSQL as the system of record, consistent with the current backend direction. Operational metadata stays relational. Flexible query descriptors, visualization parameters, style signatures, lineage graphs, and audit payloads use JSONB columns. Large artifacts such as exported page snapshots, generated thumbnails, and template bundles live in object storage. Audit and event records are append-only. + +The AI and ML layer is deliberately constrained. Prompt understanding, retrieval planning, style transfer, and template synthesis all use structured outputs. Nemoclaw never bypasses the Data Access Gateway. Style learning is based on exemplar retrieval and style signatures first, not uncontrolled model fine-tuning. This keeps the system deterministic and reviewable in Sprint 1 while leaving room for richer model adaptation later. + +The integration layer begins with the external identity provider, internal notifications, and future connector adapters. MVP should expose internal Velocity datasets first. Later releases may add data warehouse, ERP, finance, or document-system connectors under the same policy engine. + +### 5.5 End-to-End Data Flow + +When a prompt arrives, the frontend sends the prompt text, conversation context, selected branch, and page context to the Prompt Orchestrator. The orchestrator resolves the authenticated user, tenant, role, active page revision, branch state, and semantic model version. Nemoclaw receives only the contextual summary, schema dictionary, style exemplars, and allowed action surface. Nemoclaw returns a structured plan. The policy engine validates the plan. The Data Access Gateway executes the plan using service credentials with row-level tenant predicates. Results return in typed form to the Visualization Renderer. The renderer chooses a template or calls synthesis. The produced component set is validated. The Canvas Service writes a new revision. The Collaboration Service broadcasts the revision over WebSocket. The frontend applies the revision, preserves scroll anchors, and updates the branch state. + +When a share occurs, the Collaboration Service snapshots the source head as fork base and creates a new branch record and recipient page projection. When a merge request is opened, the service computes a three-way diff. If approved, the Canvas Service writes a merge revision to the target branch. If rejected, the fork persists independently, and the review result remains attached to the merge request record. + +### 5.6 Deployment Topology + +Oracle should deploy as containerized services under the existing FastAPI estate, with the option to start as a modular monolith for MVP and split along service boundaries later. Each tenant is assigned a home region for writes. Reads may be served from regional replicas, but all page revision commits and merge commits route to the tenant home region. PostgreSQL uses logical replication to regional replicas. Object storage uses cross-region replication for snapshots and template artifacts. WebSocket presence and event fanout should use an internal event backbone. The default architecture choice is PostgreSQL transactional outbox plus NATS JetStream for internal event distribution because it offers a clear migration path from a monolith to multi-service fanout without introducing a full-streaming stack too early. + +Release strategy is canary by tenant. Feature flags gate prompt-to-canvas, synthesis, sharing, merge review, and auto-promotion independently. Blue-green or canary deployment is acceptable, but the user-visible rollback unit is always the page revision first, the feature flag second, and the service deployment third. + +### 5.7 Architectural Decision Record Style + +| Decision | Chosen approach | Rationale | Rejected alternative | +| --- | --- | --- | --- | +| Oracle data store | Raw PostgreSQL plus JSONB | Matches current backend, enables strict control and auditability | Supabase-first abstraction | +| Runtime model | Hybrid Sovereign with pluggable Nemoclaw runtime | Preserves data sovereignty and vendor flexibility | Hard-coded hosted LLM dependency | +| Collaboration model | Page branches plus merge requests | Delivers reviewable analytics without live overwrite risk | Unrestricted live co-editing on main | +| Canvas model | Semantic vertical JSON canvas | Aligns with AI generation, persistence, and mergeability | Freeform pixel canvas | +| Style learning | Exemplar retrieval plus style signatures | Deterministic, explainable, easy to govern in MVP | Unbounded fine-tuning-first strategy | +| Eventing | Transactional outbox plus NATS JetStream | Durable and evolvable with moderate complexity | Pure in-process events for all stages | + +## 6. Data Model and JSON Canvas Schema + +### 6.1 Persistence Model + +Oracle stores durable page state in a normalized relational core with JSON projections. `canvas_pages` stores stable page identity and ownership. `canvas_page_revisions` stores immutable revision commits. `canvas_components` stores the latest component projection for fast page reads. `component_templates` stores premade and synthesized templates. `prompt_executions`, `merge_requests`, `fork_records`, `lineage_records`, and `audit_events` store execution, collaboration, provenance, and governance evidence. + +The canonical network contracts are JSON-first. The canonical persistence model is relational plus JSONB. This avoids coupling page rendering directly to SQL tables while still allowing efficient filtering, indexing, and audit queries. + +### 6.2 Canonical Oracle Data Contracts + +```json +{ + "$schema": "https://json-schema.org/draft/2020-12/schema", + "$id": "https://schemas.velocity.ai/oracle/v1/oracle-data-contracts.schema.json", + "$defs": { + "AuditEvent": { + "type": "object", + "properties": { + "auditEventId": { "type": "string" }, + "tenantId": { "type": "string" }, + "entityType": { "type": "string" }, + "entityId": { "type": "string" }, + "action": { "type": "string" }, + "actorId": { "type": "string" }, + "actorType": { "type": "string", "enum": ["user", "service", "ai"] }, + "correlationId": { "type": "string" }, + "executionId": { "type": "string" }, + "createdAt": { "type": "string", "format": "date-time" }, + "details": { "type": "object", "additionalProperties": true } + }, + "required": ["auditEventId", "tenantId", "entityType", "entityId", "action", "actorId", "actorType", "correlationId", "createdAt"] + }, + "LineageRecord": { + "type": "object", + "properties": { + "lineageRecordId": { "type": "string" }, + "tenantId": { "type": "string" }, + "sourceKind": { "type": "string", "enum": ["table", "view", "materialization", "prompt", "component", "template", "merge_request"] }, + "sourceId": { "type": "string" }, + "transformationType": { "type": "string" }, + "transformationSpecHash": { "type": "string" }, + "producedKind": { "type": "string" }, + "producedId": { "type": "string" }, + "policySnapshotId": { "type": "string" }, + "createdAt": { "type": "string", "format": "date-time" } + }, + "required": ["lineageRecordId", "tenantId", "sourceKind", "sourceId", "transformationType", "producedKind", "producedId", "createdAt"] + }, + "DataSourceDescriptor": { + "type": "object", + "properties": { + "descriptorId": { "type": "string" }, + "sourceType": { "type": "string", "enum": ["postgres", "warehouse", "api", "materialized_view", "derived_dataset"] }, + "connectorId": { "type": "string" }, + "dataset": { "type": "string" }, + "authContextRef": { "type": "string" }, + "queryTemplate": { "type": "string" }, + "queryParameters": { "type": "object", "additionalProperties": true }, + "rowLimit": { "type": "integer", "minimum": 1 }, + "freshnessSlaSeconds": { "type": "integer", "minimum": 0 }, + "cachePolicy": { + "type": "object", + "properties": { + "mode": { "type": "string", "enum": ["none", "ttl", "revision_scoped"] }, + "ttlSeconds": { "type": "integer", "minimum": 0 } + }, + "required": ["mode"] + }, + "privacyTier": { "type": "string", "enum": ["standard", "restricted", "sensitive"] }, + "lineageRefs": { + "type": "array", + "items": { "type": "string" } + } + }, + "required": ["descriptorId", "sourceType", "dataset", "authContextRef", "queryTemplate", "rowLimit", "privacyTier"] + }, + "PromptExecution": { + "type": "object", + "properties": { + "executionId": { "type": "string" }, + "tenantId": { "type": "string" }, + "pageId": { "type": "string" }, + "branchId": { "type": "string" }, + "actorId": { "type": "string" }, + "prompt": { "type": "string" }, + "intentClass": { "type": "string", "enum": ["analytical", "operational", "mixed"] }, + "status": { "type": "string", "enum": ["received", "planning", "validated", "executing", "completed", "failed", "clarification_required"] }, + "modelRuntime": { "type": "string" }, + "semanticModelVersion": { "type": "string" }, + "retrievalPlan": { "type": "object", "additionalProperties": true }, + "visualizationPlan": { "type": "object", "additionalProperties": true }, + "warnings": { "type": "array", "items": { "type": "string" } }, + "createdAt": { "type": "string", "format": "date-time" }, + "completedAt": { "type": "string", "format": "date-time" } + }, + "required": ["executionId", "tenantId", "pageId", "branchId", "actorId", "prompt", "intentClass", "status", "modelRuntime", "semanticModelVersion", "createdAt"] + }, + "ForkRecord": { + "type": "object", + "properties": { + "forkId": { "type": "string" }, + "sourcePageId": { "type": "string" }, + "sourceBranchId": { "type": "string" }, + "sourceRevision": { "type": "integer" }, + "forkPageId": { "type": "string" }, + "forkBranchId": { "type": "string" }, + "recipientUserId": { "type": "string" }, + "createdBy": { "type": "string" }, + "createdAt": { "type": "string", "format": "date-time" }, + "status": { "type": "string", "enum": ["active", "merged", "closed"] } + }, + "required": ["forkId", "sourcePageId", "sourceBranchId", "sourceRevision", "forkPageId", "forkBranchId", "recipientUserId", "createdBy", "createdAt", "status"] + }, + "MergeRequest": { + "type": "object", + "properties": { + "mergeRequestId": { "type": "string" }, + "tenantId": { "type": "string" }, + "sourcePageId": { "type": "string" }, + "sourceBranchId": { "type": "string" }, + "sourceHeadRevision": { "type": "integer" }, + "targetPageId": { "type": "string" }, + "targetBranchId": { "type": "string" }, + "targetBaseRevision": { "type": "integer" }, + "title": { "type": "string" }, + "description": { "type": "string" }, + "status": { "type": "string", "enum": ["open", "changes_requested", "approved", "merged", "closed"] }, + "conflicts": { + "type": "array", + "items": { "type": "object", "additionalProperties": true } + }, + "createdBy": { "type": "string" }, + "reviewedBy": { "type": "string" }, + "createdAt": { "type": "string", "format": "date-time" }, + "updatedAt": { "type": "string", "format": "date-time" } + }, + "required": ["mergeRequestId", "tenantId", "sourcePageId", "sourceBranchId", "sourceHeadRevision", "targetPageId", "targetBranchId", "targetBaseRevision", "title", "status", "createdBy", "createdAt", "updatedAt"] + }, + "ComponentTemplate": { + "type": "object", + "properties": { + "templateId": { "type": "string" }, + "tenantId": { "type": "string" }, + "name": { "type": "string" }, + "category": { "type": "string" }, + "status": { "type": "string", "enum": ["catalog_active", "tenant_draft", "tenant_active", "archived", "revoked"] }, + "origin": { "type": "string", "enum": ["premade", "synthesized", "cloned"] }, + "version": { "type": "string" }, + "acceptedShapes": { "type": "array", "items": { "type": "string" } }, + "styleSignature": { "type": "object", "additionalProperties": true }, + "validationState": { "type": "object", "additionalProperties": true }, + "provenance": { "type": "object", "additionalProperties": true }, + "createdAt": { "type": "string", "format": "date-time" }, + "updatedAt": { "type": "string", "format": "date-time" } + }, + "required": ["templateId", "tenantId", "name", "category", "status", "origin", "version", "acceptedShapes", "createdAt", "updatedAt"] + }, + "UserProfile": { + "type": "object", + "properties": { + "userId": { "type": "string" }, + "tenantId": { "type": "string" }, + "email": { "type": "string", "format": "email" }, + "displayName": { "type": "string" }, + "role": { "type": "string", "enum": ["junior_broker", "senior_broker", "sales_director", "marketing_operator", "data_steward", "compliance_reviewer", "platform_admin"] }, + "timezone": { "type": "string" }, + "locale": { "type": "string" }, + "defaultPageId": { "type": "string" }, + "canvasPreferences": { + "type": "object", + "properties": { + "defaultDensity": { "type": "string", "enum": ["compact", "comfortable"] }, + "defaultPlacementMode": { "type": "string", "enum": ["append_after_last_visible_component", "insert_after_component"] }, + "showLineageBadges": { "type": "boolean" } + }, + "required": ["defaultDensity", "defaultPlacementMode", "showLineageBadges"] + }, + "policyProfileId": { "type": "string" }, + "createdAt": { "type": "string", "format": "date-time" }, + "updatedAt": { "type": "string", "format": "date-time" } + }, + "required": ["userId", "tenantId", "email", "displayName", "role", "timezone", "locale", "defaultPageId", "canvasPreferences", "policyProfileId", "createdAt", "updatedAt"] + }, + "CanvasComponent": { + "type": "object", + "properties": { + "componentId": { "type": "string" }, + "type": { + "type": "string", + "enum": ["kpiTile", "barChart", "lineChart", "scatterPlot", "geoMap", "table", "pipelineBoard", "timeline", "heatmap", "forecastChart", "activityStream", "customMLVisualization", "errorNotice"] + }, + "title": { "type": "string" }, + "description": { "type": "string" }, + "dataSourceDescriptor": { "$ref": "#/$defs/DataSourceDescriptor" }, + "visualizationParameters": { "type": "object", "additionalProperties": true }, + "dataBindings": { + "type": "object", + "properties": { + "dimensions": { "type": "array", "items": { "type": "string" } }, + "measures": { "type": "array", "items": { "type": "string" } }, + "series": { "type": "array", "items": { "type": "string" } }, + "filters": { "type": "array", "items": { "type": "object", "additionalProperties": true } } + }, + "required": ["dimensions", "measures", "series", "filters"] + }, + "version": { "type": "integer", "minimum": 1 }, + "provenance": { + "type": "object", + "properties": { + "originType": { "type": "string", "enum": ["catalog", "prompt_generated", "cloned", "merged", "edited"] }, + "templateId": { "type": "string" }, + "promptExecutionId": { "type": "string" }, + "sourceComponentId": { "type": "string" }, + "sourceBranchId": { "type": "string" }, + "createdBy": { "type": "string" }, + "createdAt": { "type": "string", "format": "date-time" } + }, + "required": ["originType", "createdBy", "createdAt"] + }, + "renderingHints": { + "type": "object", + "properties": { + "estimatedHeightPx": { "type": "integer", "minimum": 80 }, + "skeletonVariant": { "type": "string" }, + "virtualizationPriority": { "type": "integer", "minimum": 1, "maximum": 10 } + }, + "required": ["estimatedHeightPx", "skeletonVariant", "virtualizationPriority"] + }, + "layout": { + "type": "object", + "properties": { + "orderIndex": { "type": "integer", "minimum": 1 }, + "sectionId": { "type": "string" }, + "widthMode": { "type": "string", "enum": ["full", "half", "third"] }, + "minHeightPx": { "type": "integer", "minimum": 80 }, + "stickyHeader": { "type": "boolean" } + }, + "required": ["orderIndex", "sectionId", "widthMode", "minHeightPx", "stickyHeader"] + }, + "accessControls": { + "type": "object", + "properties": { + "visibilityScope": { "type": "string", "enum": ["private", "shared_fork", "tenant_team"] }, + "allowedRoles": { "type": "array", "items": { "type": "string" } }, + "redactionPolicy": { "type": "string" } + }, + "required": ["visibilityScope", "allowedRoles", "redactionPolicy"] + }, + "styleSignature": { + "type": "object", + "properties": { + "theme": { "type": "string" }, + "paletteToken": { "type": "string" }, + "motionProfile": { "type": "string" }, + "density": { "type": "string" }, + "radiusScale": { "type": "string" }, + "typographyScale": { "type": "string" } + }, + "required": ["theme", "paletteToken", "motionProfile", "density", "radiusScale", "typographyScale"] + }, + "validationState": { + "type": "object", + "properties": { + "schema": { "type": "string", "enum": ["pass", "fail"] }, + "policy": { "type": "string", "enum": ["pass", "fail"] }, + "a11y": { "type": "string", "enum": ["pass", "fail"] }, + "performance": { "type": "string", "enum": ["pass", "fail"] }, + "status": { "type": "string", "enum": ["validated", "rejected", "needs_review"] } + }, + "required": ["schema", "policy", "a11y", "performance", "status"] + }, + "auditLog": { + "type": "array", + "items": { "type": "string" } + } + }, + "required": ["componentId", "type", "title", "dataSourceDescriptor", "visualizationParameters", "dataBindings", "version", "provenance", "renderingHints", "layout", "accessControls", "styleSignature", "validationState", "auditLog"] + }, + "CanvasPage": { + "type": "object", + "properties": { + "pageId": { "type": "string" }, + "tenantId": { "type": "string" }, + "ownerId": { "type": "string" }, + "branchId": { "type": "string" }, + "branchName": { "type": "string" }, + "pageType": { "type": "string", "enum": ["main", "fork"] }, + "title": { "type": "string" }, + "createdAt": { "type": "string", "format": "date-time" }, + "updatedAt": { "type": "string", "format": "date-time" }, + "isShared": { "type": "boolean" }, + "forks": { + "type": "array", + "items": { "$ref": "#/$defs/ForkRecord" } + }, + "mainBranchPointer": { + "type": "object", + "properties": { + "pageId": { "type": "string" }, + "branchId": { "type": "string" }, + "revision": { "type": "integer" } + }, + "required": ["pageId", "branchId", "revision"] + }, + "baseRevision": { "type": "integer", "minimum": 0 }, + "headRevision": { "type": "integer", "minimum": 0 }, + "sharingPolicy": { + "type": "object", + "properties": { + "shareMode": { "type": "string", "enum": ["private", "direct_fork_only"] }, + "allowReshare": { "type": "boolean" }, + "defaultForkVisibility": { "type": "string", "enum": ["private", "team"] } + }, + "required": ["shareMode", "allowReshare", "defaultForkVisibility"] + }, + "presence": { + "type": "object", + "properties": { + "activeViewers": { "type": "integer", "minimum": 0 }, + "activeEditors": { "type": "integer", "minimum": 0 }, + "lastPresenceAt": { "type": "string", "format": "date-time" } + }, + "required": ["activeViewers", "activeEditors", "lastPresenceAt"] + }, + "lineage": { + "type": "array", + "items": { "$ref": "#/$defs/LineageRecord" } + }, + "audit": { + "type": "object", + "properties": { + "lastAuditEventId": { "type": "string" }, + "eventCount": { "type": "integer", "minimum": 0 } + }, + "required": ["lastAuditEventId", "eventCount"] + }, + "components": { + "type": "array", + "items": { "$ref": "#/$defs/CanvasComponent" } + } + }, + "required": ["pageId", "tenantId", "ownerId", "branchId", "branchName", "pageType", "title", "createdAt", "updatedAt", "isShared", "forks", "mainBranchPointer", "baseRevision", "headRevision", "sharingPolicy", "presence", "lineage", "audit", "components"] + } + } +} +``` + +### 6.3 Example Component Object: QD-Weighted Source Comparison + +```json +{ + "componentId": "cmp_01JQWE6F4G5BARWHALESRC", + "type": "barChart", + "title": "Whale Leads by Source This Week", + "description": "Compares QD-weighted whale lead volume across lead sources in the current week.", + "dataSourceDescriptor": { + "descriptorId": "dsd_01JQWE6F4G5SRC", + "sourceType": "postgres", + "connectorId": "velocity-core-postgres", + "dataset": "lead_daily_snapshot", + "authContextRef": "authctx_sales_director_team_scope", + "queryTemplate": "select source, sum(qd_weighted_score) as qd_weighted_volume from lead_daily_snapshot where tenant_id = :tenant_id and lead_class = 'whale' and snapshot_date between :week_start and :week_end group by source order by qd_weighted_volume desc", + "queryParameters": { + "tenant_id": "tenant_binghatti_demo", + "week_start": "2026-04-06", + "week_end": "2026-04-12" + }, + "rowLimit": 20, + "freshnessSlaSeconds": 120, + "cachePolicy": { + "mode": "ttl", + "ttlSeconds": 120 + }, + "privacyTier": "standard", + "lineageRefs": ["lin_01JQWE6F4G5LEADSNAP"] + }, + "visualizationParameters": { + "xAxis": "source", + "yAxis": "qd_weighted_volume", + "sort": "desc", + "showLabels": true, + "colorScale": ["#0EA5E9", "#22D3EE"], + "legend": false + }, + "dataBindings": { + "dimensions": ["source"], + "measures": ["qd_weighted_volume"], + "series": [], + "filters": [ + { + "field": "lead_class", + "operator": "=", + "value": "whale" + } + ] + }, + "version": 1, + "provenance": { + "originType": "prompt_generated", + "templateId": "tpl_bar_source_quality_v3", + "promptExecutionId": "pex_01JQWE6F4G5PROMPTA", + "createdBy": "user_sales_director_001", + "createdAt": "2026-04-08T16:40:22Z" + }, + "renderingHints": { + "estimatedHeightPx": 340, + "skeletonVariant": "chart", + "virtualizationPriority": 8 + }, + "layout": { + "orderIndex": 100, + "sectionId": "sec_market_overview", + "widthMode": "half", + "minHeightPx": 320, + "stickyHeader": false + }, + "accessControls": { + "visibilityScope": "private", + "allowedRoles": ["senior_broker", "sales_director", "marketing_operator"], + "redactionPolicy": "aggregate_only" + }, + "styleSignature": { + "theme": "velocity_glass", + "paletteToken": "ocean_signal", + "motionProfile": "calm_reveal", + "density": "comfortable", + "radiusScale": "lg", + "typographyScale": "balanced" + }, + "validationState": { + "schema": "pass", + "policy": "pass", + "a11y": "pass", + "performance": "pass", + "status": "validated" + }, + "auditLog": ["aud_01JQWE6F4G5CREATE", "aud_01JQWE6F4G5VALIDATE"] +} +``` + +### 6.4 Example Component Object: Investor Geography Map + +```json +{ + "componentId": "cmp_01JQWE7H7G9GEOMAPINV", + "type": "geoMap", + "title": "Investor Interest Density by Dubai District", + "description": "Maps high-intent leads with at least one positive Sentinel spike in the last 30 days.", + "dataSourceDescriptor": { + "descriptorId": "dsd_01JQWE7H7G9MAP", + "sourceType": "derived_dataset", + "connectorId": "velocity-core-postgres", + "dataset": "lead_geo_interest_rollup", + "authContextRef": "authctx_sales_director_team_scope", + "queryTemplate": "select district, lat, lng, lead_count, avg_qd_score from lead_geo_interest_rollup where tenant_id = :tenant_id and activity_window = :window and min_interest = :min_interest", + "queryParameters": { + "tenant_id": "tenant_binghatti_demo", + "window": "30d", + "min_interest": "high" + }, + "rowLimit": 100, + "freshnessSlaSeconds": 300, + "cachePolicy": { + "mode": "ttl", + "ttlSeconds": 300 + }, + "privacyTier": "restricted", + "lineageRefs": ["lin_01JQWE7H7G9ROLLUP", "lin_01JQWE7H7G9SENTINEL"] + }, + "visualizationParameters": { + "mapStyle": "dubai_district_heat", + "intensityField": "lead_count", + "tooltipFields": ["district", "lead_count", "avg_qd_score"], + "interactive": true + }, + "dataBindings": { + "dimensions": ["district"], + "measures": ["lead_count", "avg_qd_score"], + "series": ["district"], + "filters": [ + { + "field": "activity_window", + "operator": "=", + "value": "30d" + } + ] + }, + "version": 1, + "provenance": { + "originType": "catalog", + "templateId": "tpl_geo_investor_heat_v2", + "promptExecutionId": "pex_01JQWE7H7G9PROMPTB", + "createdBy": "user_sales_director_001", + "createdAt": "2026-04-08T16:40:23Z" + }, + "renderingHints": { + "estimatedHeightPx": 420, + "skeletonVariant": "map", + "virtualizationPriority": 9 + }, + "layout": { + "orderIndex": 200, + "sectionId": "sec_market_overview", + "widthMode": "half", + "minHeightPx": 400, + "stickyHeader": false + }, + "accessControls": { + "visibilityScope": "private", + "allowedRoles": ["senior_broker", "sales_director"], + "redactionPolicy": "district_level_only" + }, + "styleSignature": { + "theme": "velocity_glass", + "paletteToken": "aqua_signal", + "motionProfile": "calm_reveal", + "density": "comfortable", + "radiusScale": "lg", + "typographyScale": "balanced" + }, + "validationState": { + "schema": "pass", + "policy": "pass", + "a11y": "pass", + "performance": "pass", + "status": "validated" + }, + "auditLog": ["aud_01JQWE7H7G9CREATE", "aud_01JQWE7H7G9VALIDATE"] +} +``` + +### 6.5 Example Canvas Page with Multiple Components + +```json +{ + "pageId": "page_01JQWE9MAINBROKER", + "tenantId": "tenant_binghatti_demo", + "ownerId": "user_sales_director_001", + "branchId": "branch_main", + "branchName": "main", + "pageType": "main", + "title": "Oracle Home - Pipeline and Investor Signals", + "createdAt": "2026-04-08T16:10:00Z", + "updatedAt": "2026-04-08T16:40:24Z", + "isShared": true, + "forks": [ + { + "forkId": "fork_01JQWE9TEAMSHARE", + "sourcePageId": "page_01JQWE9MAINBROKER", + "sourceBranchId": "branch_main", + "sourceRevision": 18, + "forkPageId": "page_01JQWE9FORKBROKER2", + "forkBranchId": "branch_fork_broker2", + "recipientUserId": "user_senior_broker_014", + "createdBy": "user_sales_director_001", + "createdAt": "2026-04-08T16:41:10Z", + "status": "active" + } + ], + "mainBranchPointer": { + "pageId": "page_01JQWE9MAINBROKER", + "branchId": "branch_main", + "revision": 18 + }, + "baseRevision": 0, + "headRevision": 18, + "sharingPolicy": { + "shareMode": "direct_fork_only", + "allowReshare": false, + "defaultForkVisibility": "private" + }, + "presence": { + "activeViewers": 2, + "activeEditors": 1, + "lastPresenceAt": "2026-04-08T16:40:24Z" + }, + "lineage": [ + { + "lineageRecordId": "lin_01JQWE9PAGE", + "tenantId": "tenant_binghatti_demo", + "sourceKind": "prompt", + "sourceId": "pex_01JQWE6F4G5PROMPTA", + "transformationType": "prompt_to_component_bundle", + "transformationSpecHash": "sha256:promptbundleA", + "producedKind": "page_revision", + "producedId": "page_01JQWE9MAINBROKER:18", + "policySnapshotId": "policy_2026_04_08_v4", + "createdAt": "2026-04-08T16:40:24Z" + } + ], + "audit": { + "lastAuditEventId": "aud_01JQWE9REV18", + "eventCount": 44 + }, + "components": [ + { + "componentId": "cmp_01JQWE6F4G5BARWHALESRC", + "type": "barChart", + "title": "Whale Leads by Source This Week", + "description": "Compares QD-weighted whale lead volume across lead sources in the current week.", + "dataSourceDescriptor": { + "descriptorId": "dsd_01JQWE6F4G5SRC", + "sourceType": "postgres", + "connectorId": "velocity-core-postgres", + "dataset": "lead_daily_snapshot", + "authContextRef": "authctx_sales_director_team_scope", + "queryTemplate": "select source, sum(qd_weighted_score) as qd_weighted_volume from lead_daily_snapshot where tenant_id = :tenant_id and lead_class = 'whale' and snapshot_date between :week_start and :week_end group by source order by qd_weighted_volume desc", + "queryParameters": { + "tenant_id": "tenant_binghatti_demo", + "week_start": "2026-04-06", + "week_end": "2026-04-12" + }, + "rowLimit": 20, + "freshnessSlaSeconds": 120, + "cachePolicy": { "mode": "ttl", "ttlSeconds": 120 }, + "privacyTier": "standard", + "lineageRefs": ["lin_01JQWE6F4G5LEADSNAP"] + }, + "visualizationParameters": { + "xAxis": "source", + "yAxis": "qd_weighted_volume", + "sort": "desc", + "showLabels": true, + "colorScale": ["#0EA5E9", "#22D3EE"], + "legend": false + }, + "dataBindings": { + "dimensions": ["source"], + "measures": ["qd_weighted_volume"], + "series": [], + "filters": [{ "field": "lead_class", "operator": "=", "value": "whale" }] + }, + "version": 1, + "provenance": { + "originType": "prompt_generated", + "templateId": "tpl_bar_source_quality_v3", + "promptExecutionId": "pex_01JQWE6F4G5PROMPTA", + "createdBy": "user_sales_director_001", + "createdAt": "2026-04-08T16:40:22Z" + }, + "renderingHints": { + "estimatedHeightPx": 340, + "skeletonVariant": "chart", + "virtualizationPriority": 8 + }, + "layout": { + "orderIndex": 100, + "sectionId": "sec_market_overview", + "widthMode": "half", + "minHeightPx": 320, + "stickyHeader": false + }, + "accessControls": { + "visibilityScope": "private", + "allowedRoles": ["senior_broker", "sales_director", "marketing_operator"], + "redactionPolicy": "aggregate_only" + }, + "styleSignature": { + "theme": "velocity_glass", + "paletteToken": "ocean_signal", + "motionProfile": "calm_reveal", + "density": "comfortable", + "radiusScale": "lg", + "typographyScale": "balanced" + }, + "validationState": { + "schema": "pass", + "policy": "pass", + "a11y": "pass", + "performance": "pass", + "status": "validated" + }, + "auditLog": ["aud_01JQWE6F4G5CREATE", "aud_01JQWE6F4G5VALIDATE"] + }, + { + "componentId": "cmp_01JQWE7H7G9GEOMAPINV", + "type": "geoMap", + "title": "Investor Interest Density by Dubai District", + "description": "Maps high-intent leads with at least one positive Sentinel spike in the last 30 days.", + "dataSourceDescriptor": { + "descriptorId": "dsd_01JQWE7H7G9MAP", + "sourceType": "derived_dataset", + "connectorId": "velocity-core-postgres", + "dataset": "lead_geo_interest_rollup", + "authContextRef": "authctx_sales_director_team_scope", + "queryTemplate": "select district, lat, lng, lead_count, avg_qd_score from lead_geo_interest_rollup where tenant_id = :tenant_id and activity_window = :window and min_interest = :min_interest", + "queryParameters": { + "tenant_id": "tenant_binghatti_demo", + "window": "30d", + "min_interest": "high" + }, + "rowLimit": 100, + "freshnessSlaSeconds": 300, + "cachePolicy": { "mode": "ttl", "ttlSeconds": 300 }, + "privacyTier": "restricted", + "lineageRefs": ["lin_01JQWE7H7G9ROLLUP", "lin_01JQWE7H7G9SENTINEL"] + }, + "visualizationParameters": { + "mapStyle": "dubai_district_heat", + "intensityField": "lead_count", + "tooltipFields": ["district", "lead_count", "avg_qd_score"], + "interactive": true + }, + "dataBindings": { + "dimensions": ["district"], + "measures": ["lead_count", "avg_qd_score"], + "series": ["district"], + "filters": [{ "field": "activity_window", "operator": "=", "value": "30d" }] + }, + "version": 1, + "provenance": { + "originType": "catalog", + "templateId": "tpl_geo_investor_heat_v2", + "promptExecutionId": "pex_01JQWE7H7G9PROMPTB", + "createdBy": "user_sales_director_001", + "createdAt": "2026-04-08T16:40:23Z" + }, + "renderingHints": { + "estimatedHeightPx": 420, + "skeletonVariant": "map", + "virtualizationPriority": 9 + }, + "layout": { + "orderIndex": 200, + "sectionId": "sec_market_overview", + "widthMode": "half", + "minHeightPx": 400, + "stickyHeader": false + }, + "accessControls": { + "visibilityScope": "private", + "allowedRoles": ["senior_broker", "sales_director"], + "redactionPolicy": "district_level_only" + }, + "styleSignature": { + "theme": "velocity_glass", + "paletteToken": "aqua_signal", + "motionProfile": "calm_reveal", + "density": "comfortable", + "radiusScale": "lg", + "typographyScale": "balanced" + }, + "validationState": { + "schema": "pass", + "policy": "pass", + "a11y": "pass", + "performance": "pass", + "status": "validated" + }, + "auditLog": ["aud_01JQWE7H7G9CREATE", "aud_01JQWE7H7G9VALIDATE"] + } + ] +} +``` + +### 6.6 Prompt-to-Component Transformation Example + +The following example illustrates how Oracle turns a natural-language request into multiple components. + +```json +{ + "prompt": "Show me this week's whale leads by source and map where our high-intent investors are concentrated in Dubai.", + "intentClass": "mixed", + "retrievalPlan": { + "entities": ["lead_daily_snapshot", "lead_geo_interest_rollup"], + "metrics": ["qd_weighted_volume", "lead_count", "avg_qd_score"], + "dimensions": ["source", "district"], + "filters": [ + { "field": "lead_class", "operator": "=", "value": "whale" }, + { "field": "activity_window", "operator": "=", "value": "7d" } + ], + "privacyTier": "restricted", + "resultShape": ["categorical_aggregate", "geospatial_aggregate"] + }, + "visualizationPlan": { + "componentSet": [ + { + "componentType": "barChart", + "templateId": "tpl_bar_source_quality_v3", + "placement": "append" + }, + { + "componentType": "geoMap", + "templateId": "tpl_geo_investor_heat_v2", + "placement": "append" + } + ], + "styleSignature": "velocity_glass:ocean_signal:comfortable" + } +} +``` + +### 6.7 Indexing Keys and Storage Formats + +| Store | Primary key or identity | Secondary indexes | Storage note | +| --- | --- | --- | --- | +| `canvas_pages` | `(tenant_id, page_id)` | `(tenant_id, owner_id, page_type)`, `(tenant_id, branch_id)` | Stable metadata only | +| `canvas_page_revisions` | `(tenant_id, page_id, revision_number)` | `(tenant_id, page_id, created_at desc)` | Immutable revision bodies compressed as JSONB | +| `canvas_components` | `(tenant_id, page_id, branch_id, component_id)` | `(tenant_id, page_id, branch_id, order_index)`, GIN on `data_source_descriptor`, GIN on `style_signature` | Latest projection for fast page reads | +| `component_templates` | `(tenant_id, template_id)` | `(tenant_id, category, status)`, `(tenant_id, style_signature_hash)` | JSONB template metadata plus object storage bundle ref | +| `prompt_executions` | `(tenant_id, execution_id)` | `(tenant_id, page_id, created_at desc)`, `(tenant_id, actor_id, created_at desc)` | Retains request, plan, outcome, warnings | +| `merge_requests` | `(tenant_id, merge_request_id)` | `(tenant_id, target_page_id, status)`, `(tenant_id, source_page_id, status)` | Stores diff metadata and reviewer state | +| `audit_events` | `(tenant_id, audit_event_id)` | `(tenant_id, entity_type, entity_id, created_at desc)`, `(tenant_id, correlation_id)` | Append-only evidence trail | +| `lineage_records` | `(tenant_id, lineage_record_id)` | `(tenant_id, produced_kind, produced_id)`, `(tenant_id, source_kind, source_id)` | Graph edges for provenance and replay | + +## 7. Premade Components Catalog and AI Synthesis + +### 7.1 Catalog Taxonomy + +| Category | Business meaning | Typical component forms | Accessibility and semantics | +| --- | --- | --- | --- | +| Executive overview | Quick status for leaders | KPI tile, compare card, exception notice | Must expose numeric text equivalent and threshold legend | +| Pipeline management | Deal movement and bottlenecks | Pipeline board, stage funnel, stalled-deals table | Must support keyboard navigation and color-independent stage labels | +| Broker performance | Rep activity and outcome quality | Ranked table, trend chart, quota card | Must show exact values in tooltips and tabular fallback | +| Lead quality | Source quality, whale mix, QD trend | Bar chart, stacked chart, score distribution | Must avoid color-only meaning for qualification bands | +| Geographic demand | Location-based concentration | Geo map, district matrix, map plus table | Must provide textual district summaries and aggregated counts | +| Investor timelines | Historical interactions and milestone progress | Timeline, activity stream, sentiment overlay | Must preserve chronological order in screen reader mode | +| Inventory and project analytics | Absorption, pricing, availability | Line chart, heatmap, cohort matrix | Must expose exact numeric labels on focus | +| Operational queues | Follow-ups, approvals, exceptions | Queue table, task board, merge review card | Must support compact dense mode without loss of tab order | + +The premade catalog is seeded from two sources. The first source is purpose-built Oracle templates for the canonical real-estate workflows above. The second source is style and interaction exemplars extracted from the existing Oracle UI and the component reference material already present in the repo. This lets Sprint 1 preserve the visual language the user has already established while greatly expanding functional depth. + +### 7.2 Style Learning and Aesthetic Adaptation + +Oracle does not start with uncontrolled model fine-tuning. In MVP, style learning is retrieval-based. The system ingests exemplar components and extracts a `styleSignature` that captures theme name, palette tokens, motion profile, density, radius scale, typography scale, spacing rhythm, contrast thresholds, and default interaction affordances. The synthesis engine then conditions component generation on that style signature plus a small set of canonical component archetypes. + +This is the correct first-principles choice for Sprint 1. It is explainable, reversible, and easy to validate. It lets Oracle learn the user's aesthetic without introducing a black-box dependency between visual identity and model weights. Phase 2 may add tenant-level adaptation loops that update style priors based on accepted components and rejected components, but the system of record remains the explicit `styleSignature`. + +### 7.3 Synthesis Workflow + +When Nemoclaw requests a component type that has no exact catalog match, the synthesis engine retrieves the closest semantic archetype and the closest style exemplars. It then generates a candidate template in structured form, validates the template schema, simulates render performance, runs accessibility checks, checks security constraints such as redaction and external asset policy, and renders a preview. If validation passes, Oracle persists the template as `tenant_draft`, renders the corresponding component to the canvas, and records the full provenance chain. + +Auto-promotion within the tenant occurs only after successful automated validation and repeated successful runtime use. The default promotion rule is three successful executions across at least two different pages within the same tenant with zero schema, policy, accessibility, or render failures. A sales director or data steward may accelerate promotion manually. No synthesized template may become global in this artifact. + +### 7.4 Versioning, Caching, and Invalidation + +Template versioning follows `major.minor.patch`. A major change alters accepted data bindings, required parameters, or semantics. A minor change alters default styling, interactions, or copy. A patch changes bug fixes or non-breaking defaults. Component instances record the template version used at creation time and do not automatically jump across major versions. + +Client-side template metadata is cached for five minutes by default. Server-side rendered template bundles are cached by `templateId:version:styleSignatureHash`. Cache invalidation occurs on publish, revoke, manual archive, or style signature change. Any revoked template immediately invalidates associated bundles and renders fallback notices for existing components until they are replaced or restored under review. + +### 7.5 Example Synthesis Narrative + +Assume the user asks, "Create a broker influence radar that compares follow-up speed, QD uplift, and conversion probability by team lead." Oracle will not find an exact premade template in Sprint 1. Nemoclaw classifies the request as analytical, identifies a multivariate comparison pattern, and requests a synthesized `customMLVisualization` backed by the nearest archetype for comparative team diagnostics. The style engine pulls the tenant's accepted Oracle chart tokens. The synthesis engine generates a structured component template and validates it. The component renders on the page as a tenant-private draft template. After subsequent successful use, Oracle auto-promotes it into the tenant catalog so future prompts can reuse it directly. + +## 8. Nemoclaw Integration and Data Access + +### 8.1 Nemoclaw's Role + +Nemoclaw is the planner, not the final authority. It understands the user's prompt, maps intent to business entities and metrics, proposes a retrieval plan, proposes a visualization plan, and emits reasoning metadata for audit. It does not obtain direct access to raw connector credentials. It does not commit page revisions. It does not decide policy. Those responsibilities belong to the policy engine, the Data Access Gateway, and the Canvas Service. + +Oracle should use the same runtime abstraction pattern already visible in the current backend's `backend/services/nemoclaw_client.py`. The active production runtime may be a hosted compatible model, a private compatible model, or a local model depending on tenant policy. The Oracle contract stays stable across all three. + +### 8.2 Nemoclaw Request Contract + +```json +{ + "executionId": "pex_01JQX00PLANNER", + "tenantId": "tenant_binghatti_demo", + "actor": { + "userId": "user_sales_director_001", + "role": "sales_director", + "timezone": "Asia/Dubai" + }, + "pageContext": { + "pageId": "page_01JQWE9MAINBROKER", + "branchId": "branch_main", + "headRevision": 18, + "visibleComponentIds": ["cmp_01JQWE6F4G5BARWHALESRC"] + }, + "prompt": "Show me this week's whale leads by source and map where our high-intent investors are concentrated in Dubai.", + "semanticModelVersion": "oracle_semantic_v2026_04_08_01", + "allowedDatasets": [ + "lead_daily_snapshot", + "lead_geo_interest_rollup", + "sentinel_signal_rollup" + ], + "policyConstraints": { + "crossTenantAllowed": false, + "maxRowLimit": 1000, + "piiMode": "redact_unless_explicit" + }, + "styleContext": { + "preferredTheme": "velocity_glass", + "exemplarTemplateIds": [ + "tpl_bar_source_quality_v3", + "tpl_geo_investor_heat_v2" + ] + } +} +``` + +### 8.3 Nemoclaw Response Contract + +```json +{ + "executionId": "pex_01JQX00PLANNER", + "intentClass": "mixed", + "confidence": 0.94, + "retrievalPlan": { + "datasets": [ + { + "dataset": "lead_daily_snapshot", + "grain": "lead_day", + "filters": [ + { "field": "lead_class", "operator": "=", "value": "whale" }, + { "field": "snapshot_date", "operator": "between", "value": ["2026-04-06", "2026-04-12"] } + ], + "dimensions": ["source"], + "measures": ["sum(qd_weighted_score) as qd_weighted_volume"] + }, + { + "dataset": "lead_geo_interest_rollup", + "grain": "district_30d", + "filters": [ + { "field": "min_interest", "operator": "=", "value": "high" } + ], + "dimensions": ["district", "lat", "lng"], + "measures": ["lead_count", "avg_qd_score"] + } + ], + "privacyTier": "restricted" + }, + "visualizationPlan": { + "components": [ + { + "componentType": "barChart", + "templateId": "tpl_bar_source_quality_v3", + "placementMode": "append" + }, + { + "componentType": "geoMap", + "templateId": "tpl_geo_investor_heat_v2", + "placementMode": "append" + } + ] + }, + "warnings": [], + "requiresHumanReview": false +} +``` + +### 8.4 Data Access Gateway Contract + +```json +{ + "executionId": "pex_01JQX00PLANNER", + "tenantId": "tenant_binghatti_demo", + "descriptor": { + "dataset": "lead_daily_snapshot", + "queryTemplate": "select source, sum(qd_weighted_score) as qd_weighted_volume from lead_daily_snapshot where tenant_id = :tenant_id and lead_class = 'whale' and snapshot_date between :week_start and :week_end group by source", + "queryParameters": { + "tenant_id": "tenant_binghatti_demo", + "week_start": "2026-04-06", + "week_end": "2026-04-12" + }, + "authContextRef": "authctx_sales_director_team_scope" + } +} +``` + +```json +{ + "executionId": "pex_01JQX00PLANNER", + "status": "completed", + "resultShape": "categorical_aggregate", + "schema": [ + { "field": "source", "type": "string" }, + { "field": "qd_weighted_volume", "type": "number" } + ], + "rows": [ + { "source": "website", "qd_weighted_volume": 182.4 }, + { "source": "walkin", "qd_weighted_volume": 149.2 }, + { "source": "whatsapp", "qd_weighted_volume": 93.7 } + ], + "lineageRecordIds": ["lin_01JQWE6F4G5LEADSNAP"], + "cacheKey": "cache:tenant_binghatti_demo:lead_daily_snapshot:2026w15" +} +``` + +### 8.5 Hallucination, Non-Determinism, and Data Leakage Mitigation + +Oracle must constrain Nemoclaw through structure, not trust. The prompt planner uses low-temperature structured output. The response is parsed against JSON Schema. The semantic compiler rejects unknown entities, unknown joins, non-parameterized predicates, unsafe field requests, and policy mismatches. The Data Access Gateway executes only validated plans. The Visualization Renderer consumes typed datasets and never infers field names from raw prose. Every execution stores the model runtime id, prompt hash, schema version, plan hash, and validation result so that the same execution can be replayed during debugging or audit. + +Leakage prevention starts before query execution. Nemoclaw receives only the semantic dictionary and visible page context needed for planning. It does not receive all tenant data. Sensitive prompt text is redacted in model logs according to privacy tier. Returned rows are subjected to policy transforms before rendering. When a prompt requests PII without explicit justification or scope, Oracle either redacts those fields or denies the request. + +### 8.6 AI Stewardship Workflow + +Oracle treats AI as the operational steward of records, not as the unchecked owner of schema. Nemoclaw may propose new derived fields, new rollup views, new indexes, new semantic aliases, and new component templates. Those proposals become typed change records reviewed by a data steward or platform admin. A proposal can be accepted, revised, or rejected. Accepted proposals become migrations, materializations, or catalog entries managed under version control and normal deployment controls. This preserves the "AI-owned database" operating model while keeping structural governance accountable and safe. + +## 9. Security, Privacy, and Compliance + +### 9.1 Authentication and Authorization + +Oracle should integrate with an external identity provider for production tenants and retain the current internal JWT scheme only for local or transitional environments. Access tokens should be short-lived. Refresh and session policies should be handled at the identity layer. Every request into Oracle resolves an effective principal that combines identity claims, tenant scope, role, page access, and policy profile. + +Authorization must occur at multiple layers. The API layer enforces page and action permissions. The Data Access Gateway enforces tenant and row scope. The component renderer enforces redaction and visibility scope. The collaboration layer enforces fork and merge rights. A user who can view a page is not automatically authorized to view all datasets referenced by all components on that page. Access must be recalculated at render time and export time. + +### 9.2 Zero-Trust Control Model + +| Control area | Required design | Evidence expectation | +| --- | --- | --- | +| Network trust | Mutual TLS for service-to-service traffic and private service discovery | Certificate rotation records and handshake telemetry | +| Secret handling | Secrets in managed secret store, not in repo or env files committed to git | Secret access audit and rotation policy | +| Data at rest | Envelope encryption with tenant-scoped data keys | Key hierarchy and key rotation logs | +| Data in transit | TLS 1.2+ everywhere and pinned internal trust anchors | Load balancer and service mesh policy evidence | +| Least privilege | Separate service identities for planner, query executor, renderer, and notifier | IAM policy review and quarterly access recertification | +| Runtime hardening | Signed images, patched base images, no privileged containers, dependency scanning | SBOM and vulnerability report | + +### 9.3 Sensitive Data Handling + +Prompts and results must be classified into privacy tiers. `standard` data includes aggregate counts and non-sensitive operational data. `restricted` data includes identifiable customer interaction details that require role-aware visibility. `sensitive` data includes regulated or highly personal data that demands explicit justification, stronger redaction, tighter retention, and narrower logging. + +Redaction policies must be enforced before model planning where possible and certainly before render. Differential privacy options are available only for approved benchmark and research views and are not enabled by default for ordinary broker workflows. Oracle should minimize data movement by preferring aggregates, materialized views, and tokenized identifiers over raw record dumps whenever the prompt does not require record-level access. + +### 9.4 Retention, Deletion, and Immutable Auditing + +Retention must be policy-driven by tenant and data class. Prompt executions and page revisions should default to one year of hot storage and longer archival retention when required. Audit logs should be immutable for the legal retention period. A delete request against user data must propagate through pages, exports, snapshots, and derived datasets according to lineage. When legal hold is active, logical deletion may hide data from operators but cannot physically purge protected records until the hold is lifted. + +Immutable audit logs must capture who asked what, what was planned, what datasets were touched, what component was rendered, who shared the page, who forked it, who approved a merge, and what changed. Audit payloads should store hashes for sensitive prompt bodies when tenant policy forbids storing plain text long term. + +### 9.5 Compliance Mapping + +| Regime | Oracle requirement | Design response | +| --- | --- | --- | +| GDPR | Lawful basis, purpose limitation, minimization, access and deletion rights | Policy-driven retention, lineage-based deletion, consent-aware data access, export and deletion workflows | +| CCPA | Disclosure, deletion, and no unauthorized sharing of personal data | Tenant-scoped access logs, export endpoints, retention enforcement, no cross-tenant data sharing in MVP | +| HIPAA-ready mode | Access controls, encryption, audit logging, minimum necessary access | Sensitive tier handling, mTLS, immutable audit, redaction, role and purpose checks | + +Cross-tenant analytics is not enabled in MVP. Any future cross-tenant analytics must require anonymization, explicit legal basis, tenant opt-in, and a dedicated policy gate outside the normal Oracle flow. + +## 10. Testing Strategy and Quality Assurance + +### 10.1 Test Scope + +Oracle test coverage must span unit, integration, end-to-end, performance, security, accessibility, and chaos testing. The critical path includes prompt interpretation, policy validation, data retrieval, template resolution, component generation, page revision commits, share and fork creation, merge request creation, merge conflict handling, lineage capture, and template auto-promotion. + +| Test layer | What it proves | Target | +| --- | --- | --- | +| Unit tests | Deterministic behavior of plan parsing, policy evaluation, diff logic, ordering, and redaction | >= 90% coverage on prompt planning and access control modules | +| Integration tests | Contracts across orchestrator, query gateway, canvas service, and catalog | >= 85% coverage on collaboration and persistence modules | +| End-to-end tests | Full prompt-to-canvas and share-to-merge user journeys | 100% golden path coverage for documented session flows | +| Performance tests | SLO conformance under concurrency, large canvases, and cache miss conditions | p95 targets validated before release | +| Security tests | Auth bypass, tenant escape, injection, redaction leakage, and secret misuse | Zero high-severity findings before production rollout | +| Accessibility tests | Keyboard, focus, color contrast, screen reader semantics, and reduced-motion behavior | WCAG 2.1 AA conformance for core Oracle flows | +| Chaos tests | Runtime failure resilience for model outages, query timeouts, websocket disconnects, and merge retries | No silent data loss or duplicate page commits | + +### 10.2 Test Data and Mocking Strategy + +CI must use synthetic tenant-isolated datasets seeded with realistic brokerage data including leads, brokers, projects, units, campaign activity, Sentinel scores, and vault events. Nemoclaw must be mocked in CI through deterministic structured outputs so prompt planning tests remain stable. Replay fixtures must exist for ambiguous prompts, policy denials, conflict-heavy merges, and synthesized component promotion. + +Prompt-to-query planning requires deterministic replay fixtures keyed by prompt hash, semantic model version, and policy profile. Merge conflict replays must include concurrent reorder plus edit scenarios, concurrent filter mutation scenarios, and source branch divergence after target changes. These are high-risk behaviors and cannot rely on manual testing alone. + +### 10.3 Acceptance Criteria + +Oracle is acceptable for MVP when a sales director can create a main page, run a prompt that produces at least two components, refresh the page and see the same revision, share the page to a senior broker, watch the senior broker receive an isolated fork, review a merge request, resolve or reject conflicts, and see the merged revision with intact audit history. It is also acceptable only if a junior broker cannot retrieve unauthorized restricted data, if redaction behaves correctly in exports and page render, and if page restore can recreate a prior revision without direct database mutation. + +### 10.4 CI/CD and Release Gating + +| Gate | Requirement | +| --- | --- | +| Contract gate | JSON Schemas and OpenAPI examples validate and remain backward compatible within `v1` rules | +| Quality gate | Coverage targets met and no flaky replay fixtures in critical paths | +| Security gate | No high-severity vulnerabilities or tenant-isolation regressions | +| Performance gate | Prompt, query, render, and persist p95 thresholds within tolerance | +| Accessibility gate | Core flows pass automated checks and manual keyboard review | +| Rollout gate | Canary tenant metrics stable with no SLO burn or merge integrity regression | + +## 11. Deployment, Operations, and Observability + +### 11.1 Deployment Model + +Oracle should deploy as containerized services orchestrated under the same operational umbrella as the existing backend. MVP may run as a modular FastAPI deployment with separate modules enabled behind flags. Later phases may split high-churn or high-scale paths such as synthesis, websocket fanout, and query execution into independent services. Deployment artifacts must be signed, immutable, and tied to revisioned configuration. + +Multi-region deployment uses a home-region write model. The home region owns prompt execution, page revisions, and merge commits for a tenant. Secondary regions serve read replicas, static assets, and failover web entry points. Database replication uses logical replication. Object artifacts use cross-region replication. Event outbox records are replay-safe. + +### 11.2 Monitoring and Dashboards + +Dashboards must expose prompt volume, intent mix, plan validation failures, query latency by dataset, synthesis latency, template cache hit ratio, render crash rate, page size distribution, fork creation rate, merge request throughput, merge conflict rate, merge approval lead time, and policy denial rate. Frontend dashboards must expose canvas hydration time, scroll FPS, component mount time, and websocket reconnect rates. + +### 11.3 Logging and Tracing Standards + +Structured logs must include `timestamp`, `service`, `environment`, `tenantId`, `actorId`, `correlationId`, `executionId`, `pageId`, `branchId`, `severity`, `eventType`, and `message`. Distributed traces must include spans for prompt intake, plan generation, policy validation, query execution, template resolution, synthesis, validation, revision commit, websocket publish, and client apply. Exported page snapshots and merge approvals must include the originating `correlationId` for traceability. + +### 11.4 Rollback and Incident Response + +Rollback occurs at three levels. The first level is page revision rollback, which restores user-visible state. The second level is feature rollback through configuration flags, which can disable synthesis, sharing, or merge execution while preserving read access. The third level is service rollback through deployment tooling. + +| Failure mode | Detection | Immediate response | Durable fix | +| --- | --- | --- | --- | +| Nemoclaw malformed output | Structured output parser failure spike | Route affected executions to fallback clarification or safe failure notice | Tighten schema prompts, add parser test, adjust runtime configuration | +| Data source outage | Query error rate and timeout spike | Fail closed, show diagnostic notice components, switch to stale-but-valid cache only if policy permits | Restore connector, backfill cache, replay failed executions if requested | +| Merge conflict anomaly | Unexpected merge rejection or diff mismatch | Freeze merge completion for affected tenant and preserve forks | Recompute diff, replay fixture, patch merge engine | +| Render crash after template publish | Frontend crash telemetry spike tied to template id | Revoke template version and invalidate cache | Fix template bundle and republish under new version | + +## 12. Data Governance, Provenance, and Lineage + +Oracle must provide a complete lineage path from source data to rendered artifact. A compliance reviewer must be able to answer five questions for any visible component: what prompt created it, what data sources were used, what transformations were applied, what template rendered it, and who later changed or merged it. The lineage model therefore records graph edges, not just flat audit rows. + +The lineage chain begins with source tables or materialized views. It continues through retrieval plans, query executions, result sets, template selection or synthesis, component creation, page revision commits, sharing events, forks, merge requests, and merge commits. Every edge stores timestamps, actor identity, policy snapshot, and transformation type. This allows export evidence, deletion propagation, and historical replay without forensic reconstruction. + +Governance roles are explicit. The sales organization can ask questions and build pages. Data stewards approve schema proposals and semantic model changes. Compliance reviewers audit access and retention. Platform admins operate runtime and connector policy. AI acts as a steward of operational data quality and schema proposals but not as the final authority for structural changes. + +## 13. API Contracts and Integration Points + +### 13.1 Versioning and Error Envelope + +Oracle uses path versioning at `/api/oracle/v1` and may additionally return `X-Oracle-Contract-Version` for debugging and compatibility checks. Pagination uses opaque cursor tokens. Mutation endpoints accept `Idempotency-Key` when the client may retry. + +Errors use a consistent envelope. + +```json +{ + "error": { + "code": "policy_denied", + "message": "The requested prompt references a restricted field outside your scope.", + "retryable": false, + "correlationId": "corr_01JQX999ERR", + "details": { + "field": "passport_number", + "policyProfileId": "policy_team_standard_v4" + } + } +} +``` + +### 13.2 Endpoint Surface + +| Service | Endpoint | Behavior | +| --- | --- | --- | +| Canvas Service | `GET /api/oracle/v1/canvas-pages/{pageId}` | Returns page metadata, current head revision, and current component projection for authorized branch | +| Canvas Service | `POST /api/oracle/v1/canvas-pages/{pageId}/prompts` | Starts a prompt execution and, on success, commits one new page revision | +| Canvas Service | `POST /api/oracle/v1/canvas-pages/{pageId}/rollback` | Creates a new revision restoring a prior revision snapshot | +| Collaboration Service | `POST /api/oracle/v1/canvas-pages/{pageId}/forks` | Creates an isolated fork for a recipient at a fixed source revision | +| Collaboration Service | `POST /api/oracle/v1/merge-requests` | Opens a merge request from fork branch to target branch | +| Collaboration Service | `POST /api/oracle/v1/merge-requests/{mrId}/review` | Approves, rejects, or requests changes with optional conflict resolutions | +| Component Catalog Service | `GET /api/oracle/v1/component-templates` | Lists tenant-visible templates with filters and cursor pagination | +| Component Catalog Service | `POST /api/oracle/v1/component-templates/synthesize` | Synthesizes a tenant-private draft template under policy and validation | +| Identity and Access | `GET /api/oracle/v1/me` | Returns role, tenant, page defaults, and policy profile | +| Realtime | `WS /ws/oracle/canvas/{pageId}` | Streams presence, execution progress, revision commits, and merge updates | + +### 13.3 Prompt Execution Request and Response + +```json +{ + "clientRequestId": "cli_01JQXA111PROMPT", + "branchId": "branch_main", + "prompt": "Show me this week's whale leads by source and map where our high-intent investors are concentrated in Dubai.", + "conversationContext": [ + { + "role": "user", + "content": "Focus on investor-quality leads for the current week." + } + ], + "placementMode": "append_after_last_visible_component" +} +``` + +```json +{ + "executionId": "pex_01JQXA111PROMPT", + "status": "completed", + "pageId": "page_01JQWE9MAINBROKER", + "branchId": "branch_main", + "headRevision": 18, + "componentsCreated": [ + "cmp_01JQWE6F4G5BARWHALESRC", + "cmp_01JQWE7H7G9GEOMAPINV" + ], + "summary": "Created a source comparison chart and an investor density map for high-intent whale leads.", + "warnings": [] +} +``` + +### 13.4 Fork Request and Response + +```json +{ + "recipientUserId": "user_senior_broker_014", + "sourceRevision": 18, + "visibility": "private", + "message": "Review this investor signal page and propose follow-up views." +} +``` + +```json +{ + "forkId": "fork_01JQWE9TEAMSHARE", + "forkPageId": "page_01JQWE9FORKBROKER2", + "forkBranchId": "branch_fork_broker2", + "status": "active", + "sourceRevision": 18 +} +``` + +### 13.5 Merge Request and Review Request + +```json +{ + "sourcePageId": "page_01JQWE9FORKBROKER2", + "sourceBranchId": "branch_fork_broker2", + "targetPageId": "page_01JQWE9MAINBROKER", + "targetBranchId": "branch_main", + "title": "Add broker follow-up queue and reorder investor map", + "description": "Adds one operational queue component and places it under the investor map." +} +``` + +```json +{ + "mergeRequestId": "mr_01JQXB222MERGE", + "status": "open", + "conflicts": [], + "diffSummary": { + "componentsAdded": 1, + "componentsEdited": 0, + "componentsReordered": 1, + "componentsDeleted": 0 + } +} +``` + +```json +{ + "decision": "approve", + "comment": "Queue component is valid. Order change accepted.", + "resolutions": [] +} +``` + +```json +{ + "mergeRequestId": "mr_01JQXB222MERGE", + "status": "merged", + "mergedRevision": 19, + "targetPageId": "page_01JQWE9MAINBROKER", + "targetBranchId": "branch_main" +} +``` + +### 13.6 WebSocket Contract + +The page WebSocket is the realtime backbone for execution progress, revision commits, merge review status, and presence. Messages use typed envelopes such as `presence.updated`, `prompt.execution.progress`, `canvas.revision.committed`, `merge_request.updated`, and `component.validation.revoked`. The client must treat the WebSocket as advisory for live updates and use HTTP as the source of truth after reconnect. + +### 13.7 Sample Session Summary + +The typical session begins with a `POST /api/oracle/v1/canvas-pages/{pageId}/prompts` request that yields two new components on revision 18. The user then shares the page with a senior broker using `POST /forks`. The senior broker edits the fork and opens `POST /merge-requests`. The sales director reviews the diff using `POST /merge-requests/{mrId}/review` and approves the merge. The main page advances to revision 19. The full transcript appears in Appendix 15.4. + +## 14. Roadmap, Milestones, and Rollout Plan + +### 14.1 Delivery Phases + +| Phase | Scope | Exit condition | +| --- | --- | --- | +| MVP | Prompt-to-canvas append, premade catalog, tenant-scoped data access, private sharing, forks, reviewed merges, audit logging, tenant auto-promotion after validation | One pilot tenant can complete the golden path reliably and within SLO | +| Phase 2 | Richer synthesis, exemplar learning loops, more operational actions, stronger governance workflows, broader dataset coverage | Multiple tenants use Oracle as daily operating surface | +| Phase 3 | Advanced compliance packs, broader connector ecosystem, more ML visual forms, optional controlled cross-tenant intelligence | Enterprise tenants can adopt Oracle under stricter regulatory and scale requirements | + +### 14.2 MVP Milestones + +The first milestone is the contract milestone, where `OracleQueryResult` is formally retired and the `CanvasPage` plus `CanvasComponent` contracts become the source of truth. The second milestone is the backend orchestration milestone, where prompt planning, policy validation, query execution, template resolution, and page revision commits work end to end. The third milestone is the collaboration milestone, where page sharing, fork creation, merge diff generation, review, and merge commit work with auditability. The fourth milestone is the catalog milestone, where premade templates and tenant auto-promotion are operational. The fifth milestone is the rollout milestone, where one pilot tenant uses the system behind flags under canary release. + +### 14.3 Resource Assumptions and Constraints + +This plan assumes the current React and FastAPI codebase remains the primary implementation base. It assumes PostgreSQL remains the canonical data store. It assumes Nemoclaw remains a pluggable runtime abstraction. It assumes Oracle remains CRM-first and avoids premature connector sprawl in MVP. It assumes the visual style already established in the repo is preserved rather than replaced. + +### 14.4 Risks and Mitigations + +| Risk | Why it matters | Mitigation | +| --- | --- | --- | +| Prompt planner overreach | Can lead to invalid joins, privacy errors, or unstable outputs | Strict semantic compiler, policy gate, structured outputs, replay fixtures | +| Merge complexity | Component-aware diffs can become subtle under reorder and edit concurrency | Deterministic three-way merge, explicit conflict classes, merge replay tests | +| Template synthesis instability | Could create visually inconsistent or slow components | Exemplar-based style signatures, strict validation pipeline, tenant-only auto-promotion | +| Canvas performance decay | Large pages can become unusable | Virtualization, retained measurement caches, template-level performance budgets | +| Governance drift | AI-owned operations can become unsafe without human structure review | AI stewardship proposals with human approval for structural changes | + +## 15. Appendices + +### 15.1 Glossary + +| Term | Meaning | +| --- | --- | +| Oracle | The AI-driven CRM canvas module inside Velocity Suite | +| Canvas Page | A revisioned ordered JSON page containing renderable data components | +| Component Template | A reusable visualization recipe that can render one or more compatible data shapes | +| Prompt Execution | The durable record of one prompt request from intake through completion or failure | +| Hybrid Sovereign | Deployment model where data execution stays within tenant-controlled boundaries when policy requires it while model runtime remains pluggable | +| Fork | An isolated page branch created from a specific source revision | +| Merge Request | A reviewed proposal to apply fork changes into a target branch | +| Style Signature | Explicit visual token profile extracted from accepted Oracle exemplars | +| AI Stewardship | Operating model where AI manages records and proposes structural change while humans approve migrations | +| QD | Quantum Dynamics score produced by Sentinel and consumable by Oracle | + +### 15.2 Data Dictionary + +| Schema | Field | Meaning | +| --- | --- | --- | +| CanvasPage | `pageId` | Stable page identity | +| CanvasPage | `ownerId` | User who owns the canonical page | +| CanvasPage | `branchId` | Current branch identity | +| CanvasPage | `mainBranchPointer` | Reference to the main page and revision for fork tracking | +| CanvasPage | `baseRevision` | Revision from which the current branch was created | +| CanvasPage | `headRevision` | Latest committed revision on the branch | +| CanvasPage | `forks` | Active and historical fork records | +| CanvasPage | `components` | Ordered renderable component array | +| CanvasComponent | `componentId` | Stable component identity across revisions | +| CanvasComponent | `type` | Renderable component family | +| CanvasComponent | `dataSourceDescriptor` | Authorized query and source metadata | +| CanvasComponent | `visualizationParameters` | Display, interaction, and chart settings | +| CanvasComponent | `dataBindings` | Dimensions, measures, and filter mapping | +| CanvasComponent | `provenance` | Origin, prompt execution, and source lineage | +| CanvasComponent | `styleSignature` | Visual language tokens | +| CanvasComponent | `validationState` | Schema, policy, accessibility, and performance checks | +| UserProfile | `defaultPageId` | User's main Oracle page | +| PromptExecution | `retrievalPlan` | Typed query plan before execution | +| PromptExecution | `visualizationPlan` | Planned component set and placement | +| ComponentTemplate | `acceptedShapes` | Data shapes supported by the template | +| ForkRecord | `sourceRevision` | Immutable source snapshot used to create the fork | +| MergeRequest | `targetBaseRevision` | Target revision used for three-way diff | +| LineageRecord | `transformationType` | Named transformation connecting source to produced entity | +| AuditEvent | `correlationId` | Shared request trace identity across services | + +### 15.3 Sample Component JSON Object + +```json +{ + "componentId": "cmp_01JQWE6F4G5BARWHALESRC", + "type": "barChart", + "title": "Whale Leads by Source This Week", + "dataSourceDescriptor": { + "descriptorId": "dsd_01JQWE6F4G5SRC", + "sourceType": "postgres", + "connectorId": "velocity-core-postgres", + "dataset": "lead_daily_snapshot", + "authContextRef": "authctx_sales_director_team_scope", + "queryTemplate": "select source, sum(qd_weighted_score) as qd_weighted_volume from lead_daily_snapshot where tenant_id = :tenant_id and lead_class = 'whale' group by source", + "queryParameters": { + "tenant_id": "tenant_binghatti_demo" + }, + "rowLimit": 20, + "privacyTier": "standard" + }, + "visualizationParameters": { + "xAxis": "source", + "yAxis": "qd_weighted_volume" + }, + "dataBindings": { + "dimensions": ["source"], + "measures": ["qd_weighted_volume"], + "series": [], + "filters": [] + }, + "version": 1, + "provenance": { + "originType": "prompt_generated", + "templateId": "tpl_bar_source_quality_v3", + "promptExecutionId": "pex_01JQWE6F4G5PROMPTA", + "createdBy": "user_sales_director_001", + "createdAt": "2026-04-08T16:40:22Z" + }, + "renderingHints": { + "estimatedHeightPx": 340, + "skeletonVariant": "chart", + "virtualizationPriority": 8 + }, + "layout": { + "orderIndex": 100, + "sectionId": "sec_market_overview", + "widthMode": "half", + "minHeightPx": 320, + "stickyHeader": false + }, + "accessControls": { + "visibilityScope": "private", + "allowedRoles": ["senior_broker", "sales_director"], + "redactionPolicy": "aggregate_only" + }, + "styleSignature": { + "theme": "velocity_glass", + "paletteToken": "ocean_signal", + "motionProfile": "calm_reveal", + "density": "comfortable", + "radiusScale": "lg", + "typographyScale": "balanced" + }, + "validationState": { + "schema": "pass", + "policy": "pass", + "a11y": "pass", + "performance": "pass", + "status": "validated" + }, + "auditLog": ["aud_01JQWE6F4G5CREATE"] +} +``` + +### 15.4 Sample Canvas Page JSON Object + +```json +{ + "pageId": "page_01JQWE9MAINBROKER", + "tenantId": "tenant_binghatti_demo", + "ownerId": "user_sales_director_001", + "branchId": "branch_main", + "branchName": "main", + "pageType": "main", + "title": "Oracle Home - Pipeline and Investor Signals", + "createdAt": "2026-04-08T16:10:00Z", + "updatedAt": "2026-04-08T16:40:24Z", + "isShared": true, + "forks": [], + "mainBranchPointer": { + "pageId": "page_01JQWE9MAINBROKER", + "branchId": "branch_main", + "revision": 18 + }, + "baseRevision": 0, + "headRevision": 18, + "sharingPolicy": { + "shareMode": "direct_fork_only", + "allowReshare": false, + "defaultForkVisibility": "private" + }, + "presence": { + "activeViewers": 2, + "activeEditors": 1, + "lastPresenceAt": "2026-04-08T16:40:24Z" + }, + "lineage": [], + "audit": { + "lastAuditEventId": "aud_01JQWE9REV18", + "eventCount": 44 + }, + "components": [ + { + "componentId": "cmp_01JQWE6F4G5BARWHALESRC", + "type": "barChart", + "title": "Whale Leads by Source This Week", + "dataSourceDescriptor": { + "descriptorId": "dsd_01JQWE6F4G5SRC", + "sourceType": "postgres", + "connectorId": "velocity-core-postgres", + "dataset": "lead_daily_snapshot", + "authContextRef": "authctx_sales_director_team_scope", + "queryTemplate": "select source, sum(qd_weighted_score) as qd_weighted_volume from lead_daily_snapshot where tenant_id = :tenant_id and lead_class = 'whale' group by source", + "queryParameters": { + "tenant_id": "tenant_binghatti_demo" + }, + "rowLimit": 20, + "privacyTier": "standard" + }, + "visualizationParameters": { + "xAxis": "source", + "yAxis": "qd_weighted_volume" + }, + "dataBindings": { + "dimensions": ["source"], + "measures": ["qd_weighted_volume"], + "series": [], + "filters": [] + }, + "version": 1, + "provenance": { + "originType": "prompt_generated", + "templateId": "tpl_bar_source_quality_v3", + "promptExecutionId": "pex_01JQWE6F4G5PROMPTA", + "createdBy": "user_sales_director_001", + "createdAt": "2026-04-08T16:40:22Z" + }, + "renderingHints": { + "estimatedHeightPx": 340, + "skeletonVariant": "chart", + "virtualizationPriority": 8 + }, + "layout": { + "orderIndex": 100, + "sectionId": "sec_market_overview", + "widthMode": "half", + "minHeightPx": 320, + "stickyHeader": false + }, + "accessControls": { + "visibilityScope": "private", + "allowedRoles": ["senior_broker", "sales_director"], + "redactionPolicy": "aggregate_only" + }, + "styleSignature": { + "theme": "velocity_glass", + "paletteToken": "ocean_signal", + "motionProfile": "calm_reveal", + "density": "comfortable", + "radiusScale": "lg", + "typographyScale": "balanced" + }, + "validationState": { + "schema": "pass", + "policy": "pass", + "a11y": "pass", + "performance": "pass", + "status": "validated" + }, + "auditLog": ["aud_01JQWE6F4G5CREATE"] + } + ] +} +``` + +### 15.5 Sample Session Transcript + +```text +2026-04-08T16:40:21Z user_sales_director_001 opens page_01JQWE9MAINBROKER branch_main revision 17 +2026-04-08T16:40:21Z user submits prompt: "Show me this week's whale leads by source and map where our high-intent investors are concentrated in Dubai." +2026-04-08T16:40:21Z Prompt Orchestrator creates execution pex_01JQXA111PROMPT with correlation corr_01JQXA111 +2026-04-08T16:40:21Z Nemoclaw receives semantic model version oracle_semantic_v2026_04_08_01 and allowed datasets lead_daily_snapshot, lead_geo_interest_rollup, sentinel_signal_rollup +2026-04-08T16:40:22Z Nemoclaw returns structured retrieval plan and visualization plan with barChart and geoMap +2026-04-08T16:40:22Z Policy Engine validates no cross-tenant joins, restricted fields redacted, max row limits satisfied +2026-04-08T16:40:22Z Data Access Gateway executes aggregate source query and district interest rollup query +2026-04-08T16:40:23Z Visualization Renderer binds result set A to tpl_bar_source_quality_v3 and result set B to tpl_geo_investor_heat_v2 +2026-04-08T16:40:23Z Validation pipeline passes schema, policy, accessibility, and performance checks for both components +2026-04-08T16:40:24Z Canvas Service commits revision 18 with components cmp_01JQWE6F4G5BARWHALESRC and cmp_01JQWE7H7G9GEOMAPINV +2026-04-08T16:40:24Z WebSocket emits canvas.revision.committed to all page viewers +2026-04-08T16:41:10Z user_sales_director_001 shares page revision 18 with user_senior_broker_014 +2026-04-08T16:41:10Z Collaboration Service creates fork page_01JQWE9FORKBROKER2 branch_fork_broker2 from branch_main@18 +2026-04-08T16:49:55Z user_senior_broker_014 adds a follow-up queue component and reorders the investor map below it on the fork +2026-04-08T16:50:30Z user_senior_broker_014 opens merge request mr_01JQXB222MERGE targeting page_01JQWE9MAINBROKER branch_main +2026-04-08T16:50:30Z Merge engine computes three-way diff with zero conflicts and identifies one component add plus one reorder +2026-04-08T16:52:02Z user_sales_director_001 approves merge request with comment "Queue component is valid. Order change accepted." +2026-04-08T16:52:03Z Canvas Service writes merge revision 19 to branch_main and preserves fork provenance +2026-04-08T16:52:03Z Audit trail records prompt execution, share, fork, merge request, approval, and merged revision under correlation corr_01JQXA111 +``` + +## 16. Detailed Execution Blueprint + +### 16.1 Why This Annex Exists + +The first fifteen sections define the product and system contract. This annex translates that contract into an implementation blueprint that can be assigned directly to frontend, backend, data, platform, and QA workstreams. The aim here is not to restate the architecture at a higher level. The aim is to eliminate ambiguity in how the existing Velocity codebase should evolve from a polished placeholder Oracle UI into the production Oracle system described above. + +The guiding rule for implementation is that the current Oracle page remains the visual seed, not the data contract. The production system should preserve the premium visual shell that already exists in the repository, but every state transition, persistence rule, collaboration action, and data fetch path must move from mocked single-response behavior into revisioned canvas behavior. + +### 16.2 Frontend Build Blueprint + +The Oracle frontend should be implemented as a domain-owned vertical slice inside the existing React and Vite application. The current `app/src/app/oracle/page.tsx` should be refactored into an orchestration page rather than a monolithic view switcher. The visual primitives that already work, such as glass panels, prompt rail patterns, header rhythm, and premium motion, should be preserved. The internal architecture should be reorganized around page state, branch state, component registry, execution timeline, and collaboration overlays. + +The page should be split into five conceptual regions. The first region is the branch and execution bar at the top, which shows the page title, branch identity, revision number, unsynced local changes if any, current execution status, and share or merge affordances. The second region is the canvas viewport, which is a virtualized vertically scrollable component tree backed by the current page revision. The third region is the prompt and conversation rail, which preserves the current Oracle interaction language while now reflecting durable `PromptExecution` history. The fourth region is the secondary collaboration surface for presence, merge notices, and review comments. The fifth region is a transient overlay system for template preview, component inspector, rollback confirmation, and conflict resolution. + +The frontend should maintain three distinct state planes. The first plane is remote canonical state, which represents the page revision, component projection, branch metadata, merge request state, and template catalog data fetched from the backend. The second plane is execution state, which includes in-flight prompt executions, pending websocket events, optimistic placeholders, and temporary layout measurements. The third plane is UI-local state, which includes input text, selected component, collapsed groups, visible review drawer, and viewport state. This separation matters because optimistic feedback is useful for responsiveness, but only committed revisions may become durable user-visible history. + +The component registry should become the central rendering mechanism. Each `CanvasComponent.type` resolves to a renderer implementation that accepts a typed component object and a standardized render context. The render context includes viewport information, tenant style tokens, redaction flags, formatter utilities, and event hooks for drill-down, edit, clone, fork, and export. The registry must support lazy loading for expensive renderers such as maps, large tables, or custom ML visualizations. + +The virtualized canvas must preserve scroll stability during inserts and realtime updates. When a new component is appended, the viewport should remain stable if the user is reading older content and should auto-scroll only when the user is already near the bottom or explicitly opts into follow mode. During merge replay or rollback, the renderer should diff component identity and order indices so that only affected regions remount. Stable keys must use `componentId` plus `version`, not array index. + +The prompt rail should preserve conversational continuity, but it should stop being the place where results render. Instead, each prompt turn links to the revision it produced and lists the components created or updated by that execution. A user should be able to click from a prompt turn to the first component created by that turn, review the assumptions Nemoclaw made, inspect the datasets touched, and open the audit trail for the execution. + +The collaboration surface should expose branch identity clearly because this product borrows Jira-like merge semantics. The UI should make it impossible for a user to forget whether they are editing `main` or a fork. The share action must explain that recipients receive a fork, not live edit access. The merge review surface must explain adds, deletes, reorders, parameter edits, and access-control changes using domain terms rather than raw JSON only. + +The recommended frontend module split is shown below. + +| Frontend module | Responsibility | Notes | +| --- | --- | --- | +| `oracle/page.tsx` | Page orchestration and route entry | Preserves shell, delegates all data and render logic | +| `oracle/components/CanvasViewport.tsx` | Virtualized vertical canvas renderer | Owns measurement cache and viewport anchoring | +| `oracle/components/PromptRail.tsx` | Prompt input, history, execution state | Replaces current single-result assumptions | +| `oracle/components/BranchBar.tsx` | Branch identity, share, merge, revision affordances | Must remain visible at all times | +| `oracle/components/ComponentRegistry.tsx` | Resolver from `type` to renderer implementation | Supports lazy loading and fallback notice rendering | +| `oracle/components/review/MergeReviewDrawer.tsx` | Merge diff review and conflict resolution UI | Domain-specific conflict explanations | +| `oracle/hooks/useOraclePage.ts` | Fetch and subscribe to page state | Owns page hydration lifecycle | +| `oracle/hooks/useOracleExecution.ts` | Prompt submit and execution progress handling | Owns optimistic placeholders and status tracking | +| `oracle/hooks/useOracleSocket.ts` | WebSocket lifecycle and event reconciliation | Must recover from reconnect cleanly | +| `oracle/lib/oracleApiClient.ts` | Typed network client | Replaces the mock query client | + +React 19 features should be used deliberately. `startTransition` is appropriate when applying large revision updates so the prompt rail and branch bar remain responsive. `useDeferredValue` is appropriate for local filter controls on large tables or maps. Event handlers that depend on current state but should not cause unnecessary re-subscription, such as websocket message handlers, should use `useEffectEvent` when the codebase conventions allow it. The goal is not novelty. The goal is to keep the canvas interactive under real payload size. + +### 16.3 Backend Build Blueprint + +The backend should adopt Oracle as a first-class domain inside the current FastAPI runtime. The existing `backend/main.py` should mount a versioned Oracle router explicitly. The empty placeholder Oracle and CRM routes should not be incrementally patched into production shape. They should be superseded by a `v1` Oracle API surface that follows the contracts in Sections 6 and 13. + +The Oracle backend should be organized into bounded modules instead of one large controller. The Prompt Orchestrator owns prompt intake, semantic context assembly, Nemoclaw invocation, and orchestration state. The Canvas Service owns page creation, revision commits, component ordering, rollback, and retrieval. The Collaboration Service owns share, fork, merge request, review, and merge commit behavior. The Catalog Service owns premade template retrieval, synthesized template persistence, validation records, and promotion decisions. The Data Access Gateway owns query compilation and execution. The Policy Service owns authorization, privacy tier enforcement, and redaction policy. The Audit and Lineage Service owns immutable event recording and lineage edge persistence. + +The recommended backend module split is shown below. + +| Backend module | Responsibility | Expected runtime behavior | +| --- | --- | --- | +| `backend/oracle/router_v1.py` | Public HTTP and WebSocket surface | Mounted in `main.py` under `/api/oracle/v1` and `/ws/oracle/...` | +| `backend/oracle/prompt_orchestrator.py` | Prompt intake through validated execution plan | Stateless orchestrator over durable execution records | +| `backend/oracle/canvas_service.py` | Page creation, revision writes, rollback, retrieval | Must guarantee exactly one visible revision per commit | +| `backend/oracle/collaboration_service.py` | Fork, merge request, review, merge replay | Owns three-way merge rules | +| `backend/oracle/catalog_service.py` | Template query, synthesis persistence, promotion | Must enforce tenant-only auto-promotion | +| `backend/oracle/data_access_gateway.py` | Plan compilation and data execution | Must never execute unvalidated plan fragments | +| `backend/oracle/policy_service.py` | Tenant scope, row scope, privacy tier, redaction | Invoked before query and before render | +| `backend/oracle/renderer_service.py` | Component instance assembly and renderer metadata | Returns structured component objects only | +| `backend/oracle/audit_service.py` | Immutable event storage and query helpers | Shared by all mutation paths | +| `backend/oracle/lineage_service.py` | Lineage edge writes and replay helpers | Shared by execution and merge flows | +| `backend/oracle/semantic_model.py` | Business vocabulary and dataset mapping | Versioned independently from database schema | + +The backend write path should always use transaction boundaries that correspond to user-visible guarantees. A prompt execution may involve several internal steps, but the user-visible success event is the revision commit. Therefore a component set must be validated before the final commit transaction opens, and the commit transaction must atomically write the page revision, component projection updates, audit events, and lineage edges. Events for websocket fanout should be emitted through a transactional outbox rather than directly from inside the business transaction so that failures are replayable. + +### 16.4 Physical PostgreSQL Schema Mapping + +The logical contracts from Section 6 should map to a concrete relational schema. The following physical schema is the recommended baseline for Sprint 1. Types are shown in PostgreSQL form because the existing backend already uses raw PostgreSQL and asyncpg. + +```sql +create table if not exists oracle_canvas_pages ( + tenant_id uuid not null, + page_id uuid primary key, + owner_id uuid not null references users_and_roles(id), + branch_id uuid not null, + branch_name text not null, + page_type text not null check (page_type in ('main', 'fork')), + title text not null, + source_page_id uuid, + source_branch_id uuid, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now(), + is_shared boolean not null default false, + base_revision integer not null default 0, + head_revision integer not null default 0, + sharing_policy jsonb not null default '{}'::jsonb, + presence_snapshot jsonb not null default '{}'::jsonb, + audit_summary jsonb not null default '{}'::jsonb, + unique (tenant_id, page_id), + unique (tenant_id, branch_id) +); + +create index if not exists idx_oracle_pages_owner + on oracle_canvas_pages (tenant_id, owner_id, updated_at desc); +``` + +```sql +create table if not exists oracle_canvas_page_revisions ( + tenant_id uuid not null, + page_id uuid not null references oracle_canvas_pages(page_id) on delete cascade, + branch_id uuid not null, + revision_number integer not null, + parent_revision integer, + commit_kind text not null check (commit_kind in ('prompt', 'merge', 'rollback', 'manual_edit')), + committed_by uuid not null references users_and_roles(id), + prompt_execution_id uuid, + merge_request_id uuid, + revision_payload jsonb not null, + created_at timestamptz not null default now(), + primary key (tenant_id, page_id, revision_number) +); + +create index if not exists idx_oracle_page_revisions_branch + on oracle_canvas_page_revisions (tenant_id, branch_id, revision_number desc); +``` + +```sql +create table if not exists oracle_canvas_components ( + tenant_id uuid not null, + page_id uuid not null references oracle_canvas_pages(page_id) on delete cascade, + branch_id uuid not null, + component_id uuid not null, + version integer not null, + order_index integer not null, + section_id text not null, + component_type text not null, + component_payload jsonb not null, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now(), + primary key (tenant_id, page_id, branch_id, component_id) +); + +create index if not exists idx_oracle_components_page_order + on oracle_canvas_components (tenant_id, page_id, branch_id, order_index); + +create index if not exists idx_oracle_components_payload_gin + on oracle_canvas_components using gin (component_payload jsonb_path_ops); +``` + +```sql +create table if not exists oracle_prompt_executions ( + tenant_id uuid not null, + execution_id uuid primary key, + page_id uuid not null references oracle_canvas_pages(page_id) on delete cascade, + branch_id uuid not null, + actor_id uuid not null references users_and_roles(id), + prompt text not null, + prompt_hash text not null, + intent_class text not null, + status text not null, + model_runtime text not null, + semantic_model_version text not null, + retrieval_plan jsonb, + visualization_plan jsonb, + warnings jsonb not null default '[]'::jsonb, + result_summary jsonb, + created_at timestamptz not null default now(), + completed_at timestamptz +); + +create index if not exists idx_oracle_prompt_executions_page + on oracle_prompt_executions (tenant_id, page_id, created_at desc); +``` + +```sql +create table if not exists oracle_component_templates ( + tenant_id uuid not null, + template_id uuid primary key, + name text not null, + category text not null, + origin text not null, + status text not null, + version text not null, + style_signature_hash text not null, + accepted_shapes jsonb not null, + template_payload jsonb not null, + validation_state jsonb not null, + provenance jsonb not null, + created_at timestamptz not null default now(), + updated_at timestamptz not null default now() +); + +create index if not exists idx_oracle_component_templates_category + on oracle_component_templates (tenant_id, category, status); +``` + +```sql +create table if not exists oracle_forks ( + tenant_id uuid not null, + fork_id uuid primary key, + source_page_id uuid not null references oracle_canvas_pages(page_id), + source_branch_id uuid not null, + source_revision integer not null, + fork_page_id uuid not null references oracle_canvas_pages(page_id), + fork_branch_id uuid not null, + recipient_user_id uuid not null references users_and_roles(id), + created_by uuid not null references users_and_roles(id), + status text not null, + created_at timestamptz not null default now() +); + +create table if not exists oracle_merge_requests ( + tenant_id uuid not null, + merge_request_id uuid primary key, + source_page_id uuid not null references oracle_canvas_pages(page_id), + source_branch_id uuid not null, + source_head_revision integer not null, + target_page_id uuid not null references oracle_canvas_pages(page_id), + target_branch_id uuid not null, + target_base_revision integer not null, + title text not null, + description text, + status text not null, + diff_payload jsonb not null, + conflicts_payload jsonb not null default '[]'::jsonb, + created_by uuid not null references users_and_roles(id), + reviewed_by uuid references users_and_roles(id), + created_at timestamptz not null default now(), + updated_at timestamptz not null default now() +); +``` + +```sql +create table if not exists oracle_lineage_records ( + tenant_id uuid not null, + lineage_record_id uuid primary key, + source_kind text not null, + source_id text not null, + transformation_type text not null, + transformation_spec_hash text, + produced_kind text not null, + produced_id text not null, + policy_snapshot_id text, + created_at timestamptz not null default now() +); + +create table if not exists oracle_audit_events ( + tenant_id uuid not null, + audit_event_id uuid primary key, + entity_type text not null, + entity_id text not null, + action text not null, + actor_id uuid not null references users_and_roles(id), + actor_type text not null, + correlation_id text not null, + execution_id uuid, + details jsonb not null default '{}'::jsonb, + created_at timestamptz not null default now() +); + +create index if not exists idx_oracle_audit_entity + on oracle_audit_events (tenant_id, entity_type, entity_id, created_at desc); +``` + +This schema is intentionally revision-friendly. `oracle_canvas_page_revisions` holds immutable snapshots for replay and rollback. `oracle_canvas_components` holds the latest projection for fast page fetch. `oracle_merge_requests` persists full diff payloads so that review decisions remain reproducible even after target branches advance. `oracle_audit_events` and `oracle_lineage_records` remain append-only. + +### 16.5 Event and WebSocket Blueprint + +The Oracle realtime layer should use the page WebSocket as a consumer-facing transport only. Internal fanout should happen over the event backbone. The page socket therefore subscribes to page-level events already committed or safely staged, not raw in-transaction state. + +The minimum event catalog for Sprint 1 should include `oracle.prompt.received`, `oracle.prompt.validated`, `oracle.prompt.failed`, `oracle.page.revision.committed`, `oracle.page.rollback.committed`, `oracle.fork.created`, `oracle.merge_request.opened`, `oracle.merge_request.updated`, `oracle.merge_request.merged`, `oracle.component.template.promoted`, and `oracle.presence.updated`. Every event must carry `tenantId`, `pageId` where relevant, `branchId` where relevant, `correlationId`, and a monotonic event timestamp. Client reconciliation should prefer revision numbers over event order whenever there is disagreement. + +## 17. Collaboration and Merge Algorithm Specification + +### 17.1 Revision Model + +Oracle pages are revisioned append-only objects. A branch has exactly one current head revision and zero or more prior revisions. A write request must always declare the branch head it believes it is modifying. If the declared base head does not match the actual current head at commit time, Oracle does not silently overwrite. For direct prompt executions on `main`, the system should automatically rebase if the new execution only appends components at the end and touches no existing component. For edits, reorders, or merge commits, the system should require either a safe automatic rebase or explicit user-visible conflict handling. + +Revision numbers are monotonic integers scoped to a page branch. Revision numbers never decrement and are never reused. Rollback is represented as a new revision with `commit_kind = 'rollback'` and a payload equal to or derived from a previously valid revision. The branch history therefore remains explainable without hidden mutation. + +### 17.2 Three-Way Merge Behavior + +Oracle merge behavior should be deterministic and component-aware. The merge base is the fork source revision. The merge source is the fork head. The merge target is the current target head at merge time. The diff engine compares the component identities, order indices, layout sections, visualization parameters, data bindings, and access controls across the three versions. + +The merge engine should classify changes before resolving them. If the source adds a new component and the target does not touch the same order window, the merge is automatic. If the source reorders components and the target edits unrelated components, the merge is automatic if the final order can be represented without overlapping slot ownership. If both source and target edit the same component's title but no other field, the merge becomes a text conflict. If both modify filters, data descriptors, access controls, or layout slots on the same component, the merge becomes a structured conflict that requires reviewer selection. + +The conflict classes are defined below. + +| Conflict class | Trigger | Default behavior | +| --- | --- | --- | +| `component_content_conflict` | Source and target change the same component field set | Manual review required | +| `query_descriptor_conflict` | Source and target modify the same data source descriptor or filter semantics | Manual review required | +| `layout_slot_conflict` | Source and target claim the same order window or section positioning in incompatible ways | Manual review required | +| `access_policy_conflict` | Source and target change role visibility or redaction policy differently | Manual review required | +| `delete_edit_conflict` | One side deletes a component while the other edits it | Manual review required | +| `safe_append` | Source adds only new components at free order indices | Auto-merge | +| `safe_reorder` | Reorders are compatible after normalization | Auto-merge | + +### 17.3 Order Index Strategy + +Order indices should be stored as integers with gaps, not as dense 1..N sequences rewritten on every insert. The recommended initial spacing is increments of 100. This allows local inserts and merge-time reorders with minimal rewrite pressure. When gaps become too tight within a section, Oracle may normalize order indices during a maintenance revision that preserves visible order but rewrites indices. That normalization revision should be treated as a first-class page commit and audited. + +### 17.4 Merge Review UX Contract + +The merge review UI must not force reviewers to inspect raw JSON to understand common changes. For component additions, the UI should show the rendered component preview plus the prompt or source branch explanation. For reorders, it should show before and after page position. For edits, it should show field-level change cards grouped by display text, filters, measure definitions, visibility, and layout. For access changes, it should show a security warning surface because those are high-risk changes even when visually small. + +### 17.5 Conflict Resolution Serialization + +When a reviewer resolves a conflict, the choice must be serialized as part of the merge decision. This makes the merge replayable and auditable. A resolution record should state which conflict id was resolved, whether the source or target version won or whether a manual composite was created, and the exact resolved payload hash. This is critical for legal traceability and for deterministic replay in CI. + +An example resolution payload is shown below. + +```json +{ + "mergeRequestId": "mr_01JQXB222MERGE", + "resolutions": [ + { + "conflictId": "conf_01JQXB222LAYOUT", + "resolutionType": "manual_composite", + "resolvedPayloadHash": "sha256:resolvedLayoutA", + "comment": "Accepted source follow-up queue but retained target map placement in section two." + } + ] +} +``` + +## 18. CRM Operating Model and Product Behavior + +### 18.1 First-Principles CRM Design + +The best CRM in this category is not the one with the most menus. It is the one that minimizes duplicate work, makes ownership explicit, preserves institutional memory, and helps a broker move a lead forward at the right moment. Oracle should therefore follow five first-principles rules. + +The first rule is that every lead action should have one canonical home. If a broker asks Oracle to create a task, tag a lead, or change ownership, that action should update the underlying record system directly and then appear everywhere else from the same event source. There should be no parallel note pad, shadow task list, or sidecar spreadsheet workflow. + +The second rule is that analysis should become action without context loss. A chart that identifies the top five under-served whale leads should let the user create follow-ups, assign brokers, or open a fork for team review directly from the component. Oracle should not force the user to leave insight space and reopen the same data in another module to act on it. + +The third rule is that branch review should replace version confusion. Teams often circulate screenshots and exported PDFs because shared analytics are fragile. Oracle replaces that with durable page branches, merge requests, and revision history so that decisions are debated on live artifacts rather than stale exports. + +The fourth rule is that AI should remove clerical effort, not remove accountability. AI may ingest notes, summarize visits, propose tasks, suggest schema extensions, and compose component layouts, but ownership of sensitive merges, role access, and structural change remains explicit and reviewable. + +The fifth rule is that the CRM must remain operable for brokers under time pressure. The default workflows should be fast, language-based, and forgiving. The product should always feel simpler than Salesforce while remaining more trustworthy than a generic chat front end. + +### 18.2 Core Business Entities + +The CRM model should revolve around a concise set of entities tailored to real-estate sales instead of generic B2B deal abstractions alone. The principal entities are `Lead`, `HouseholdOrInvestor`, `BrokerUser`, `Project`, `Unit`, `VisitSession`, `SentinelSignalRollup`, `CampaignTouch`, `Task`, `OfferOrReservation`, `VaultShare`, `CanvasPage`, and `MergeRequest`. The reason for keeping this set small is to reduce semantic drift between the operational database and the AI planner. Every extra entity the model cannot explain cleanly becomes a long-term source of hallucination and reporting inconsistency. + +The recommended operational sequence is lead capture, enrichment, qualification, assignment, engagement, visit evidence, follow-up, offer progression, reservation or close, and post-close relationship management. Oracle components should map naturally onto this lifecycle. Pipeline boards, visit timelines, unit-fit matrices, campaign quality views, and broker follow-up queues should feel like native product surfaces rather than reporting afterthoughts. + +### 18.3 Persona Journeys + +For a developer sales gallery, the key user is the sales director who wants to know which investor profiles are reacting to which projects, which brokers are moving high-value leads fast enough, and which units need narrative repositioning. Oracle should let this user open a main page for a project launch, compare lead source quality, map investor concentration, inspect Sentinel reaction patterns, share the page to a senior broker, and accept a merge that adds an operational call queue. + +For a brokerage principal, the key user is the owner or operations manager who fears lead leakage and inconsistent follow-up. Oracle should make lead ownership visible, document every assignment and branch of reasoning, and prevent data wandering into rogue spreadsheets. The principal should be able to ask for pipeline health by broker, see which leads have gone cold despite strong prior sentiment, and publish an official team canvas that brokers can fork but not overwrite. + +For a boutique founder, the key user is a small-team operator who needs leverage more than administrative breadth. Oracle should help this user look enterprise-grade without requiring a data team. The default catalog should therefore include project overview, investor geography, follow-up gaps, unit-fit suggestions, and campaign quality views that work out of the box on a small but growing lead base. + +### 18.4 Edge Cases the CRM Must Handle + +Oracle must handle duplicated leads arriving from multiple channels without producing conflicting canvases or inconsistent ownership. It must handle leads who visit a gallery without giving full identity until later. It must handle retroactive data corrections from brokers or stewards without corrupting historical page revisions. It must handle data retention requests without breaking audit integrity. It must handle large campaigns that create bursts of low-quality leads without degrading the responsiveness of whale-level analysis. + +It must also handle a social and organizational edge case: multiple brokers forming competing interpretations of the same data. That is exactly why the branch and merge model matters. The product should encourage experimentation on forks and clarity on mainline, rather than forcing teams into a fake live consensus that silently overwrites dissenting work. + +## 19. Delivery Work Packages and Sequencing + +### 19.1 Recommended Build Order + +The implementation should proceed in a sequence that unlocks user-visible value early while de-risking the hard parts before pilot rollout. The correct order is contract foundation first, then persistence and prompt orchestration, then canvas rendering, then collaboration, then synthesis and hardening. + +| Work package | Primary outcome | Dependency | Acceptance signal | +| --- | --- | --- | --- | +| WP1 Contract foundation | JSON Schemas, API contracts, semantic model baseline, Oracle router skeleton | None | Mock client fully replaced with typed contract stubs | +| WP2 Persistence and page core | Page tables, revision tables, component projection, page fetch and rollback | WP1 | Page can persist and replay deterministic revisions | +| WP3 Prompt orchestration | Prompt execution records, Nemoclaw structured planning, policy validation, query gateway | WP1 and WP2 | Prompt can create committed components on a page | +| WP4 Frontend canvas | Virtualized canvas, component registry, prompt rail refactor, branch bar | WP2 and WP3 | User can see persisted multi-component page revisions | +| WP5 Collaboration | Share, fork, merge request, review UI, merge commit flow | WP2 and WP4 | Team can complete the share to merge golden path | +| WP6 Catalog and synthesis | Premade catalog API, synthesis persistence, validation pipeline, tenant auto-promotion | WP3 and WP4 | New template can be synthesized and reused safely | +| WP7 Hardening and pilot ops | SLO tuning, observability, security review, canary controls, incident playbooks | All prior packages | Pilot tenant can run daily workload under guardrails | + +### 19.2 Team Responsibilities + +The frontend team owns the page shell refactor, canvas viewport, prompt rail, branch bar, merge review UI, component registry, and performance instrumentation. The backend team owns Oracle routing, orchestration, persistence, query gateway, collaboration engine, and websocket behavior. The data team or backend data owner owns semantic model versioning, derived rollups, indexes, and stewardship proposal flow. The platform owner owns deployment, secret handling, runtime policies, monitoring, and canary controls. QA owns replay fixtures, conflict scenarios, accessibility verification, and pilot acceptance gates. + +### 19.3 Sprint-to-Pilot Timeline + +The recommended timeline assumes one sprint for contract and persistence foundation, one sprint for prompt-to-canvas flow, one sprint for collaboration and merge review, and one sprint for synthesis and hardening. If the team needs to compress delivery, synthesis should compress last, not first. The highest-value MVP is a reliable prompt-to-canvas system with branch review, not a flashy synthesis engine with weak governance. + +### 19.4 Pilot Rollout Pattern + +The first pilot should use one design-led or process-disciplined tenant rather than the largest possible tenant. The ideal pilot tenant is large enough to exercise the collaboration model and small enough to support close observation. The pilot should start with private pages and direct-fork sharing only. Team-wide branch promotion, auto-promotion thresholds, and heavier operational actions should be enabled after the first two weeks of stable usage and after merge integrity metrics remain healthy. + +The pilot should include structured office hours with the sales director and at least one senior broker so that confusing merge behavior, ambiguous prompt patterns, and missing catalog templates are captured early. Oracle will live or die on whether the team trusts it as a daily operating surface. That trust is built by clean revisions, clear explanations, and predictable merge behavior more than by raw model cleverness. + +## 20. Production Readiness Exit Criteria + +Oracle should not be considered production-ready merely because the UI is complete or the first prompt works. The system is production-ready only when the product, platform, and governance layers all pass together. + +The product layer is ready when a sales director can use Oracle to ask a real business question, receive a durable multi-component page, share it to a teammate as a fork, review a merge request, and rely on the page as a repeatable operational artifact. The platform layer is ready when prompt, query, render, persist, and websocket metrics remain inside the SLO envelope under pilot load and when rollback is proven in rehearsal. The governance layer is ready when audit, lineage, retention, access control, and stewardship proposal flows have all been exercised with real operators. + +The following table defines the final go-live bar. + +| Area | Exit requirement | +| --- | --- | +| Contracts | All `v1` schemas and OpenAPI examples frozen and published | +| Persistence | Revision replay, rollback, and merge replay proven in automated tests | +| Security | Tenant isolation and access control penetration checks show no high-severity gaps | +| Observability | Dashboards, alerts, trace correlation, and incident playbooks operational | +| Performance | Prompt, render, and canvas scroll SLOs met on pilot-like datasets | +| Collaboration | Share, fork, merge, reject, and rollback all pass deterministic end-to-end tests | +| Catalog | At least the baseline premade catalog is live and synthesis auto-promotion is bounded to tenant scope | +| Governance | Data steward can review and approve an AI schema proposal without manual database patching | + +### 20.1 Immediate Next Implementation Moves + +The first concrete move after accepting this artifact should be to freeze the current mock Oracle query client as deprecated and create the typed Oracle API client that speaks in page and execution contracts. The second move should be to add Oracle page tables and revision tables to PostgreSQL. The third move should be to mount the new Oracle router in `backend/main.py`. Only after those three foundations exist should the team invest in merge review UI or template synthesis. That order keeps the product grounded in durable state rather than another generation of temporary mocks. + +## 21. Repository Cutover and File Mapping + +### 21.1 Purpose of the Cutover Map + +The Oracle artifact is intentionally specific to this repository rather than written as a generic platform brief. This section translates the design into concrete repo movement so the implementation team can see which current files are visual references, which files are temporary scaffolding, which runtime entry points need modification, and which new modules should be introduced first. + +The current Oracle frontend should be treated as a design shell and interaction seed. The current Oracle backend placeholders should be treated as disposable stubs. The Sentinel work already present in the repo should be treated as the closest backend precedent for audited AI-assisted flows, especially around websocket usage, asyncpg integration, and Nemoclaw client patterns. + +### 21.2 Existing File Roles + +| Existing path | Current role | Oracle cutover decision | +| --- | --- | --- | +| `app/src/app/oracle/page.tsx` | Premium Oracle UI shell with mock view switching | Keep visual language, refactor into page orchestration and virtualized canvas host | +| `app/src/lib/oracleQueryClient.ts` | Temporary single-response mock client | Deprecate and replace with typed `oracleApiClient` speaking page and execution contracts | +| `backend/main.py` | Active FastAPI entrypoint without mounted Oracle runtime | Extend to mount Oracle `v1` router and Oracle websocket namespace | +| `backend/api/routes_oracle.py` | Empty placeholder | Do not preserve as contract surface; replace with new domain router | +| `backend/api/routes_crm.py` | Empty placeholder | Treat as unused placeholder unless later repurposed intentionally | +| `backend/services/nemoclaw_client.py` | Active structured AI runtime abstraction | Reuse for Oracle planning path with Oracle-specific prompt wrappers and validation | +| `backend/db/schema.sql` | Current PostgreSQL schema baseline focused on Sentinel and auth-adjacent data | Extend with Oracle tables through migrations rather than ad hoc inline edits | +| `app/src/store/useStore.ts` | General app state store with mocked Oracle data assumptions | Reduce Oracle-specific mock state and move Oracle remote state into domain hooks or slice modules | + +### 21.3 Recommended New File and Module Additions + +| Proposed path | Responsibility | +| --- | --- | +| `app/src/oracle/components/CanvasViewport.tsx` | Virtualized canvas renderer and scroll-anchor control | +| `app/src/oracle/components/BranchBar.tsx` | Branch identity, revision status, share and merge entrypoints | +| `app/src/oracle/components/PromptRail.tsx` | Prompt input, execution history, and turn-to-revision linking | +| `app/src/oracle/components/review/MergeReviewDrawer.tsx` | Merge diff and conflict resolution UI | +| `app/src/oracle/lib/oracleApiClient.ts` | Typed Oracle HTTP and websocket client | +| `app/src/oracle/hooks/useOraclePage.ts` | Page hydration, branch refresh, optimistic reconciliation | +| `app/src/oracle/hooks/useOracleExecution.ts` | Prompt submission and execution progress | +| `backend/oracle/router_v1.py` | Mounted Oracle API and websocket surface | +| `backend/oracle/prompt_orchestrator.py` | Prompt intake through validated plan production | +| `backend/oracle/canvas_service.py` | Page, revision, rollback, and component projection writes | +| `backend/oracle/collaboration_service.py` | Share, fork, merge request, diff, merge commit | +| `backend/oracle/catalog_service.py` | Template list, synthesis persistence, validation and promotion | +| `backend/oracle/data_access_gateway.py` | Plan-to-query compilation and execution | +| `backend/oracle/policy_service.py` | Tenant scope, role scope, privacy tier, redaction | +| `backend/oracle/audit_service.py` | Oracle-specific immutable audit writes and queries | +| `backend/oracle/lineage_service.py` | Oracle lineage edge persistence and replay | +| `backend/oracle/semantic_model.py` | Business vocabulary and dataset alias layer | +| `backend/tests/oracle/` | Contract, execution, merge, and replay test suite | + +### 21.4 Implementation Cutover Sequence in the Repo + +The first repo change should introduce Oracle domain modules without deleting the current UI. The second should route the current page through the new API client while still rendering a minimal premade component set. The third should remove direct dependence on `OracleQueryResult` from the page. The fourth should add collaboration and revision history. Only then should the team delete the legacy mock query client entirely. This sequence keeps the UI visually stable while moving the data plane underneath it. + +The migration rule should be that temporary compatibility shims are allowed only when they preserve forward movement toward the page-and-revision contract. No new code should be added against the deprecated single-result Oracle contract after the Oracle `v1` API client exists. + +## 22. Success Metrics and Adoption Governance + +### 22.1 Why Oracle Needs Post-Launch Governance + +A sophisticated CRM artifact is not successful because it can answer prompts. It is successful because operators trust it enough to replace lower-trust behaviors such as screenshots, spreadsheets, duplicated notes, and ad hoc dashboard exports. Oracle therefore needs a post-launch success model that measures not only runtime health but operator adoption, merge hygiene, decision velocity, and revenue relevance. + +### 22.2 Product and Usage KPIs + +| Metric | Definition | Initial target | +| --- | --- | --- | +| Weekly active Oracle operators | Unique tenant users who execute at least one prompt or review one merge request in a week | 70% of licensed pilot users by week 4 | +| Prompt-to-committed-page rate | Share of prompt executions that result in at least one committed component revision | >= 75% in pilot after initial tuning | +| Reuse rate of committed components | Share of new prompts that reuse existing catalog templates instead of requiring synthesis | >= 60% by end of pilot | +| Fork-to-merge completion rate | Share of opened merge requests that reach approved or closed-with-decision state | >= 85% within 5 business days | +| Median time from insight to action | Time from prompt completion to task creation, assignment, or follow-up action | <= 10 minutes for action-oriented workflows | +| Canvas revisit rate | Share of committed pages reopened within 7 days | >= 50% for team-owned pages | + +### 22.3 Operational and Governance KPIs + +| Metric | Definition | Initial target | +| --- | --- | --- | +| Policy denial precision | Share of denied requests later confirmed as correctly blocked | >= 95% | +| Merge integrity incidents | Count of silent overwrite, lost component, or non-replayable merge outcomes | 0 | +| Template validation escape rate | Share of promoted templates later revoked due to missed validation failure | < 1% | +| Audit completeness | Share of mutating Oracle actions with full correlation, actor, and entity metadata | 100% | +| Lineage completeness | Share of rendered components with valid lineage links to source data and prompt execution | >= 99.5% | +| Stewardship turnaround time | Median time for a data steward to approve or reject an AI schema proposal | <= 3 business days | + +### 22.4 Business Outcome KPIs + +Oracle should also be evaluated on business impact rather than product usage alone. For developer and brokerage deployments, the important business signals are improved follow-up speed, reduced lead leakage, faster identification of high-intent investors, improved broker prioritization, and stronger operational confidence in AI-assisted reporting. The recommended business scorecard should track response time to high-value leads, conversion movement among whale or high-intent segments, percentage of team decisions backed by shared Oracle pages instead of exported decks, and reduction in duplicate manual reporting effort. + +### 22.5 Governance Cadence + +The recommended governance cadence is weekly during pilot and biweekly after stabilization. The weekly review should include one product owner, one engineering lead, one platform or SRE lead, one data steward, and one business operator from the tenant side. The review agenda should inspect adoption metrics, merge behavior, denied prompts, slow components, repeated synthesis failures, schema proposal queue, and any evidence that brokers are still falling back to external spreadsheets or screenshots for core workflows. + +The most important governance rule is that Oracle should evolve through evidence. New catalog categories, new synthesis heuristics, and new access policies should be driven by observed operator patterns, not by abstract feature accumulation. This keeps the CRM simple, functional, and aligned with the original goal of building the most effective real-estate operating system rather than the most complicated one. diff --git a/.Agent Context/Bibels/oracle_development_status.md b/.Agent Context/Bibels/oracle_development_status.md new file mode 100644 index 00000000..bd978810 --- /dev/null +++ b/.Agent Context/Bibels/oracle_development_status.md @@ -0,0 +1,94 @@ +# Oracle Development Status & Transition Blueprint + +This artifact summarizes the current state, remaining work, and architectural limitations/constraints for the **Project Velocity - The Oracle** module, based on the *Master Architecture and Implementation Artifact (v1.0)*. It is intended to serve as a comprehensive briefing for a coding agent tasked with completing the Oracle implementation. + +> [!NOTE] +> **Contextual Awareness**: The current `implementation_plan.md` and `task.md` in the agent's brain directory are mapped to **The Catalyst** (Digital Marketing Agency module), which is a separate product vertical. The Oracle requires an independent set of execution tasks focused completely on the CRM's AI operational intelligence layer. + +## 1. What Development is Done (Current State in Repo) + +The repository currently contains the **"visual shell" and technical foundation**, but no production Oracle paths. + +### Frontend +- **Polished UI Shell Exists**: `app/src/app/oracle/page.tsx` exists and provides a premium glassmorphic UI, a right-side conversation rail, and a top insight banner. +- **Mock Data Layer**: The UI uses a single-response mock client (`app/src/lib/oracleQueryClient.ts`) operating on a legacy `OracleQueryResult` contract. It switches between fixed, hardcoded views instead of an extensible, revision-controlled canvas. + +### Backend +- **FastAPI / Python Foundation**: A robust FastAPI backend is running (`backend/main.py`), utilizing `asyncpg` for PostgreSQL and JWT-based authentication. +- **AI Runtime Abstraction Exists**: A production-grade `Nemoclaw` client (`backend/services/nemoclaw_client.py`) is already implemented and currently handles Sentinel's AI needs. This is the abstract LLM planner. +- **Database Baseline**: `backend/db/schema.sql` possesses the baseline auth and Sentinel schemas, but lacks Oracle's revisioned canvas components. +- **Placeholders**: `backend/api/routes_oracle.py` and `backend/api/routes_crm.py` exist but are entirely empty. + +--- + +## 2. What is Remaining (Sprint 1 / MVP Blueprint) + +The implementation must transition the system from "mocked single-response behavior" into "persistent, branchable, revisioned vertical JSON canvas behavior". This is broken down into Work Packages (WPs): + +### WP1: Contract Foundation (First Next Step) +- Create JSON Schemas and API contracts (Section 6 & 13 types: `CanvasPage`, `CanvasComponent`, `PromptExecution`). +- Deprecate the mock query client; build a typed `oracleApiClient.ts`. +- Build the `v1` Oracle router skeleton in FastAPI. + +### WP2: Persistence & DB Core +- Implement the revision-friendly PostgreSQL database schemas outlined in Section 16.4 (`oracle_canvas_pages`, `oracle_canvas_page_revisions`, `oracle_canvas_components`, `oracle_prompt_executions`). + +### WP3: Prompt Orchestrator & Data Access +- Wire up `backend/oracle/prompt_orchestrator.py` to route prompts to `Nemoclaw_client.py`. +- **Constraint**: Implement strict structured parsing for Nemoclaw's output. +- Build the `Data Access Gateway` and `Policy Engine` to securely query tenant PostgreSQL data based on the validated LLM plan. + +### WP4: Frontend Virtualized Canvas +- Refactor `app/src/app/oracle/page.tsx`. +- Create a virtualized vertical component scroller (`oracle/components/CanvasViewport.tsx`). +- Introduce a Component Registry (`oracle/components/ComponentRegistry.tsx`) that translates backend JSON components (e.g., `barChart`, `geoMap`) into React views. +- Make the prompt rail rely on the execution history instead of mock triggers. + +### WP5: Collaboration & Merge Model +- **Jira-Style Branching**: Implement page sharing (Fork creation) instead of live shared edits. +- Implement Merge Requests, 3-way component diffs, and conflict resolution UI (`MergeReviewDrawer.tsx`). + +### WP6: Component Catalog & Synthesis +- Build a catalog of pre-made templates (KPI tiles, pipelines, investor geographic heatmaps). +- Implement component synthesis: where an LLM generates a brand new visualization template utilizing extracted `styleSignatures` to match the tenant's exact design language. + +--- + +## 3. Limitations & Constraints Imposed by the Master Artifact + +The architecture document outlines very strict constraints on *why* parts of the system are deferred and *how* the remaining work must be executed. + +> [!WARNING] +> **Strict Implementation Ordering** +> The system must be built sequentially. **Do not** build the frontend canvas (WP4) or Collaboration (WP5) before the API Contracts (WP1) and Persistence (WP2) are real. +> *Quoting the document:* "The first concrete move... should be to freeze the current mock Oracle query client... The second move should be to add Oracle page tables... Only after those three foundations exist should the team invest in merge review UI or template synthesis. That order keeps the product grounded in durable state rather than another generation of temporary mocks." + +### Structural Constraints +- **No Unrestricted AI Executing SQL**: `Nemoclaw` acts only as a *planner*. It must **never** execute arbitrary SQL or touch connector APIs directly to mitigate hallucination and data leakage. It produces a JSON query plan that is executed strictly by the non-AI *Data Access Gateway*. +- **No Live Co-Editing**: Overwriting another user's Oracle dashboard live is forbidden. Oracle requires an explicit asynchronous **Fork & Merge** collaboration structure. +- **Immutable Pages**: Pages are append-only. When the frontend mutates a visualization, it must execute a new commit revision or a rollback revision. It must *never* silently overwrite a past revision log. +- **Synthesis is Delayed & Isolated**: Auto-promotion of LLM-synthesized UI components is explicitly isolated to the originating tenant for Sprint 1. Global (cross-tenant) sharing of synthesized components is forbidden. +- **No Uncontrolled Fine-Tuning**: Visual generation must use *exemplar retrieval* (a JSON dictionary `styleSignature`), instead of raw model-weight fine-tuning, to ensure styling remains fully deterministic. + +### Security / Scope Constraints +- **Cross-Tenant Blocking**: Because of Hybrid Sovereign deployment architecture, querying across tenants is explicitly rejected and unsupported in this phase. +- **Connectors**: MVP must rely primarily on internal Velocity PostgreSQL datasets. External CRM connectors are deferred to later phases. + +--- + +## 4. Oracle Development Status Dashboard + +| Task Category | Status | Details / Scope | Specific Limitations / Constraints | +| :--- | :--- | :--- | :--- | +| **Frontend UI Shell** | ✅ Completed | Premium glassmorphic UI, right-side conversation rail, and top insight banner are all in place (`app/src/app/oracle/page.tsx`). | N/A - Serves as visual foundation. | +| **Backend Foundation** | ✅ Completed | FastAPI router base, `asyncpg` for PostgreSQL, and JWT authentication flow. | N/A - Foundation only. | +| **AI Planner Engine** | ✅ Completed | `Nemoclaw` client is operational and abstracted (currently used by Sentinel). | Operates merely as a planner; the LLM cannot execute SQL directly. | +| **Mock Data Layer** | ⏸️ Deprecated | Legacy `oracleQueryClient.ts` powering single-response fixed views with hardcoded data. | Cannot be used for Phase 2. Must be replaced immediately prior to frontend expansion. | +| **WP1: Contract Foundation** | ⏳ Remaining | JSON Schemas (`CanvasPage`, `CanvasComponent`) and typed `oracleApiClient.ts`. | **Must be built first.** No UI development can proceed until the API contracts replace the legacy mocks. | +| **WP2: Persistence & DB Core** | ⏳ Remaining | PostgreSQL schema implementation for `canvas_pages`, `revisions`, `components`, and `executions`. | **Sequential Dependency.** Must be completed immediately after WP1 to ensure all UI actions rely on durable states, avoiding fake in-memory changes. | +| **WP3: Prompt Orchestrator** | ⏳ Remaining | Wire backend (`prompt_orchestrator.py`) to Nemoclaw for structured JSON retrieval and visualization planning. | **Redaction & Security.** The Data Access Gateway must validate the plan. Nemoclaw is forbidden from directly querying external connectors, preventing data hallucination/leakage. | +| **WP4: Frontend Canvas** | ⏳ Remaining | Refactor Oracle UI into a virtualized, vertically scrolling JSON canvas with a proper Component Registry. | **No Live Co-Editing.** State changes (mutations) must execute explicit revision commits or rollbacks. Overwriting another user's current view directly is forbidden. | +| **WP5: Collaboration & Merging** | ⏳ Remaining | Jira-style Share (Fork) workflows, Merge Requests, 3-way component diffs, and review UI. | **Isolated Revisions.** All sharing forces a new branch (Fork). No cross-tenant data sharing is allowed under any circumstance (Hybrid Sovereign constraint). | +| **WP6: Catalog & Synthesis** | ⏳ Remaining | AI-driven synthesis of custom UI components using extracted tenant styling. | **No Global Promotion.** Auto-promotion of LLM-generated components is isolated to the origin tenant. Uses deterministic `styleSignatures`, avoiding uncontrolled weight fine-tuning. | +| **WP7: Hardening & Ops** | ⏳ Remaining | Performance testing, telemetry, replay fixtures, and SLA tuning limiters. | Must ensure canvas scrolling maintains 60 FPS up to 5,000 components via strict virtualization limits. | + diff --git a/app/dist/index.html b/app/dist/index.html index 8ec91bfc..870fc272 100644 --- a/app/dist/index.html +++ b/app/dist/index.html @@ -4,8 +4,8 @@ Velocity WebOS - - + +
diff --git a/app/node_modules/.tmp/tsconfig.app.tsbuildinfo b/app/node_modules/.tmp/tsconfig.app.tsbuildinfo index b14b6c56..51fe1201 100644 --- a/app/node_modules/.tmp/tsconfig.app.tsbuildinfo +++ b/app/node_modules/.tmp/tsconfig.app.tsbuildinfo @@ -1 +1 @@ -{"root":["../../src/app.tsx","../../src/main.tsx","../../src/app/oracle/page.tsx","../../src/components/layout/loginscreen.tsx","../../src/components/layout/sidebar.tsx","../../src/components/modules/catalyst.tsx","../../src/components/modules/dashboard.tsx","../../src/components/modules/inventory.tsx","../../src/components/modules/oracle.tsx","../../src/components/modules/sentinel.tsx","../../src/components/modules/settings.tsx","../../src/components/oracle/leadinspector.tsx","../../src/components/oracle/pipelineview.tsx","../../src/components/oracle/mockleads.ts","../../src/components/sentinel/journeyriver/inspectorpanel.tsx","../../src/components/sentinel/journeyriver/riverpath.tsx","../../src/components/sentinel/journeyriver/index.tsx","../../src/components/ui/accordion.tsx","../../src/components/ui/alert-dialog.tsx","../../src/components/ui/alert.tsx","../../src/components/ui/aspect-ratio.tsx","../../src/components/ui/avatar.tsx","../../src/components/ui/badge.tsx","../../src/components/ui/breadcrumb.tsx","../../src/components/ui/button-group.tsx","../../src/components/ui/button.tsx","../../src/components/ui/calendar.tsx","../../src/components/ui/card.tsx","../../src/components/ui/carousel.tsx","../../src/components/ui/chart.tsx","../../src/components/ui/checkbox.tsx","../../src/components/ui/collapsible.tsx","../../src/components/ui/command.tsx","../../src/components/ui/context-menu.tsx","../../src/components/ui/dialog.tsx","../../src/components/ui/drawer.tsx","../../src/components/ui/dropdown-menu.tsx","../../src/components/ui/empty.tsx","../../src/components/ui/field.tsx","../../src/components/ui/form.tsx","../../src/components/ui/hover-card.tsx","../../src/components/ui/input-group.tsx","../../src/components/ui/input-otp.tsx","../../src/components/ui/input.tsx","../../src/components/ui/item.tsx","../../src/components/ui/kbd.tsx","../../src/components/ui/label.tsx","../../src/components/ui/menubar.tsx","../../src/components/ui/navigation-menu.tsx","../../src/components/ui/pagination.tsx","../../src/components/ui/popover.tsx","../../src/components/ui/progress.tsx","../../src/components/ui/radio-group.tsx","../../src/components/ui/resizable.tsx","../../src/components/ui/scroll-area.tsx","../../src/components/ui/select.tsx","../../src/components/ui/separator.tsx","../../src/components/ui/sheet.tsx","../../src/components/ui/sidebar.tsx","../../src/components/ui/skeleton.tsx","../../src/components/ui/slider.tsx","../../src/components/ui/sonner.tsx","../../src/components/ui/spinner.tsx","../../src/components/ui/switch.tsx","../../src/components/ui/table.tsx","../../src/components/ui/tabs.tsx","../../src/components/ui/textarea.tsx","../../src/components/ui/toggle-group.tsx","../../src/components/ui/toggle.tsx","../../src/components/ui/tooltip.tsx","../../src/hooks/use-mobile.ts","../../src/lib/oraclequeryclient.ts","../../src/lib/utils.ts","../../src/store/usemarketingstore.ts","../../src/store/usestore.ts","../../src/types/crm.ts","../../src/types/index.ts","../../src/utils/curvegenerator.ts"],"version":"5.9.3"} \ No newline at end of file +{"root":["../../src/app.tsx","../../src/main.tsx","../../src/app/oracle/page.tsx","../../src/components/layout/loginscreen.tsx","../../src/components/layout/sidebar.tsx","../../src/components/modules/catalyst.tsx","../../src/components/modules/dashboard.tsx","../../src/components/modules/groundtruthpicker.tsx","../../src/components/modules/inventory.tsx","../../src/components/modules/oracle.tsx","../../src/components/modules/sentinel.tsx","../../src/components/modules/settings.tsx","../../src/components/oracle/leadinspector.tsx","../../src/components/oracle/pipelineview.tsx","../../src/components/oracle/mockleads.ts","../../src/components/sentinel/journeyriver/inspectorpanel.tsx","../../src/components/sentinel/journeyriver/riverpath.tsx","../../src/components/sentinel/journeyriver/index.tsx","../../src/components/ui/accordion.tsx","../../src/components/ui/alert-dialog.tsx","../../src/components/ui/alert.tsx","../../src/components/ui/aspect-ratio.tsx","../../src/components/ui/avatar.tsx","../../src/components/ui/badge.tsx","../../src/components/ui/breadcrumb.tsx","../../src/components/ui/button-group.tsx","../../src/components/ui/button.tsx","../../src/components/ui/calendar.tsx","../../src/components/ui/card.tsx","../../src/components/ui/carousel.tsx","../../src/components/ui/chart.tsx","../../src/components/ui/checkbox.tsx","../../src/components/ui/collapsible.tsx","../../src/components/ui/command.tsx","../../src/components/ui/context-menu.tsx","../../src/components/ui/dialog.tsx","../../src/components/ui/drawer.tsx","../../src/components/ui/dropdown-menu.tsx","../../src/components/ui/empty.tsx","../../src/components/ui/field.tsx","../../src/components/ui/form.tsx","../../src/components/ui/hover-card.tsx","../../src/components/ui/input-group.tsx","../../src/components/ui/input-otp.tsx","../../src/components/ui/input.tsx","../../src/components/ui/item.tsx","../../src/components/ui/kbd.tsx","../../src/components/ui/label.tsx","../../src/components/ui/menubar.tsx","../../src/components/ui/navigation-menu.tsx","../../src/components/ui/pagination.tsx","../../src/components/ui/popover.tsx","../../src/components/ui/progress.tsx","../../src/components/ui/radio-group.tsx","../../src/components/ui/resizable.tsx","../../src/components/ui/scroll-area.tsx","../../src/components/ui/select.tsx","../../src/components/ui/separator.tsx","../../src/components/ui/sheet.tsx","../../src/components/ui/sidebar.tsx","../../src/components/ui/skeleton.tsx","../../src/components/ui/slider.tsx","../../src/components/ui/sonner.tsx","../../src/components/ui/spinner.tsx","../../src/components/ui/switch.tsx","../../src/components/ui/table.tsx","../../src/components/ui/tabs.tsx","../../src/components/ui/textarea.tsx","../../src/components/ui/toggle-group.tsx","../../src/components/ui/toggle.tsx","../../src/components/ui/tooltip.tsx","../../src/hooks/use-mobile.ts","../../src/lib/oraclequeryclient.ts","../../src/lib/utils.ts","../../src/oracle/components/branchbar.tsx","../../src/oracle/components/canvasviewport.tsx","../../src/oracle/components/componentregistry.tsx","../../src/oracle/components/promptrail.tsx","../../src/oracle/components/rollbackconfirmmodal.tsx","../../src/oracle/components/sharemodal.tsx","../../src/oracle/components/renderers/activitystreamrenderer.tsx","../../src/oracle/components/renderers/barchartrenderer.tsx","../../src/oracle/components/renderers/errornoticerenderer.tsx","../../src/oracle/components/renderers/geomaprenderer.tsx","../../src/oracle/components/renderers/kpitilerenderer.tsx","../../src/oracle/components/renderers/linechartrenderer.tsx","../../src/oracle/components/renderers/pipelineboardrenderer.tsx","../../src/oracle/components/renderers/rendererwrapper.tsx","../../src/oracle/components/renderers/tablerenderer.tsx","../../src/oracle/components/renderers/timelinerenderer.tsx","../../src/oracle/components/review/mergereviewdrawer.tsx","../../src/oracle/hooks/useoracleexecution.ts","../../src/oracle/hooks/useoraclepage.ts","../../src/oracle/lib/oracleapiclient.ts","../../src/oracle/lib/oracledemodata.ts","../../src/oracle/types/canvas.ts","../../src/store/usecurrencystore.ts","../../src/store/usemarketingstore.ts","../../src/store/usestore.ts","../../src/types/crm.ts","../../src/types/index.ts","../../src/utils/curvegenerator.ts"],"version":"5.9.3"} \ No newline at end of file diff --git a/app/node_modules/.vite/deps/@radix-ui_react-avatar.js b/app/node_modules/.vite/deps/@radix-ui_react-avatar.js index b7eaa4bd..fe29feb1 100644 --- a/app/node_modules/.vite/deps/@radix-ui_react-avatar.js +++ b/app/node_modules/.vite/deps/@radix-ui_react-avatar.js @@ -2,14 +2,14 @@ import { createSlot } from "./chunk-YWBEB5PG.js"; +import { + require_shim +} from "./chunk-TXHHHGR3.js"; import { useCallbackRef, useLayoutEffect2 } from "./chunk-23FVUG5N.js"; import "./chunk-2VUH7NEY.js"; -import { - require_shim -} from "./chunk-TXHHHGR3.js"; import { require_react_dom } from "./chunk-YF4B4G2L.js"; diff --git a/app/node_modules/.vite/deps/@react-three_drei.js b/app/node_modules/.vite/deps/@react-three_drei.js index 7b8a93ba..53904cdd 100644 --- a/app/node_modules/.vite/deps/@react-three_drei.js +++ b/app/node_modules/.vite/deps/@react-three_drei.js @@ -1,9 +1,9 @@ -import { - require_client -} from "./chunk-6MXH2QM6.js"; import { subscribeWithSelector } from "./chunk-O4L7C4YS.js"; +import { + create +} from "./chunk-7GZ4CI6Q.js"; import { Events } from "./chunk-OAEA5FZL.js"; @@ -22,8 +22,8 @@ import { useInstanceHandle, useLoader, useThree -} from "./chunk-O5V7GNMB.js"; -import "./chunk-GUQHL3N7.js"; +} from "./chunk-5ESDTKMP.js"; +import "./chunk-NJ4V5H3P.js"; import { AddEquation, AdditiveBlending, @@ -219,9 +219,9 @@ import { ZeroFactor } from "./chunk-L3Z576C2.js"; import { - create -} from "./chunk-7GZ4CI6Q.js"; -import "./chunk-NJ4V5H3P.js"; + require_client +} from "./chunk-6MXH2QM6.js"; +import "./chunk-GUQHL3N7.js"; import { _extends } from "./chunk-EQCCHGRT.js"; diff --git a/app/node_modules/.vite/deps/@react-three_fiber.js b/app/node_modules/.vite/deps/@react-three_fiber.js index 638f81cc..8bf8ff47 100644 --- a/app/node_modules/.vite/deps/@react-three_fiber.js +++ b/app/node_modules/.vite/deps/@react-three_fiber.js @@ -28,10 +28,10 @@ import { useLoader, useStore, useThree -} from "./chunk-O5V7GNMB.js"; -import "./chunk-GUQHL3N7.js"; -import "./chunk-L3Z576C2.js"; +} from "./chunk-5ESDTKMP.js"; import "./chunk-NJ4V5H3P.js"; +import "./chunk-L3Z576C2.js"; +import "./chunk-GUQHL3N7.js"; import "./chunk-TXHHHGR3.js"; import "./chunk-2YVA4HRZ.js"; import "./chunk-WUR7D6NS.js"; diff --git a/app/node_modules/.vite/deps/_metadata.json b/app/node_modules/.vite/deps/_metadata.json index 3b798b02..da24170c 100644 --- a/app/node_modules/.vite/deps/_metadata.json +++ b/app/node_modules/.vite/deps/_metadata.json @@ -2,126 +2,132 @@ "hash": "b2c5007d", "configHash": "d9a82a01", "lockfileHash": "8a04eea8", - "browserHash": "5d6343ae", + "browserHash": "c208d4ff", "optimized": { "react": { "src": "../../react/index.js", "file": "react.js", - "fileHash": "4ea9824e", + "fileHash": "a26efb1d", "needsInterop": true }, "react-dom": { "src": "../../react-dom/index.js", "file": "react-dom.js", - "fileHash": "5b549105", + "fileHash": "c6e68a6f", "needsInterop": true }, "react/jsx-dev-runtime": { "src": "../../react/jsx-dev-runtime.js", "file": "react_jsx-dev-runtime.js", - "fileHash": "41193e59", + "fileHash": "7531df54", "needsInterop": true }, "react/jsx-runtime": { "src": "../../react/jsx-runtime.js", "file": "react_jsx-runtime.js", - "fileHash": "ad8008f8", + "fileHash": "a247453f", "needsInterop": true }, "@radix-ui/react-avatar": { "src": "../../@radix-ui/react-avatar/dist/index.mjs", "file": "@radix-ui_react-avatar.js", - "fileHash": "e6d8d406", + "fileHash": "d86de0d6", "needsInterop": false }, "@radix-ui/react-dropdown-menu": { "src": "../../@radix-ui/react-dropdown-menu/dist/index.mjs", "file": "@radix-ui_react-dropdown-menu.js", - "fileHash": "26caaf75", + "fileHash": "51cbea4a", "needsInterop": false }, "@radix-ui/react-slot": { "src": "../../@radix-ui/react-slot/dist/index.mjs", "file": "@radix-ui_react-slot.js", - "fileHash": "e034d698", + "fileHash": "d49e4181", "needsInterop": false }, "@react-three/drei": { "src": "../../@react-three/drei/index.js", "file": "@react-three_drei.js", - "fileHash": "d81d1332", + "fileHash": "49af0e0c", "needsInterop": false }, "@react-three/fiber": { "src": "../../@react-three/fiber/dist/react-three-fiber.esm.js", "file": "@react-three_fiber.js", - "fileHash": "dcc73392", + "fileHash": "6e6f65b1", "needsInterop": false }, "class-variance-authority": { "src": "../../class-variance-authority/dist/index.mjs", "file": "class-variance-authority.js", - "fileHash": "5fa3c3f8", + "fileHash": "0e514934", "needsInterop": false }, "clsx": { "src": "../../clsx/dist/clsx.mjs", "file": "clsx.js", - "fileHash": "1428051e", + "fileHash": "e71ef32c", "needsInterop": false }, "framer-motion": { "src": "../../framer-motion/dist/es/index.mjs", "file": "framer-motion.js", - "fileHash": "dd6ef86d", + "fileHash": "5b5818ee", "needsInterop": false }, "lucide-react": { "src": "../../lucide-react/dist/esm/lucide-react.js", "file": "lucide-react.js", - "fileHash": "df6c668f", + "fileHash": "fb6a8921", "needsInterop": false }, "react-dom/client": { "src": "../../react-dom/client.js", "file": "react-dom_client.js", - "fileHash": "23087df0", + "fileHash": "032f0a73", "needsInterop": true }, "react-router-dom": { "src": "../../react-router-dom/dist/index.mjs", "file": "react-router-dom.js", - "fileHash": "48fbca8f", + "fileHash": "fa45c285", "needsInterop": false }, "recharts": { "src": "../../recharts/es6/index.js", "file": "recharts.js", - "fileHash": "9faf094d", + "fileHash": "b3d6765a", "needsInterop": false }, "tailwind-merge": { "src": "../../tailwind-merge/dist/bundle-mjs.mjs", "file": "tailwind-merge.js", - "fileHash": "fc48e7d8", + "fileHash": "47183e49", "needsInterop": false }, "three": { "src": "../../three/build/three.module.js", "file": "three.js", - "fileHash": "a0b83871", + "fileHash": "d78c89ed", "needsInterop": false }, "zustand": { "src": "../../zustand/esm/index.mjs", "file": "zustand.js", - "fileHash": "ad25a55f", + "fileHash": "39f09270", "needsInterop": false }, "zustand/middleware": { "src": "../../zustand/esm/middleware.mjs", "file": "zustand_middleware.js", - "fileHash": "b84fa2e5", + "fileHash": "ce09abfc", + "needsInterop": false + }, + "sonner": { + "src": "../../sonner/dist/index.mjs", + "file": "sonner.js", + "fileHash": "b1e28aee", "needsInterop": false } }, @@ -135,45 +141,45 @@ "chunk-U7P2NEEE": { "file": "chunk-U7P2NEEE.js" }, + "chunk-O4L7C4YS": { + "file": "chunk-O4L7C4YS.js" + }, + "chunk-7GZ4CI6Q": { + "file": "chunk-7GZ4CI6Q.js" + }, + "chunk-OAEA5FZL": { + "file": "chunk-OAEA5FZL.js" + }, + "chunk-5ESDTKMP": { + "file": "chunk-5ESDTKMP.js" + }, + "chunk-NJ4V5H3P": { + "file": "chunk-NJ4V5H3P.js" + }, + "chunk-L3Z576C2": { + "file": "chunk-L3Z576C2.js" + }, + "chunk-6MXH2QM6": { + "file": "chunk-6MXH2QM6.js" + }, + "chunk-GUQHL3N7": { + "file": "chunk-GUQHL3N7.js" + }, + "chunk-EQCCHGRT": { + "file": "chunk-EQCCHGRT.js" + }, "chunk-YWBEB5PG": { "file": "chunk-YWBEB5PG.js" }, + "chunk-TXHHHGR3": { + "file": "chunk-TXHHHGR3.js" + }, "chunk-23FVUG5N": { "file": "chunk-23FVUG5N.js" }, "chunk-2VUH7NEY": { "file": "chunk-2VUH7NEY.js" }, - "chunk-6MXH2QM6": { - "file": "chunk-6MXH2QM6.js" - }, - "chunk-O4L7C4YS": { - "file": "chunk-O4L7C4YS.js" - }, - "chunk-OAEA5FZL": { - "file": "chunk-OAEA5FZL.js" - }, - "chunk-O5V7GNMB": { - "file": "chunk-O5V7GNMB.js" - }, - "chunk-GUQHL3N7": { - "file": "chunk-GUQHL3N7.js" - }, - "chunk-L3Z576C2": { - "file": "chunk-L3Z576C2.js" - }, - "chunk-7GZ4CI6Q": { - "file": "chunk-7GZ4CI6Q.js" - }, - "chunk-NJ4V5H3P": { - "file": "chunk-NJ4V5H3P.js" - }, - "chunk-EQCCHGRT": { - "file": "chunk-EQCCHGRT.js" - }, - "chunk-TXHHHGR3": { - "file": "chunk-TXHHHGR3.js" - }, "chunk-YF4B4G2L": { "file": "chunk-YF4B4G2L.js" }, diff --git a/app/src/App.tsx b/app/src/App.tsx index d1414a7b..3a3832b7 100644 --- a/app/src/App.tsx +++ b/app/src/App.tsx @@ -96,7 +96,7 @@ function MainLayout() { {/* Main Content Area */} {/* Top Bar */} -
+
{/* Module Content — animated on route change */} -
- - +
+ + +
diff --git a/app/src/app/oracle/page.tsx b/app/src/app/oracle/page.tsx index fc21d82f..2a19d5bc 100644 --- a/app/src/app/oracle/page.tsx +++ b/app/src/app/oracle/page.tsx @@ -1,792 +1,444 @@ 'use client'; -import { type ReactNode, useMemo, useState } from 'react'; -import { AnimatePresence, motion } from 'framer-motion'; -import { CalendarClock, ChevronDown, Kanban, MapPinned, MessageSquareText, Mic, Phone, Send, Users } from 'lucide-react'; -import { Line, LineChart, ResponsiveContainer, Tooltip, XAxis, YAxis } from 'recharts'; +/** + * Oracle Page — Orchestration Host (v2 Refactor) + * Implements the vertical JSON canvas architecture from the Oracle spec §10–§13. + * + * Architecture: + * BranchBar — page identity, branch, revision, quick actions (top) + * CanvasViewport — virtualized component canvas (center, flex-1) + * PromptRail — durable execution history (right sidebar, collapsible) + * PromptBar — floating bottom prompt input (preserved premium glass treatment) + * ShareModal — fork-based sharing (overlay) + * RollbackConfirmModal — revision history + rollback (overlay) + * MergeReviewDrawer — diff/conflict reviewer (right drawer overlay) + * + */ +import { useState, useCallback, useRef, useEffect } from 'react'; +import { motion, AnimatePresence } from 'framer-motion'; +import { Send, Mic, Kanban, Users, Phone, MapPinned, CalendarClock, ChevronDown, History, BarChart2 } from 'lucide-react'; import { Input } from '@/components/ui/input'; -import { Avatar, AvatarFallback, AvatarImage } from '@/components/ui/avatar'; -import { Badge } from '@/components/ui/badge'; -import { Button } from '@/components/ui/button'; -import { - mockOracleResultForPrompt, - type OracleCanvasView, - queryOracle, - type OracleQueryResult, - type PipelineCardData, -} from '@/lib/oracleQueryClient'; +import type { CanvasPageRevision, MergeRequest, UserProfile } from '@/oracle/types/canvas'; +import type { ComponentRenderContext } from '@/oracle/components/ComponentRegistry'; +import { useOraclePage } from '@/oracle/hooks/useOraclePage'; +import { useOracleExecution } from '@/oracle/hooks/useOracleExecution'; +import { BranchBar } from '@/oracle/components/BranchBar'; +import { CanvasViewport } from '@/oracle/components/CanvasViewport'; +import { PromptRail } from '@/oracle/components/PromptRail'; +import { ShareModal } from '@/oracle/components/ShareModal'; +import { RollbackConfirmModal } from '@/oracle/components/RollbackConfirmModal'; +import { MergeReviewDrawer } from '@/oracle/components/review/MergeReviewDrawer'; +import { createFork, fetchMe, listRevisions, reviewMergeRequest, rollbackPage } from '@/oracle/lib/oracleApiClient'; -interface QueryTurn { - role: 'user' | 'assistant'; - content: string; -} - -const START_PROMPT = 'Show me a pipeline view by stage for Q4.'; -const VIEW_LABELS: Record = { - pipeline: 'Pipeline', - team_performance: 'Team Performance', - account_timeline: 'Account Timeline', - lead_map: 'Lead Map', - calendar_tasks: 'Calendar and Tasks', -}; - -const PROMPT_MODES: Array<{ view: OracleCanvasView; label: string; samplePrompt: string }> = [ - { view: 'pipeline', label: 'Kanban Pipeline', samplePrompt: 'Show me a pipeline view by stage for Q4.' }, - { view: 'team_performance', label: 'Team Performance', samplePrompt: "What's the performance of the sales team this month?" }, - { view: 'account_timeline', label: 'Account Timeline', samplePrompt: "Find all contacts at 'Apex Innovations' and their recent activity." }, - { view: 'lead_map', label: 'Geographic Lead Map', samplePrompt: 'Give me a map of all leads in California.' }, - { view: 'calendar_tasks', label: 'Calendar and Tasks', samplePrompt: 'Schedule a follow-up with the top 3 high-value leads.' }, +const PROMPT_MODES: Array<{ view: string; label: string; samplePrompt: string; icon: React.ComponentType<{ className?: string }> }> = [ + { view: 'pipeline', label: 'Pipeline', samplePrompt: 'Show me a pipeline view by stage for Q2 2026.', icon: Kanban }, + { view: 'team_performance', label: 'Team Performance', samplePrompt: 'What is the performance of the sales team this month?', icon: Users }, + { view: 'account_timeline', label: 'Account Timeline', samplePrompt: "Find all activities for Apex Innovations this quarter.", icon: Phone }, + { view: 'lead_map', label: 'Geographic Map', samplePrompt: 'Show me a map of all whale leads in Dubai Marina.', icon: MapPinned }, + { view: 'calendar_tasks', label: 'Calendar & Tasks', samplePrompt: 'Schedule follow-ups with the top 3 leads with no contact in 72h.', icon: CalendarClock }, + { view: 'kpi', label: 'KPI Summary', samplePrompt: 'Give me a KPI summary of total pipeline value today.', icon: BarChart2 }, ]; -/** - * ─── Design System: Glass Neumorphism + Blue Glow ───────────────────────────── - */ -const GLASS_PANEL = { - background: 'rgba(10, 12, 20, 0.70)', - border: '1px solid rgba(59,130,246,0.15)', - backdropFilter: 'blur(24px)', - WebkitBackdropFilter: 'blur(24px)', - boxShadow: '0 8px 32px rgba(0,0,0,0.5), 0 0 0 1px rgba(255,255,255,0.03)', - borderRadius: '16px', -} as const; +// ── Render context ──────────────────────────────────────────────────────────── -const GLASS_CARD = { - background: 'rgba(255,255,255,0.02)', - border: '1px solid rgba(255,255,255,0.05)', - borderRadius: '12px', - transition: 'all 0.3s cubic-bezier(0.4, 0, 0.2, 1)', -} as const; +const BASE_CTX: ComponentRenderContext = { + tenantId: '', + actorRole: 'sales_director', + showLineageBadges: true, + density: 'comfortable', +}; -const GLASS_CARD_HOVER = { - background: 'rgba(59,130,246,0.08)', - border: '1px solid rgba(59,130,246,0.3)', - boxShadow: '0 0 20px rgba(59,130,246,0.15), 0 0 0 1px rgba(59,130,246,0.1)', -} as const; +// ── Oracle Page ─────────────────────────────────────────────────────────────── -function GlassFrame({ children, className = '' }: { children: ReactNode; className?: string }) { - return ( -
- {children} -
- ); -} - -// ─── Pipeline View ──────────────────────────────────────────────────────────── -function PipelineCard({ item }: { item: PipelineCardData }) { - return ( - - {/* Left accent bar on hover */} -
- -
- - - {item.name.slice(0, 2)} - -
-

{item.name}

-

{item.company}

-
-
-
-

Deal Value

-

{item.value}

-
- - ); -} - -function PipelineCanvas({ result }: { result: OracleQueryResult }) { - const columns = result.payload.pipeline ?? {}; - return ( -
- {Object.entries(columns).map(([stage, cards]) => ( - -
-

{stage}

- {cards.length} -
-
- {cards.map((item) => ( - - ))} -
-
- ))} -
- ); -} - -// ─── Team Performance View ──────────────────────────────────────────────────── -function TeamPerformanceCanvas({ result }: { result: OracleQueryResult }) { - const data = result.payload.revenueSeries ?? []; - const quota = result.payload.quotaAttainment ?? 0; - const team = result.payload.team ?? []; - return ( -
-
- -

- - Monthly Revenue vs. Goal -

-
- - - - - - - - - - - - - - - -
-
- - {/* Background glow for gauge */} -
- -

- - Quota Attainment -

-
-
- {/* Gauge track */} -
- {/* Gauge value */} -
-
-
-

- {quota}% -

-

Attained

-
-
-
-
- -
- - -

- - Team Member Performance -

-
- {team.map((member) => ( - -
-
- - - {member.name.slice(0, 2)} - -
-
-
-

{member.name}

-

{member.dealsClosed} deals closed

-
-
- - {member.revenueGenerated} - - - ))} -
- -
- ); -} - -// ─── Account Timeline View ──────────────────────────────────────────────────── -function AccountTimelineCanvas({ result }: { result: OracleQueryResult }) { - const account = result.payload.account; - if (!account) return null; - return ( -
-
- -
-

Account Overview

-

- {account.name} -

- -
-
-

Total Deal Value

-

{account.totalDealValue}

-
-
-
-

Primary Contact

-

{account.primaryContact}

-
-
-

Industry

-

{account.industry}

-
-
-
- - - -

Key Stakeholders

-
- {account.contacts.map((contact) => ( - - - - {contact.name.slice(0, 2)} - -
-

{contact.name}

-

{contact.role}

-
-
- ))} -
-
-
- - -

- - Activity Timeline -

-
- {/* Glowing timeline line */} -
- - {account.timeline.map((event, idx) => ( - - {/* Timeline dot with glowing halo */} -
- -
- {/* Hover shine effect */} -
- -
-
- - {event.title} - -

{event.summary}

-
- {event.when} -
-
- - ))} -
- -
- ); -} - -// ─── Lead Map View ──────────────────────────────────────────────────────────── -function LeadMapCanvas({ result }: { result: OracleQueryResult }) { - const map = result.payload.map; - if (!map) return null; - return ( -
- {/* Map Background */} -
- US Map -
- {/* Overlay Gradient for depth */} -
- - {/* Legend Panel */} -
-

Lead Intensity

-
-
-
- Cold Lead -
-
-
- Warm Lead -
-
-
- Hot Lead -
-
-
- - {/* Filter Button */} -
- -
- - {/* Map Pins */} - {map.pins.map((pin) => { - const pinStyle = - pin.temperature === 'cold' - ? 'bg-slate-400/80 shadow-[0_0_15px_rgba(148,163,184,0.4)]' - : pin.temperature === 'warm' - ? 'bg-cyan-400 shadow-[0_0_20px_rgba(34,211,238,0.6)]' - : 'bg-blue-600 border-2 border-white/80 shadow-[0_0_25px_rgba(59,130,246,0.9)]'; - - return ( - - -
- {pin.label} - {pin.count && ({pin.count})} -
-
- ); - })} -
- ); -} - -// ─── Calendar View ──────────────────────────────────────────────────────────── -function CalendarCanvas({ result }: { result: OracleQueryResult }) { - const calendar = result.payload.calendar; - if (!calendar) return null; - const days = ['Sun', 'Mon', 'Tue', 'Wed', 'Thu', 'Fri', 'Sat']; - return ( -
- -
-

- - Weekly {calendar.weekLabel} -

- -
-
- {days.map((day) => ( -
- {day} -
- ))} - {days.map((day) => ( -
-
- {calendar.events - .filter((event) => event.day === day) - .map((event) => ( - -

{event.time}

-

{event.title}

-
- ))} -
-
- ))} -
-
- -

- - Tasks and Actions -

-
- {calendar.tasks.map((task) => ( - -
-

{task.title}

- Action -
-

{task.subtitle}

-
-
- - {task.due} -
- -
-
- ))} -
-
-
- ); -} - -// ─── Dynamic Switcher ───────────────────────────────────────────────────────── -function DynamicCanvas({ result }: { result: OracleQueryResult }) { - if (result.view === 'pipeline') return ; - if (result.view === 'team_performance') return ; - if (result.view === 'account_timeline') return ; - if (result.view === 'lead_map') return ; - return ; -} - -// ─── Main Oracle Page ───────────────────────────────────────────────────────── export default function OraclePage() { - const [prompt, setPrompt] = useState(START_PROMPT); - const [selectedMode, setSelectedMode] = useState('pipeline'); - const [viewDropOpen, setViewDropOpen] = useState(false); - const [submitting, setSubmitting] = useState(false); - const [listening, setListening] = useState(false); - const [queryHistory, setQueryHistory] = useState([{ role: 'user', content: START_PROMPT }]); - const [result, setResult] = useState(mockOracleResultForPrompt(START_PROMPT)); - const canvasKey = useMemo(() => `${result.view}-${result.summary}`, [result.view, result.summary]); + const [me, setMe] = useState(null); + const [profileError, setProfileError] = useState(null); + const [revisions, setRevisions] = useState([]); + const [revisionsLoading, setRevisionsLoading] = useState(false); - const handleMic = () => { + // Page state & WebSocket + const { page, isLoading, error: pageError, isConnected, applyRevision, refresh } = useOraclePage(me?.defaultPageId ?? null); + + // Prompt execution + const { history, inFlight, lastError, submit } = useOracleExecution(); + + // UI state + const [prompt, setPrompt] = useState(''); + const [selectedMode, setSelectedMode] = useState(PROMPT_MODES[0]); + const [viewDropOpen, setViewDropOpen] = useState(false); + const [listening, setListening] = useState(false); + const [railOpen, setRailOpen] = useState(false); + const [selectedComponentId, setSelectedComponentId] = useState(null); + + // Overlay state + const [shareOpen, setShareOpen] = useState(false); + const [rollbackOpen, setRollbackOpen] = useState(false); + const [mergeReviewOpen, setMergeReviewOpen] = useState(false); + const [activeMergeRequest, setActiveMergeRequest] = useState(null); + + const promptRef = useRef(null); + + useEffect(() => { + void fetchMe() + .then((profile) => { + setMe(profile); + setProfileError(null); + }) + .catch((err) => { + setProfileError(err instanceof Error ? err.message : 'Failed to load Oracle profile'); + }); + }, []); + + // ── Prompt submission ─────────────────────────────────────────────────────── + + const submitPrompt = useCallback(async () => { + const clean = prompt.trim(); + if (!clean || inFlight || !page || !me) return; + setPrompt(''); + + await submit({ + pageId: page.pageId, + branchId: page.branchId, + prompt: clean, + tenantId: me.tenantId, + actorId: me.userId, + placementMode: me.canvasPreferences.defaultPlacementMode, + conversationContext: history.map((e) => [ + { role: 'user' as const, content: e.execution.prompt }, + { role: 'assistant' as const, content: e.execution.summary ?? '' }, + ]).flat(), + onExecutionCommitted: ({ headRevision, components }) => { + applyRevision(headRevision, components); + }, + }); + }, [prompt, inFlight, submit, page, history, me, applyRevision]); + + // ── Mic handler ───────────────────────────────────────────────────────────── + + const handleMic = useCallback(() => { // eslint-disable-next-line @typescript-eslint/no-explicit-any const w = window as any; - const SR: new () => any = w.SpeechRecognition ?? w.webkitSpeechRecognition; + const SR = w.SpeechRecognition ?? w.webkitSpeechRecognition; if (!SR) return; - const recognition = new SR(); + // eslint-disable-next-line @typescript-eslint/no-explicit-any + const recognition = new SR() as any; recognition.lang = 'en-US'; recognition.interimResults = false; recognition.onstart = () => setListening(true); recognition.onend = () => setListening(false); recognition.onerror = () => setListening(false); - // eslint-disable-next-line @typescript-eslint/no-explicit-any - recognition.onresult = (event: any) => { - const transcript = (event.results[0][0] as { transcript: string }).transcript; - setPrompt(transcript); + recognition.onresult = (event: { results: ArrayLike> }) => { + const result = event.results[0][0]; + if (result) setPrompt(result.transcript); }; recognition.start(); + }, []); + + // ── Share handler ─────────────────────────────────────────────────────────── + + const handleShare = useCallback(async (params: { recipientEmail: string; visibility: 'private' | 'team'; message: string; sourceRevision: number }) => { + if (!page) return; + await createFork(page.pageId, { + recipientUserId: params.recipientEmail, + sourceRevision: params.sourceRevision, + visibility: params.visibility, + message: params.message, + }); + }, [page]); + + // ── Rollback handler ──────────────────────────────────────────────────────── + + const handleRollback = useCallback(async (targetRevision: number) => { + if (!page) return; + const result = await rollbackPage(page.pageId, targetRevision, `cli_rollback_${Date.now()}`); + applyRevision(result.headRevision, result.components); + await refresh(); + }, [page, applyRevision, refresh]); + + const handleOpenRollback = useCallback(() => { + if (!page) return; + setRollbackOpen(true); + setRevisionsLoading(true); + void listRevisions(page.pageId) + .then((items) => setRevisions(items)) + .catch(() => setRevisions([])) + .finally(() => setRevisionsLoading(false)); + }, [page]); + + const handleOpenMergeReview = useCallback(async () => { + if (!activeMergeRequest) return; + setMergeReviewOpen(true); + }, [activeMergeRequest]); + + const handleReviewMR = useCallback(async (decision: 'approve' | 'reject' | 'changes_requested', comment?: string) => { + if (!activeMergeRequest) return; + await reviewMergeRequest(activeMergeRequest.mergeRequestId, { decision, comment }); + setActiveMergeRequest(null); + setMergeReviewOpen(false); + }, [activeMergeRequest]); + + // ── Components to render ──────────────────────────────────────────────────── + + const components = page?.components ?? []; + const combinedError = profileError ?? pageError ?? null; + const renderCtx: ComponentRenderContext = { + ...BASE_CTX, + tenantId: me?.tenantId ?? '', + actorRole: me?.role ?? 'sales_director', + showLineageBadges: me?.canvasPreferences.showLineageBadges ?? true, + density: me?.canvasPreferences.defaultDensity ?? 'comfortable', }; - const submitPrompt = async () => { - const clean = prompt.trim(); - if (!clean || submitting) return; - - setSubmitting(true); - const selectedModeLabel = VIEW_LABELS[selectedMode]; - const directedPrompt = `[UI Direction: ${selectedModeLabel}] ${clean}`; - const nextHistory = [...queryHistory, { role: 'user' as const, content: clean }]; - setQueryHistory(nextHistory); - try { - const response = await queryOracle({ - prompt: directedPrompt, - history: nextHistory, - mode: 'cot-rag', - preferredView: selectedMode, - }); - setResult(response); - setQueryHistory((prev) => [...prev, { role: 'assistant', content: response.summary }]); - } catch { - setQueryHistory((prev) => [ - ...prev, - { - role: 'assistant', - content: 'Could not reach COT-RAG endpoint. Showing cached intelligent view.', - }, - ]); - } finally { - setSubmitting(false); - } - }; - - const viewIcon = - result.view === 'pipeline' - ? MessageSquareText - : result.view === 'team_performance' - ? Users - : result.view === 'account_timeline' - ? Phone - : result.view === 'lead_map' - ? MapPinned - : CalendarClock; - const ViewIcon = viewIcon; + // ── Render ────────────────────────────────────────────────────────────────── return (
- {/* Ambient background */} -
+ {/* Ambient background glow */} +
- {/* ── AI Insight card ─────────────────────────────────────────────── */} -
-
- {/* Left blue accent bar */} -
-
-
-

AI Insight

-

{result.insight}

+ {/* Loading veil */} + + {isLoading && ( + +
+
+ +
+

Loading Oracle canvas…

-
- - {VIEW_LABELS[result.view]} + + )} + + + {/* ── BranchBar ──────────────────────────────────────────────────────── */} + setShareOpen(true)} + onRollback={handleOpenRollback} + onOpenMergeReview={() => void handleOpenMergeReview()} + /> + + {/* ── AI Insight strip ───────────────────────────────────────────────── */} + {page && history.length > 0 && history[history.length - 1].execution.summary && ( +
+
+
+
+

Oracle

+

+ {history[history.length - 1].execution.summary} +

+ )} + + {/* ── Main content area: canvas + rail ───────────────────────────────── */} +
+ {/* Canvas viewport */} + + + {/* Prompt Rail */} + setRailOpen((p) => !p)} + />
- {/* ── Canvas ──────────────────────────────────────────────────────── */} -
- - - - - -
- - {/* ── Prompt Bar (floating bottom-center) ─────────────────────────── */} -
+ {/* ── Floating Prompt Bar ─────────────────────────────────────────────── */} +
- - {/* Blue glow blobs */} + {/* Blue glow */}
- {/* Prompt container */} + {/* Container */}
- {/* Text input */} + {/* Error banner */} + + {(combinedError || lastError) && ( + +
+

{combinedError ?? lastError}

+
+
+ )} +
+ + {/* Input */}
setPrompt(event.target.value)} - onKeyDown={(event) => { - if (event.key === 'Enter') { - event.preventDefault(); - void submitPrompt(); - } + onChange={(e) => setPrompt(e.target.value)} + onKeyDown={(e) => { + if (e.key === 'Enter') { e.preventDefault(); void submitPrompt(); } }} - placeholder="Ask Oracle anything…" - className="border-0 bg-transparent text-[15px] text-zinc-100 placeholder:text-zinc-500 focus-visible:ring-0 px-0 h-auto py-0" + placeholder="Ask Oracle anything — build your canvas with a prompt…" + className="border-0 bg-transparent text-[15px] text-zinc-100 placeholder:text-zinc-600 focus-visible:ring-0 px-0 h-auto py-0" />
{/* Toolbar */}
+ {/* Left: view dropdown + rail toggle */} +
+ {/* View mode dropdown */} +
+ - {/* View dropdown trigger */} -
+ + {viewDropOpen && ( + + {PROMPT_MODES.map((mode) => { + const isActive = mode.view === selectedMode.view; + return ( + + ); + })} + + )} + + + {viewDropOpen && ( +
setViewDropOpen(false)} /> + )} +
+ + {/* Rail toggle */} - - {/* Dropdown — rendered inline below the button, no fixed/portal needed */} - - {viewDropOpen && ( - - {PROMPT_MODES.map((mode) => { - const isActive = mode.view === selectedMode; - return ( - - ); - })} - - )} - - - {/* Click-away backdrop */} - {viewDropOpen && ( -
setViewDropOpen(false)} /> - )}
{/* Right: mic + send */} -
+
- {listening ? 'Listening…' : 'Voice'} + {listening ? 'Listening…' : 'Voice'} void submitPrompt()} - disabled={submitting} - className="h-8 w-8 rounded-full flex items-center justify-center flex-shrink-0" + disabled={!!inFlight || !prompt.trim()} + className="h-8 w-8 rounded-full flex items-center justify-center flex-shrink-0 disabled:opacity-40" style={{ background: 'hsl(217 91% 60%)', boxShadow: '0 0 18px hsl(217 91% 60% / 0.5)' }} - whileHover={{ scale: 1.1 }} + whileHover={{ scale: 1.08 }} whileTap={{ scale: 0.91 }} > @@ -796,6 +448,30 @@ export default function OraclePage() {
+ + {/* ── Overlays ────────────────────────────────────────────────────────── */} + setShareOpen(false)} + onShare={handleShare} + /> + + setRollbackOpen(false)} + onRollback={handleRollback} + /> + + setMergeReviewOpen(false)} + onReview={handleReviewMR} + />
); } diff --git a/app/src/components/modules/Catalyst.tsx b/app/src/components/modules/Catalyst.tsx index ff9e7a87..2eb0fe58 100644 --- a/app/src/components/modules/Catalyst.tsx +++ b/app/src/components/modules/Catalyst.tsx @@ -3,7 +3,7 @@ import { motion, AnimatePresence } from 'framer-motion'; import { Megaphone, Clapperboard, BarChart3, Globe, Settings2, Zap, TrendingUp, Eye, MousePointerClick, DollarSign, - Upload, Play, Image, Film, RefreshCw, ArrowRight, Plus, + Upload, Play, Image, Film, RefreshCw, ArrowRight, Plus, X, AlertTriangle, ArrowRightLeft, PlusCircle, SlidersHorizontal, Activity, Check, Link2, type LucideIcon, @@ -13,7 +13,10 @@ import { XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer, } from 'recharts'; import { useMarketingStore } from '@/store/useMarketingStore'; +import { useCurrency } from '@/store/useCurrencyStore'; import type { Campaign, MarketingAsset, LiveOptimizationEvent, LiveEventType } from '@/types'; +import { GroundTruthPicker } from './GroundTruthPicker'; +import type { GroundTruthSelection } from './GroundTruthPicker'; // ── Design tokens ───────────────────────────────────────────────────────────── const GLASS = { @@ -243,22 +246,151 @@ function AssetCard({ asset }: { asset: MarketingAsset }) { ); } +// ── Reference Slot ─────────────────────────────────────────────────────────── + +interface RefSelection { name: string; preview: string; } + +function ReferenceSlot({ value, onSelect, onClear, onRemove }: { + value: RefSelection | null; + onSelect: (sel: RefSelection) => void; + onClear: () => void; + onRemove?: () => void; +}) { + const inputRef = useRef(null); + + function handleFile(e: React.ChangeEvent) { + const file = e.target.files?.[0]; + if (!file) return; + onSelect({ name: file.name, preview: URL.createObjectURL(file) }); + e.target.value = ''; + } + + // Show X when: slot has content (clear it), or slot is removable (remove it) + const showX = !!value || !!onRemove; + const handleX = (e: React.MouseEvent) => { + e.stopPropagation(); + if (onRemove) onRemove(); + else onClear(); + }; + + return ( +
+ inputRef.current?.click()} + whileHover={{ scale: 1.04 }} + whileTap={{ scale: 0.96 }} + title="Add reference image" + > + {value?.preview ? ( + {value.name} + ) : value?.name ? ( + <> + + {value.name} + + ) : ( + + + )} + + + + {showX && ( + + + + )} + + + +
+ ); +} + function WorkflowInput() { const [mode, setMode] = useState<'image' | 'video'>('image'); const [prompt, setPrompt] = useState(''); const [keywords, setKeywords] = useState(''); const [textCopy, setTextCopy] = useState(''); + const [groundTruth, setGroundTruth] = useState(null); + const [pickerOpen, setPickerOpen] = useState(false); + const [refs, setRefs] = useState<(RefSelection | null)[]>([null, null]); + const anchorRef = useRef(null); return ( - +
{/* Top Section: Ground Truth & References */}
+ {/* Ground Truth slot */}
-
- +
+ setPickerOpen(v => !v)} + className="w-[60px] h-[60px] rounded-2xl flex flex-col items-center justify-center gap-1 overflow-hidden transition-colors" + style={{ + background: groundTruth?.preview ? 'transparent' : pickerOpen ? 'rgba(59,130,246,0.12)' : 'rgba(255,255,255,0.03)', + border: pickerOpen ? '1px solid rgba(59,130,246,0.4)' : '1px solid rgba(255,255,255,0.08)', + }} + whileHover={{ scale: 1.04 }} + whileTap={{ scale: 0.96 }} + title="Select Ground Truth image" + > + {groundTruth?.preview ? ( + ground truth + ) : groundTruth?.name ? ( + <> + + {groundTruth.name} + + ) : ( + {pickerOpen ? '✕' : '+'} + )} + + + {/* Clear selection button */} + + {groundTruth && ( + { e.stopPropagation(); setGroundTruth(null); setPickerOpen(false); }} + initial={{ opacity: 0, scale: 0.6 }} + animate={{ opacity: 1, scale: 1 }} + exit={{ opacity: 0, scale: 0.6 }} + transition={{ duration: 0.12 }} + whileHover={{ scale: 1.2, backgroundColor: 'rgba(239,68,68,0.8)' }} + title="Remove selection" + > + + + )} + + + + {pickerOpen && ( + { setGroundTruth(sel); setPickerOpen(false); }} + onClose={() => setPickerOpen(false)} + /> + )} +
Ground Truth
@@ -269,11 +401,25 @@ function WorkflowInput() {
- +
References
@@ -389,6 +535,7 @@ function TheStudio() { function CampaignCommand() { const { campaigns, adInsights } = useMarketingStore(); + const { formatAmount, rate } = useCurrency(); const totalSpend = campaigns.reduce((s, c) => s + c.lifetimeSpend / 100, 0); const totalImpr = campaigns.reduce((s, c) => s + c.impressions, 0); @@ -398,7 +545,7 @@ function CampaignCommand() { // Build spend-over-time from insights (last 14 days) const spendData = adInsights.slice(0, 14).map((d) => ({ date: d.date.slice(5), // MM-DD - spend: d.spend, + spend: Math.round(d.spend * rate), impressions: Math.round(d.impressions / 1000), })).reverse(); @@ -407,9 +554,9 @@ function CampaignCommand() { {/* KPI row */}
c.status === 'ACTIVE').length)} sub={`${campaigns.length} total campaigns`} glowColor="rgba(59,130,246,0.2)" delay={0} /> - + - +
{/* Spend chart + campaign list */} @@ -463,7 +610,7 @@ function CampaignCommand() {

{c.name}

- AED {(c.lifetimeSpend / 100).toLocaleString()} · {c.impressions.toLocaleString()} impr. + {formatAmount(c.lifetimeSpend / 100)} · {c.impressions.toLocaleString()} impr.

@@ -487,24 +634,25 @@ function CampaignCommand() { function IntelligenceROI() { const { campaigns, adInsights } = useMarketingStore(); + const { formatAmount, symbol, rate } = useCurrency(); const cpaData = adInsights.slice(0, 7).map((d) => ({ date: d.date.slice(5), - cpa: d.cpa, + cpa: Math.round(d.cpa * rate), roi: d.roi, })).reverse(); const adSetPerf = [ - { name: '3BHK Dubai', ctr: 2.1, cpa: 210, spend: 3400 }, - { name: 'PH Retarget', ctr: 3.2, cpa: 2100, spend: 5800 }, - { name: '1BHK Lookalike', ctr: 1.8, cpa: 270, spend: 980 }, + { name: '3BHK Dubai', ctr: 2.1, cpa: Math.round(210 * rate), spend: Math.round(3400 * rate) }, + { name: 'PH Retarget', ctr: 3.2, cpa: Math.round(2100 * rate), spend: Math.round(5800 * rate) }, + { name: '1BHK Lookalike', ctr: 1.8, cpa: Math.round(270 * rate), spend: Math.round(980 * rate) }, ]; return (
{/* CPA / ROI KPIs */}
- s+c.cpa,0)/campaigns.length).toFixed(0)}`} sub="Cost per acquisition" glowColor="rgba(34,211,238,0.2)" delay={0} /> + s+c.cpa,0)/campaigns.length)} sub="Cost per acquisition" glowColor="rgba(34,211,238,0.2)" delay={0} /> s+c.roi,0)/campaigns.length).toFixed(1)}%`} sub="Blended across all ad sets" glowColor="rgba(74,222,128,0.2)" delay={0.06} /> s+c.ctr,0)/campaigns.length).toFixed(2)}%`} sub="Click-through rate" glowColor="rgba(167,139,250,0.2)" delay={0.12} /> @@ -529,7 +677,7 @@ function IntelligenceROI() { - +
@@ -547,7 +695,7 @@ function IntelligenceROI() { - +
@@ -572,6 +720,7 @@ const EVENT_ICON: Record )} {event.value && ( - {event.value} + {formatText(event.value)} )}
-

{event.message}

+

{formatText(event.message)}

{event.timestamp.toLocaleTimeString([], { hour: '2-digit', minute: '2-digit', second: '2-digit' })}

diff --git a/app/src/components/modules/GroundTruthPicker.tsx b/app/src/components/modules/GroundTruthPicker.tsx new file mode 100644 index 00000000..2c3d2a09 --- /dev/null +++ b/app/src/components/modules/GroundTruthPicker.tsx @@ -0,0 +1,219 @@ +import { useState, useEffect, useLayoutEffect, useRef } from 'react'; +import { createPortal } from 'react-dom'; +import { motion, AnimatePresence } from 'framer-motion'; +import { Plus, Film, Check, X } from 'lucide-react'; + +// ─── Recent designs mock data ───────────────────────────────────────────────── + +const RECENT_DESIGNS = [ + { id: 'rd1', name: 'Penthouse Sea View', type: 'video' as const, gradient: 'linear-gradient(135deg,#1a2a4a,#0f1928)', accent: '#60a5fa', date: '2h ago' }, + { id: 'rd2', name: 'Arabic 3BHK Poster', type: 'image' as const, gradient: 'linear-gradient(135deg,#2a1a3a,#180f28)', accent: '#a78bfa', date: '5h ago' }, + { id: 'rd3', name: 'Amenity Deck Reel', type: 'video' as const, gradient: 'linear-gradient(135deg,#1a3a2a,#0f2818)', accent: '#4ade80', date: '8h ago' }, + { id: 'rd4', name: 'Penthouse En Poster', type: 'image' as const, gradient: 'linear-gradient(135deg,#3a2a1a,#281808)', accent: '#fbbf24', date: '1d ago' }, + { id: 'rd5', name: 'Dubai Marina Aerial', type: 'video' as const, gradient: 'linear-gradient(135deg,#1a3a3a,#0f2828)', accent: '#22d3ee', date: '2d ago' }, + { id: 'rd6', name: 'Investment Lifestyle', type: 'image' as const, gradient: 'linear-gradient(135deg,#3a1a1a,#280f0f)', accent: '#f87171', date: '3d ago' }, +]; + +// ─── Types ──────────────────────────────────────────────────────────────────── + +export interface GroundTruthSelection { + name: string; + preview: string; +} + +interface GroundTruthPickerProps { + anchorRef: React.RefObject; + onSelect: (sel: GroundTruthSelection) => void; + onClose: () => void; +} + +// ─── Portal popup component ─────────────────────────────────────────────────── + +function PickerPopup({ anchorRef, onSelect, onClose }: GroundTruthPickerProps) { + const popupRef = useRef(null); + const galleryRef = useRef(null); + const cameraRef = useRef(null); + + + // ── Synchronously measure anchor and position popup ────────────────────── + const [style, setStyle] = useState({ visibility: 'hidden' }); + + useLayoutEffect(() => { + if (!anchorRef.current) return; + const r = anchorRef.current.getBoundingClientRect(); + const W = 440; + const vw = window.innerWidth; + + // Prefer aligning left edge with button; clamp so popup stays on screen + let left = r.left; + if (left + W > vw - 12) left = Math.max(12, vw - W - 12); + + setStyle({ + position: 'fixed', + top: r.bottom + 8, + left, + width: W, + visibility: 'visible', + }); + }, [anchorRef]); + + // ── Dismiss on outside click ────────────────────────────────────────────── + useEffect(() => { + function handleDown(e: MouseEvent) { + const target = e.target as Node; + const inPopup = popupRef.current?.contains(target); + const inAnchor = anchorRef.current?.contains(target); + if (!inPopup && !inAnchor) onClose(); + } + document.addEventListener('mousedown', handleDown); + return () => document.removeEventListener('mousedown', handleDown); + }, [anchorRef, onClose]); + + // ── File handler ────────────────────────────────────────────────────────── + function handleFile(e: React.ChangeEvent) { + const file = e.target.files?.[0]; + if (!file) return; + onSelect({ name: file.name, preview: URL.createObjectURL(file) }); + e.target.value = ''; + } + + return ( + + {/* Header */} +
+

+ Recent Designs +

+ + + +
+ + {/* 3×2 recent designs grid */} +
+ {RECENT_DESIGNS.map((d, i) => ( + onSelect({ name: d.name, preview: '' })} + initial={{ opacity: 0, y: 8 }} + animate={{ opacity: 1, y: 0 }} + transition={{ duration: 0.15, delay: i * 0.04 }} + whileHover={{ scale: 1.03 }} + whileTap={{ scale: 0.97 }} + > + + {d.type === 'video' ? '▶ Video' : '■ Image'} + + + {/* Glow */} +
+ +
+

{d.name}

+

{d.date}

+
+ + {/* Hover overlay */} +
+ +
+ + ))} +
+ + {/* Bottom: gallery + camera */} +
+ + {/* Gallery */} +
+ galleryRef.current?.click()} + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.97 }} + > + + Add from Gallery + + + +
+ + {/* Camera */} +
+ cameraRef.current?.click()} + whileHover={{ scale: 1.02 }} + whileTap={{ scale: 0.97 }} + > + + Take Photo + + + +
+
+ + ); +} + +// ─── Public export — renders via portal ────────────────────────────────────── + +export function GroundTruthPicker(props: GroundTruthPickerProps) { + return createPortal( + + + , + document.body, + ); +} diff --git a/app/src/components/modules/Inventory.tsx b/app/src/components/modules/Inventory.tsx index 102041e9..18e85119 100644 --- a/app/src/components/modules/Inventory.tsx +++ b/app/src/components/modules/Inventory.tsx @@ -20,7 +20,7 @@ import { Canvas } from '@react-three/fiber'; import { Bounds, Html, OrbitControls, useGLTF } from '@react-three/drei'; import * as THREE from 'three'; import { useStore } from '@/store/useStore'; -import { formatCurrency } from '@/lib/utils'; +import { useCurrency } from '@/store/useCurrencyStore'; import type { Unit } from '@/types'; // Penthouse preview images — one per unit (u1–u8) for card thumbnails @@ -227,6 +227,7 @@ function PropertyDetailModal({ }) { const details = UNIT_DETAILS[unit.id] ?? DEFAULT_DETAILS; const preview = UNIT_PREVIEWS[unit.id] ?? UNIT_PREVIEWS['u1']; + const { formatAmount } = useCurrency(); const statusColors: Record = { available: 'text-emerald-300 border-emerald-400/30 bg-emerald-500/10', @@ -282,8 +283,8 @@ function PropertyDetailModal({ {/* Overlay price */}

Starting from

-

{formatCurrency(unit.price)}

-

{formatCurrency(pricePerSqm)} / m²

+

{formatAmount(unit.price)}

+

{formatAmount(pricePerSqm)} / m²

@@ -371,8 +372,8 @@ function PropertyDetailModal({ {/* Pricing card */}

Pricing

-

{formatCurrency(unit.price)}

-

{formatCurrency(pricePerSqm)} per m²

+

{formatAmount(unit.price)}

+

{formatAmount(pricePerSqm)} per m²

@@ -441,6 +442,7 @@ function UnitCard({ }) { const preview = UNIT_PREVIEWS[unit.id] ?? UNIT_PREVIEWS['u1']; const [hovered, setHovered] = useState(false); + const { formatAmount } = useCurrency(); // Status accent color for glow const statusGlow = @@ -524,7 +526,7 @@ function UnitCard({ {/* Price */}

Starting from

-

{formatCurrency(unit.price)}

+

{formatAmount(unit.price)}

{/* Divider */} @@ -731,6 +733,7 @@ function StudioWindow({ } function RightMapPane({ units }: { units: Unit[] }) { + const { formatAmount } = useCurrency(); return (