commit 96b6bc1e78c3b217c6d0ef333a4ff66ccc3e3126 Author: David Kong Date: Fri Jun 26 21:11:17 2026 +1000 docs: harmonize AGENTS.md, DOMAIN.md, and README.md — fix inconsistencies across all three documents - Expand BDP section in AGENTS.md from 4 entities to 18+ (Invoices, Inventory, Employees, Business Units, Locations, Suppliers, Manufacturers, Returns/RMA, Templates, Tasks & Task Lists, Service Jobs, Status Definitions, Sequences, Financial Accounts) - Add Rust edition 2024 and missing crates to AGENTS.md Technical Implementation (serde, config, dotenvy, thiserror, tracing) - Add Coding Conventions table for BDP section in AGENTS.md - Update Database Configuration with specific variables (DATABASE_URL, server__host, server__port) - Add Domain Concepts section referencing DOMAIN.md in AGENTS.md (_underscore fields, reference resolution pattern, AUD default currency, BU allocation) - Add Domain Concepts section to README.md filling gaps - Peer harmonization approach: no single source of truth; all three docs are peers with cross-references See HARMONIZATION_DIFF.md for before/after diff summary. diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..9377c1b --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,322 @@ +# Agents + +This repository is a Business Data Platform (BDP) that provides a GraphQL API. The API allows AI agents to manage business entities including orders, customers, sales quotes, and products through GraphQL operations. + +For comprehensive domain terminology, entity relationships, enums, and business concepts, see [`DOMAIN.md`](./DOMAIN.md). + +## Available Operations + +AI agents can perform the following operations via GraphQL: + +### Orders +- Create new orders (supports MASTER/SUBORDER aggregation) +- Update existing orders +- Delete orders +- Retrieve order information + +### Customers +- Create new customer records +- Update customer information +- Delete customer records +- Retrieve customer details + +### Sales Quotes +- Create sales quotes +- Update quote information +- Delete quotes +- Retrieve quote data + +### Products +- Create product listings +- Update product information +- Delete products +- Retrieve product details + +### Invoices +- Create billing documents (sales invoices, RMA returns, employee invoices) +- Update invoice information +- Delete invoices +- Retrieve invoice data with payment tracking and tax breakdowns + +### Inventory +- Manage individual stock units tracked by serial number or batch +- Support scan-in/out operations and manufacturing tracking +- Retrieve inventory records linked to products and supplier invoices + +### Employees +- Create staff records with payroll, schedule, entitlements, PAYG tax settings +- Update employee information including authentication links +- Delete employee records +- Retrieve employee details + +### Business Units +- Create top-level organizational entities grouping employees, locations, customers, and financial accounts +- Define currency, locale (en-AU default), and tax settings (GST or No GST) +- Update business unit configuration +- Delete business units +- Retrieve business unit records + +### Locations +- Create physical sites associated with business units (warehouses, offices) with operating hours and addresses +- Update location information +- Delete locations +- Retrieve location data + +### Suppliers +- Create external vendor/service provider records with credit terms and registration +- Update supplier category profiles and credit details +- Delete suppliers +- Retrieve supplier information + +### Manufacturers +- Create entities that produce goods; referenced by products for identification +- Update manufacturer information +- Delete manufacturers +- Retrieve manufacturer records + +### Returns (RMA) +- Create return merchandise authorization tracking: fault reporting, replacements, resolution actions +- Manage customer and supplier returns with completion date tracking +- Delete RMA records +- Retrieve RMA data including inventory references + +### Templates +- Create reusable blueprints for orders, quotes, emails, leads, tickets, and articles +- Update template content (can be scoped: global or personal/owned by a specific Customer) +- Delete templates +- Retrieve template records + +### Tasks & Task Lists +- Create work items with scheduling, assignment, progress tracking, status history +- Manage work done entries and task list organization +- Delete tasks +- Retrieve task records + +### Service Jobs +- Create paid or warranty service performed for customers; includes work tracking and scheduling +- Update service job details +- Delete service jobs +- Retrieve service job data + +### Status Definitions +- Create central registry of reusable status states with visual styling and allowed actions +- Update status definitions (styling, permitted transitions) +- Delete status definitions +- Retrieve status records + +### Sequences +- Manage shared counters for auto-generating unique identifiers (order numbers, invoice numbers) +- Each sequence belongs to a model (e.g., `Order`, `Invoice`) and increments the corresponding field +- Default starting count is 200000 +- Update sequence configuration +- Delete sequences +- Retrieve sequence records + +### Financial Accounts +- Create bank accounts or payment methods used by the business for transactions +- Update account information +- Delete financial accounts +- Retrieve financial account data + +## Technical Implementation + +The API is built using: +- Rust programming language (**edition 2024**) +- **Juniper** GraphQL framework (`juniper`, `juniper_actix`) +- **Actix-web** HTTP server (`actix-web`, `actix-files`, `actix-cors`) +- Tokio async runtime +- SQLx for database operations (via `sqlx-postgres`) connecting to PostgreSQL +- Serde / serde_json for serialization +- Config crate for configuration (`.env` + environment variables with `__` separator) +- Dotenvy for `.env` file loading +- Thiserror for error types +- Tracing, tracing-subscriber, tracing-actix-web for logging and request tracing + +## Database Configuration + +Database connection parameters are configured via environment variables using the `__` separator convention: + +| Variable | Purpose | +|---|---| +| `DATABASE_URL` | Full PostgreSQL connection string (e.g. `postgres://user:pass@host:port/dbname`) | +| `server__host` | Server bind host address | +| `server__port` | Server bind port number | + +Create a `.env` file with your configuration values before running the server. The config crate loads defaults → `.env` → environment variables in that priority order. + +## Coding Conventions + +| Convention | Detail | +|---|---| +| **Indentation** | 4 spaces for Rust; 2 spaces for YAML/JSON/TOML | +| **File naming** | One struct/type per file. File name matches the type (e.g. `product_card.rs` for `ProductCard`) | +| **Module visibility** | `pub(crate)` for internal-only modules; `pub` for public-facing ones (`graphql/models`, `canonical`) | +| **Struct naming** | PascalCase (e.g. `ProductCard`, `MeilisearchRepository`, `AppConfig`) | +| **Enum naming** | PascalCase struct names, uppercase variants (e.g. `ProductTagStyle::STANDARD`) | +| **Field naming** | snake_case (e.g. `product_id`, `price_as_cents`, `invoice_date`) | +| **Derive macros** | GraphQL objects: `#[derive(Debug, Deserialize, Serialize, GraphQLObject)]`; Enums: `GraphQLObject` → `GraphQLEnum` | +| **Error handling** | `Box` in repository layer; context errors propagated via `FieldResult` | +| **Async traits** | Repository interfaces use `#[async_trait]` for async method definitions | +| **Configuration loading** | Use `config` crate builder: defaults → `.env` (via dotenvy) → environment variables (`__` separator). E.g. `server__host` maps to `server.host` | +| **Repository pattern** | Abstract interface (`trait`) in `infra/repositories/traits/`, concrete impl in `infra/repositories/`. Client wrapper (e.g. `MeilisearchClient`) is a separate struct that wraps `reqwest::Client` with auth headers | +| **Context object** | Single `AppContext` struct passed to all GraphQL resolvers; holds `AppConfig` + repository clones | + +## Domain Concepts + +When working with the BDP, agents should be aware of key domain concepts defined in [`DOMAIN.md`](./DOMAIN.md): + +- **_underscore fields** — System-managed metadata prefixed with underscore (`_id`, `_entered.by`, `_modified.ts`, `_total`, `_active`). Never set by agents directly. +- **Reference resolution pattern** — All entity references follow `reference._id` → query the referenced entity. See DOMAIN.md for full reference resolution guide. +- **Default currency** — AUD (Australian Dollar) across the platform. +- **Business Unit & Location allocation** — Every participating entity must be assigned to a Business Unit and often a Location (required on save). +- **Master/Suborder aggregation** — Orders can be grouped hierarchically; Master orders aggregate totals from linked Suborders. +- **Classification/Profile pattern** — Customers and Products use extendable schema-driven classification via `classification.name`, `classification.profile._id`, `classification.profile.fields`. + +## Authentication and Authorization + +This API does not specify authentication or authorization mechanisms in the requirements, but AI agents would typically require appropriate access tokens or credentials to perform operations. + +--- + +# Shoppy BFF (`~/Projects/shoppy/bff`) + +This repository is a **Backend For Frontend (BFF)** GraphQL service for the Shoppy mobile app. It aggregates data from external search services and exposes it via a single GraphQL endpoint, serving as an API gateway between the frontend apps (iOS/Android) and backend infrastructure. + +## Available Operations + +AI agents can perform the following operations via its `/graphql` endpoint: + +### Queries +- `productList(listType, argument, page_number?, facets?, sort_by?)` — Search or browse products with faceted filtering and sorting +- `app()` — Retrieve app configuration including store selection pages, product pages, and account menu +- `querySuggestions(query_term)` — Get search autocomplete suggestions +- `cartProducts(input)` — Resolve cart items by product IDs (returns product cards or not-found placeholders) +- `smartSwaps(input)` — Fetch "swap and save" alternative products for given cart items + +### Mutations +- `submitFeedback(feedback)` — Submit user feedback (stub implementation, always returns true) + +## Technical Implementation + +The service is built using: +- Rust programming language (**edition 2024**) +- **Juniper** GraphQL framework (`juniper`, `juniper_actix`) +- **Actix-web** HTTP server (`actix-web`, `actix-files`, `actix-cors`) + +- Tokio async runtime +- `config` crate for configuration (`.env` + environment variables with `__` separator) +- `dotenvy` for `.env` file loading +- `thiserror` for error types +- `tracing` / `tracing-subscriber` / `tracing-actix-web` for logging and request tracing +- `async_trait` for async trait implementations +- `serde` / `serde_json` for serialization + +## Folder Structure + +``` +src/ +├── main.rs # Entry point: server boot, schema registration, middleware chain +├── mod.rs # Root module declarations (pub(crate) vs pub) +├── app_config.rs # Configuration struct + loading logic (config crate builder pattern) +│ +├── graphql/ # GraphQL layer — public-facing schema & resolvers +│ ├── schema.rs # Schema type alias + factory function +│ ├── mod.rs +│ ├── models/ # GraphQL output types (PascalCase structs/enums, all #[derive(GraphQLObject)]) +│ │ └── mod.rs # One file per model struct/type +│ ├── queries/ +│ │ ├── query_root.rs # QueryRoot struct + #[juniper::graphql_object] impl +│ │ ├── helpers.rs # Shared resolver helpers (create_product_list, facet utilities) +│ │ └── resolvers/ # Per-domain resolver modules (app_content_resolvers, etc.) +│ └── mutations/ +│ └── mutation_root.rs # MutationRoot struct + #[juniper::graphql_object] impl +│ +├── infra/ # Infrastructure layer — external service integrations +│ ├── mod.rs +│ ├── models/ # Internal domain types (infrastructure response structs, helpers) +│ │ └── helpers/ # Helper modules (category_helpers, image_helpers, pricing_helpers, stores_helpers) +│ └── repositories/ +│ ├── meilisearch_client.rs # reqwest Client wrapper with auth headers +│ ├── meilisearch_repository.rs # Main repository implementation +│ ├── meilisearch_helpers.rs # Query-building helpers (apply_facets, create_filters) +│ ├── traits/ # Abstract repository interface +│ └── mod.rs +│ +├── canonical/ # Canonical / shared domain models (clean types without infra coupling) +│ └── models/ +│ ├── search.rs # SortOption, SearchFacet — pure domain structs +│ └── mod.rs +│ +└── middleware/ # HTTP middleware + ├── shoppy_api_key.rs # API-key validation (checks shoppy-api-key header against config) + └── mod.rs +``` + +## Coding Conventions + +| Convention | Detail | +|---|---| +| **Indentation** | 4 spaces for Rust; 2 spaces for YAML/JSON/TOML; tabs for Makefiles | +| **File naming** | One struct/type per file. File name matches the type (e.g. `product_card.rs` for `ProductCard`) | +| **Module visibility** | `pub(crate)` for internal-only modules; `pub` for public-facing ones (`graphql/models`, `canonical`) | +| **Struct naming** | PascalCase (e.g. `ProductCard`, `MeilisearchRepository`, `AppConfig`) | +| **Enum naming** | PascalCase struct names, uppercase variants (e.g. `ProductTagStyle::STANDARD`) | +| **Field naming** | snake_case (e.g. `product_id`, `price_as_cents`, `meilisearch_url`) | +| **Derive macros** | GraphQL objects: `#[derive(Debug, Deserialize, Serialize, GraphQLObject)]`; Enums: `GraphQLObject` → `GraphQLEnum` | +| **Error handling** | `Box` in repository layer; context errors propagated via `FieldResult` | +| **Async traits** | Repository interfaces use `#[async_trait]` for async method definitions | +| **Configuration loading** | Use `config` crate builder: defaults → `.env` (via dotenvy) → environment variables (`__` separator). E.g. `server__host` maps to `server.host` | +| **Logging (current)** | Unstructured `println!("[INFO] -- ...")` across ~40 call sites; `tracing-actix-web::TracingLogger` wired as middleware but silent due to missing subscriber init. Requirement: migrate to structured `tracing` with `EnvFilter`, JSON output in production, compact output locally, and activate request-level tracing | +| **Repository pattern** | Abstract interface (`trait`) in `infra/repositories/traits/`, concrete impl in `infra/repositories/`. Client wrapper (e.g. `MeilisearchClient`) is a separate struct that wraps `reqwest::Client` with auth headers | +| **Context object** | Single `AppContext` struct passed to all GraphQL resolvers; holds `AppConfig` + repository clones | +| **Docker build** | Multi-stage using `cargo-chef` for dependency caching: chef → planner (recipe) → builder (cook + build) → runtime (debian slim, binary only) | + +## Configuration + +Environment variables use the `__` separator convention: +- `server__host`, `server__port` — server bind settings + +- `proxy__http`, `proxy__https` — proxy settings (optional) +- `auth__sx_ios_api_key`, `auth__android_sx_api_key` — API keys for iOS/Android app authentication + +## Authentication + +The BFF validates a `shoppy-api-key` header against the configured `auth.sx_ios_api_key` or `auth.android_sx_api_key`. The `/graphql` endpoint and `/healthz` are exempt from this check. GraphQL itself does not require auth — only non-GraphQL routes do. + +## Logging Requirement + +### Current state + +All logging uses raw `println!("[INFO] -- ...")` with no timestamps, log levels, or field context (~40 call sites). This makes logs unparseable and hard to filter in production. The project already has the right crates (`tracing`, `tracing-subscriber`, `tracing-actix-web`) but they are unused. + +### Required approach: layered structured tracing + +| Layer | Purpose | +|---|---| +| **EnvFilter** | Runtime log level control via `RUST_LOG` env var (e.g. `shoppy_bff=debug`, `reqwest=warn`) | +| **Fmt layer — JSON in prod, Compact in dev** | Structured logs for production aggregation; readable compact output locally | +| **tracing-actix-web::TracingLogger** | Middleware auto-logs every HTTP request (method, path, status, latency) | + +### Implementation notes + +- Replace `println!("[INFO] -- ...")` → `tracing::info!(...)` with structured fields (e.g., `tracing::error!(status = ?response_status, "Error getting products by search")`) +- Initialize subscriber in `main.rs`: + ```rust + let fmt_layer = tracing_subscriber::fmt::layer().with_target(false); + let filter = EnvFilter::try_from_default_env() + .or_else(|_| EnvFilter::try_new("info")).unwrap(); + #[cfg(debug_assertions)] + let fmt_layer = fmt_layer.pretty(); + #[cfg(not(debug_assertions))] + let fmt_layer = fmt_layer.json(); + tracing_subscriber::registry() + .with(filter) + .with(fmt_layer) + .init(); + ``` +- The `TracingLogger` middleware would then actually emit request traces (currently silent) + +### Scope + +- **In scope**: replace all `println!` logging, init subscriber in `main.rs`, activate TracingLogger output +- **Out of scope**: OpenTelemetry integration, log crate bridge, multi-layer stdout/JSON split — these can be added later if log aggregation becomes a requirement \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..9e67eb8 --- /dev/null +++ b/README.md @@ -0,0 +1,124 @@ +# GraphQL Business Data Platform + +A performant GraphQL API that provides structured access, storage, and organisation of business data, optimized for agentic applications. + + +## Background + +A business data platform serving as the backbone for agentic and user facing applications. It stores and manages a comprehensive array of business data such as orders, documents, customers and products providing the foundation to build more complex applications and workflows. + +## Tech Stack + +| Component | Details | +|---|---| +| Language | Rust (edition 2024) | +| GraphQL | Juniper (`juniper`, `juniper_actix`) | +| HTTP Server | Actix-web | +| Async Runtime | Tokio | +| Database | PostgreSQL via SQLx (`sqlx-postgres`) | +| Serialization | Serde / serde_json | +| Config | Config crate (.env + env vars with `__` separator) | +| Env Loading | Dotenvy | +| Error Handling | Thiserror | +| Logging | Tracing, tracing-subscriber, tracing-actix-web | + +## Folder Structure + +*Pending — source code has not yet been scaffolded.* + +The server repo is in early-stage development. Once the codebase is scaffolded, a folder structure section will be added here reflecting the actual layout. Refer to `AGENTS.md` for architectural patterns (repository pattern, context object, middleware chain) that guide the expected structure. + +## Coding Conventions + +| Convention | Detail | +|---|---| +| **Indentation** | 4 spaces for Rust; 2 spaces for YAML/JSON/TOML | +| **File naming** | One struct/type per file; file name matches type (e.g. `product_card.rs`) | +| **Module visibility** | `pub(crate)` for internal-only modules; `pub` for public-facing ones (`graphql/models`, `canonical`) | +| **Struct naming** | PascalCase (e.g. `ProductCard`, `MeilisearchRepository`) | +| **Enum naming** | PascalCase names, uppercase variants (e.g. `ProductTagStyle::STANDARD`) | +| **Field naming** | snake_case (e.g. `product_id`, `price_as_cents`) | +| **Derive macros** | GraphQL objects: `#[derive(Debug, Deserialize, Serialize, GraphQLObject)]`; enums use `GraphQLEnum` | +| **Error handling** | `Box` in repository layer; context errors propagated via `FieldResult` | +| **Async traits** | Repository interfaces use `#[async_trait]` for async method definitions | +| **Configuration loading** | Config crate builder pattern: defaults → `.env` (via dotenvy) → env vars (`__` separator). E.g. `server__host` maps to `server.host` | + +## Build Setup + +### Prerequisites + +- Rust toolchain (edition 2024 compatible) +- PostgreSQL database server + +### Running the Server + +```bash +cargo run +``` + +### Environment Configuration + +Database connection parameters are configured via environment variables using the `__` separator convention: + +| Variable | Purpose | +|---|---| +| `DATABASE_URL` | Full PostgreSQL connection string (e.g. `postgres://user:pass@host:port/dbname`) | +| `server__host` | Server bind host address | +| `server__port` | Server bind port number | + +Create a `.env` file with your configuration values before running the server. The config crate loads defaults → `.env` → environment variables in that priority order. + +## Architecture Decisions + +### Repository Pattern + +Abstract repository interfaces are defined as traits, with concrete implementations for each external data source or database operation. This decouples GraphQL resolvers from infrastructure details: +- **Interface**: trait definitions in `infra/repositories/traits/` (once scaffolded) +- **Implementation**: concrete structs in `infra/repositories/` (once scaffolded) + +### Context Object + +A single shared context struct is passed to all GraphQL resolvers. It holds application configuration and repository clones, providing a uniform dependency injection point across the resolver layer. + +### Middleware Chain + +HTTP middleware sits between Actix-web request handling and GraphQL execution, handling cross-cutting concerns (authentication, logging, CORS) before requests reach the schema layer. + +## Entity Overview Summary + +The API manages the following entity types: + +| Entity | Purpose | +|---|---| +| **Orders** | Business transactions linking customers to products/services; supports MASTER/SUBORDER aggregation | +| **Customers** | Person or company records with contacts, addresses, credit terms, and web portal access | +| **Sales Quotes** | Proposed price lists presented to customers before order creation; includes optional items and system quotes | +| **Products** | Items/services sold by the business with pricing, stock info, supplier part numbers, and category hierarchy | +| **Invoices** | Billing documents (sales invoices, RMA returns, employee invoices) with payment tracking and tax breakdown | +| **Inventory** | Individual stock units tracked by serial number or batch; supports scan-in/out and manufacturing tracking | +| **Employees** | Staff records with payroll, schedule, entitlements, PAYG tax settings, and authentication links | +| **Business Units** | Top-level organizational entities grouping employees, locations, customers, and financial accounts | +| **Locations** | Physical sites associated with business units (warehouses, offices) with operating hours and addresses | +| **Suppliers** | External vendors/service providers with credit terms, registration, and category profiles | +| **Manufacturers** | External entities that produce goods; referenced by products for identification | +| **Returns (RMA)** | Return merchandise authorization tracking: fault reporting, replacements, resolution actions | +| **Templates** | Reusable blueprints for orders, quotes, emails, leads, tickets, and articles | +| **Tasks & Task Lists** | Work items with scheduling, assignment, progress tracking, status history, and work done entries | +| **Service Jobs** | Paid or warranty service performed for customers; includes work tracking and scheduling | +| **Status Definitions** | Central registry of reusable status states with visual styling and allowed actions | +| **Sequences** | Shared counters for auto-generating unique identifiers (order numbers, invoice numbers) | +| **Financial Accounts** | Bank accounts or payment methods used by the business for transactions | + +## Domain Concepts + +Key domain concepts that agents should understand when working with the BDP: + +- **_underscore fields** — System-managed metadata prefixed with underscore (`_id`, `_entered.by`, `_modified.ts`, `_total`, `_active`). Never set by agents directly — computed or managed by the platform internally. See [`DOMAIN.md`](./DOMAIN.md) for full list. +- **Reference resolution pattern** — All entity references follow `reference._id` → query the referenced entity using GraphQL. The referenced entity may have its own nested references (e.g., Order → Product → Category). See [`DOMAIN.md`](./DOMAIN.md) for reference resolution guide. +- **Default currency** — AUD (Australian Dollar) across the platform. Other currencies supported: USD, EUR, NZD, GBP, HKD, SGD, RMB, CAD, AED, INR, PHP. +- **Business Unit & Location allocation** — Every entity that participates in business operations must be assigned to a Business Unit and often a Location (required on save). Business Units define currency, locale (`en-AU` default), and tax settings (`GST` or `No GST`). +- **Master/Suborder aggregation** — Orders can be grouped hierarchically; Master orders aggregate totals (`_total`, `_exTotal`, `_payments`, `_owing`) from all linked Suborders. +- **Classification/Profile pattern** — Customers and Products use extendable schema-driven classification via `classification.name`, `classification.profile._id`, `classification.profile.fields` without hard-coding every possible attribute. + +For comprehensive domain terminology, entity relationships, enums, and business concepts, see [`DOMAIN.md`](./DOMAIN.md). +