# Gravity Standard Architecture — AI Reference

> Condensed, machine-oriented reference for the Gravity Standard Architecture: a reference architecture and foundation for business applications that AI builds from specifications. Human-readable site: https://gravitystandard.com. Companion method: https://gravityscheduling.com
> Version 1.1, 2026-10-30. Derived from the full documentation (Vision 1.3, Foundation 1.2, Architecture 1.1, Data Domain Strategy, Conventions, Definitions, Permitting). Where this summary and the full documents differ, the full documents win.

## 0. How to use this reference

- Keywords **must**, **should** and **may** carry their RFC 2119 meaning.
- Rule IDs are stable and citable: `DATA-nnn` (Data Domain rules), `CONV-nnn` (specification conventions), `PERMIT-nnn` (permit requirements), `DESIGN-nnn` (an application's recorded design decision or deviation), `QUESTION-nnn` (open questions). A spec's own business rules use the spec's code, e.g. `PER-001`, `LR-003`.
- Section references like "Architecture 8.4" refer to the full architecture's numbered sections.
- When a situation isn't covered, reason from the principles in section 4. When you must deviate, record a `DESIGN-nnn` decision with the reason and compensating controls; never deviate silently.
- An AI assistant **must not** weaken, drop or reinterpret a rule. It may strengthen an implementation at any time.

## 1. What it is

"Define the data. Define the process. The Standard supplies the rest."

- A **foundation** (shared Go library, tooling, Terraform modules, shared infrastructure) on which a company builds **many separately deployed business applications**. Applications share code by referencing it at build time, never by copying it, and never share a runtime.
- An application = **specifications + the code the specifications cannot express**. Specifications generate most of the code. Real business logic (e.g. a payroll calculation) is ordinary Go in an **automation Lambda** that a Step Function calls.
- Two things are formal and first-class: **Data Domains** (governed data) and **Business Processes** (governed workflow). Forms, navigation, task lists, notifications, permissions and dashboards follow from them.
- Position: the **front door and workflow layer** for business processes. Existing systems of record stay authoritative; results are handed to them by automation Lambdas.

**Problem it solves:** intake/review/approve/fulfil/report applications get rebuilt repeatedly, with duplicated identity/access/workflow/audit effort, uneven security and operations quality, slow change (rule tweaks become code releases), and processes that exist only in code and people's heads.

**Non-goals:** not a general low-code platform (no games, simulations, content sites); not a system of record for core data; no Turing-complete expression language in definitions; no browser-based designer at launch; no per-tenant forks of definitions or processes; not a shared runtime; no reporting/BI engine.

## 2. Glossary

| Term | Meaning |
|---|---|
| Foundation | Library + tooling + infrastructure modules + foundation infrastructure. Built and versioned by the foundation team. |
| Library | A Go module every application imports at a pinned version. Holds code that must be identical everywhere. Referenced, never copied; a copy in an application is a defect. |
| Deployment | One set of foundation infrastructure, foundation applications and business applications, for one company (or a business unit needing complete separation). |
| Tenant | One of the company's clients; the data isolation boundary (`app.tenant_id`, RLS). |
| Tenant Configuration | A versioned definition binding processes to a tenant and supplying parameter values (approvers, thresholds, SLAs, branding). Tenants parameterize company-level processes; they never own copies. |
| Application | A named bundle of specifications plus hand-written code serving one business purpose. Its own deployment: own subdomain, Lambdas, state machines, pipeline, permit and database role. |
| Foundation application | Applications every company needs, provided by the foundation: **home** (launcher, slash-command bar, command registry), **login**, **tasks** (skill queues, dispatch, inbox), **directory** (tenants, users, memberships, skills). |
| Data Domain | A governed boundary around related tables = a Postgres **schema**. One owner, typed and classified fields, version history. Separate from any application. |
| Record type | The unit of shape inside a domain (e.g. Loan, Person, Payment) = a real table. |
| Participation | An application's declared use of a domain: **writer** or **reader**, per record type. |
| Business Process | A versioned definition of work (steps, branching, human tasks, approvals, timers, automation calls), compiled to AWS Step Functions. |
| Task | A unit of human work placed in the **queue for a skill**; dispatch offers it to a worker holding that skill. Has a form, a skill, three timeouts (offer, work, overall) and a timeout policy (default abort). A process never names a person. |
| Automation | A Go Lambda a process step calls for logic or connectivity. Application code, not foundation code. |
| Specification | Markdown written by people (with AI help) in business terms. An application's specs together are its **operator's manual**. The source of truth. |
| Definition | The formal JSON form of a spec, produced by AI, validated against a published JSON Schema, reviewed and published. Never edited by hand. |

**Three manuals:** Operator's manual (the specs; written by business stakeholders with AI help), Service manual (architecture, runbooks, permitting; engineers), Installation manual (foundation; foundation team). A stakeholder writes only the operator's manual and never restates platform concerns such as security, tenant isolation or logging (CONV-017, CONV-039).

## 3. Technology choices

| Concern | Choice |
|---|---|
| Back-end language | Go, native arm64, `CGO_ENABLED=0` |
| Compute | AWS Lambda, `provided.al2023`, arm64 |
| Relational data | Postgres: Amazon RDS by default (standard Postgres keeps local dump/restore testing faithful); Aurora only when scale demands, recorded as a design decision |
| Key-value | DynamoDB, sanctioned where access patterns are known |
| Data access | sqlc-generated, type-safe, parameterized queries. No hand-written SQL strings. |
| Identifiers | `bigint GENERATED ALWAYS AS IDENTITY (START WITH 1000001)`; XID when the ID is needed before the data |
| Object data | Amazon S3 |
| Workflow | AWS Step Functions |
| Push | API Gateway WebSocket; database triggers via the `aws_lambda` extension |
| Front end | HTML + htmx + vanilla JS + CSS on S3/CloudFront; server-side rendering (Go `html/template`) for sensitive screens. No React/Angular, no heavy component suites. |
| Edge | AWS WAF on every public entry point; GuardDuty Malware Protection for uploads |
| Authentication | SSO preferred; passkeys (WebAuthn) minimum; no stored passwords |
| Authorization | Amazon Verified Permissions (Cedar) at the API; Postgres row level security underneath |
| Encryption | AWS KMS; envelope encryption with per-tenant encryption context for PII |
| Secrets | IAM roles and ephemeral credentials; Secrets Manager where unavoidable |
| Infrastructure | Terraform, applied by pipeline only |
| Observability | CloudWatch logs (structured JSON via `log/slog`), metrics, dashboards, alarms; X-Ray or OpenTelemetry tracing |

## 4. Guiding principles

1. **Simplicity over cleverness.** Boring, small, well-understood technology. Every dependency is a liability.
2. **Secure by default.** The default outcome of a bug is "no data", not "all data".
3. **The database is the foundation.** The schema is the contract; code is generated from it.
4. **Events over polling.**
5. **Managed over self-run.**
6. **Maintainable by a stranger** (a new developer or an AI).
7. **Push governance left.** Compliance lives in permits and policy, not approval emails.
8. **Say it, do it, prove it.** State what you'll do, do it, demonstrate you did.

## 5. Non-negotiables

1. **The UI shapes; the server enforces.** Hiding a menu item or disabling a button is usability, never security. Every handler calls Verified Permissions (`IsAuthorized`); RLS backs it up. A pipeline test fails any route lacking an authorization call.
2. **No secrets in environment variables**, source control, front-end code or local storage.
3. **A database trigger is a hint, not a guarantee.** Anything vital uses a transactional outbox with positive confirmation.
4. **Never swallow an error.** A failed handler returns failure so retries and dead-letter queues engage.
5. **Every dead-letter queue has an alarm and a runbook.**
6. **Every `UPDATE` and `DELETE` has a `WHERE` clause**, and write handlers assert rows affected.
7. **No Lambda-to-Lambda `Invoke`.** Services call each other over HTTPS. (Step Functions and `aws_lambda` triggers invoking Lambdas is by design.)
8. **A Lambda is not publishable** without readable logs, interior timings, defined metrics and an alarm.
9. **HTTPS and WSS only, with a WAF** in front of every public entry point.
10. **Uploads are hostile until scanned.**
11. **Build once and promote the same artifact, one environment at a time.**
12. **Every alarm links to a runbook.**

## 6. Foundation and deployment model

### 6.1 Library contents (identical in every application)
Handler pipeline (fixed order, correlation ID); authorization client (AVP incl. `BatchIsAuthorized` for task dispatch; entities built from the directory); tenant context (`app.user_id`, `app.tenant_id`, `app.scope_id`, transaction-local); domain access base (validation, same-transaction history, optimistic concurrency, row-count assertions); process runtime (task tokens, process log, timeouts, abort path, tasks-app client); sessions (code exchange, host-only cookie); rendering (`html/template` components, htmx helpers, shared stylesheet, command bar); transactional outbox; observability (logs, timings, metrics, tracing); service-call HTTPS client carrying correlation ID and caller identity.

### 6.2 Tooling (command-line tool)
`validate` (check specs; errors fail, warnings fail only with `--strict`), `compile` (write definition skeletons, deterministic, never overwrites without `--force`), `definition check` / `definition format` (schema, references, rules, canonical form), `generate` (Go data access and handlers, schema transitions, sqlc queries, state machines, Cedar policies, forms), `check` (rule coverage, derived controls, participation), `publish` (record published definition versions).

### 6.3 Infrastructure modules (Terraform, versioned with the library)
Standard Lambda module (retained KMS-encrypted logs, structured logging, alarms, tracing); application stack module (CloudFront distribution, function URL restricted to it, shared WAF association, DNS alias, certificate); process module (state machines and roles); observability module (two-tier dashboard, baseline alarms each linked to a runbook); database role module.

### 6.4 Foundation infrastructure (once per deployment, owned by the foundation team)
One Postgres database (a schema per Data Domain + a private operational schema per application); one shared WAF web ACL associated with every distribution; the DNS zone and certificates; KMS keys for per-tenant envelope encryption; the Verified Permissions policy store per environment (one namespace per application).

### 6.5 How an application is deployed
- `Browser → <app>.<company-domain> → CloudFront (+ shared WAF) → Lambda function URL (accepts only that distribution, via origin access control) → handlers (generated code + library) → domain schemas through the app's own database role / Step Functions → automation Lambdas`.
- Other environments insert the environment name: `support.dev.tidewater.com`. No application hard-codes an address; endpoints come from infrastructure as code.
- Each Lambda reaches Postgres with **ephemeral IAM credentials** mapped to the application's database role, which holds only the grants its declared participation allows.
- Each application has its own state machines, pipeline and permit. Applications deploy independently; a library release never forces a redeploy. An application adopts a new library version when it chooses.
- **Service-to-service:** a second, non-public function URL with IAM authorization. The caller signs with SigV4 using its execution role; the user's identity travels in a signed header.
- **Dependency rules:** business apps call foundation apps over HTTPS, never the reverse. Applications don't call each other except through a declared HTTPS interface. Foundation apps read the `directory` domain by participation like anyone else.
- A shared thing becomes an application only if it has its **own state, rules or screens**; otherwise it is library code or a static asset.

### 6.6 Tenants
- Applications and processes are defined once at company level and shared by all tenants. Tenant isolation is a data and permission concern.
- Tenants vary through **Tenant Configuration, never forked processes**. A process declares parameters and extension points; configuration supplies values and is validated against them.
- A process version compiles to Step Functions **once**. Tenant parameters travel as execution input, so onboarding or reconfiguring a tenant never compiles or deploys a state machine. A tenant needing a different shape gets a new company-level process or version.
- Kinds of end user (one identity and authorization model, differing only in membership data): client employee (one tenant), company employee (may work across assigned tenants, role per assignment), client's end user (later phase).

### 6.7 Authorization is generated
A spec names, per human stage, the **skill** it needs; the directory holds who has which skill for which tenants. The generator emits Cedar policies from a fixed set of templates (task actions view/accept/complete/decline/reassign, data actions, `forbid` policies for regional/regulatory limits). Builders do not hand-write Cedar in the prototype.

### 6.8 Integration patterns (Architecture 5.7)
| Pattern | Use when | Implementation |
|---|---|---|
| Synchronous API | Caller needs an immediate answer | HTTPS call to the service's published URL |
| Client push | A browser must learn data changed | API Gateway WebSocket change signals |
| Workflow orchestration | Multi-step, stateful, retries or human steps | AWS Step Functions |
| Publish/subscribe | One producer, many independent consumers | EventBridge (or SNS); vital events published by the transactional outbox, never by a trigger alone |
| Queue-based load levelling | Absorb bursts; decouple rates | SQS with a DLQ on the source queue |
| Change data capture to external systems | Downstream needs a reliable change stream | Outbox relayed to EventBridge; logical replication or AWS DMS for bulk/analytics only by recorded decision with a classification filter |
| Managed file transfer | Partner can exchange only files | AWS Transfer Family (SFTP) landing in the **quarantine bucket**; only a clean scan starts processing |

Rules for all: messages carry identifiers, not data; consumers are idempotent with an alarmed failure destination and runbook; feeds leaving the application exclude or mask `confidential`/`restricted` columns unless the receiver is equally controlled and the permit grants it; an S3 event on a landing bucket must never start processing directly; every pattern is an entry point in the threat model. A pattern not listed is a recorded design decision. Authentication between systems across organizational boundaries is not yet specified.

## 7. Specifications, definitions and conventions

### 7.1 Three layers
| Layer | Written by | Form | Fate |
|---|---|---|---|
| Specification | People, with AI help | Markdown | Source of truth |
| Definition | AI, from the spec | JSON with a published JSON Schema | Reviewed, validated, published; regenerated when the spec changes |
| Implementation | AI, from spec and definition | Code, schema, forms, state machines, tests | Rebuildable; accepted when the rule tests pass |

Central idea: **business rules are the stable part; implementation is allowed to change.** When spec and definition disagree, the spec wins and the definition is regenerated (CONV-026). Nothing generated is published without human review.

### 7.2 Layout (CONV-007, -008, -009, -034, -035, -036)
```
domains/
  <domain>/              e.g. borrowing  (Postgres schema name)
    domain.md            purpose, owner, scope
    <record-type>.md     one record type per file
    definitions/         generated JSON, reviewed in Git
apps/
  <application>/         e.g. lending  (permit application ID, subdomain)
    application.md       purpose, participation table, dependencies
    processes/<process>.md
    tenants/             tenant configuration (later)
    definitions/         generated JSON, reviewed in Git
    code/                hand-written Go (automation Lambdas)
```
- File and directory names are lower-case kebab-case; the name is the identifier. `loan-request.md` has title `# Loan Request`.
- Generated implementation output lives outside `domains/` and `apps/` (e.g. `build/`) and may be wiped.
- Domains never live under an application. A domain spec never lists its applications.

### 7.3 Names are derived, never chosen twice (CONV-006, -032, -033, -010)
| From | Derived |
|---|---|
| `domains/borrowing/` | Postgres schema `borrowing` |
| `person.md` | table `borrowing.people`, key `person_id`; slash command `/person` |
| `apps/lending/` | subdomain `lending.<company-domain>`; permit application ID `lending` (never changes; a rename is a new app with a new permit) |
| `apps/support-desk/` | operational schema and database role `app_support_desk` |
| `loan-request.md` | commands `/new loan-request`, `/loan-requests` |

Overrides (`Plural:`, `Command:`) are the exception (CONV-011). Tables are plural, keys singular + `_id`.

### 7.4 Writing rules (CONV-012 to CONV-017, CONV-041)
- Every spec declares a unique short code; each rule is `<CODE>-<nnn>`. Codes must not collide with reserved prefixes (`DATA`, `DESIGN`, `CONV`, `QUESTION`, `BACKLOG`, `PERMIT`). Numbers are never reused; retired rules stay marked `(retired)`.
- A rule is **one plain sentence stating an outcome**, with nothing about enforcement, and must be testable.
- Every enforcement and every test **cites its rule ID** (in a test name or comment, constraint name, code comment). A rule may be enforced in several places.
- Rule coverage is checked by searching for IDs; a rule with no citing test is a gap.
- Put a rule where it is true: always-true-of-the-data rules in the record type spec; procedure-specific rules in the process spec. Never both.
- Platform rules (tenant isolation, classification handling, history, authorization, failure handling) are never restated in a spec.
- A spec says **what**, not how: no tables, Lambdas or state-machine constructs (CONV-002).

### 7.5 Spec templates (structured parts a machine reads, CONV-038)
- **Record type:** header (`Code:`, `Domain:`, `Retention:`), description, `## Fields` table (Field | Type | Required | Class | Meaning), optional `## Statuses` (status table, `Initial status:` line, From/To/When moves table), `## Rules`, `## Notes`.
- **Business process:** header (`Code:`, `Application:`), description, `## To start (initiation)` table (Input | Type | Source: asked/derived/optional | Meaning), `## Parameters` (tenant-settable; or `None.` with why), `## Stages` (numbered; human with skill, automated, or sub-process), `## Tasks` (per human stage: decisions table), `## Flow` (From | When | To), `## Timeouts` (Stage | Offer | Work | Overall | If not done; defaults 3d offer, 5d work, 7d overall, abort), `## Who can start it`, `## Rules`, `## If aborted`, `## Troubleshooting`, `## Notes`.
- **Data Domain:** `domain.md` with scope and owner, record types list, notes.
- **Application:** `## Participates in` (Domain | Record types | Mode | Version range), `## Depends on`, `## Hand-written code`, `## Troubleshooting`, `## Notes`.
- Process and application each have a **Troubleshooting** table: what the operator sees, what it means, what to do, in operator terms (CONV-040).

### 7.6 How an AI assistant works (CONV-023 to CONV-026)
1. Read the spec and the foundation documents it relies on.
2. Propose any spec change (new rule, stage, field) **before** changing anything else, and show it for review.
3. Compile the spec into its definition and validate it against the published schema.
4. Realize the definition: schema, forms, state machine, queries, automation, tests.
5. Cite rule IDs in tests and enforcement; report rule coverage.
- Ask when a rule is ambiguous; record the answer in the spec (Notes or a sharper rule), not only in conversation.
- May improve implementation without a spec change whenever rule tests pass. Must not weaken, drop or reinterpret a rule.

### 7.7 Definitions
- Three kinds: `domain` (`domains/<d>/definitions/domain.json`), `process` (`apps/<a>/definitions/<process>.json`), `application` (`apps/<a>/definitions/application.json`). Tenant Configuration kind is not yet defined.
- Every definition starts with keys in this order: `kind`, `schemaVersion`, `name`, `version`, `source` (spec path and commit), `description`. Keys follow a fixed canonical order, two-space indentation, final newline.
- **Mechanical parts** (names, codes, types, fields, classes, statuses, moves, participation, stages, skills, parameters, decisions, ISO 8601 timeouts like `P2D`, flow targets, troubleshooting, rule IDs/text) are filled by the deterministic skeleton generator.
- **Judgment parts** are left **owed** (encoded as `null`/empty object, which the schema rejects) for the AI to fill: each rule's enforcement, a reader's access (table, or a view with columns and a recorded reason, DATA-035), required-when conditions, flow conditions, who can start (user kinds), derived input sources, abort effects. The checker's failures are the AI's to-do list; a half-compiled definition can't be published.

### 7.8 Rule enforcement kinds (no expression language)
A condition is a comparison on one field, or `all`/`any` of comparisons; operators `=`, `!=`, `<`, `<=`, `>`, `>=`, `in`, `present`, `absent`. Anything more complex is hand-written code.

| Kind | Meaning |
|---|---|
| `required-when` | Field must have a value when a condition holds |
| `unique` | Fields unique together within tenant or company |
| `compare` | Compare a field with a value, field or tenant parameter |
| `range` | number/decimal/money between min and max |
| `age` | date gives an age between limits, in years |
| `pattern` | text matches a regular expression |
| `length` | text length within a range |
| `key-exists` | a business key in another domain names something that exists at write time |
| `status-moves` | status changes only along declared moves |
| `initial-status` | new record starts at the initial status |
| `flow` | the process flow itself makes it true |
| `code` | hand-written code makes it true; names the handler |
| `retired` | rule retired; ID never reused |

## 8. Data

### 8.1 Domain rules
- **DATA-001/002:** A domain is a governed boundary with one owner holding one or more record types. Draw boundaries where ownership, access policy, retention or rate of change differ; otherwise prefer fewer domains. No nesting.
- **DATA-026:** A domain is a Postgres schema; each record type is a real table. Tables change only by pipeline-applied schema transitions generated from the definition. **No runtime DDL. No generic payload tables.**
- **DATA-029:** One owner (the only party that changes the definition or applies transitions), any number of participating applications.
- **DATA-004:** Every domain declares scope `tenant` (default) or `company`. Tenant is set from the verified principal, **never** the request body. Tenant users may read company-scoped data only where authorized and must not write it.
- **DATA-005/036:** Every field has a type from the fixed catalog and is required or optional.
- **DATA-007:** Identifiers are platform-assigned; business keys are ordinary fields that may be declared unique.
- **DATA-008:** References stay inside a domain and are foreign keys (default on delete: `restrict`). **No references across domains.**
- **DATA-028:** Domains relate by **business key** (`key of <domain>.<type>`), with no database integrity guarantee. Consumers tolerate a key that no longer resolves by surfacing it for review.
- **DATA-030:** A domain's data is reached **only through its generated Go module** (types, validation, history, queries, write path), enforced by database roles. Callers see the module contract, not tables.
- **DATA-031:** One **writer** per record type by default; others are readers. A second writer needs a declared reason and approval.
- **DATA-032:** Participation is declared, least privilege, and visible to the threat model and permit. The build refuses code using an undeclared record type. Role grants match the declaration exactly. Cross-schema joins only within an application participating in all of them.
- **DATA-033:** Each application has a private operational schema (process instances, process log, task tokens, outbox, idempotency keys). Not a domain; not shared.
- **DATA-034/014:** The owner's pipeline applies transitions; participants declare the module/schema version range they support, and deployment refuses outside it. A release works with the previous release of every participant (N-1). A definition referencing something that doesn't exist can't be published.
- **DATA-010:** Every write is validated against the definition (types, required, constraints, cross-field rules, references) for every caller: forms, process steps, automation. Failure returns field-level errors and writes nothing.
- **DATA-012:** Handlers call AVP for the action (read, create, update, transition, remove) and fields touched; RLS on `tenant_id` (and scope) is the fail-closed fallback. AVP-allowed but RLS-stopped = `defense_layer_breach`.
- **DATA-013:** Every record has current state + immutable history, appended **in the same transaction**: who, when, why (process instance and step, or user action), what changed. History is append-only. Removal is a soft delete; hard deletion only by the retention job. Writes use optimistic concurrency and assert row counts; stale writes are rejected, not merged.
- **DATA-015:** Definitions are semantically versioned. Minor: add optional field, add allowed value, widen constraint. Patch: descriptions/labels. Major (needs declared migration and re-approval of dependents): remove/rename field, narrow type or constraint, make optional required, lower a classification. Records carry the definition version they were written under; readers tolerate older versions. Contracts change by expand-then-contract.
- **DATA-016:** Definitions change only through draft → validate → review → publish; never edited in place in production.
- **DATA-017:** Every record type declares retention; a platform job applies it.
- **DATA-018:** Only declared fields are searchable; search respects authorization and never reveals unreadable records; `restricted` fields aren't searchable without a declared protected mechanism.
- **DATA-027:** Cross-domain access is minimized and declared; constant mutual need signals a misplaced boundary.

### 8.2 Type catalog (DATA-036)
`text`, `long text`, `number` (whole), `decimal` (exact), `money` (exact, single company currency), `date` (no time), `timestamp` (UTC instant), `boolean`, `choice(a, b, c)`, `status` (at most one per record type; values and moves in Statuses), `reference: <Type>` (same domain, FK), `key of <domain>.<type>` (business key into another domain; writer must participate as reader). Units go in the Meaning; ranges/patterns/lengths are written as rules.

### 8.3 Classification (DATA-006, DATA-011)
| Class | Meaning | Example |
|---|---|---|
| `public` | Safe for anyone who can see the record | Plan name |
| `internal` | Business data, not for outsiders | Remittance period |
| `confidential` | Sensitive business or personal data | Salary |
| `restricted` | Identifying or financial; highest harm | SSN, bank account |

Classification drives protection: `restricted` and `confidential` fields get tenant-bound envelope encryption; reads mask classified fields unless authorized; classified values **never** appear in logs, events, metrics, queue messages or error text (events carry identifiers); screens showing `restricted` data use server-side rendering.

### 8.4 Time, corrections and untrusted data
- **DATA-020:** A date in data describes the business; it is never an instruction. Nothing happens because a field holds a future date. No triggers keyed on date fields.
- **DATA-021:** Future work is scheduled by a process timer. On waking, the process re-reads and re-validates current data and acts on what is true then. Data changes reach waiting processes through events.
- **DATA-022:** Model expectation with explicit statuses (`planned`, `confirmed`, `cancelled`, `occurred`), not by comparing dates with today.
- **DATA-023:** One time axis (when the change was recorded). No platform effective dating; "as of" dates are ordinary fields.
- **DATA-024:** A correction is a Business Process; its history entry is the record of the change. No reconstruction of past beliefs beyond history.
- **DATA-025:** Unvalidated data (uploaded files) stays in S3 (quarantine → scan). A process parses and validates from storage; **domains are written only at the commit step**, through the module. No staging record types. A domain may hold a record of the submission itself (id, location, submitter, status, counts, outcome); error reports are artifacts in S3.

### 8.5 Database practice
- **Identifiers:** `bigint` identity starting at **1000001**. At the API edge only, identifiers are shown as **identifier codes**: Crockford base-32 lowercase (alphabet `0123456789abcdefghjkmnpqrstvwxyz`) + one **Luhn mod-32** check symbol; e.g. `1000001` → `ygj1d`. The API emits and accepts codes; a bad check symbol is a 400. Server-generated object keys use XID.
- **Connections:** ephemeral IAM auth per Lambda role; static DB credentials only for break-glass. Session context set with `set_config(..., true)` (transaction-local) inside a transaction. RDS Proxy when contention appears. Use `verify-full` TLS against the endpoint name the certificate matches.
- **Schema evolution:** semantically versioned transition files, first is `empty->v1.0.0.sql`; transitions form a DAG and the shortest path is planned; convenience edges let fresh environments jump to current; reverse transitions support rollback. The pipeline applies all schema changes.
- **Views** may limit what a participant reaches (DATA-035: the generator decides table vs view and records why).
- **sqlc** generates all data access. CI fails hand-written SQL and any `UPDATE`/`DELETE` without `WHERE`.
- **Index health (4.11):** the generator indexes every foreign key, every declared-unique field set (`_uq`) and every searchable field; any other index is added in a transition naming the query it serves. On tables with data use `CREATE/DROP INDEX CONCURRENTLY`, applied outside the transition's transaction, then verify validity (a failed build leaves an invalid index: drop it and fail the deploy). A scheduled check reports invalid (alarm), duplicate, unused (`idx_scan` flat over ~30 days, not enforcing a constraint), missing (large tables read by sequential scans, unindexed FKs) and bloated indexes; bloat is fixed by `REINDEX INDEX CONCURRENTLY` as scheduled maintenance with a runbook, not as a schema transition.

### 8.6 Database-originated events
Classify each event:
- **Best-effort** (e.g. WebSocket "something changed"): trigger → async Lambda is fine; clients do a full refresh on reconnect.
- **Vital** (start a Step Functions execution, update a downstream system, required notification): **transactional outbox with positive confirmation**:
  1. Insert an `outbox` row (`pending`, identifiers, attempts, created time) in the **same transaction** as the data change.
  2. A processor reads pending rows, does the work, marks `done` with a timestamp. The trigger or a schedule only wakes it.
  3. The processor is idempotent by outbox row ID (e.g. a Step Functions execution named by the outbox ID rejects duplicates).
  4. `done` only after the downstream system acknowledges.
  5. A scheduled **sweeper** re-drives rows pending past a threshold and alarms after an attempt limit.
- Triggers do minimal work and catch their own errors (an exception can roll back the write). Async invokes have an on-failure destination with an alarm. Payloads carry identifiers, not data. Dashboard shows outbox depth and oldest pending age.

## 9. Business processes

- Authored as markdown specs, compiled by the AI to a process definition, then **compiled to Step Functions** by the application's pipeline, one state machine per process version. Neither authors nor AI hand-write ASL.
- **About five stages per state machine**; compose larger processes from subflows (5 → 25 → 125 stages with nesting). Small machines are testable on their own.
- The compiler enforces: bounded stage counts, `Retry` with backoff, **no empty `Catch`**, timeouts and escalation on human tasks, idempotent steps.
- Every instance has visible state, history and an audit trail. A running instance stays on the definition version it started on unless explicitly migrated.

### 9.1 Human tasks
- A human stage creates a task over HTTPS in the tasks application (skill queue) and stores the **callback task token** in the owning application's operational schema. Dispatch offers it to a worker with the skill. The **owning application renders the form and completes the task**.
- The token is opaque: never parse it, never put it in a URL, never log it; send it in a request body. Knowing a token is not entitlement: the handler **must authorize the signed-in user for that specific task** before calling `SendTaskSuccess` (the Lambda calls it under its own IAM role).
- Timeouts are split: the tasks application enforces offer and work timeouts and re-dispatches; Step Functions enforces the overall timeout and abort policy.
- Every task is idempotent. Each task is actioned separately; task forms are built for fast, keyboard-first throughput.
- Users see their work, not the data model: the home screen is their dispatched tasks plus queues they may pull from.

### 9.2 Failure handling
Common mistakes: swallowing the error (logging it and returning success; instead wrap it with `%w` and return it, and the handler's entry point logs it once and returns failure to the runtime); DLQ on the function instead of the source queue for SQS; visibility timeout < 6× function timeout; failing the whole batch (use `ReportBatchItemFailures`); retrying what can't succeed (retry only throttling, timeouts, 5xx with exponential backoff, jitter, max attempts; send validation/4xx straight to failure); unwatched DLQs; non-idempotent handlers.

| Source | Retry | Failure destination |
|---|---|---|
| Async Lambda invoke | Lambda async retries; set max attempts and age | On-failure destination (SQS DLQ or EventBridge) |
| SQS | Redrive policy on the source queue, `maxReceiveCount` 3–5 | DLQ on the source queue |
| Step Functions task | `Retry` with backoff, jitter, `MaxAttempts` | `Catch` into a failure state that records and notifies |
| Streams | Until record expiry unless limited | Max record age, retry limit, bisect on error, on-failure destination |
| HTTP API | Caller retries; endpoint idempotent | Error response + ERROR log |

DLQ standards: alarm on messages visible and oldest age; on the dashboard beside its source; max retention (14 days SQS), KMS-encrypted; identifiers not data; an owner and a **redrive runbook**; reconcile where the originator was told the effect happened; a test forces a failure and asserts the DLQ and alarm.

### 9.3 Automation and file ingestion
- Automation Lambdas: typed input/output contract with standard success/failure/retry reporting; published through the standard module; Lambdas never call each other (the process orchestrates).
- File ingestion is a foundation capability: authorized upload → quarantine and malware scan → parse → validate → load, process-driven, in **bounded chunks** with progress, partial failure and idempotent retry; row-level errors reported to the submitter in actionable terms. Each file format has its own ingestion sub-process, so adding a format never changes the receiving process.

## 10. Front end

- Static HTML + htmx + small vanilla JS + CSS on S3/CloudFront. Layout uses CSS grid. Tables are plain HTML filled from JSON with server-side sorting/filtering/paging.
- **Task-oriented by default** (also: data-oriented where the problem is data; information-oriented for consumption; simulation-oriented only by recorded decision). Designing a task UI: name the actors; list each actor's jobs as verb phrases (from processes, human tasks and API operations, never from the table list); one task = one screen or short flow named for the verb; back each with a task endpoint (`POST /purchases/{code}/approve`) composing writes in one transaction, not CRUD per table; **ask only for decisions**, derive everything else; show the user's waiting tasks as home; use the schema for constraints only. The server validates again.
- **Server-side rendering** through a rendering Lambda (the only thing the browser reaches) when data is sensitive/restricted, field shape is itself sensitive, masking must happen first, or internal services must not be named. Server emits semantic HTML fragments only; always `html/template`; send a Content Security Policy.
- **Sessions:** `HttpOnly`, `Secure`, `SameSite` cookie; CSRF protection (SameSite + required custom header). Never tokens, secrets or PII in local storage. Local storage may cache slow, rarely-changing, non-sensitive data, versioned for invalidation.
- **WAF baseline:** AWS managed rule groups (Core, Known bad inputs, IP reputation), per-IP rate rule, body size limits, bot control where abuse is expected; new rules start in `Count`. HTTPS only: redirect HTTP, HSTS ≥ 1 year with `includeSubDomains`, TLS ≥ 1.2.
- **Uploads:** direct to S3 via a short-lived presigned POST (constrained `content-length-range`, `Content-Type`, key prefix; server-generated XID key). Quarantine bucket (no public access, KMS, lifecycle expiry, no app-role read) → GuardDuty scan → only clean-tagged objects promoted; anything else fails closed. Scan results are vital events with a sweeper. Validate magic bytes, sizes; refuse archives unless needed. Serve downloads from a separate origin with `Content-Disposition: attachment` and `nosniff`.
- **Push:** API Gateway WebSocket; authorize at `$connect`; scope subscriptions to readable data; send "something changed" signals, not payloads; prune on `410 Gone`; clients reconnect with backoff and full refresh.

## 11. Security

### 11.1 Defense in depth
Each layer assumes the one above may have failed, and reports when it's confirmed to have.

| Layer | Control | Guards against | Tripwire |
|---|---|---|---|
| Edge | WAF managed rules, rate limits | Exploits, floods, bots | Blocked-request metrics, spike alarm |
| Transport | HTTPS/WSS, ACM | Eavesdropping, tampering | Cert-expiry alarm, TLS errors |
| Identity | SSO or passkeys; no stored passwords | Anonymous access, credential theft | Auth-failure rate alarm |
| API | AVP `IsAuthorized` on every handler | Missing/incorrect app checks | `event=authz_denied` logged; spike alarm |
| Data | Postgres RLS | Handler forgetting a `WHERE` | `event=defense_layer_breach` at ERROR, paged |
| Access | sqlc queries, views | Injection, over-broad reads | CI fails hand-written SQL and unbounded writes; `event=unexpected_rowcount` |
| Storage | KMS, envelope encryption | Stolen snapshots, DB compromise | KMS token-bucket alarm; denied decrypts |
| Credentials | Ephemeral IAM auth | Leaked static secrets | IAM auth failures; CI secret scanner |

RLS-blocked reads return zero rows silently, so use `WITH CHECK` policies on writes and run a synthetic canary (a cross-tenant read that must return zero rows).

### 11.2 Authentication
No stored passwords. SSO (OIDC/SAML) preferred; passkeys (WebAuthn) minimum. Design for SSO from the start so identities can be linked later without duplicates. Email verification may be skippable only in local/test configuration.

### 11.3 Authorization
- Every API handler calls `IsAuthorized` with the request context. The UI may call `BatchIsAuthorized` **only to shape** what it shows.
- **Membership data lives in Postgres** (source of truth; RLS reads it). AVP is the API-layer policy point and does not own memberships. At call time the handler reads memberships (and the resource's parent chain) and passes them as **entities** to AVP. AVP is the gate; RLS is the fallback.
- Hierarchical resources use `resource in Parent::{id}`. Access levels are cumulative (e.g. `read` ⊂ `write` ⊂ `admin`).

### 11.4 Row level security
- Session keys (single `app.` namespace, snake_case): `app.user_id`, `app.tenant_id`, `app.scope_id`. Set transaction-locally. Policies **fail closed** when a required key is unset.
- Application roles never own tables and are never superusers (owners bypass RLS unless `FORCE ROW LEVEL SECURITY`). Tests assert cross-tenant reads return zero rows.
- **Mass-change safeguards:** all SQL sqlc-generated; CI fails `UPDATE`/`DELETE` without `WHERE` (deliberate mass changes are reviewed migrations); write handlers run in a transaction and assert rows affected (usually 1), else roll back and log `event=unexpected_rowcount`; roles usually lack `DELETE` and never have `TRUNCATE`/`DROP`/`ALTER`; prefer soft deletes; point-in-time recovery is the last line.

### 11.5 Encryption and secrets
- Encrypt at rest and in transit everywhere. PII uses **envelope encryption**: `GenerateDataKey` with encryption context `tenant=<id>`, encrypt locally, discard the plaintext key, store ciphertext + encrypted data key; decrypt only with the matching context. A **token bucket** on key use bounds compromise rate and alarms when exhausted.
- Secrets: prefer IAM roles and ephemeral credentials. Unavoidable secrets go in Secrets Manager with rotation, read at init and cached with expiry. Environment variables hold only the secret's **name/ARN**. The pipeline fails deployments whose env var names look like secrets (`*KEY*`, `*SECRET*`, `*TOKEN*`, `*PASSWORD*`); CI runs a secret scanner.
- Environment variables are for non-sensitive deploy-time config only (environment name, region, endpoints, resource names, log level, default flags), injected by IaC. A Lambda validates required config at startup and fails loudly if missing.

### 11.6 Threat model (say it, do it, prove it)
Required contents: (1) one system/data-flow diagram; (2) assets and data classification; (3) trust boundaries and entry points; (4) actors and abuse cases (at least external attacker, curious user inside a tenant, insider, compromised dependency or pipeline); (5) top threats each mapped to a defense layer with residual risk; (6) deviations and compensating controls linked to the decision log; (7) controls the permit grants; (8) detection and response, including a **security dashboard** (auth failures, AVP denials, `defense_layer_breach`, KMS bucket level, WAF blocks, DLQ and outbox depth, upload scan findings); (9) owner, last-reviewed date, update triggers; (10) detection under all four failure modes. AI may draft it; a person must review and own it.

**Four failure modes:** hard (errors, throttles, DLQ depth alarms); silent (canary transactions, outbox sweeper, reconciliation, alarms treating missing data as breaching); high-latency (p99 alarms, a timeout on every outbound call shorter than its caller's, connection/concurrency alarms); version mismatch (N-1 compatibility, expand-and-contract, services advertise their version via header and `/version`, declared peer version constraints checked by the pipeline, skew alarm).

## 12. Governance: development permits

- A permit is an **x509 certificate** stating: this application may run [stage] in [environment] until [date], with a semantic-version constraint. Issued by a governance body outside the application teams, from a **permit-dedicated CA owned by the adopting organization** (separate from TLS/workforce CAs). Pipelines trust only that CA.
- Each application (including foundation applications) has its own permit; its application ID is the `apps/` directory name. Applications inherit what the library and modules provide by library version; controls are derived from definitions and participation. Tenant Configuration needs no permit.

| Permit | Principle | Authorizes | Requires |
|---|---|---|---|
| Build | Begin | Building the application | Registered application ID, owner and technical lead, repository, stated purpose and data classes, requested controls with reasons (unrequested controls are denied), infrastructure as code, permit-presenting reproducible pipeline, decision log location |
| Test | **Say it** | Test pipelines and test environments | A defined, owned, current threat model and the standards in place, including the stated rollback practice (PERMIT-078) |
| (between) | **Do it** | — | Stated controls implemented and exercised in test |
| Production | **Prove it** | Deploying to and running in production | Each top threat linked to dated evidence (automated test or recorded exercise); detection under all four failure modes; DLQ standards met; restore and rollback rehearsed, exercising the stated rollback practice (PERMIT-045); resilience-tier mechanisms rehearsed; required runbooks exercised; dashboards live and alarm routes tested; same artifact that passed test, within the version constraint; waivers current; a named operational owner |

**Pipeline validation** (all must hold; any missing extension fails): (1) chain verifies to the permit CA and within validity; (2) `CA:FALSE`, Key Usage includes `digitalSignature`; (3) application ID matches (and repository, if present); (4) EKU contains the stage OID; (5) Certificate Policies contain the target environment OID; (6) app version within the version constraint; (7) every required control present; (8) caller proves possession of the private key and the serial is not on the deny list. Then the pipeline proceeds with **no human approval step**. Expired, mismatched or invalid permits **fail closed**.

**Validity is the control:** permits are long-lived; expiry forces governance review. No CRL/OCSP; the permit's private key lives in the pipeline's secret store; optional deny list of serials for compromised keys.

**Standard demonstrations** (production evidence where applicable): cross-tenant query returns zero rows; AVP denies an unauthorized action and RLS still returns nothing if the check is removed; hostile input stored or rejected as data; PII is ciphertext without the KMS key and a context mismatch is denied; exhausting the KMS token bucket raises the alarm; a pipeline run fails closed on an expired or mismatched permit.

## 13. Delivery and operations

### 13.1 Environments and release
- A standard ladder (e.g. dev → test/staging → production) mirroring permit stages. Environments differ in configuration and **may** differ in release; never in how a release is built.
- **Build once, promote the artifact** (by digest). **One rung at a time:** deploy only an artifact that passed the previous rung. **One environment per pipeline run.** Continuous deployment shortcuts need a recorded design decision with compensating controls; an assistant must not shorten the ladder.
- Deploy is not release: feature flags. Before deploying, check schema ordering and peer-version compatibility.
- Build: `GOOS=linux GOARCH=arm64 CGO_ENABLED=0`; vet, lint, tests; apply schema transitions; deploy Lambdas; upload front-end assets.
- **Branching (10.6):** trunk-based with short-lived branches; merge by pull request with required CI checks and at least one human reviewer (enforces CONV-026 for AI-written code); main is always deployable; incomplete work behind feature flags; every release has a semantic version, tagged in source control and embedded in the artifact, which is the version checked against the permit's `applicationVersionConstraint`; artifacts are immutable (a fix is a new version).
- **Progressive delivery (10.7):** Lambdas deploy to a published version behind an alias with CodeDeploy canary or linear traffic shifting and **automatic rollback when an application alarm fires** (within a rung; doesn't replace promotion). Front-end assets go to versioned immutable paths (`/assets/<version>/`); rollback re-points entry pages. Stale versions are garbage collected by lifecycle rule or scheduled cleanup, keeping the current, the previous N (at least the rollback target) and anything newer than the longest cache lifetime.
- **Rollback declared (4.5, 10.7):** every major schema transition declares a reverse transition or that it is irreversible with a roll-forward plan; the pipeline fails one that declares neither. Whether reverse transitions are mandatory stays the team's decision.

### 13.2 Testing
Unit tests against sqlc interfaces; integration tests against a real Postgres (RLS, schema transitions forward and backward); workflow tests per subflow; failure-path tests (error returned not swallowed, DLQ reached, alarm fires, sweeper re-drives). **100% statement coverage** of application code enforced in CI (combine unit and integration coverage); exclusions only for generated code and thin `main` wiring, listed with reasons; other gaps need a `DESIGN-nnn` waiver. Tests must assert outcomes.

### 13.3 Publishing a Lambda (via the standard module; pipeline fails it otherwise)
Log group with explicit retention, KMS-encrypted; structured JSON logs (`log/slog`) with at least `correlation_id`, `event`, `function`, `version`, `env`, `outcome` (`ok`/`error`/`denied`), `duration_ms`, plus pseudonymous principal and domain; start and end of every handler logged; failures at ERROR with actionable context and no secrets/PII; interior timings `authn_ms`, `authz_ms`, `db_ms`, `downstream_ms` and total; documented `event` vocabulary with metric filters before first deploy; an alarm per failure condition with owner and runbook; tracing on. Correlation IDs propagate through HTTP calls, Step Functions and WebSocket messages.

### 13.4 Observability
- **Dashboard per application per environment, as code, with two required tiers.** Infrastructure: Lambda errors/duration/throttles/concurrency, DB connections/CPU, API 4xx/5xx, WebSocket delivery failures, KMS token bucket, DLQ depth and age, outbox depth and oldest pending age, WAF blocks. Application: distinct active users/day, data changes/day, workflow executions run/succeeded/failed, open human tasks and age, auth successes/failures, connected clients, product-owner counts.
- Every failure widget has an alarm routed to an owner. **Every alarm links to a runbook** in `ops/runbooks/` (pipeline fails an alarm without one). Required runbooks: DLQ redrive, outbox sweeper alarm, restore and rollback, version-skew response, infected upload. Runbooks are rehearsed; stale is a defect.
- Saved Logs Insights queries deployed as code, named `<application>/<environment>/<purpose>`.
- Log levels: ERROR = a person may need to act; WARN = unexpected but handled; INFO = normal; DEBUG off outside development.

### 13.5 Resilience and cost
- Choose a **resilience tier** first (business decision): 3 backup/restore single region (hours–day); 2 backups to a second region (hours); 1 warm standby (minutes); 0 active-active (near zero). An app can't promise a better tier than its dependencies. All tiers: automated backups with PITR; rehearse restore + schema rollback on a schedule. Cross-region needs KMS multi-region keys, replicated secrets/certs, WAF per region, same Terraform per region, Route 53 failover.
- **Tagging standard (11.6.1).** Mandatory tags on every taggable resource: `application` (app ID or `foundation`), `environment`, `owner` (team alias), `domain` (where there is one), `data-classification` (highest class held or processed), `cost-center`, `managed-by=terraform`, plus any client-required tags. Applied once via Terraform `default_tags`; enforced by AWS Organizations tag policies and a pipeline check that fails untagged resources. `owner` drives alarm routing. A resource tagged `confidential`/`restricted` holding personal data requires permit control `P.3.1`, or the pipeline fails closed. **Tags are never access control.** Activate cost allocation tags in Billing; budget and anomaly alert per application and environment.

## 14. Go standards

- Standard library first (`net/http`, `encoding/json`, `log/slog`, `context`). One layout (`cmd/<service>`, `internal/...`). `gofmt`, `go vet`, `staticcheck`/`golangci-lint`, `govulncheck` in CI. Pin the toolchain in `go.mod`. `CGO_ENABLED=0`.
- Prefer iteration; recursion only for recursive data with a depth limit and a test. Bound every loop on external input; every retry loop has max attempts and backoff. Every goroutine takes a `context.Context`, has a bounded lifetime and is awaited before the handler returns. Return early; no `panic` outside startup.
- **Handle each error exactly once:** either handle it where it occurs (recover, retry or translate, logging what was done) or wrap it with `fmt.Errorf("...: %w", err)` and return it for the caller to decide. Never log and also return the same error; never discard with `_`. At the handler's entry point an unhandled error is logged once and returned to the runtime as failure. Logging and then returning `nil`/success is a bug.
- Comment why, not what. Package comments and godoc for every exported identifier. Always comment non-obvious invariants, security-relevant decisions, workarounds (with issue link) and non-obvious SQL intent. No commented-out code; `TODO(owner-or-ticket)`.
- Each service advertises its version (response header and `/version`).

## 15. Reference company: Tidewater

A fictional small-business lender servicing loans for ~40 partner community banks (the tenants), used by every example and test. Business applications: **Lending** (form-driven, human tasks, nested sub-processes), **Payments** (file-driven, waiting, timers, reconciliation; reads Loan by declared participation), **Support Desk** (simplest human-task process, built from specs alone). Domains: `borrowing`, `payments`, `support`; foundation domains `registry`, `identity`, `work`, `directory`. Fictional core banking system **Anchor Core** remains the system of record. User kinds: bank staff (client employees), borrowers (client end users, later), Tidewater staff (company employees across banks).

**Success measures:** Support Desk defined entirely by specs with no hand-written code; a process/form change reaches production as a reviewed, versioned change redeploying only that application; a second unrelated application built with no new library code; a cross-tenant read test for every domain returns zero rows; every process instance's history reconstructable from the audit trail; a new solution builder or AI can produce a valid application from the documentation and validators.

## 16. AWS Well-Architected mapping (Appendix B)

| Pillar | Addressed by |
|---|---|
| Operational Excellence | 5.5 publishing; 10 delivery (10.6 branching, 10.7 progressive delivery); 11 observability (11.6.1 tagging, 11.7 runbooks); 4.11 index health |
| Security | 8 security; 9 permits; 7.8 uploads; 5.7 integration rules |
| Reliability | 4.8 outbox; 6.3–6.4 workflow and failure handling; 8.7 failure modes and version skew; 10.7 automatic rollback; 11.5 resilience tiers |
| Performance Efficiency | 3 Go on arm64; 4.2 RDS/Aurora by need; 4.6 pooling; 4.11 index health; 5.5 interior timings; 5.7 queue load levelling |
| Cost Optimization | principles 1 and 5; 11.6 cost; 11.6.1 tags; 11.5 tier choice |
| Sustainability | arm64 on Graviton; serverless with no idle capacity; small static binaries and framework-free front end; events over polling; declared retention, log retention, unused-index removal, front-end version garbage collection, S3 lifecycle tiers; periodic right-sizing |
