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.
This commit is contained in:
David Kong 2026-06-26 21:11:17 +10:00
commit 96b6bc1e78
2 changed files with 446 additions and 0 deletions

322
AGENTS.md Normal file
View file

@ -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<dyn Error>` 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<dyn Error>` 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

124
README.md Normal file
View file

@ -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<dyn Error>` 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).