AGOG System Constraints
Defines 12 non-negotiable architectural constraints for a PostgreSQL multi-tenant SaaS system, covering schema design, deployment, and documentation rules.
What this file does
Defines 12 non-negotiable architectural constraints for a PostgreSQL multi-tenant SaaS system, covering schema design, deployment, and documentation rules.
When to use it
- Starting a new multi-tenant SaaS project that needs hard rules for data isolation
- Enforcing UUIDv7 primary keys and tenant_id columns across all tables
- Requiring blue-green deployments with zero-downtime rollback capability
- Standardizing documentation navigation paths and terminology across a team
Assumes this stack
📍 Navigation Path: AGOG Home → Constraints
AGOG System Constraints
🚫 Critical: These are HARD RULES that must NEVER be violated
⚠️ Warning: Violating these constraints will cause architectural problems
📅 Last Updated: 2025-10-31
Purpose of This Document
This document lists non-negotiable architectural and technical constraints for the AGOG system. These aren't suggestions or best practices - they are requirements that must be followed in all code and documentation.
If you're unsure whether a constraint applies: It probably does. Ask before violating.
If you think a constraint should change: Document the rationale in DECISION_LOG.md and discuss with Todd.
PostgreSQL Requirements
✅ MUST USE: PostgreSQL 15+
Rule: AGOG requires PostgreSQL version 15 or later
Rationale:
- Need
uuid_generate_v7()(added in PostgreSQL 15) - Need enhanced MERGE statement capabilities
- Need logical replication improvements
- Need performance optimizations for multi-tenant workloads
Impact:
- Deployment environments must have PostgreSQL 15+
- Cannot use earlier PostgreSQL versions
- Cannot use other databases (MySQL, SQL Server, etc.)
Examples:
-- ✅ CORRECT
-- Assumes PostgreSQL 15+ features available
CREATE TABLE example (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
data JSONB
);
-- ❌ WRONG
-- Using generic SQL or older PostgreSQL syntax
CREATE TABLE example (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- UUIDv4, not v7
data TEXT -- Should use JSONB
);
✅ MUST USE: Required PostgreSQL Extensions
Rule: Install and use these PostgreSQL extensions
Required Extensions:
uuid-ossp- For uuid_generate_v7()pgcrypto- For cryptographic functions (if needed)pg_trgm- For text search optimization (when implemented)
Installation:
CREATE EXTENSION IF NOT EXISTS "uuid-ossp";
CREATE EXTENSION IF NOT EXISTS "pgcrypto";
CREATE EXTENSION IF NOT EXISTS "pg_trgm";
UUID Requirements
✅ MUST USE: UUIDv7 for Primary Keys
Rule: ALL primary keys MUST use uuid_generate_v7(), NOT gen_random_uuid()
Rationale:
- UUIDv7 is time-ordered (first 48 bits = timestamp)
- Better B-tree index performance than random UUIDs
- Easier debugging (can see record creation order)
- Still globally unique
- Industry best practice
Impact:
- All
CREATE TABLEstatements useuuid_generate_v7() - All SQL examples use UUIDv7
- Migration from UUIDv4 if changing existing tables
Examples:
-- ✅ CORRECT
CREATE TABLE customers (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
tenant_id UUID NOT NULL,
customer_number VARCHAR(50) NOT NULL
);
-- ❌ WRONG - Random UUID (UUIDv4)
CREATE TABLE customers (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(), -- NO!
tenant_id UUID NOT NULL,
customer_number VARCHAR(50) NOT NULL
);
-- ❌ WRONG - SERIAL (not globally unique)
CREATE TABLE customers (
id SERIAL PRIMARY KEY, -- NO!
tenant_id UUID NOT NULL,
customer_number VARCHAR(50) NOT NULL
);
Multi-Tenant Requirements
✅ MUST HAVE: Tenant Isolation in All Tables
Rule: ALL tables (except system tables) MUST include tenant_id UUID NOT NULL
Rationale:
- Multi-tenant SaaS architecture foundation
- Data isolation for security and compliance
- Required for Row Level Security (RLS)
- Prevents cross-tenant data leaks
Impact:
- Every table has
tenant_idcolumn - Every query filters by
tenant_id - Foreign keys include
tenant_idfor referential integrity
Examples:
-- ✅ CORRECT
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
sales_point_id UUID NOT NULL REFERENCES sales_points(id),
order_number VARCHAR(50) NOT NULL,
UNIQUE (tenant_id, sales_point_id, order_number)
);
-- ❌ WRONG - No tenant_id
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
order_number VARCHAR(50) NOT NULL -- Missing tenant isolation!
);
✅ MUST FILTER: Always Include tenant_id in Queries
Rule: ALL queries MUST filter by tenant_id (except system/tenant management queries)
Rationale:
- Prevents cross-tenant data access (security)
- Enables query optimization (partition pruning)
- Required for compliance (data isolation)
- Prevents accidental data leaks
Impact:
- WHERE clauses include
tenant_id = $1 - JOIN conditions include
tenant_idmatching - Indexes include
tenant_idas first column
Examples:
-- ✅ CORRECT
SELECT * FROM orders
WHERE tenant_id = $1
AND sales_point_id = $2
AND order_number = $3;
-- ✅ CORRECT - Index supports tenant filtering
CREATE INDEX idx_orders_lookup
ON orders(tenant_id, sales_point_id, order_number);
-- ❌ WRONG - No tenant_id filter (security issue!)
SELECT * FROM orders
WHERE order_number = $1; -- Can access any tenant's data!
-- ❌ WRONG - Index doesn't start with tenant_id
CREATE INDEX idx_orders_lookup
ON orders(order_number); -- Won't use partition pruning
✅ MUST HAVE: Sales Point Isolation for Transactional Data
Rule: Transactional tables (orders, inventory, etc.) MUST include sales_point_id
Rationale:
- Multi-location tenants need per-location isolation
- Sales points have separate inventory, pricing, workflows
- Enables location-specific reporting
- Required for distributed operations
Impact:
- Transactional tables include
sales_point_id UUID NOT NULL - Reference data (products, customers) may omit
sales_point_id - Queries for transactional data filter by both
tenant_idANDsales_point_id
Examples:
-- ✅ CORRECT - Transactional data (orders)
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
sales_point_id UUID NOT NULL REFERENCES sales_points(id),
order_number VARCHAR(50) NOT NULL,
UNIQUE (tenant_id, sales_point_id, order_number)
);
-- ✅ CORRECT - Reference data (products) - no sales_point_id
CREATE TABLE products (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
product_code VARCHAR(50) NOT NULL,
UNIQUE (tenant_id, product_code)
);
-- ❌ WRONG - Order without sales_point_id
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
order_number VARCHAR(50) NOT NULL -- Missing sales_point_id!
);
ID Strategy Requirements
✅ MUST USE: Surrogate UUID + Business Key Pattern
Rule: Tables MUST have both a surrogate UUID id AND a unique constraint on business identifier
Rationale:
- ORMs expect single-column primary key (UUID id)
- Humans need recognizable identifiers (order_number, customer_number)
- Surrogate key for system use, business key for user-facing operations
- Unique constraint prevents duplicates within tenant scope
Pattern:
- Surrogate Key:
id UUID PRIMARY KEY DEFAULT uuid_generate_v7() - Business Key:
UNIQUE (tenant_id, sales_point_id, {business_identifier})
Examples:
-- ✅ CORRECT - Both surrogate and business keys
CREATE TABLE customers (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
customer_number VARCHAR(50) NOT NULL,
customer_name VARCHAR(200) NOT NULL,
UNIQUE (tenant_id, customer_number) -- Business key constraint
);
-- ✅ CORRECT - Transactional data includes sales_point_id
CREATE TABLE orders (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
tenant_id UUID NOT NULL REFERENCES tenants(id),
sales_point_id UUID NOT NULL REFERENCES sales_points(id),
order_number VARCHAR(50) NOT NULL,
UNIQUE (tenant_id, sales_point_id, order_number) -- Business key
);
-- ❌ WRONG - Only surrogate key, no business key
CREATE TABLE customers (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
tenant_id UUID NOT NULL,
customer_number VARCHAR(50) NOT NULL -- Should be UNIQUE constraint!
);
-- ❌ WRONG - Composite primary key (ORMs struggle with this)
CREATE TABLE customers (
tenant_id UUID NOT NULL REFERENCES tenants(id),
customer_number VARCHAR(50) NOT NULL,
customer_name VARCHAR(200) NOT NULL,
PRIMARY KEY (tenant_id, customer_number) -- NO! Use surrogate id
);
Material Tracking Requirements
✅ MUST IMPLEMENT: End-to-End Lot Genealogy
Rule: Material tracking with lot genealogy is ARCHITECTURAL FOUNDATION, not optional feature
Rationale:
- Core competitive differentiator
- Required for print industry compliance
- Enables quality root-cause analysis
- Customer requirement (raw materials → finished goods traceability)
Impact:
- All material-related tables include lot tracking
- Parent-child relationships tracked (lot genealogy)
- Material movements logged
- Cannot skip lot tracking to "simplify" implementation
Examples:
-- ✅ CORRECT - Lot tracking in material tables
CREATE TABLE inventory_lots (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
tenant_id UUID NOT NULL,
sales_point_id UUID NOT NULL,
lot_number VARCHAR(100) NOT NULL,
material_id UUID NOT NULL,
parent_lot_id UUID, -- For genealogy
UNIQUE (tenant_id, sales_point_id, lot_number)
);
-- ✅ CORRECT - Track material consumption
CREATE TABLE material_consumption (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
tenant_id UUID NOT NULL,
work_order_id UUID NOT NULL,
lot_id UUID NOT NULL REFERENCES inventory_lots(id),
quantity_consumed DECIMAL(18,4) NOT NULL
);
-- ❌ WRONG - Generic inventory without lot tracking
CREATE TABLE inventory (
id UUID PRIMARY KEY DEFAULT uuid_generate_v7(),
tenant_id UUID NOT NULL,
material_id UUID NOT NULL,
quantity DECIMAL(18,4) NOT NULL -- No lot tracking!
);
Schema-Driven Development Requirements
✅ MUST USE: YAML Schemas as Source of Truth
Rule: Entity definitions MUST start as YAML schemas before code generation
Rationale:
- Single source of truth (DRY principle)
- Enables AI code generation
- Structured input → higher quality output
- Self-documenting
Impact:
- Define entities in
Implementation/print-industry-erp/data-models/schemas/*.yaml - Generate TypeScript types, SQL DDL, API endpoints from schemas
- Update schema first, then regenerate code
- Do not hand-write what can be generated
Examples:
# ✅ CORRECT - YAML schema first
# File: schemas/customer.yaml
entity: Customer
description: Customer master data
fields:
- name: id
type: uuid
primaryKey: true
default: uuid_generate_v7()
- name: tenant_id
type: uuid
nullable: false
references: tenants.id
- name: customer_number
type: varchar(50)
nullable: false
businessKey: true
Process:
- Define entity in YAML
- AI generates TypeScript types, SQL DDL, API routes
- Human reviews and refines generated code
- Commit both YAML and generated code
Documentation Requirements
✅ MUST HAVE: Navigation Path in All Documentation
Rule: ALL documentation files MUST include Navigation Path at top and bottom
Rationale:
- Prevents users getting lost in nested documentation
- Provides context (where am I?)
- Enables quick navigation
- International team-friendly (clear, literal terminology)
Pattern:
**📍 Navigation Path:** [AGOG Home](../README.md) → Parent → Current
# Document Title
[content]
---
[⬆ Back to top](#anchor) | [🏠 AGOG Home](../README.md) | [Parent](../README.md)
Impact:
- All new documentation includes Navigation Path
- Update existing documentation to add Navigation Path
- Remove old
[← Back]links (redundant with bottom navigation)
Standard: See .github/NAVIGATION_PATH_STANDARD.md
✅ MUST AVOID: Unvalidated Performance Claims
Rule: Do NOT include specific percentages or performance claims without source attribution
Rationale:
- Credibility - unvalidated claims hurt trust
- Accuracy - targets vs. proven results must be clear
- Professional - marketing speak doesn't belong in technical docs
Impact:
- Remove unvalidated percentages from technical docs
- Qualify claims in business docs ("target outcome", "based on industry research")
- Use qualitative language where specific numbers not essential
Examples:
<!-- ✅ CORRECT - Qualified claim -->
Material tracking enables faster root cause analysis compared to
systems without end-to-end traceability.
<!-- ✅ CORRECT - Qualified target -->
**Target Outcome:** Based on industry research, lot genealogy can
reduce time to identify defect root cause by 70-90%.
<!-- ❌ WRONG - Unvalidated claim -->
AGOG provides 90% faster root cause analysis.
Deployment Requirements
✅ MUST USE: Blue-Green Deployment Pattern
Rule: Production deployments MUST use blue-green pattern with database replication
Rationale:
- Zero-downtime deployments
- Zero-loss rollback capability
- 48-96 hour stabilization window for confidence
- Database safety critical for production
Impact:
- Deployment requires Blue and Green environments
- Bidirectional logical replication during transition
- Cannot do direct production deployments
- Rollback plan required for every deployment
Standard: See Standards/code/blue-green-deployment.md
Terminology Requirements
✅ MUST USE: Standard Terminology
Rule: Use these terms consistently (not alternatives)
Rationale: International team clarity, avoid confusion
Standard Terms:
- Navigation Path (NOT "breadcrumbs")
- Multi-tenant (NOT "Multitenant" or "Multi-Tenant")
- Sales point (NOT "location" or "store")
- Lot genealogy (NOT "lot tracking" for parent-child relationships)
- Material tracking (for inventory/lot system)
Impact:
- Documentation uses standard terms
- Code comments use standard terms
- API naming uses standard terms
Testing Requirements (Future)
✅ MUST TEST: Multi-Tenant Isolation
Rule: ALL features MUST be tested for tenant isolation
Rationale:
- Security critical - cross-tenant access = data breach
- Compliance requirement
- Cannot ship without isolation testing
Impact:
- Write tests that verify tenant_id filtering
- Test that User A (tenant 1) cannot access User B's data (tenant 2)
- Test Row Level Security policies
What's NOT Constrained
These are flexible and can be adjusted based on needs:
- Specific frameworks (within TypeScript/Node.js ecosystem)
- UI library choice (React chosen but could change)
- Specific PostgreSQL minor version (15.x)
- Deployment infrastructure (as long as supports blue-green)
- Development tools and IDEs
- Code formatting preferences (as long as consistent)
- Testing frameworks (as long as comprehensive)
Constraint Violations
If You Violate a Constraint
Accidentally:
- Fix immediately
- Review related code for same issue
- Note in commit message what was fixed
Intentionally (with good reason):
- Document rationale in
.github/DECISION_LOG.md - Discuss with Todd
- Update this CONSTRAINTS.md file if constraint changes
- Update affected standards documents
If You Think a Constraint is Wrong
- Don't ignore it - follow it until changed
- Document your reasoning - why should it change?
- Propose alternative - what should it be instead?
- Discuss with Todd - get agreement before changing
- Update documentation - CONSTRAINTS.md, DECISION_LOG.md, affected standards
Enforcement
Human Review:
- Todd reviews all code and documentation
- Violations caught in review process
Automated (Future):
- Linting rules for code
- Schema validation for YAML
- SQL linting for database scripts
- Documentation structure validation
Quick Reference Checklist
Before creating a table:
- Uses
uuid_generate_v7()for primary key? - Includes
tenant_id UUID NOT NULL? - Includes
sales_point_idif transactional? - Has UNIQUE constraint on business key?
- Pattern:
UNIQUE (tenant_id, [sales_point_id,] business_identifier)?
Before writing a query:
- Filters by
tenant_id? - Filters by
sales_point_idif transactional? - Index includes
tenant_idas first column?
Before creating documentation:
- Includes Navigation Path at top?
- Includes bottom navigation with horizontal rule?
- No unvalidated percentage claims?
- Uses standard terminology?
Before making architectural decision:
- Violates any constraints in this document?
- Documented in
.github/DECISION_LOG.md? - Discussed with Todd?
Related Documentation
- .github/DECISION_LOG.md - WHY constraints exist
- Standards/data/database-standards.md - Implementation details
- .github/AI_ONBOARDING.md - Quick patterns reference
- Standards/code/schema-driven-development.md - Schema workflow
⚠️ When in doubt, follow these constraints. They exist for good reasons documented in DECISION_LOG.md.
What's inside
14 constraint sections, 8 SQL code examples, 3 checklists, and 4 related document links
Change this for your project
- Replace
toddjhayden/AgogSaaswith your own repository name - Replace
Toddwith your own decision-maker name in violation procedures - Replace
Implementation/print-industry-erp/data-models/schemas/*.yamlwith your schema directory path - Replace
sales_point_idwith your own location or branch identifier if different
Where it goes
Keep it in your repository where the agent or team that needs it will read it.
Worth borrowing
- Hard rules with rationale, impact, and correct/wrong examples make constraints enforceable
- Quick-reference checklists before creating tables, queries, or documentation reduce violations
- Explicitly listing what is NOT constrained prevents unnecessary rigidity
Related Documents
Xilinx Constraints
Explains Xilinx XDC constraint ordering, compilation precedence, and key references for FPGA timing closure.
Technical Constraints & Requirements
Defines platform, performance, security, and deployment constraints for a Scottish mountain weather app with native iOS/Android and PWA fallback.
Token Constraints Implementation
Defines a token constraint system that counts tokens, truncates content, and streams responses to stay within an LLM's token limit.
Constraints
Explains how to model constraints as Ceres Solver cost functions in the fuse optimization framework, with a complete code example.