docs(milestone): complete v0.1 — initial framework
---ci--- project: atelier phase: 7 milestone: v0.1 status: complete phase_role: final milestone_complete: true requirements: covered: [ATELIER-01, ATELIER-02, ATELIER-03, ATELIER-04, ATELIER-05, ATELIER-06, ATELIER-07, ATELIER-08, ATELIER-09, ATELIER-10, ATELIER-11, ATELIER-12, ATELIER-13, ATELIER-14, ATELIER-15, ATELIER-16, ATELIER-17, ATELIER-18, ATELIER-19, ATELIER-20, ATELIER-21, ATELIER-22, ATELIER-23, ATELIER-24, ATELIER-25, ATELIER-26, ATELIER-27, ATELIER-28, ATELIER-29, ATELIER-30, ATELIER-31, ATELIER-32, ATELIER-33, ATELIER-34, ATELIER-35] partial: [] ship: milestone: v0.1 type: NFR tag: v0.0.7 merge: milestone/v0.1-atelier -> main release: https://git.cloudinit.dev/cloudinit-bot/atelier/releases/tag/v0.0.7 ---/ci--- Milestone v0.1 — Initial Framework (NFR, complete). 8 core principles (C1-C8), 11 domains, 110 domain principles, 27 derived docs, 4 good + 3 bad examples, 4 language docs, full matrix, 3 review docs. All 35 requirements covered. 7 patches (v0.0.0 pre-execution through v0.0.7 final). v0.0.7 IS the v0.1.0 milestone release.
This commit is contained in:
@@ -0,0 +1,63 @@
|
||||
# Error Responses — Derived Rules
|
||||
|
||||
> Derives from `domains/api/first-principles.md` P9 (Error Transparency) and `domains/errors/first-principles.md`.
|
||||
|
||||
## The Error Contract
|
||||
|
||||
Every error response is a JSON object with:
|
||||
|
||||
```json
|
||||
{
|
||||
"error": {
|
||||
"code": "STRING_ERROR_CODE",
|
||||
"message": "Human-readable description",
|
||||
"details": {},
|
||||
"request_id": "uuid"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `code`: machine-consumable, stable, UPPER_SNAKE_CASE. Never a free-text message.
|
||||
- `message`: human-readable, for logs and developers. Not for end users (see `domains/errors/` P8).
|
||||
- `details`: structured, typed additional context (which field, what value, what constraint).
|
||||
- `request_id`: correlation ID for tracing. Every error is traceable.
|
||||
|
||||
## Error Codes (P3 Predictability, P9)
|
||||
|
||||
- Codes are stable. Renaming an error code is a breaking change.
|
||||
- Codes are specific: `VALIDATION_FAILED` not `BAD_REQUEST`. `DUPLICATE_EMAIL` not `CONFLICT`.
|
||||
- Codes are namespaced: `USER_NOT_FOUND`, `ORDER_NOT_FOUND` — not just `NOT_FOUND`.
|
||||
|
||||
## Status Code Mapping (P1 Contract Fidelity)
|
||||
|
||||
| Code | Meaning | Error code example |
|
||||
|------|---------|-------------------|
|
||||
| 400 | Malformed request | `MALFORMED_REQUEST` |
|
||||
| 401 | Auth required | `AUTH_REQUIRED` |
|
||||
| 403 | Forbidden | `FORBIDDEN` |
|
||||
| 404 | Not found | `<RESOURCE>_NOT_FOUND` |
|
||||
| 409 | Conflict | `DUPLICATE_<RESOURCE>` |
|
||||
| 422 | Semantic invalid | `VALIDATION_FAILED` |
|
||||
| 429 | Rate limited | `RATE_LIMITED` |
|
||||
| 500 | Server bug | `INTERNAL_ERROR` |
|
||||
|
||||
- Never return 200 with an error body. The status code is the first signal.
|
||||
- Never return 500 for a client error. 500 means "the server has a bug."
|
||||
|
||||
## Information Disclosure (P8 Security, domains/security P9)
|
||||
|
||||
- Error messages do not leak internal state: no stack traces, no SQL fragments, no file paths.
|
||||
- A 401 does not say "user not found" vs "wrong password" — both say "invalid credentials."
|
||||
- A 404 does not confirm the resource exists but is forbidden — return 404, not 403, for unauthenticated requests to hidden resources.
|
||||
- Detailed errors are logged server-side with `request_id`; the client gets the safe version.
|
||||
|
||||
## Retryability (P6 Idempotency)
|
||||
|
||||
- Errors that are safe to retry: 409, 422 (if the fix is applied), 429 (after backoff), 5xx.
|
||||
- Errors that are not safe to retry: 400, 401 (without re-auth), 403.
|
||||
- The error body indicates retryability: `retryable: true/false` or via the code's known semantics.
|
||||
|
||||
## Partial Errors (GraphQL, see `domains/api/graphql.md`)
|
||||
|
||||
- GraphQL returns data and errors together. Do not conflate.
|
||||
- A null field with no error is a bug. A null field with an error is a partial failure.
|
||||
@@ -0,0 +1,84 @@
|
||||
# API Design — First Principles
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Status:** Foundational
|
||||
**Audience:** AI agents and humans designing APIs (REST, GraphQL,
|
||||
gRPC, RPC, libraries).
|
||||
|
||||
## 1. Manifesto
|
||||
|
||||
An API is a contract between systems and the people who build on
|
||||
them. The cost of an API is paid by every consumer, forever. The
|
||||
highest quality API is one that a stranger can use correctly without
|
||||
reading the source.
|
||||
|
||||
## 2. The Principles
|
||||
|
||||
### P1. Contract Fidelity
|
||||
The API does what its documentation says, and the documentation says
|
||||
what the API does. Nothing more, nothing less.
|
||||
|
||||
### P2. Clarity
|
||||
Endpoints, methods, parameters, and responses are named and structured
|
||||
for the consumer — not for the implementer.
|
||||
|
||||
### P3. Predictability
|
||||
Consumers can guess behavior without reading docs. Patterns repeat.
|
||||
Surprises are bugs.
|
||||
|
||||
### P4. Composability
|
||||
Resources and operations combine cleanly. The whole is greater than
|
||||
the sum of its parts, and the parts are reusable in new wholes.
|
||||
|
||||
### P5. Versioning
|
||||
Changes are managed explicitly, not implicitly. Consumers know what
|
||||
will break, and when.
|
||||
|
||||
### P6. Idempotency
|
||||
Repeated identical calls have the same effect as a single call. Retry
|
||||
is a first-class operation.
|
||||
|
||||
### P7. Performance
|
||||
Latency, payload size, and call count are designed in — not optimized
|
||||
out.
|
||||
|
||||
### P8. Security
|
||||
Authentication, authorization, validation, and rate limiting are
|
||||
defaults, not add-ons.
|
||||
|
||||
### P9. Error Transparency
|
||||
Failures are communicated specifically, structurally, and actionably.
|
||||
|
||||
### P10. Stability
|
||||
Consumers can build on the API without fear of breakage. Backward
|
||||
compatibility is a default.
|
||||
|
||||
## 3. Conflict Resolution
|
||||
|
||||
1. Contract Fidelity — never sacrificed.
|
||||
2. Security — never sacrificed.
|
||||
3. Stability — sacrificed only with a documented deprecation cycle.
|
||||
4. Clarity — sacrificed only for Performance with evidence.
|
||||
5. Predictability — sacrificed for Composability when patterns diverge.
|
||||
6. Composability — sacrificed for Clarity when abstractions confuse.
|
||||
7. Idempotency — sacrificed only for genuinely non-idempotent operations.
|
||||
8. Performance — sacrificed only with measurement.
|
||||
9. Error Transparency — sacrificed only for security-sensitive errors.
|
||||
10. Versioning — never sacrificed (always have a version policy).
|
||||
|
||||
## 4. What Violates These Principles
|
||||
|
||||
| Violation | Principle Breached |
|
||||
|------------------------------------|----------------------|
|
||||
| Endpoint name exposes DB schema | P2 Clarity |
|
||||
| Breaking change without deprecation | P10 Stability |
|
||||
| Generic 500 with stack trace | P9 Error Transparency|
|
||||
| Auth as opt-in | P8 Security |
|
||||
| Non-idempotent POST without key | P6 Idempotency |
|
||||
| Inconsistent naming across endpoints | P3 Predictability |
|
||||
| Required response field undocumented | P1 Contract Fidelity |
|
||||
| 10MB response payload by default | P7 Performance |
|
||||
|
||||
## 5. Relationship to Core
|
||||
|
||||
Subordinate to `core/first-principles.md`. See `matrix/principles-matrix.md`.
|
||||
@@ -0,0 +1,53 @@
|
||||
# GraphQL — Derived Rules
|
||||
|
||||
> Derives from `domains/api/first-principles.md`. Applies P1–P10 to GraphQL specifically.
|
||||
|
||||
## Schema First (P1 Contract Fidelity)
|
||||
|
||||
- The schema is the contract. Every field has a type, a description, and a deprecation status.
|
||||
- The schema is versioned. Breaking schema changes (removing a field, changing a type) require a deprecation cycle.
|
||||
- Never expose raw database types in the schema. Map them to domain types.
|
||||
|
||||
## Query Design (P2 Clarity, P3 Predictability)
|
||||
|
||||
- Field names are nouns, camelCase: `userOrders`, not `UserOrders` or `user_orders`.
|
||||
- Arguments are descriptive: `first`, `after`, `orderBy` — not `arg1`, `arg2`.
|
||||
- Connections for lists: `users(first: 10, after: "cursor")` — never bare arrays.
|
||||
- Mutations are verbs: `createUser`, `deleteOrder` — not `userCreate`.
|
||||
|
||||
## N+1 Prevention (P7 Performance)
|
||||
|
||||
- Use a dataloader for every list field that resolves to another resource.
|
||||
- A resolver that does a database query per item is an N+1 bug.
|
||||
- Test resolvers under a list query, not just a single-item query.
|
||||
|
||||
## Authorization at the Field Level (P8 Security)
|
||||
|
||||
- Every resolver checks authorization. The query graph is not a trust boundary by default.
|
||||
- A user who can query `user { email }` is not automatically authorized to query `user { passwordHash }`.
|
||||
- Field-level authz is the floor, not an optimization.
|
||||
|
||||
## Deprecation (P5 Versioning, P10 Stability)
|
||||
|
||||
- Deprecate fields with `@deprecated(reason: "...")`. Never remove a field without deprecating first.
|
||||
- A deprecated field is removed in the next major schema version, not sooner.
|
||||
- Track field usage. A deprecated field with no usage can be removed sooner.
|
||||
|
||||
## Error Handling (P9 Error Transparency)
|
||||
|
||||
- Errors are partial by default: a query can return data and errors simultaneously.
|
||||
- Errors are structured: `{ message, path, extensions: { code, ... } }`.
|
||||
- Use `extensions.code` for machine-consumable error types, not free-text messages.
|
||||
- Never swallow a resolver error silently. A null field with no error is a bug.
|
||||
|
||||
## Complexity Budget (P7 Performance, P8 Security)
|
||||
|
||||
- Enforce a query complexity limit. Unbounded depth/breadth is a DoS vector.
|
||||
- Cost-based analysis (not just depth) catches expensive nested queries.
|
||||
- Reject queries over budget with a 400, not a 500.
|
||||
|
||||
## Federation (P6 Composability)
|
||||
|
||||
- A federated subgraph owns its entities. Cross-graph references use `@external` and `@requires`.
|
||||
- Never reach into another subgraph's database. The graph boundary is the contract.
|
||||
- The gateway composes; subgraphs do not know about each other.
|
||||
@@ -0,0 +1,69 @@
|
||||
# Pagination — Derived Rules
|
||||
|
||||
> Derives from `domains/api/first-principles.md` P7 (Performance) and P3 (Predictability).
|
||||
|
||||
## Three Patterns
|
||||
|
||||
### 1. Offset/Limit (`?page=2&limit=20`)
|
||||
- Simple, supports jumping to a page.
|
||||
- Unstable under inserts: page 2 becomes page 1's content after an insert.
|
||||
- Slow for large offsets: `OFFSET 10000` scans 10000 rows.
|
||||
- Use for: small, stable collections, admin UIs.
|
||||
|
||||
### 2. Cursor (`?cursor=base64token&limit=20`)
|
||||
- Stable under inserts: the cursor points to a position, not a page number.
|
||||
- Fast: indexed lookup, no scan.
|
||||
- No random access (cannot jump to page 5).
|
||||
- Use for: infinite scroll, feeds, large collections, anything user-facing.
|
||||
|
||||
### 3. Keyset (`?after_id=123&limit=20`)
|
||||
- Like cursor but uses the actual sort key (e.g., `after_id=123`).
|
||||
- Most stable and fast. Requires a unique, monotonic sort key.
|
||||
- Use for: ordered collections with a natural unique key.
|
||||
|
||||
## Defaults (P3 Predictability)
|
||||
|
||||
- Default `limit`: 20 or 50. Never unbounded.
|
||||
- Max `limit`: 100 or 200. Reject `limit=10000` with 400.
|
||||
- Default sort: by created_at descending, or by the resource's natural order.
|
||||
- Always return the total count if cheap; never if it requires a separate COUNT query on a large table.
|
||||
|
||||
## Response Shape (P2 Clarity)
|
||||
|
||||
```json
|
||||
{
|
||||
"data": [...],
|
||||
"pagination": {
|
||||
"cursor": "next-base64-token",
|
||||
"has_more": true
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
- `cursor` is null when there is no next page.
|
||||
- `has_more` is the boolean convenience (some clients prefer it).
|
||||
- Never return `data` as a bare array — always wrap so you can add pagination without breaking.
|
||||
|
||||
## Link Header (alternative)
|
||||
|
||||
```
|
||||
Link: <https://api.example.com/users?cursor=X>; rel="next", <https://api.example.com/users?cursor=Z>; rel="prev"
|
||||
```
|
||||
|
||||
- Useful for HTTP-level clients (curl, browser fetch).
|
||||
- Less convenient for JSON-parsing clients.
|
||||
|
||||
## Consistency (P8 Consistency across endpoints)
|
||||
|
||||
- Every collection endpoint paginates the same way.
|
||||
- A client that learns pagination on `/users` should know it on `/orders`.
|
||||
- Mixed pagination (cursor here, offset there) is a tax on every consumer.
|
||||
|
||||
## What Violates Pagination
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| Returning 10000 items by default | P7 Performance |
|
||||
| `limit` with no max | P8 Security (DoS) |
|
||||
| Page numbers on a frequently-inserted table | P3 Predictability |
|
||||
| Bare array response (no pagination wrapper) | P1 Contract Fidelity (can't add pagination later without breaking) |
|
||||
@@ -0,0 +1,68 @@
|
||||
# REST — Derived Rules
|
||||
|
||||
> Derives from `domains/api/first-principles.md`. Applies P1–P10 to REST specifically.
|
||||
|
||||
## Resource Naming (P2 Clarity, P3 Predictability)
|
||||
|
||||
- Nouns, not verbs: `/users`, `/orders`, not `/getUsers`.
|
||||
- Plural: `/users` (collection), `/users/{id}` (item).
|
||||
- Lowercase, hyphenated: `/order-items`, not `/OrderItems` or `/order_items`.
|
||||
- Nesting max 2 levels: `/users/{id}/orders`, not `/users/{id}/orders/{oid}/items/{iid}`.
|
||||
|
||||
## HTTP Methods (P1 Contract Fidelity, P6 Idempotency)
|
||||
|
||||
| Method | Semantics | Idempotent | Safe |
|
||||
|--------|-----------|------------|------|
|
||||
| GET | Read | Yes | Yes |
|
||||
| POST | Create | No | No |
|
||||
| PUT | Replace (full) | Yes | No |
|
||||
| PATCH | Update (partial) | No | No |
|
||||
| DELETE | Remove | Yes | No |
|
||||
|
||||
- PUT requires the full resource. PATCH requires only the delta. Never accept a partial PUT.
|
||||
- POST creates; never use POST for read operations. POST is not cacheable.
|
||||
|
||||
## Status Codes (P9 Error Transparency, P1 Contract Fidelity)
|
||||
|
||||
| Code | Meaning | When |
|
||||
|------|---------|------|
|
||||
| 200 | OK | Successful GET, PUT, PATCH |
|
||||
| 201 | Created | Successful POST that created a resource |
|
||||
| 204 | No Content | Successful DELETE, or empty response |
|
||||
| 400 | Bad Request | Malformed request (client error) |
|
||||
| 401 | Unauthorized | Authentication required or failed |
|
||||
| 403 | Forbidden | Authenticated but not permitted |
|
||||
| 404 | Not Found | Resource does not exist |
|
||||
| 409 | Conflict | State conflict (e.g., duplicate) |
|
||||
| 422 | Unprocessable | Well-formed but semantically invalid |
|
||||
| 429 | Too Many Requests | Rate limited |
|
||||
| 500 | Internal Error | Server bug — never leak stack trace |
|
||||
|
||||
- Never return 200 on an error. Never return 500 with a stack trace.
|
||||
- 401 vs 403: 401 = "who are you?", 403 = "I know who you are, but you can't."
|
||||
|
||||
## Idempotency (P6 Idempotency)
|
||||
|
||||
- POST: not idempotent. Use an idempotency key (`Idempotency-Key` header) for safe retry.
|
||||
- PUT: idempotent by definition — same PUT twice = same state.
|
||||
- DELETE: idempotent — deleting a non-existent resource is success (204).
|
||||
- PATCH: not idempotent by default; can be made idempotent with explicit versioning.
|
||||
|
||||
## Pagination (P7 Performance, see `pagination.md`)
|
||||
|
||||
- Default to cursor pagination for collections > 100 items.
|
||||
- Never return unbounded collections.
|
||||
- `Link` header or `cursor` field in response body.
|
||||
|
||||
## Versioning (P5 Versioning, P10 Stability, see `versioning.md`)
|
||||
|
||||
- Version in the URL (`/v1/users`) or in the header (`Accept: application/vnd.atelier.v1+json`).
|
||||
- Pick one. Be consistent across all endpoints.
|
||||
- Never make a breaking change without a new version and a deprecation cycle.
|
||||
|
||||
## Security (P8 Security, see `domains/security/`)
|
||||
|
||||
- HTTPS only. Redirect HTTP to HTTPS.
|
||||
- Authentication on every non-public endpoint. No opt-in auth.
|
||||
- Rate limiting on write endpoints (POST, PUT, PATCH, DELETE).
|
||||
- Validate every input against a schema. Never pass raw request body to the database.
|
||||
@@ -0,0 +1,54 @@
|
||||
# API Versioning — Derived Rules
|
||||
|
||||
> Derives from `domains/api/first-principles.md` P5 (Versioning) and P10 (Stability).
|
||||
|
||||
## The Default: No Breaking Changes
|
||||
|
||||
- A breaking change is a new version. There is no "minor" breaking change.
|
||||
- Breaking changes: removing a field, changing a field type, changing a field's semantics, changing required vs optional, changing error codes.
|
||||
- Non-breaking changes: adding a field, adding an endpoint, adding an optional parameter, loosening validation.
|
||||
|
||||
## Version Policies
|
||||
|
||||
### URL Versioning (`/v1/users`)
|
||||
- Simple, visible, cacheable.
|
||||
- Breaking changes bump the major version: `/v1` → `/v2`.
|
||||
- Old versions are supported in parallel during the deprecation window.
|
||||
|
||||
### Header Versioning (`Accept: application/vnd.atelier.v1+json`)
|
||||
- Invisible in the URL; harder to test.
|
||||
- Useful when the URL must stay stable (e.g., public webhooks).
|
||||
|
||||
### Semantic Versioning (for libraries/SDKs)
|
||||
- Major: breaking. Minor: additive. Patch: fix.
|
||||
- Follow semver strictly. A "minor" that breaks is a lie.
|
||||
|
||||
## Deprecation Cycle (P5 Reversibility)
|
||||
|
||||
1. **Announce**: mark the field/endpoint `@deprecated` with a sunset date.
|
||||
2. **Support**: keep the old version working until the sunset date.
|
||||
3. **Monitor**: track usage of the deprecated surface.
|
||||
4. **Retire**: when usage drops below threshold (or sunset passes), remove.
|
||||
5. **Never** remove without announcing. The cost of a silent break is paid by every consumer.
|
||||
|
||||
## Sunset Headers (P9 Error Transparency)
|
||||
|
||||
- Deprecated endpoints return `Sunset: <date>` header.
|
||||
- Deprecated endpoints return `Deprecation: <date>` header.
|
||||
- A consumer who reads headers knows when to migrate.
|
||||
|
||||
## Versioning vs Compatibility
|
||||
|
||||
- Versioning is the mechanism. Compatibility is the property.
|
||||
- Backward compatibility: old consumers work with the new version.
|
||||
- Forward compatibility: new consumers work with the old version (harder, rarer, usually not worth it).
|
||||
- Aim for backward compatibility. Forward compatibility is for protocols, not APIs.
|
||||
|
||||
## What Violates Versioning
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| Removing a field without deprecation | P5, P10 |
|
||||
| Changing a field's type in a "minor" release | P1, P5 |
|
||||
| No sunset header on a deprecated endpoint | P9 |
|
||||
| Two versions with divergent semantics for the same field | P1 |
|
||||
@@ -0,0 +1,41 @@
|
||||
# Concurrency — First Principles
|
||||
|
||||
## 1. The Principles
|
||||
|
||||
### P1. Immutability by Default
|
||||
Mutable shared state is the enemy. The default is immutable; mutation
|
||||
is justified.
|
||||
|
||||
### P2. Single Responsibility for Threads
|
||||
Each unit of work has one owner. No "anyone can touch this" state.
|
||||
|
||||
### P3. Boundaries are Locks
|
||||
Synchronization happens at well-defined places. Lock scope is
|
||||
minimal and explicit.
|
||||
|
||||
### P4. Determinism Over Speed
|
||||
Correct concurrent code is faster than incorrect concurrent code.
|
||||
Race conditions are not "fast enough" — they are wrong.
|
||||
|
||||
### P5. Lock Minimization
|
||||
Locks are expensive and dangerous. Lock-free, wait-free, and
|
||||
message-passing are preferred where possible.
|
||||
|
||||
### P6. No Silent Races
|
||||
Race conditions are caught, not hidden. Tools (TSan, Go race
|
||||
detector) are part of CI.
|
||||
|
||||
### P7. Cancellation Support
|
||||
Every async operation can be cancelled. Cancellation is fast and
|
||||
complete.
|
||||
|
||||
### P8. Timeout Discipline
|
||||
Every blocking call has a timeout. Forever is not a duration.
|
||||
|
||||
### P9. Bounded Queues
|
||||
Unbounded queues are memory leaks in disguise. Bounded queues
|
||||
expose backpressure.
|
||||
|
||||
### P10. Test for Race Conditions
|
||||
Concurrent code is tested under concurrent load, not just happy-path
|
||||
correctness.
|
||||
@@ -0,0 +1,62 @@
|
||||
# Concurrency Patterns — Derived Rules
|
||||
|
||||
> Derives from `domains/concurrency/first-principles.md`. Common concurrency patterns and when to use them.
|
||||
|
||||
## Pattern 1: Message Passing (P5 Lock Minimization)
|
||||
|
||||
- Threads/goroutines communicate via channels/queues, not shared memory.
|
||||
- "Don't communicate by sharing memory; share memory by communicating." (Go proverb)
|
||||
- Use when the data flows naturally in one direction. Avoids locks entirely.
|
||||
|
||||
## Pattern 2: Read-Write Lock (P5 Lock Minimization)
|
||||
|
||||
- Multiple readers, one writer. A `RwLock` allows concurrent reads, exclusive writes.
|
||||
- Use when reads vastly outnumber writes (e.g., a config cache).
|
||||
- Avoid when writes are frequent — the lock degrades to a mutex.
|
||||
|
||||
## Pattern 3: Actor Model (P2 Single Responsibility)
|
||||
|
||||
- Each actor owns its state. Actors communicate via messages. No shared state.
|
||||
- Use for isolated, long-lived workers (e.g., a session handler, a chat room).
|
||||
- Erlang/Akka/Pony are built on this. Implementable in any language with channels.
|
||||
|
||||
## Pattern 4: Immutable Data Structures (P1 Immutability by Default)
|
||||
|
||||
- Data is never mutated; a "change" creates a new value. Old values are safe to share.
|
||||
- Use in functional languages (Haskell, Clojure) or via persistent data structures (Immer.js).
|
||||
- Eliminates entire classes of races. The trade-off is allocation cost.
|
||||
|
||||
## Pattern 5: Bounded Queue with Backpressure (P9 Bounded Queues)
|
||||
|
||||
- A queue with a max size. When full, the producer is blocked or signaled.
|
||||
- Use to bound memory and propagate slowness from consumer to producer.
|
||||
- An unbounded queue hides a slow consumer until OOM. Always bound.
|
||||
|
||||
## Pattern 6: Timeout on Every Block (P8 Timeout Discipline)
|
||||
|
||||
- Every blocking call (lock acquire, queue send, HTTP request) has a timeout.
|
||||
- Use a timeout, not a forever-block. Forever is not a duration.
|
||||
- On timeout: cancel, retry, or fail. Do not hang.
|
||||
|
||||
## Pattern 7: Cancellation Propagation (P7 Cancellation Support)
|
||||
|
||||
- A cancellation signal propagates to all spawned work. Cancel the parent, the children stop.
|
||||
- Use context (`context.Context` in Go, `AbortController` in JS, `CancellationToken` in C#).
|
||||
- Cancellation is fast and complete. No orphaned goroutines/threads.
|
||||
|
||||
## Pattern 8: Lock-Free Where Possible (P5)
|
||||
|
||||
- Atomic operations (compare-and-swap) for simple state. No lock.
|
||||
- Use for counters, flags, simple pointers.
|
||||
- Avoid for complex state — lock-free code is subtle and easy to get wrong.
|
||||
|
||||
## What Violates Concurrency Patterns
|
||||
|
||||
| Violation | Pattern |
|
||||
|-----------|---------|
|
||||
| Shared mutable state with no lock | (race, P1) |
|
||||
| Unbounded queue | P5 (OOM) |
|
||||
| `channel.send()` with no timeout | P6 (hang) |
|
||||
| Spawned goroutine with no cancellation | P7 (orphan) |
|
||||
| A mutex held across an I/O call | P3 (lock scope) |
|
||||
| `sync.Mutex` for a counter | P8 (use atomic) |
|
||||
@@ -0,0 +1,43 @@
|
||||
# Data — First Principles
|
||||
|
||||
## 1. The Principles
|
||||
|
||||
### P1. Truth
|
||||
The schema reflects the domain, not the application. If the data
|
||||
model lies, every query lies.
|
||||
|
||||
### P2. Normalization Discipline
|
||||
Duplication is a bug waiting to happen. The same fact lives in one
|
||||
place.
|
||||
|
||||
### P3. Invariants in the Schema
|
||||
Constraints live where the data lives. Application-layer checks are
|
||||
defense, not enforcement.
|
||||
|
||||
### P4. Migration Safety
|
||||
Schema changes are reversible, non-destructive, and tested. Production
|
||||
data is sacred.
|
||||
|
||||
### P5. Indexing with Intent
|
||||
Indexes exist for known query patterns. Every index earns its write
|
||||
cost.
|
||||
|
||||
### P6. Naming Consistency
|
||||
Same concept, same name, always. Across tables, columns, code, and
|
||||
APIs.
|
||||
|
||||
### P7. Type Fidelity
|
||||
Types match domain meaning. A `string` is rarely the right type for
|
||||
an email, an ID, or a status.
|
||||
|
||||
### P8. Lifecycle Awareness
|
||||
Data has a creation, a lifetime, and an end. Archival and deletion
|
||||
are first-class.
|
||||
|
||||
### P9. Referential Integrity
|
||||
Relationships are enforced, not assumed. Foreign keys exist. CASCADE
|
||||
is intentional.
|
||||
|
||||
### P10. Performance Awareness
|
||||
Schema choices have cost. Query plans are reviewed. Cardinality is
|
||||
understood.
|
||||
@@ -0,0 +1,49 @@
|
||||
# Indexing — Derived Rules
|
||||
|
||||
> Derives from `domains/data/first-principles.md` P5 (Indexing with Intent), P10 (Performance Awareness).
|
||||
|
||||
## Index for Queries, Not Tables (P5)
|
||||
|
||||
- An index serves a query. No query, no index.
|
||||
- The query plan is the spec. `EXPLAIN` is the test. An index that is not used is dead weight.
|
||||
- Index the columns you filter on (`WHERE`), join on (`JOIN`), and sort on (`ORDER BY`).
|
||||
|
||||
## Composite Indexes (P5, P10)
|
||||
|
||||
- Order matters: `INDEX(a, b)` serves `WHERE a = ? AND b = ?` and `WHERE a = ?`, but NOT `WHERE b = ?`.
|
||||
- Put the most selective column first. Or the column used in every query. Depends on the workload.
|
||||
- An index on every column is not a strategy. It is write amplification.
|
||||
|
||||
## Unique Indexes (P3 Invariants in Schema)
|
||||
|
||||
- A uniqueness constraint is a unique index. Use it for invariants: `email`, `username`.
|
||||
- Unique indexes enforce; application checks defend. Both belong.
|
||||
- A partial unique index: `UNIQUE(email) WHERE deleted_at IS NULL` — allows soft-deleted duplicates.
|
||||
|
||||
## Covering Indexes (P10)
|
||||
|
||||
- An index that covers all columns of a query is an "index-only scan" — no table lookup.
|
||||
- PostgreSQL: `INCLUDE` clause. MySQL: all columns in the index.
|
||||
- Use for hot queries. Don't cover everything; index size matters.
|
||||
|
||||
## Don't Over-Index (P10, core C8 Economy)
|
||||
|
||||
- Every index costs a write. The write budget is the index count.
|
||||
- Indexes take disk and memory. A 1GB index on a 500MB table is a smell.
|
||||
- Remove unused indexes. `pg_stat_user_indexes` shows usage. An unused index is debt.
|
||||
|
||||
## Migration and Indexes (P4 Migration Safety)
|
||||
|
||||
- Adding an index on a large table is expensive. Do it concurrently (`CREATE INDEX CONCURRENTLY`).
|
||||
- An index migration that locks the table blocks writes. Plan for it.
|
||||
- Build the index, then deploy the query that uses it. Not the reverse.
|
||||
|
||||
## What Violates Indexing Discipline
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| Index on every column | P10, C8 Economy |
|
||||
| No index on a foreign key | P10 (join performance) |
|
||||
| `WHERE b = ?` with only `INDEX(a, b)` | P5 (wrong order) |
|
||||
| Index created without checking the query plan | P5 (no intent) |
|
||||
| `CREATE INDEX` (non-concurrent) on a 10M-row table in prod | P4 Migration Safety |
|
||||
@@ -0,0 +1,53 @@
|
||||
# Migrations — Derived Rules
|
||||
|
||||
> Derives from `domains/data/first-principles.md` P4 (Migration Safety), P5 (Reversibility via core C5).
|
||||
|
||||
## Every Change is a Migration (P4)
|
||||
|
||||
- No manual schema changes. No `ALTER TABLE` in a shell. Every change is a versioned migration file.
|
||||
- Migrations are code: reviewed, tested, committed.
|
||||
- The migration tool is the only way to change the schema (`prisma migrate`, `alembic`, `flyway`, `golang-migrate`).
|
||||
|
||||
## Forward and Reverse (P5 Reversibility, core C5)
|
||||
|
||||
- Every migration has an `up` and a `down`. The `down` reverses the `up`.
|
||||
- A migration without a `down` is irreversible. Irreversible migrations are rare and flagged.
|
||||
- Test the `down` in CI. A `down` that fails is a migration that cannot be rolled back.
|
||||
|
||||
## Expand, Migrate, Contract (P5)
|
||||
|
||||
For non-breaking schema changes:
|
||||
|
||||
1. **Expand**: add the new column/ table (nullable, no constraint). Deploy. Old code still works.
|
||||
2. **Migrate**: backfill data, run the data migration. Deploy. Both old and new code work.
|
||||
3. **Contract**: add constraints, remove the old column. Deploy after all code uses the new schema.
|
||||
|
||||
Never do all three in one migration. Each step is its own deploy.
|
||||
|
||||
## Avoid Destructive Changes (P4)
|
||||
|
||||
- Never `DROP COLUMN` in a migration that could be in use. Expand-contract first.
|
||||
- Never `DROP TABLE` without confirming no code references it.
|
||||
- Never `ALTER TYPE` in a way that locks the table on a large dataset. Use a phased approach.
|
||||
|
||||
## Backward Compatibility (P5, P1)
|
||||
|
||||
- A migration must not break the running code. Old code reads the new schema (with expand).
|
||||
- The schema is always compatible with the previous code version. Two-version compatibility.
|
||||
- A breaking migration is deployed in lockstep with the code, with a maintenance window.
|
||||
|
||||
## Testing Migrations (P3 Determinism via testing P3)
|
||||
|
||||
- Run migrations on a copy of production data in CI. A migration that works on dev may fail on prod scale.
|
||||
- Test the `down` on the migrated state, not just the `up`.
|
||||
- Test with the largest table sizes you have. `ALTER TABLE` on 10 rows is fast; on 10M rows, it may lock.
|
||||
|
||||
## What Violates Migration Safety
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| Manual `ALTER TABLE` in prod | P4 |
|
||||
| Migration with no `down` | P5 Reversibility |
|
||||
| `DROP COLUMN` in the same deploy as the new code | P4, P5 |
|
||||
| No migration test on prod-scale data | P3 Determinism |
|
||||
| A migration that locks a table for 10 minutes | P4 (downtime) |
|
||||
@@ -0,0 +1,53 @@
|
||||
# Schema Design — Derived Rules
|
||||
|
||||
> Derives from `domains/data/first-principles.md` P1 (Truth), P3 (Invariants in Schema), P7 (Type Fidelity).
|
||||
|
||||
## The Schema Reflects the Domain (P1 Truth)
|
||||
|
||||
- A `users` table has columns that are attributes of a user, not attributes of the application.
|
||||
- If a column is named `is_active_for_feature_X`, the schema is lying. The domain does not have "feature X."
|
||||
- Normalize until it hurts, then denormalize only with evidence (P10 Performance Awareness).
|
||||
|
||||
## Invariants in the Schema (P3)
|
||||
|
||||
- NOT NULL where the value is required. UNIQUE where the value is unique.
|
||||
- CHECK constraints for range/domain: `age >= 0`, `status IN ('draft', 'published')`.
|
||||
- FOREIGN KEY for relationships. The database enforces; the application defends.
|
||||
- A constraint in the application but not the schema is a constraint that can be bypassed.
|
||||
|
||||
## Types (P7 Type Fidelity)
|
||||
|
||||
- `UUID` for IDs, not `VARCHAR`. `UUID` is a type; `VARCHAR(36)` is a string that looks like a UUID.
|
||||
- `TIMESTAMPTZ` for timestamps, not `VARCHAR` or `INTEGER`. Timezone-aware by default.
|
||||
- `ENUM` for finite domains, `VARCHAR` with CHECK for evolving domains.
|
||||
- `JSONB` for unstructured/semi-structured; not for data that should be a column.
|
||||
- `DECIMAL`/`NUMERIC` for money, never `FLOAT`. Floating point is for measurements, not money.
|
||||
|
||||
## Naming (P6 Naming Consistency)
|
||||
|
||||
- snake_case for tables and columns (PostgreSQL convention): `user_accounts`, `created_at`.
|
||||
- Singular table names (`user` not `users`) OR plural (`users` not `user`) — pick one, be consistent.
|
||||
- Foreign keys: `<singular_table>_id` (`user_id`), not `uid` or `user`.
|
||||
- Junction tables: alphabetical (`order_products`, not `products_orders`).
|
||||
|
||||
## Avoid (P2 Normalization Discipline)
|
||||
|
||||
- Computed columns that duplicate derivable data. Use a view or compute on read.
|
||||
- `created_by_name` (denormalized) when `created_by_id` + JOIN suffices. Denormalize only with evidence.
|
||||
- Soft-delete columns (`is_deleted`) without a corresponding constraint/behavior. Soft delete is a lifecycle decision (P8).
|
||||
|
||||
## Soft Delete vs Hard Delete (P8 Lifecycle Awareness)
|
||||
|
||||
- Soft delete (`deleted_at TIMESTAMP`) preserves auditability but complicates every query.
|
||||
- Hard delete loses history. Choose based on the domain's legal/audit requirements.
|
||||
- If soft delete: every query filters `WHERE deleted_at IS NULL` by default. A missing filter is a bug.
|
||||
|
||||
## What Violates Schema Design
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| `VARCHAR` for a UUID | P7 Type Fidelity |
|
||||
| No FOREIGN KEY on a relationship | P3, P9 Referential Integrity |
|
||||
| `FLOAT` for money | P7, P1 Truth |
|
||||
| `is_deleted` without consistent filtering | P8 Lifecycle |
|
||||
| A column named after a feature, not a domain concept | P1 Truth |
|
||||
@@ -0,0 +1,50 @@
|
||||
# CI/CD — Derived Rules
|
||||
|
||||
> Derives from `domains/devops/first-principles.md` P2 (Automation), P4 (Rollback First), P5 (Progressive Delivery).
|
||||
|
||||
## The Pipeline is the Process (P2)
|
||||
|
||||
- If it is not in the pipeline, it does not happen. Manual deploys are a bug.
|
||||
- The pipeline: lint → test → build → deploy → verify.
|
||||
- Every step is scripted, versioned, and reproducible. No "run this command on the server."
|
||||
|
||||
## Lint (P2, core C2 Clarity)
|
||||
|
||||
- Run the linter on every commit. Fail the build on lint errors.
|
||||
- Format check (prettier, gofmt, rustfmt). Format is not a debate; it is automated.
|
||||
- Security lint (eslint-plugin-security, bandit, gosec). Catch the obvious ones.
|
||||
|
||||
## Test (P2, domains/testing)
|
||||
|
||||
- Unit tests in the pipeline. Fast. Every commit.
|
||||
- Integration tests on merge to main. Slower. Every merge.
|
||||
- E2E tests before deploy. Slowest. Every deploy candidate.
|
||||
|
||||
## Build (P7 Immutability)
|
||||
|
||||
- Build once. The artifact is immutable. The same artifact goes to every environment.
|
||||
- The build is reproducible: same commit → same artifact (modulo timestamps, which are stripped).
|
||||
- Build artifacts are signed and stored. A deploy is a reference to an artifact, not a rebuild.
|
||||
|
||||
## Deploy (P4 Rollback First, P5 Progressive Delivery)
|
||||
|
||||
- Every deploy has a rollback. The rollback is tested before the deploy.
|
||||
- Progressive: canary (1% → 10% → 100%), blue-green, or feature flags.
|
||||
- No big-bang deploys. A big-bang deploy is a rollback with no rehearsal.
|
||||
|
||||
## Verify (P3 Observability)
|
||||
|
||||
- After deploy, verify: health checks, smoke tests, metric watching.
|
||||
- A deploy is not "done" when the code is on the server. It is done when the metrics are healthy.
|
||||
- Auto-rollback on metric regression. The pipeline watches; humans sleep.
|
||||
|
||||
## What Violates CI/CD Discipline
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| Manual deploy script | P2 Automation |
|
||||
| No rollback path | P4 Rollback First |
|
||||
| Big-bang deploy to prod | P5 Progressive Delivery |
|
||||
| Rebuild per environment | P7 Immutability |
|
||||
| Deploy without health check | P3 Observability |
|
||||
| No lint in CI | P2, C2 |
|
||||
@@ -0,0 +1,44 @@
|
||||
# Environments — Derived Rules
|
||||
|
||||
> Derives from `domains/devops/first-principles.md` P1 (Reproducibility), P6 (Configuration as Code), P7 (Immutability).
|
||||
|
||||
## Environment Parity (P1 Reproducibility)
|
||||
|
||||
- Dev, staging, prod are the same system, different data.
|
||||
- The same artifact runs in all three. The same config schema, different values.
|
||||
- "Works on my machine" is a parity failure. The machine is the pipeline.
|
||||
|
||||
## Configuration (P6 Configuration as Code)
|
||||
|
||||
- Config is in the repo (default values) + environment overrides (secrets, endpoints).
|
||||
- No snowflake servers. No "this one is different because we edited it in prod."
|
||||
- Config changes are PRs, not SSH sessions.
|
||||
|
||||
## Secrets (P9 Secret Hygiene via security)
|
||||
|
||||
- Secrets are per-environment. Dev secrets ≠ prod secrets.
|
||||
- Secrets come from a secrets manager (Vault, AWS Secrets Manager, Doppler), not env files in prod.
|
||||
- `.env` files are for local dev only. Prod uses the manager.
|
||||
|
||||
## Promotion (P5 Progressive Delivery via devops)
|
||||
|
||||
- Code moves dev → staging → prod. Never the reverse.
|
||||
- A hotfix to prod is backported to staging and dev. Don't let them diverge.
|
||||
- Promotion is automated. The pipeline decides when code is ready, not a human.
|
||||
|
||||
## Data (P1, domains/data P8 Lifecycle Awareness)
|
||||
|
||||
- Prod data is sacred. Never copy prod to dev without anonymization.
|
||||
- Staging uses prod-like data (anonymized, sampled). Dev uses synthetic data.
|
||||
- A test that runs against prod data is a test that can destroy prod data. Don't.
|
||||
|
||||
## What Violates Environment Discipline
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| "It works on my machine" | P1 Parity |
|
||||
| Manual config edit in prod | P6 Configuration as Code |
|
||||
| Dev secret reused in prod | P9 Secret Hygiene |
|
||||
| Copy prod DB to dev | P1, data P8 |
|
||||
| Hotfix in prod not backported | P5 (divergence) |
|
||||
| A snowflake server | P1, P6 |
|
||||
@@ -0,0 +1,43 @@
|
||||
# DevOps — First Principles
|
||||
|
||||
## 1. The Principles
|
||||
|
||||
### P1. Reproducibility
|
||||
Any environment can be rebuilt from source. Configuration is
|
||||
declarative and versioned.
|
||||
|
||||
### P2. Automation
|
||||
Manual steps are bugs waiting to happen. Everything is scripted,
|
||||
testable, repeatable.
|
||||
|
||||
### P3. Observability
|
||||
You cannot operate what you cannot see. Logs, metrics, and traces
|
||||
are first-class.
|
||||
|
||||
### P4. Rollback First
|
||||
Every deploy has a known-good rollback path. Rollback is rehearsed,
|
||||
not improvised.
|
||||
|
||||
### P5. Progressive Delivery
|
||||
Changes go out gradually — canary, blue-green, feature flags.
|
||||
Big-bang deploys are for prototypes.
|
||||
|
||||
### P6. Configuration as Code
|
||||
No snowflake servers. No "this one is different". Configuration is
|
||||
in the repo.
|
||||
|
||||
### P7. Immutability
|
||||
Build once, deploy many. Artifacts are immutable. Servers are not
|
||||
mutated in place.
|
||||
|
||||
### P8. Security at Every Layer
|
||||
Scanning, signing, SBOM, and supply chain integrity are part of the
|
||||
pipeline, not bolt-ons.
|
||||
|
||||
### P9. Documentation in the Pipeline
|
||||
The pipeline is the documentation. Reading the pipeline tells you
|
||||
how the system ships.
|
||||
|
||||
### P10. Failure as Expected
|
||||
Design for the failure mode, not the happy path. Chaos engineering
|
||||
is a discipline.
|
||||
@@ -0,0 +1,83 @@
|
||||
# Doc Templates — Derived Rules
|
||||
|
||||
> Derives from `domains/documentation/first-principles.md` P7 (Structure), P2 (Audience Awareness), P3 (Examples are Mandatory).
|
||||
|
||||
## Document Structure (P7)
|
||||
|
||||
Every framework document follows a consistent structure:
|
||||
|
||||
```
|
||||
# <Title>
|
||||
|
||||
> One-line purpose. Who reads this and when.
|
||||
|
||||
## 1. Manifesto (or Introduction)
|
||||
Why this document exists. The core belief.
|
||||
|
||||
## 2. The Principles (or Rules)
|
||||
The numbered, named, derivable rules. Each rule has:
|
||||
- A name (P1, P2, ...)
|
||||
- A one-line definition
|
||||
- A "what it means" paragraph
|
||||
- A "what violates it" entry
|
||||
|
||||
## 3. Conflict Resolution (for first-principles docs)
|
||||
Precedence among the rules. Non-tradeable declarations.
|
||||
|
||||
## 4. What Violates These Principles
|
||||
A table of violations and the principle they breach.
|
||||
|
||||
## 5. Relationship to Core
|
||||
Derivation link. See `matrix/principles-matrix.md`.
|
||||
```
|
||||
|
||||
- The structure is the contract. A reader can scan any framework doc and find the same sections.
|
||||
- Derived (topic) docs simplify: drop §3, replace §5 with "Derives from `<domain>/first-principles.md`."
|
||||
|
||||
## Audience Templates (P2)
|
||||
|
||||
### For Agents
|
||||
- Lead with what to check before completing a task.
|
||||
- Bullet lists, not paragraphs.
|
||||
- "Run this checklist" framing.
|
||||
|
||||
### For Humans (Onboarding)
|
||||
- Lead with what this is and who it's for.
|
||||
- Reading order. Quickstart.
|
||||
- Conversational tone, not terse.
|
||||
|
||||
### For Humans (Reference)
|
||||
- Lead with the rules, indexed.
|
||||
- Tables for lookup.
|
||||
- Cross-references to other docs.
|
||||
|
||||
## Example Template (P3 Examples are Mandatory)
|
||||
|
||||
Every rule includes an example. The template:
|
||||
|
||||
```
|
||||
### P<n>. <Name>
|
||||
<one-line definition>
|
||||
|
||||
<what it means>
|
||||
|
||||
Good:
|
||||
<example>
|
||||
|
||||
Bad:
|
||||
<counter-example> (violates P<n>)
|
||||
```
|
||||
|
||||
- The "good" example is realistic, not a strawman.
|
||||
- The "bad" example cites the principle it violates.
|
||||
- Examples are code, not prose. Show, don't tell.
|
||||
|
||||
## What Violates Doc Templates
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| A doc with no examples | P3 Examples are Mandatory |
|
||||
| A first-principles doc with no "what violates" table | P7 Structure |
|
||||
| A doc that does not link to its core derivation | P5 Discoverability |
|
||||
| Inconsistent structure across domain docs | P7 |
|
||||
| A doc with no audience statement | P2 Audience Awareness |
|
||||
@@ -0,0 +1,37 @@
|
||||
# Documentation — First Principles
|
||||
|
||||
## 1. The Principles
|
||||
|
||||
### P1. Documentation is Code
|
||||
It is versioned, reviewed, tested, and owned. Unowned docs rot.
|
||||
|
||||
### P2. Audience Awareness
|
||||
Different readers need different docs. A new user, an operator, and
|
||||
a contributor are different audiences.
|
||||
|
||||
### P3. Examples are Mandatory
|
||||
Code without examples is incomplete. Show, then explain.
|
||||
|
||||
### P4. Currency
|
||||
Docs that lie are worse than no docs. Stale docs are technical debt.
|
||||
|
||||
### P5. Discoverability
|
||||
The right doc is findable in under a minute. Structure, search, and
|
||||
indexing are part of the doc.
|
||||
|
||||
### P6. Conciseness
|
||||
Say what is needed, no more. Verbose docs are skimmed, then ignored.
|
||||
|
||||
### P7. Structure
|
||||
Consistent structure aids scanning. Headings, ordering, and
|
||||
formatting follow conventions.
|
||||
|
||||
### P8. Why Over What
|
||||
Document intent, decisions, and tradeoffs. The "what" is in the code.
|
||||
|
||||
### P9. Living Documents
|
||||
Docs evolve with code, not after. Doc PRs ship with code PRs.
|
||||
|
||||
### P10. Public by Default
|
||||
If it is not documented, it does not exist. The absence of docs is
|
||||
a feature gap.
|
||||
@@ -0,0 +1,41 @@
|
||||
# Error Handling — First Principles
|
||||
|
||||
## 1. The Principles
|
||||
|
||||
### P1. Errors are Data
|
||||
Errors are structured, typed, and intentional. They are values, not
|
||||
exceptions to the flow of code.
|
||||
|
||||
### P2. Fail Loudly
|
||||
Never swallow an error. Silent failure is worse than visible failure.
|
||||
|
||||
### P3. Fail Specifically
|
||||
Generic errors are debugging enemies. "Something went wrong" is
|
||||
never acceptable.
|
||||
|
||||
### P4. Preserve Context
|
||||
Errors carry where (file, line, function), when (timestamp, request),
|
||||
why (cause), and what (user-facing message).
|
||||
|
||||
### P5. Recoverable When Possible
|
||||
Retry, fallback, or degrade. Do not crash what can be salvaged.
|
||||
|
||||
### P6. Unrecoverable Means Stop
|
||||
When recovery is impossible or unsafe, fail fast. Do not limp on
|
||||
after fatal errors.
|
||||
|
||||
### P7. Errors are Boundaries
|
||||
Define how errors cross API, service, and module boundaries. Translation
|
||||
is explicit, not accidental.
|
||||
|
||||
### P8. User-Facing Errors are UX
|
||||
Error messages are a feature. They are written for the user, not the
|
||||
developer.
|
||||
|
||||
### P9. Errors are Logged
|
||||
Even when handled, errors are recorded. The handling is the recovery;
|
||||
the log is the memory.
|
||||
|
||||
### P10. Errors Don't Lie
|
||||
Never catch what you cannot handle. Never claim success on failure.
|
||||
Never claim failure on success.
|
||||
@@ -0,0 +1,94 @@
|
||||
# Error Patterns — Derived Rules
|
||||
|
||||
> Derives from `domains/errors/first-principles.md`. Common error-handling patterns and when to use them.
|
||||
|
||||
## Pattern 1: Result Type (P1 Errors are Data)
|
||||
|
||||
```typescript
|
||||
type Result<T, E> = { ok: true; value: T } | { ok: false; error: E };
|
||||
|
||||
function divide(a, b): Result<number, string> {
|
||||
if (b === 0) return { ok: false, error: "division by zero" };
|
||||
return { ok: true, value: a / b };
|
||||
}
|
||||
```
|
||||
|
||||
- Errors are values, not exceptions. The caller handles them explicitly.
|
||||
- Use when errors are expected (parsing, validation, fallible operations).
|
||||
- Avoid when errors are truly exceptional (out of memory, programmer error) — use exceptions/panics.
|
||||
|
||||
## Pattern 2: Sentinel Error (P4 Preserve Context)
|
||||
|
||||
```go
|
||||
var ErrNotFound = errors.New("not found")
|
||||
if errors.Is(err, ErrNotFound) { ... }
|
||||
```
|
||||
|
||||
- A sentinel is a known error value the caller checks against.
|
||||
- Use for a small, known set of error conditions.
|
||||
- Wrap with context: `fmt.Errorf("load user %d: %w", id, ErrNotFound)`.
|
||||
|
||||
## Pattern 3: Typed Error (P1, P3 Fail Specifically)
|
||||
|
||||
```rust
|
||||
enum AppError {
|
||||
NotFound(String),
|
||||
Invalid(String),
|
||||
Internal(String),
|
||||
}
|
||||
```
|
||||
|
||||
- A typed error carries the kind and the detail.
|
||||
- The caller matches on kind; the detail is for logging/display.
|
||||
- Use when there are distinct error categories the caller handles differently.
|
||||
|
||||
## Pattern 4: Error Wrapping (P4 Preserve Context)
|
||||
|
||||
```go
|
||||
return fmt.Errorf("query users: %w", err)
|
||||
```
|
||||
|
||||
- Wrap errors as they cross boundaries. The outer error says "what was happening"; the inner says "what went wrong."
|
||||
- The error chain is the stack trace of intent. Read it top-down: "I was doing X, which failed because Y, which was caused by Z."
|
||||
- Never wrap with a generic message ("operation failed"). Wrap with the specific operation.
|
||||
|
||||
## Pattern 5: Fail Fast (P6 Unrecoverable Means Stop)
|
||||
|
||||
```typescript
|
||||
if (config.secret === undefined) throw new Error("config.secret is required");
|
||||
```
|
||||
|
||||
- For unrecoverable conditions, fail immediately. Do not limp on.
|
||||
- Use at startup: missing required config, missing database, missing secrets.
|
||||
- Do not use for recoverable conditions (a 404 is recoverable; a missing secret is not).
|
||||
|
||||
## Pattern 6: Retry with Backoff (P5 Recoverable When Possible)
|
||||
|
||||
```python
|
||||
for attempt in range(3):
|
||||
try:
|
||||
return do_thing()
|
||||
except TransientError:
|
||||
sleep(2 ** attempt)
|
||||
raise PermanentError()
|
||||
```
|
||||
|
||||
- Retry transient errors (network, 429, 5xx). Do not retry permanent errors (400, 401).
|
||||
- Exponential backoff with jitter. A retry storm is worse than the original failure.
|
||||
- Bounded retries. Infinite retry is infinite hang (P8 Timeout Discipline).
|
||||
|
||||
## Pattern 7: Circuit Breaker (P3 Defense in Depth via errors P7)
|
||||
|
||||
- After N consecutive failures, stop trying. Return a fallback or error immediately.
|
||||
- Use for external dependencies (a downstream service, an API).
|
||||
- The breaker resets after a cooldown. Protects the system and the downstream.
|
||||
|
||||
## What Violates Error Patterns
|
||||
|
||||
| Violation | Pattern |
|
||||
|-----------|---------|
|
||||
| `catch (e) { return null }` | (anti-pattern, P2 Fail Loudly) |
|
||||
| `throw new Error("error")` | P3 Fail Specifically |
|
||||
| Retry without backoff | P5, P8 |
|
||||
| `return null` for "not found" | P1 (errors are data, not absence) |
|
||||
| `throw` in a recovery path | P6 (fail fast in the wrong place) |
|
||||
@@ -0,0 +1,43 @@
|
||||
# Observability — First Principles
|
||||
|
||||
## 1. The Principles
|
||||
|
||||
### P1. Structured by Default
|
||||
Logs, metrics, and traces are structured. Free-form text is for
|
||||
humans; machines need fields.
|
||||
|
||||
### P2. Correlation
|
||||
Every event is traceable to a request, a user, an action, a trace ID.
|
||||
Context flows through the system.
|
||||
|
||||
### P3. Sufficient Context
|
||||
The information needed to debug is in the event itself, not in tribal
|
||||
knowledge. "What was the user doing?" is answerable from logs.
|
||||
|
||||
### P4. Cardinality Discipline
|
||||
Labels and tags have bounded cardinality. Unbounded labels are an
|
||||
unbounded bill.
|
||||
|
||||
### P5. Sampling with Intent
|
||||
Sampling is deliberate, documented, and consistent. Head-based,
|
||||
tail-based, or none — chosen with reason.
|
||||
|
||||
### P6. No Secrets in Observability
|
||||
Observability data is not a secrets channel. Tokens, passwords, and
|
||||
PII do not enter logs, metrics, or traces.
|
||||
|
||||
### P7. Actionable Alerts
|
||||
Alerts are for things humans must act on. Every alert has a runbook.
|
||||
Alert fatigue is a defect.
|
||||
|
||||
### P8. SLI/SLO Awareness
|
||||
"Good enough" is defined. SLOs are targets, not aspirations.
|
||||
Error budgets are real.
|
||||
|
||||
### P9. Cost Awareness
|
||||
Observability has a cost — storage, compute, attention. Spend it on
|
||||
what earns it.
|
||||
|
||||
### P10. Debuggability Over Coverage
|
||||
A few high-cardinality traces beat millions of low-context logs.
|
||||
Signal beats volume.
|
||||
@@ -0,0 +1,49 @@
|
||||
# Logging — Derived Rules
|
||||
|
||||
> Derives from `domains/observability/first-principles.md` P1 (Structured by Default), P3 (Sufficient Context), P6 (No Secrets in Observability).
|
||||
|
||||
## Structured by Default (P1)
|
||||
|
||||
- Logs are JSON (or structured key-value). Free-form text is for humans; machines need fields.
|
||||
- Every log entry has: `timestamp`, `level`, `message`, `request_id`, plus domain-specific fields.
|
||||
- A log you cannot query is a log you cannot use. Structure is the query API.
|
||||
|
||||
## Levels (P3 Sufficient Context)
|
||||
|
||||
| Level | When |
|
||||
|-------|------|
|
||||
| ERROR | Something failed; an operator must look |
|
||||
| WARN | Something unexpected; not a failure but notable |
|
||||
| INFO | Significant application events (start, stop, deploy, user signup) |
|
||||
| DEBUG | Diagnostic detail; off in production by default |
|
||||
|
||||
- ERROR is not for "this branch ran." ERROR is for "this failed and someone should know."
|
||||
- Logging everything at ERROR means nothing is an error. Alert fatigue is a defect (observability P7).
|
||||
|
||||
## Context (P3)
|
||||
|
||||
- Every log in a request includes `request_id` (correlation ID). Trace the request across services.
|
||||
- Include the user ID, the action, the resource. "What was the user doing?" is answerable.
|
||||
- A log that says `"failed"` with no context is worse than no log. It is noise.
|
||||
|
||||
## No Secrets (P6, domains/security P9)
|
||||
|
||||
- Never log tokens, passwords, API keys, session IDs, PII.
|
||||
- Redact: replace the secret with `[REDACTED]` or a hash. Log the hash, not the value.
|
||||
- Never log the full request body. It may contain a token, a password, or PII.
|
||||
|
||||
## Volume (P4 Cardinality Discipline, core C8 Economy)
|
||||
|
||||
- Don't log every request at INFO. Log significant events.
|
||||
- A million logs a minute is not "good observability"; it is a storage bill and a signal-to-noise problem.
|
||||
- Sample high-volume logs (P5 Sampling with Intent). Sample deliberately, not randomly.
|
||||
|
||||
## What Violates Logging Discipline
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| `console.log("here")` | P1 (not structured) |
|
||||
| `logger.error("done")` | P3 (wrong level) |
|
||||
| `logger.info(req.body)` | P6 (secrets), volume |
|
||||
| A log with no `request_id` | P3 (no correlation) |
|
||||
| 10M logs/day at INFO | P4, C8 |
|
||||
@@ -0,0 +1,50 @@
|
||||
# Metrics — Derived Rules
|
||||
|
||||
> Derives from `domains/observability/first-principles.md` P1 (Structured by Default), P4 (Cardinality Discipline), P8 (SLI/SLO Awareness).
|
||||
|
||||
## The Four Golden Signals
|
||||
|
||||
| Signal | What |
|
||||
|--------|------|
|
||||
| Latency | Time to serve a request (p50, p95, p99) |
|
||||
| Traffic | Request rate (req/s) |
|
||||
| Errors | Error rate (errors/s, or % of traffic) |
|
||||
| Saturation | How full is the system (CPU, memory, queue depth) |
|
||||
|
||||
- All four are needed. Missing one is a blind spot.
|
||||
- Latency is percentiles, not average. Average hides the long tail.
|
||||
|
||||
## Cardinality (P4)
|
||||
|
||||
- Labels have bounded cardinality. `user_id` as a label = unbounded cardinality = unbounded bill.
|
||||
- High-cardinality dimensions belong in traces, not metrics.
|
||||
- A metric with `user_id` as a label is a 1M-series metric. That is a budget bomb.
|
||||
|
||||
## Counter vs Gauge vs Histogram
|
||||
|
||||
| Type | What | Example |
|
||||
|------|------|---------|
|
||||
| Counter | Monotonically increasing | `http_requests_total` |
|
||||
| Gauge | A value at a point in time | `active_connections` |
|
||||
| Histogram | Distribution of values | `http_request_duration_seconds` |
|
||||
|
||||
- A counter never decreases. Use `rate()` over time to get the rate.
|
||||
- A gauge can go up and down. Use it for saturation.
|
||||
- A histogram gives percentiles. Use it for latency.
|
||||
|
||||
## SLI/SLO (P8)
|
||||
|
||||
- SLI (Service Level Indicator): a metric of good/total (e.g., 99.9% of requests < 500ms).
|
||||
- SLO (Service Level Objective): the target for the SLI (e.g., 99.9% over 30 days).
|
||||
- Error budget: 1 - SLO. If SLO is 99.9%, error budget is 0.1%. Spend it on feature risk, not bugs.
|
||||
- When the error budget is exhausted, freeze features. Fix reliability.
|
||||
|
||||
## What Violates Metrics Discipline
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| `user_id` as a label | P4 Cardinality |
|
||||
| Average latency only | P8 (hides the tail) |
|
||||
| No error rate metric | P8 (no SLI) |
|
||||
| 1000 metrics, no SLO | P8 (no objective) |
|
||||
| A counter that decreases | (type error) |
|
||||
@@ -0,0 +1,45 @@
|
||||
# Tracing — Derived Rules
|
||||
|
||||
> Derives from `domains/observability/first-principles.md` P2 (Correlation), P5 (Sampling with Intent), P10 (Debuggability Over Coverage).
|
||||
|
||||
## Distributed Tracing (P2)
|
||||
|
||||
- A trace is a tree of spans. Each span is a unit of work with a start, end, and context.
|
||||
- `trace_id` ties spans across services. `span_id`/`parent_span_id` form the tree.
|
||||
- Every request has a `trace_id`. Propagate it in headers (`traceparent`, W3C standard).
|
||||
|
||||
## Sampling (P5)
|
||||
|
||||
- Head-based: sample at the start. Simple, but you miss the interesting failures.
|
||||
- Tail-based: sample at the end. Keep all errors, sample the successs. Better signal, harder to build.
|
||||
- A 100% trace rate is too expensive. 1% is often enough for debugging.
|
||||
- Sample deliberately: keep all errors, all slow requests, and a fraction of the rest.
|
||||
|
||||
## Context (P3 Sufficient Context, P10 Debuggability)
|
||||
|
||||
- A span has: name, start time, duration, attributes (key-value), events, status.
|
||||
- Attributes: `http.method`, `http.url`, `db.statement`, `user.id`. The fields you need to debug.
|
||||
- Events: notable points within a span (e.g., "cache miss", "retry").
|
||||
- A span with no attributes is a span that tells you nothing.
|
||||
|
||||
## Where to Span (P4 Locality)
|
||||
|
||||
- Span at service boundaries (HTTP in/out, DB query, queue send/receive).
|
||||
- Span at significant internal operations (a long computation, a batch step).
|
||||
- Don't span every function call. Span the meaningful units.
|
||||
|
||||
## Traces vs Logs (P10)
|
||||
|
||||
- Logs are events; traces are causality. Logs answer "what happened"; traces answer "why it was slow."
|
||||
- A trace contains log events (span events). They are not separate systems.
|
||||
- Use traces for the request flow; use logs for the details.
|
||||
|
||||
## What Violates Tracing Discipline
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| No `trace_id` propagation | P2 Correlation |
|
||||
| 100% trace rate | P5 Sampling |
|
||||
| Span per function | P4 (too noisy) |
|
||||
| Span with no attributes | P10 (no debug value) |
|
||||
| Traces for successful requests only | P5 (miss the failures) |
|
||||
@@ -0,0 +1,51 @@
|
||||
# Backend Performance — Derived Rules
|
||||
|
||||
> Derives from `domains/performance/first-principles.md` P1 (Measure First), P3 (Complexity Awareness), P4 (Resource Bounds).
|
||||
|
||||
## Measure First (P1)
|
||||
|
||||
- p50, p95, p99 latencies. The average hides the long tail.
|
||||
- Throughput (req/s) under load. Saturation point (where latency rises).
|
||||
- Resource utilization: CPU, memory, I/O, network. Each is a budget.
|
||||
|
||||
## N+1 Queries (P3 Complexity Awareness)
|
||||
|
||||
- A query in a loop is an N+1. It is O(N) queries instead of O(1).
|
||||
- Detect with a query counter in tests. A test that issues 100 queries is failing.
|
||||
- Fix with a JOIN, a batch load, or a dataloader. Never "we'll fix it later."
|
||||
|
||||
## Caching (P5 Caching with Intent)
|
||||
|
||||
- Cache what is: expensive to compute, stable, read often.
|
||||
- Invalidation is designed: TTL, event-based, or version-based. Never "we'll just clear it."
|
||||
- A cache without an invalidation strategy is a cache that serves stale data forever.
|
||||
- Multi-level: HTTP cache → CDN → app cache → DB. Each layer has its own rules.
|
||||
|
||||
## Async and Concurrency (P7 Async When Independent, see `domains/concurrency/`)
|
||||
|
||||
- I/O-bound work is async. Don't block a thread on a network call.
|
||||
- CPU-bound work is in a worker, not the request path.
|
||||
- Bounded queues everywhere (P9 Bounded Queues). Unbounded = OOM.
|
||||
|
||||
## Database (P4 Resource Bounds, see `domains/data/indexing.md`)
|
||||
|
||||
- Connection pool: bounded. The DB has a connection limit; the pool respects it.
|
||||
- Slow queries: logged, explained, fixed. A 10-second query is a bug.
|
||||
- Pagination on large tables: cursor, not offset. Offset scans rows.
|
||||
|
||||
## Resource Bounds (P4)
|
||||
|
||||
- Memory: bounded. A request that allocates unbounded memory is a DoS vector.
|
||||
- Timeouts: every external call has one. A call without a timeout is a call that can hang forever (P8 Timeout Discipline).
|
||||
- File handles, DB connections, HTTP connections: all bounded, all pooled.
|
||||
|
||||
## What Violates Backend Performance
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| N+1 query in a loop | P3 |
|
||||
| No timeout on an HTTP call | P4, P8 (concurrency) |
|
||||
| Unbounded in-memory sort | P4 |
|
||||
| Cache with no invalidation | P5 |
|
||||
| `SELECT *` | P4 (data P10) |
|
||||
| Connection pool size = 1000 | P4 (DB limit) |
|
||||
@@ -0,0 +1,40 @@
|
||||
# Performance — First Principles
|
||||
|
||||
## 1. The Principles
|
||||
|
||||
### P1. Measure First
|
||||
No optimization without measurement. Intuition about performance is
|
||||
usually wrong.
|
||||
|
||||
### P2. Critical Path Focus
|
||||
Optimize what users actually wait for. The 95th percentile matters
|
||||
more than the average.
|
||||
|
||||
### P3. Complexity Awareness
|
||||
Algorithmic cost is known. Big-O is a design conversation, not an
|
||||
afterthought.
|
||||
|
||||
### P4. Resource Bounds
|
||||
Memory, CPU, I/O, network — all bounded. Unbounded growth is a bug.
|
||||
|
||||
### P5. Caching with Intent
|
||||
Cache what is expensive, stable, and read often. Invalidation is
|
||||
designed, not bolted on.
|
||||
|
||||
### P6. Lazy by Default
|
||||
Compute only when needed. Pay only for what is used.
|
||||
|
||||
### P7. Async When Independent
|
||||
Work that does not depend on other work runs in parallel.
|
||||
|
||||
### P8. Budget Discipline
|
||||
Performance is a design constraint. The budget is set, not negotiated
|
||||
after the fact.
|
||||
|
||||
### P9. Perceived Performance
|
||||
What the user feels is what matters. A 200ms perceived response beats
|
||||
a 50ms measured one with no feedback.
|
||||
|
||||
### P10. Regression Prevention
|
||||
Performance tests catch what functional tests miss. The slow path
|
||||
is tested as a path.
|
||||
@@ -0,0 +1,55 @@
|
||||
# Frontend Performance — Derived Rules
|
||||
|
||||
> Derives from `domains/performance/first-principles.md` P1 (Measure First), P9 (Perceived Performance).
|
||||
|
||||
## Measure First (P1)
|
||||
|
||||
- Lighthouse, Core Web Vitals (LCP, FID/INP, CLS), RUM (Real User Monitoring).
|
||||
- A performance claim without a measurement is an opinion.
|
||||
- Measure the 75th percentile (P75), not the average. The average hides the long tail.
|
||||
|
||||
## The Three Core Web Vitals
|
||||
|
||||
| Vital | What | Target (P75) |
|
||||
|-------|------|--------------|
|
||||
| LCP (Largest Contentful Paint) | When the main content loads | ≤ 2.5s |
|
||||
| INP (Interaction to Next Paint) | When input is responded to | ≤ 200ms |
|
||||
| CLS (Cumulative Layout Shift) | Visual stability | ≤ 0.1 |
|
||||
|
||||
- LCP > 4s is poor. INP > 500ms is poor. CLS > 0.25 is poor.
|
||||
- Measure on mobile, not just desktop. Mobile is the long tail.
|
||||
|
||||
## Perceived Performance (P9)
|
||||
|
||||
- A skeleton screen beats a spinner. A spinner beats nothing.
|
||||
- Optimistic UI updates: the click responds immediately; the server confirms later.
|
||||
- Prefetch the next page on hover (if cheap). Prefetch is a bet, not a certainty.
|
||||
|
||||
## Bundle Size (P4 Resource Bounds, core C8 Economy)
|
||||
|
||||
- Ship less JavaScript. Every KB is parsed, compiled, and executed on the client.
|
||||
- Code-split routes. Lazy-load below-the-fold. Don't ship the admin bundle to the user bundle.
|
||||
- A 500KB JS bundle is large. A 2MB JS bundle is a defect.
|
||||
|
||||
## Images (P4, P6 Lazy by Default)
|
||||
|
||||
- WebP/AVIF, not JPEG/PNG. Modern formats are 30–50% smaller.
|
||||
- `loading="lazy"` on below-the-fold images. `width`/`height` to prevent CLS.
|
||||
- `srcset` for responsive images. Ship the right size to the right device.
|
||||
- Never ship a 4K image to a 360px screen.
|
||||
|
||||
## Rendering (P7 Async When Independent)
|
||||
|
||||
- Server-render the first paint (SSR/SSG). Hydrate after.
|
||||
- Avoid hydration waterfalls: a 3-second hydration is a 3-second blank page with a "loaded" script.
|
||||
- Defer non-critical hydration. Interactive above the fold first; below the fold later.
|
||||
|
||||
## What Violates Frontend Performance
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| 3MB JS bundle | P4, C8 |
|
||||
| LCP > 4s on mobile | P1 (measured) |
|
||||
| Layout shift on image load | CLS |
|
||||
| Synchronous hydration of a 50KB page | P7 |
|
||||
| No image optimization | P4 |
|
||||
@@ -0,0 +1,62 @@
|
||||
# Authentication — Derived Rules
|
||||
|
||||
> Derives from `domains/security/first-principles.md` P1 (Zero Trust), P2 (Least Privilege), P6 (Crypto Correctness).
|
||||
|
||||
## The Default: Authenticated
|
||||
|
||||
- Every endpoint is authenticated unless explicitly public.
|
||||
- "Public" is an explicit declaration, not a default.
|
||||
- A missing auth check is a bug, not an oversight.
|
||||
|
||||
## Authentication Methods
|
||||
|
||||
### Session-based (browser)
|
||||
- Server-side session, cookie-borne session ID.
|
||||
- Cookie: `HttpOnly`, `Secure`, `SameSite=Lax` (or `Strict`).
|
||||
- Session ID: cryptographically random, ≥ 128 bits.
|
||||
- Session timeout: bounded. Idle timeout + absolute timeout.
|
||||
|
||||
### Token-based (API, SPA)
|
||||
- Bearer token in `Authorization: Bearer <token>`.
|
||||
- Token: JWT (signed) or opaque (server-stored).
|
||||
- JWT: signed (HS256/RS256), never `none`. Short TTL (≤ 1 hour). Refresh token for long sessions.
|
||||
- Opaque: server-stored, revocable. Use when revocation matters.
|
||||
|
||||
### API Keys (service-to-service)
|
||||
- Long-lived, scoped, rotatable.
|
||||
- Sent in header (`X-API-Key`), not query string (logged in URLs).
|
||||
- Stored in a secrets manager, never in code.
|
||||
|
||||
## What Never to Do (P6 Crypto Correctness)
|
||||
|
||||
- Never roll your own auth. Use a vetted library or framework.
|
||||
- Never store passwords in plaintext. Use bcrypt/scrypt/argon2 with a work factor.
|
||||
- Never use MD5 or SHA1 for password hashing.
|
||||
- Never put a token in a URL. URLs are logged.
|
||||
- Never accept `alg: none` in a JWT.
|
||||
- Never trust a token without verifying its signature.
|
||||
|
||||
## Password Rules (P4 Input Validation)
|
||||
|
||||
- Minimum length: 12 characters (NIST 800-63B). No maximum (don't prevent long passwords).
|
||||
- No composition rules (no "must contain a symbol"). They don't help and frustrate users.
|
||||
- Check against a breach corpus (HIBP API or similar).
|
||||
- Rate limit login attempts. Lockout after N failures (with exponential backoff, not a hard lock).
|
||||
|
||||
## Multi-Factor (P3 Defense in Depth)
|
||||
|
||||
- MFA is the default for privileged accounts.
|
||||
- TOTP (RFC 6238) or WebAuthn. SMS is deprecated (SIM swapping).
|
||||
- MFA is a layer, not a replacement for strong primary auth.
|
||||
|
||||
## Session Lifecycle (P2 Least Privilege, P5 Reversibility)
|
||||
|
||||
- Sessions are revocable. A logout invalidates the session server-side, not just client-side.
|
||||
- Tokens are revocable. A refresh token revocation list is maintained.
|
||||
- "Remember me" extends the session, it does not make it permanent.
|
||||
|
||||
## Audit (P7 Auditability)
|
||||
|
||||
- Every auth event is logged: login (success/fail), logout, token issuance, token revocation.
|
||||
- Logs include: user ID, timestamp, IP, user agent, outcome.
|
||||
- Logs do not include: passwords, tokens, session IDs (use a hash).
|
||||
@@ -0,0 +1,61 @@
|
||||
# Authorization — Derived Rules
|
||||
|
||||
> Derives from `domains/security/first-principles.md` P2 (Least Privilege), P3 (Defense in Depth), P1 (Zero Trust).
|
||||
|
||||
## The Default: Deny
|
||||
|
||||
- Every request is denied unless explicitly authorized.
|
||||
- "Authorized by default" is an anti-pattern. The absence of a rule means denial.
|
||||
- A missing authz check is a bug, not a feature gap.
|
||||
|
||||
## Authorization Models
|
||||
|
||||
### RBAC (Role-Based)
|
||||
- Users have roles; roles have permissions.
|
||||
- Roles are coarse: `admin`, `editor`, `viewer`. Permissions are fine: `post:create`, `post:delete`.
|
||||
- Check permissions, not roles: `can(user, 'post:create')`, not `user.role === 'admin'`.
|
||||
- Roles can change; permission checks are stable.
|
||||
|
||||
### ABAC (Attribute-Based)
|
||||
- Authorization based on attributes of the user, resource, and context.
|
||||
- More expressive: "user can edit a post if user.department == post.department and post.status == 'draft'".
|
||||
- Use when RBAC is too coarse. Beware: complex ABAC is hard to audit.
|
||||
|
||||
### ReBAC (Relationship-Based)
|
||||
- Authorization based on relationships (e.g., Zanzibel).
|
||||
- "user:alice is editor of document:42" — check the relationship graph.
|
||||
- Scales for fine-grained, resource-specific access (Google Docs-style).
|
||||
|
||||
## Where to Check (P4 Locality)
|
||||
|
||||
- Check at the boundary: the API endpoint, the resolver, the controller.
|
||||
- Check at the data layer: defense in depth. A query that bypasses the controller still respects row-level security.
|
||||
- Never check only in the UI. The UI is a convenience, not a security boundary.
|
||||
|
||||
## Principle of Least Privilege (P2)
|
||||
|
||||
- A token/role gets the minimum permissions to do its job.
|
||||
- No "admin" role for daily work. Admin is for administration; daily work uses a scoped role.
|
||||
- Service tokens are scoped to one service's resources, not "all resources."
|
||||
|
||||
## IDOR (Insecure Direct Object Reference) (P1 Zero Trust)
|
||||
|
||||
- `/api/users/123` — does the requester own 123? Check.
|
||||
- Never assume the user can access any ID they request. The ID is input; inputs are untrusted.
|
||||
- Use scoped queries: `User.find({ id, owner: userId })`, not `User.find(id)`.
|
||||
|
||||
## Caching and Authz (P3 Defense in Depth)
|
||||
|
||||
- Authorization is not cached across users. A cached response for user A is not served to user B.
|
||||
- Cache keys include the user/role, not just the resource.
|
||||
- "Cache it as public if anyone can see it" — only if truly anyone (no auth).
|
||||
|
||||
## What Violates Authorization
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| `/admin` endpoint with no authz check | P1 Zero Trust |
|
||||
| `user.role === 'admin'` instead of permission check | P2 (roles change) |
|
||||
| IDOR: `User.find(req.params.id)` with no ownership check | P1 Zero Trust |
|
||||
| Cached authz decision reused across users | P3 Defense in Depth |
|
||||
| Service token with "all resources" scope | P2 Least Privilege |
|
||||
@@ -0,0 +1,102 @@
|
||||
# Security — First Principles
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Status:** Foundational
|
||||
**Audience:** AI agents and humans handling authentication, data,
|
||||
trust boundaries, or any security-relevant code.
|
||||
|
||||
## 1. Manifesto
|
||||
|
||||
Security is not a feature. It is a property of correct code. The
|
||||
highest quality code is code that does what it is supposed to do —
|
||||
and nothing else, no matter who asks.
|
||||
|
||||
An AI agent using this framework does not "add security". It writes
|
||||
secure code by default. There is no version of correct code that is
|
||||
insecure.
|
||||
|
||||
## 2. The Principles
|
||||
|
||||
### P1. Zero Trust
|
||||
No request, user, system, or input is trusted by default. Trust is
|
||||
earned at every boundary, every time.
|
||||
|
||||
### P2. Least Privilege
|
||||
Every actor — user, service, process — gets the minimum access
|
||||
required to do its job, for the minimum time required.
|
||||
|
||||
### P3. Defense in Depth
|
||||
Security is layered. No single control is load-bearing. The failure
|
||||
of one control does not compromise the system.
|
||||
|
||||
### P4. Input Validation
|
||||
All input is untrusted until proven otherwise. Validation happens at
|
||||
the boundary, against a schema, with explicit failure modes.
|
||||
|
||||
### P5. Output Safety
|
||||
All output is encoded, escaped, or filtered for its destination
|
||||
context. The system never trusts its callers, including itself.
|
||||
|
||||
### P6. Cryptographic Correctness
|
||||
Crypto is hard. Use vetted, maintained libraries. Never roll your
|
||||
own. Never invent your own primitives. Never bypass a primitive to
|
||||
"make it work".
|
||||
|
||||
### P7. Auditability
|
||||
Security-relevant events — auth attempts, authz decisions, data
|
||||
access, configuration changes — are logged with sufficient context
|
||||
to investigate.
|
||||
|
||||
### P8. Fail Securely
|
||||
When security fails, it fails closed. The default state is denied,
|
||||
disabled, or safe. Errors never grant access by accident.
|
||||
|
||||
### P9. Secret Hygiene
|
||||
Secrets are not in code, configs, logs, error messages, URLs, or
|
||||
screenshots. Secrets are loaded from a secrets manager and treated
|
||||
as transient.
|
||||
|
||||
### P10. Surface Minimization
|
||||
The smaller the attack surface, the smaller the risk. Dependencies
|
||||
are minimized. Endpoints are minimized. Features are minimized. Code
|
||||
that does not exist cannot be exploited.
|
||||
|
||||
## 3. Conflict Resolution
|
||||
|
||||
1. Zero Trust — never sacrificed.
|
||||
2. Least Privilege — never sacrificed.
|
||||
3. Defense in Depth — never sacrificed.
|
||||
4. Input Validation — never sacrificed.
|
||||
5. Output Safety — never sacrificed.
|
||||
6. Cryptographic Correctness — never sacrificed.
|
||||
7. Fail Securely — never sacrificed.
|
||||
8. Auditability — sacrificed only when logging itself is the threat.
|
||||
9. Secret Hygiene — never sacrificed.
|
||||
10. Surface Minimization — sacrificed only when a feature is required.
|
||||
|
||||
Eight of ten principles are non-tradeable. Security does not
|
||||
trade-off. It is either present or it is not.
|
||||
|
||||
## 4. What Violates These Principles
|
||||
|
||||
| Violation | Principle Breached |
|
||||
|------------------------------------|----------------------|
|
||||
| `SELECT *` from user input | P4 Input Validation |
|
||||
| `eval()` of any string | P4, P5 |
|
||||
| Hardcoded API key in source | P9 Secret Hygiene |
|
||||
| Catch-all `catch (e) {}` | P7 Auditability, P8 Fail Securely |
|
||||
| `md5` or `sha1` for security | P6 Crypto Correctness |
|
||||
| Open CORS to `*` in production | P1 Zero Trust, P10 |
|
||||
| Detailed error to end user | P7 Auditability, P5 |
|
||||
| `chmod 777` | P2 Least Privilege |
|
||||
| Long-lived session token | P1, P2 |
|
||||
| Logging the request body | P9 Secret Hygiene |
|
||||
|
||||
These are never acceptable. They are not "to be reviewed later".
|
||||
They are rejected on sight.
|
||||
|
||||
## 5. Relationship to Core
|
||||
|
||||
Subordinate to `core/first-principles.md`. Note: security principles
|
||||
overlap heavily with core Correctness (C1) and Observability (C7).
|
||||
See `matrix/principles-matrix.md`.
|
||||
@@ -0,0 +1,64 @@
|
||||
# Input Validation — Derived Rules
|
||||
|
||||
> Derives from `domains/security/first-principles.md` P4 (Input Validation), P5 (Output Safety).
|
||||
|
||||
## The Rule
|
||||
|
||||
All input is untrusted until validated. Validation happens at the boundary, against a schema, with explicit failure modes.
|
||||
|
||||
## Validate at the Boundary (P4 Locality)
|
||||
|
||||
- The API endpoint, the controller, the message handler — the entry point validates.
|
||||
- Internal code trusts validated input. Unvalidated input never reaches the database.
|
||||
- Defense in depth: the database also has constraints (P3 Defense in Depth).
|
||||
|
||||
## Schema Validation
|
||||
|
||||
- Use a schema library (zod, joi, pydantic, json-schema). Never hand-write validation.
|
||||
- The schema is the contract. The schema is versioned. The schema is tested.
|
||||
- Reject unknown fields (`additionalProperties: false` by default). Be explicit.
|
||||
|
||||
## Validation Types
|
||||
|
||||
### Type Validation
|
||||
- `id` is a UUID, not a string. `age` is an integer ≥ 0. `email` matches a regex (or better, is parsed).
|
||||
- Never accept `any`. Never accept `string` for a typed value.
|
||||
|
||||
### Range Validation
|
||||
- `limit` ≤ 100. `page` ≥ 1. `quantity` ≥ 1 and ≤ stock.
|
||||
- Bounds are explicit. No "unbounded" inputs.
|
||||
|
||||
### Format Validation
|
||||
- `email` is parsed (not just regex). `url` is parsed. `date` is parsed.
|
||||
- A regex for email is wrong (RFC 5322 is not a regular language). Use a parser.
|
||||
|
||||
### Semantic Validation
|
||||
- `start_date < end_date`. `user_id` exists. `product_id` is in stock.
|
||||
- Semantic validation may require a database lookup. That's fine.
|
||||
|
||||
### Presence Validation
|
||||
- Required fields are present. Optional fields are absent or null.
|
||||
- Empty string `""` is not the same as null. Be explicit about which you accept.
|
||||
|
||||
## Failure Modes (P8 Fail Securely)
|
||||
|
||||
- Validation failure → 400 Bad Request with a structured error (`domains/api/error-responses.md`).
|
||||
- Never coerce: `"5" + 3` is not validation. Reject, don't guess.
|
||||
- Never default: a missing required field is an error, not a default value.
|
||||
|
||||
## Output Safety (P5 Output Safety)
|
||||
|
||||
- Validation is for input. Encoding is for output.
|
||||
- Output to HTML: HTML-encode. Output to SQL: parameterize. Output to URL: URL-encode.
|
||||
- Never trust validated input for output. Validate on the way in, encode on the way out.
|
||||
|
||||
## What Violates Input Validation
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| `JSON.parse(req.body)` with no schema | P4 Input Validation |
|
||||
| `parseInt(req.query.id)` with no range check | P4 |
|
||||
| `additionalProperties: true` by default | P1 Contract Fidelity |
|
||||
| Coercing `"5"` to `5` silently | P8 Fail Securely |
|
||||
| SQL string interpolation (even of "validated" input) | P5 Output Safety |
|
||||
| Regex for email validation | P4 (use a parser) |
|
||||
@@ -0,0 +1,72 @@
|
||||
# Secrets — Derived Rules
|
||||
|
||||
> Derives from `domains/security/first-principles.md` P9 (Secret Hygiene), P1 (Zero Trust), P6 (Crypto Correctness).
|
||||
|
||||
## What is a Secret
|
||||
|
||||
A secret is any value whose disclosure compromises the system. Examples:
|
||||
- API keys, access tokens, refresh tokens
|
||||
- Database passwords, service passwords
|
||||
- Private keys (TLS, signing, encryption)
|
||||
- OAuth client secrets, JWT signing keys
|
||||
- Encryption keys (KMS, envelope encryption)
|
||||
|
||||
## Never in Code (P9)
|
||||
|
||||
- No secrets in source files. No secrets in comments. No secrets in string constants.
|
||||
- No secrets in config files committed to git. Use `.env` (gitignored) or a secrets manager.
|
||||
- No secrets in test fixtures. Tests use fake/dummy values, never real secrets.
|
||||
|
||||
## Never in Logs (P9, domains/observability P6)
|
||||
|
||||
- No secrets in log messages, error messages, or stack traces.
|
||||
- Redact before logging: replace the secret with `[REDACTED]` or a hash.
|
||||
- Never log the request body (it may contain a token). Log the request ID, not the body.
|
||||
|
||||
## Never in URLs (P9, P1)
|
||||
|
||||
- URLs are logged (server logs, proxy logs, browser history, referrer headers).
|
||||
- A token in the URL is a token in everyone's logs.
|
||||
- Use headers (`Authorization: Bearer ...`), not query strings.
|
||||
|
||||
## Never in Error Messages (P9, domains/errors)
|
||||
|
||||
- "Authentication failed: invalid API key sk-abc123" — the secret is in the error.
|
||||
- "Authentication failed: invalid API key" — the secret is not.
|
||||
- Error messages are for humans; humans do not need the secret to debug.
|
||||
|
||||
## Storage (P6 Crypto Correctness)
|
||||
|
||||
- At rest: encrypted (KMS, envelope encryption). Never plaintext on disk.
|
||||
- In memory: minimal lifetime. Load on use, not on boot. Zero after use (where the language allows).
|
||||
- In transit: TLS only. No plaintext HTTP for secrets, ever.
|
||||
|
||||
## Rotation (P5 Reversibility, P2 Least Privilege)
|
||||
|
||||
- Secrets are rotatable. A secret that cannot be rotated is a liability.
|
||||
- Rotation is documented and rehearsed. Not improvised during an incident.
|
||||
- Old secrets are revoked after rotation, not "kept just in case."
|
||||
- Short-lived secrets (≤ 1 hour) are better than long-lived secrets (≤ forever).
|
||||
|
||||
## Scope (P2 Least Privilege)
|
||||
|
||||
- A secret has the minimum scope. A secret for service A does not work for service B.
|
||||
- Scoped tokens: `scope: read:orders`, not `scope: *`.
|
||||
- One secret per environment. Dev, staging, prod use different secrets.
|
||||
|
||||
## The `.gitignore` Rule
|
||||
|
||||
- `.env`, `.env.secrets`, `.env.*` are in `.gitignore` by default (see Atelier's own `.gitignore`).
|
||||
- A secret committed to git is a leaked secret. Rotate immediately. History is forever.
|
||||
- Pre-commit hooks scan for high-entropy strings. Use them.
|
||||
|
||||
## What Violates Secret Hygiene
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| `API_KEY = "sk-abc123"` in source | P9 |
|
||||
| `?token=abc` in a URL | P9, P1 |
|
||||
| `console.log(req.body)` where body contains a token | P9, observability P6 |
|
||||
| `catch (e) { throw new Error("DB password is pwd123") }` | P9, errors |
|
||||
| Same secret in dev and prod | P2 |
|
||||
| A 5-year-old API key with no rotation | P5 |
|
||||
@@ -0,0 +1,50 @@
|
||||
# Supply Chain — Derived Rules
|
||||
|
||||
> Derives from `domains/security/first-principles.md` P10 (Surface Minimization), P3 (Defense in Depth), P7 (Auditability).
|
||||
|
||||
## Dependencies are Attack Surface (P10)
|
||||
|
||||
- Every dependency is code you did not write but must trust. Minimize it.
|
||||
- A dependency you do not need is a vulnerability you do not have.
|
||||
- Audit dependencies regularly. Remove unused ones (`npm prune`, `pip-autoremove`).
|
||||
|
||||
## Lockfiles (P1 Correctness, P5 Reversibility)
|
||||
|
||||
- Pin exact versions in a lockfile (`package-lock.json`, `yarn.lock`, `Pipfile.lock`, `Cargo.lock`).
|
||||
- Commit the lockfile. A reproducible build requires a committed lock.
|
||||
- `npm ci` (not `npm install`) in CI. `pip install -r requirements.txt` with pinned versions.
|
||||
|
||||
## Integrity (P6 Crypto Correctness)
|
||||
|
||||
- Subresource integrity for web assets: `<script src="..." integrity="sha384-...">`.
|
||||
- Package signatures where available (signed npm packages, GPG-signed apt packages).
|
||||
- Verify checksums on downloaded artifacts. A tarball without a checksum is untrusted.
|
||||
|
||||
## Vulnerability Scanning (P3 Defense in Depth)
|
||||
|
||||
- `npm audit`, `pip-audit`, `cargo audit`, `trivy`, `snyk` — run in CI.
|
||||
- Fail the build on high/critical vulnerabilities (configurable threshold).
|
||||
- Auto-merge security PRs from Dependabot/Renovate when the patch is non-breaking.
|
||||
|
||||
## Provenance (P7 Auditability)
|
||||
|
||||
- SBOM (Software Bill of Materials): `cyclonedx` or `spdx` output. Know what is in your build.
|
||||
- SLSA (Supply-chain Levels for Software Artifacts): provenance attestation for builds.
|
||||
- Signed artifacts: cosign, sigstore. A build you cannot verify is untrusted.
|
||||
|
||||
## Private Registries (P2 Least Privilege)
|
||||
|
||||
- Internal packages come from a private registry, not public npm/PyPI.
|
||||
- A typo-squatted public package is a supply chain attack (`lodash` vs `lodahs`).
|
||||
- Scope your registry: `@myorg:registry=https://registry.myorg.com`.
|
||||
|
||||
## What Violates Supply Chain
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| `npm install` (no lockfile) in CI | P1, P5 |
|
||||
| Unpinned dependency `^1.2.3` in production | P1 |
|
||||
| No vulnerability scanning in CI | P3 |
|
||||
| `eval` of a package's README | P10 (surface) |
|
||||
| A dependency with 0 weekly downloads | P10 (no eyes) |
|
||||
| No SBOM for a shipped artifact | P7 |
|
||||
@@ -0,0 +1,44 @@
|
||||
# Testing — First Principles
|
||||
|
||||
## 1. The Principles
|
||||
|
||||
### P1. Tests as Specification
|
||||
Tests document what the code should do. Reading the tests is reading
|
||||
the contract.
|
||||
|
||||
### P2. Independence
|
||||
Tests do not depend on each other. Order does not matter. Parallelism
|
||||
is the default.
|
||||
|
||||
### P3. Determinism
|
||||
Same input, same output, every time. No time, randomness, network, or
|
||||
filesystem in the test path unless explicitly modeled.
|
||||
|
||||
### P4. Fast Feedback
|
||||
Tests run in seconds, not minutes. Slow tests are skipped, then
|
||||
deleted.
|
||||
|
||||
### P5. Coverage of Behavior
|
||||
Cover what the code does, not what it is. Lines covered is not the
|
||||
goal. Behaviors exercised is the goal.
|
||||
|
||||
### P6. Failure Specificity
|
||||
A failing test names the file, the function, the input, the
|
||||
expectation, and the actual. A test that fails unhelpfully is
|
||||
broken.
|
||||
|
||||
### P7. Realism
|
||||
Test data resembles production data in shape, distribution, and
|
||||
edge cases. Toy data hides bugs.
|
||||
|
||||
### P8. Maintainability
|
||||
Tests are first-class code. They are read, reviewed, and refactored.
|
||||
Test code is not throwaway.
|
||||
|
||||
### P9. Edge Case Coverage
|
||||
Boundaries, nulls, empty sets, maximums, minimums, and invalid inputs
|
||||
are tested. The middle of the range is the easy part.
|
||||
|
||||
### P10. No Test Theater
|
||||
A test that cannot fail is not a test. A test that asserts nothing
|
||||
is a lie. Tests earn their place by being able to catch real bugs.
|
||||
@@ -0,0 +1,49 @@
|
||||
# Test Fixtures — Derived Rules
|
||||
|
||||
> Derives from `domains/testing/first-principles.md` P7 (Realism), P2 (Independence), P3 (Determinism).
|
||||
|
||||
## Fixtures are Real Data (P7 Realism)
|
||||
|
||||
- A fixture resembles production data in shape, distribution, and edge cases.
|
||||
- A fixture with `name: "test"` and `email: "a@b.c"` hides bugs that real data surfaces.
|
||||
- Use realistic names, realistic emails, realistic dates. `"Jane Doe", "jane.doe@example.com", "2026-03-15"`.
|
||||
|
||||
## Factory Over Fixture (P2 Independence, P3 Determinism)
|
||||
|
||||
- A fixture file is shared state. A factory is fresh state per test.
|
||||
- Prefer factories (e.g., `factory.User()` returning a new instance) over shared fixture files.
|
||||
- A shared fixture is mutated by one test, breaks another. Independence is violated.
|
||||
|
||||
## Builders for Complex Data
|
||||
|
||||
- A builder (`UserBuilder().withEmail().withAdmin().build()`) composes only the fields the test needs.
|
||||
- A builder with defaults: every field has a sensible default; tests override only what they test.
|
||||
- A builder is the test's API to data. Stable, composable, readable.
|
||||
|
||||
## Setup and Teardown (P2 Independence)
|
||||
|
||||
- Every test cleans up after itself. No test leaves state for the next.
|
||||
- `setUp`/`tearDown` (or `beforeEach`/`afterEach`) restore the world.
|
||||
- A test that depends on the order of execution is not independent.
|
||||
|
||||
## Determinism (P3)
|
||||
|
||||
- No `Date.now()`, no `Math.random()` in fixtures. Inject the clock, inject the RNG.
|
||||
- A fixture that uses "now" is non-deterministic. It passes today and fails tomorrow.
|
||||
- Fix timestamps: `createdAt: new Date("2026-01-01T00:00:00Z")`.
|
||||
|
||||
## Edge Case Fixtures (P9 Edge Case Coverage)
|
||||
|
||||
- A fixture set includes: the empty case, the single-item case, the max-size case, the unicode case.
|
||||
- A fixture set includes invalid data: malformed email, negative age, future date.
|
||||
- Edge case fixtures are first-class, not "extra credit."
|
||||
|
||||
## What Violates Fixture Discipline
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| `name: "test"` fixture | P7 Realism |
|
||||
| Shared fixture file mutated across tests | P2 Independence |
|
||||
| `createdAt: new Date()` (now) in fixture | P3 Determinism |
|
||||
| No edge-case fixtures | P9 Edge Case Coverage |
|
||||
| A 500-line fixture file | P3 (complexity) |
|
||||
@@ -0,0 +1,60 @@
|
||||
# Test Pyramid — Derived Rules
|
||||
|
||||
> Derives from `domains/testing/first-principles.md` P4 (Fast Feedback), P5 (Coverage of Behavior), P10 (No Test Theater).
|
||||
|
||||
## The Pyramid
|
||||
|
||||
```
|
||||
/\
|
||||
/e2e\ few, slow, integration
|
||||
/------\
|
||||
/ integ \ some, medium, contract
|
||||
/----------\
|
||||
/ unit \ many, fast, isolated
|
||||
/--------------\
|
||||
```
|
||||
|
||||
- **Unit (many):** test a function/class in isolation. Fast (< 10ms each). The bulk of tests.
|
||||
- **Integration (some):** test components together (DB, API client, queue). Medium (< 1s each).
|
||||
- **E2E (few):** test the whole system from outside. Slow (> 1s each). The tip of the pyramid.
|
||||
|
||||
## Why a Pyramid (P4 Fast Feedback)
|
||||
|
||||
- A pyramid inverts to a "ice cream cone" (many e2e, few unit) when devs avoid unit tests.
|
||||
- Inverted pyramids are slow and flaky. The feedback loop breaks.
|
||||
- The pyramid shape preserves fast feedback: most failures are unit failures, found in < 10ms.
|
||||
|
||||
## What Goes Where
|
||||
|
||||
| Test Type | What it Covers | Speed | Count |
|
||||
|-----------|----------------|-------|-------|
|
||||
| Unit | A function, a class, a pure module | < 10ms | Many |
|
||||
| Integration | DB queries, API contract, queue behavior | < 1s | Some |
|
||||
| E2E | A user flow, an API request → response end-to-end | > 1s | Few |
|
||||
|
||||
- A unit test does not hit the database. A unit test does not make a network call.
|
||||
- An integration test does not test business logic; it tests the integration.
|
||||
- An e2e test does not test edge cases; it tests the happy path. Edge cases are unit tests.
|
||||
|
||||
## Anti-Patterns (P10 No Test Theater)
|
||||
|
||||
- **Ice cream cone:** many e2e, few unit. Slow, flaky, no signal.
|
||||
- **Cupcake:** same count at every level. No pyramid shape. Slow.
|
||||
- **Only unit:** 100% unit coverage, 0% integration. The system is untested as a whole.
|
||||
- **Only e2e:** every edge case is an e2e test. The suite takes an hour.
|
||||
|
||||
## Coverage (P5)
|
||||
|
||||
- Unit coverage of behavior: every branch, every edge case, every error path.
|
||||
- Integration coverage of contracts: every API endpoint, every DB query, every queue interaction.
|
||||
- E2E coverage of flows: the top 3–5 critical user flows. Not every permutation.
|
||||
|
||||
## What Violates the Pyramid
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| E2E test for an edge case | P4 (slow feedback) |
|
||||
| Unit test that hits the DB | P2 (not isolated) |
|
||||
| 0 integration tests | P5 (no coverage of contracts) |
|
||||
| 500 e2e tests, 50 unit tests | P10 (theater) |
|
||||
| A 30-second test suite | P4 (feedback loop broken) |
|
||||
@@ -0,0 +1,50 @@
|
||||
# Accessibility Requirements
|
||||
|
||||
> Detailed, enforceable accessibility rules. Every component, page,
|
||||
> and flow must pass these. Failure is disqualifying — see
|
||||
> `first-principles.md` P2.
|
||||
|
||||
## Perceivable
|
||||
|
||||
- [ ] Every image has `alt` text or is marked `alt=""` if decorative.
|
||||
- [ ] Every video has captions. Every audio has transcripts.
|
||||
- [ ] Color contrast meets WCAG 2.1 AA (4.5:1 text, 3:1 UI).
|
||||
- [ ] Information is not conveyed by color alone.
|
||||
- [ ] Text resizes to 200% without loss of content or function.
|
||||
|
||||
## Operable
|
||||
|
||||
- [ ] Every interactive element is keyboard-reachable.
|
||||
- [ ] Focus order is logical and matches visual order.
|
||||
- [ ] Focus is always visible (≥ 3:1 contrast).
|
||||
- [ ] No keyboard traps.
|
||||
- [ ] Touch targets are ≥ 44×44 CSS pixels.
|
||||
- [ ] Motion can be disabled via `prefers-reduced-motion`.
|
||||
- [ ] No flashing content > 3 flashes per second.
|
||||
|
||||
## Understandable
|
||||
|
||||
- [ ] Page language is declared.
|
||||
- [ ] Form fields have associated labels.
|
||||
- [ ] Error messages identify the field and the problem.
|
||||
- [ ] Navigation is consistent across pages.
|
||||
- [ ] Abbreviations and jargon are explained on first use.
|
||||
|
||||
## Robust
|
||||
|
||||
- [ ] HTML validates.
|
||||
- [ ] ARIA is used correctly (roles, states, properties).
|
||||
- [ ] Components work across assistive technologies.
|
||||
- [ ] No ARIA is used where native HTML would suffice.
|
||||
|
||||
## Testing
|
||||
|
||||
Every accessibility requirement is verified by:
|
||||
|
||||
1. Automated tool (axe-core, Lighthouse, etc.)
|
||||
2. Keyboard-only navigation
|
||||
3. Screen reader (NVDA, VoiceOver) walkthrough
|
||||
4. Zoom to 200%
|
||||
5. Reduced motion enabled
|
||||
|
||||
All five must pass. Automated-only is not acceptance.
|
||||
@@ -0,0 +1,87 @@
|
||||
# Component Design Principles
|
||||
|
||||
> Sibling to `first-principles.md` in this domain. These rules govern
|
||||
> how individual UI components are designed, named, composed, and
|
||||
> evolved.
|
||||
|
||||
## 1. Single Responsibility
|
||||
A component does one thing, completely.
|
||||
|
||||
- If a component's name contains "And", split it.
|
||||
- If a component has more than one primary action, split it.
|
||||
- If a component's props cannot be described in one sentence, split it.
|
||||
|
||||
## 2. Composition Over Configuration
|
||||
Components combine. They do not configure.
|
||||
|
||||
- Prefer small, composable primitives over large, configurable ones.
|
||||
- Variants are separate components, not boolean props.
|
||||
- Layout is composition. The component owns its content, not its
|
||||
position.
|
||||
- A `Button` is not `Button primary large loading disabled`. It is
|
||||
`Button` composed with `<Icon>`, `<Spinner>`, and styled by context.
|
||||
|
||||
## 3. Explicit Boundaries
|
||||
A component's contract is its props and its events.
|
||||
|
||||
- All inputs are typed. Required inputs are required.
|
||||
- All outputs are typed. Events are named for what happened, not what
|
||||
was clicked.
|
||||
- A component never reads from global state implicitly.
|
||||
- A component never mutates its inputs.
|
||||
|
||||
## 4. Predictable State
|
||||
A component's state is owned at the lowest level that can manage it.
|
||||
|
||||
- If only the component cares, the component owns it.
|
||||
- If siblings care, the parent owns it.
|
||||
- If the world cares, the application owns it.
|
||||
- State is never duplicated across levels.
|
||||
|
||||
## 5. Render Purity
|
||||
Given the same props and state, a component renders the same output.
|
||||
|
||||
- No hidden inputs (time, randomness, network) inside the render path.
|
||||
- Side effects are in effects, event handlers, or data loaders — not
|
||||
in render.
|
||||
- A component's render is safe to call repeatedly.
|
||||
|
||||
## 6. Accessible by Default
|
||||
A component is not finished until it is accessible.
|
||||
|
||||
- Every interactive component is keyboard-reachable and screen-reader
|
||||
announced.
|
||||
- Every form control has a label.
|
||||
- Every image has alt text or is marked decorative.
|
||||
- Every focusable element has a visible focus state.
|
||||
- Accessibility is in the component contract, not a wrapper.
|
||||
|
||||
## 7. Style via Tokens
|
||||
A component references design tokens, never raw values.
|
||||
|
||||
- No hardcoded colors, sizes, or fonts in component code.
|
||||
- Tokens are the API to the design system.
|
||||
- A component without a token is a design debt.
|
||||
|
||||
## 8. Stable Identity
|
||||
A component's identity is its public name, not its implementation.
|
||||
|
||||
- Renaming a component is a breaking change.
|
||||
- Removing a prop is a breaking change.
|
||||
- Changing a prop's semantics is a breaking change.
|
||||
- Deprecate before you delete. Migrate before you rename.
|
||||
|
||||
## 9. Testable in Isolation
|
||||
A component can be rendered, interacted with, and verified in isolation.
|
||||
|
||||
- Components ship with stories, examples, or fixtures.
|
||||
- Tests cover behavior, not implementation.
|
||||
- Visual regression is part of the contract.
|
||||
|
||||
## 10. Documented Intent
|
||||
A component is shipped with a "why" and a "when".
|
||||
|
||||
- What is it for?
|
||||
- When should it be used?
|
||||
- When should it NOT be used?
|
||||
- What are the common mistakes with it?
|
||||
@@ -0,0 +1,45 @@
|
||||
# UI Copywriting — Derived Rules
|
||||
|
||||
> Derives from `domains/uiux/first-principles.md` P3 (Clarity), P1 (User Primacy), P5 (Forgiveness).
|
||||
|
||||
## Write for the User (P1 User Primacy)
|
||||
|
||||
- The reader is a user, not a developer. "Sign in" not "Authenticate."
|
||||
- The reader is busy. Short sentences. Active voice. Verbs first.
|
||||
- The reader is anxious. Reassure. "Your changes are saved." Not "State persisted."
|
||||
|
||||
## Clarity (P3)
|
||||
|
||||
- One idea per sentence. One action per button.
|
||||
- Labels are nouns: "Email", "Password". Actions are verbs: "Sign in", "Save".
|
||||
- Errors are specific: "Email is invalid" not "Something went wrong."
|
||||
- Empty states are instructive: "No projects yet. Create one." Not "No data."
|
||||
|
||||
## Forgiveness (P5)
|
||||
|
||||
- Destructive actions warn: "This will delete 42 items. This cannot be undone."
|
||||
- Confirmations name the consequence, not just the action. "Delete user" → "Delete Jane Doe and 12 associated items?"
|
||||
- Undo is offered when possible. "Deleted. Undo." beats "Are you sure?" when undo is cheap.
|
||||
|
||||
## Tone (P3, P1)
|
||||
|
||||
- Helpful, not clever. Cleverness ages. Helpfulness does not.
|
||||
- Apologetic when the system is at fault. "We couldn't save that. Try again." Not "Error."
|
||||
- Neutral, not excited. "Saved." Not "Awesome! Saved!! 🎉" (unless the brand is intentionally playful).
|
||||
|
||||
## Microcopy (P3 Clarity, P9 Simplicity)
|
||||
|
||||
- Buttons: 1–3 words. "Sign in", "Create account", "Send invite".
|
||||
- Tooltips: 1 sentence. Explain what the field does, not what the label says.
|
||||
- Empty states: 1 sentence of explanation + 1 action. "No team members yet. Invite your first."
|
||||
- Loading: present tense. "Saving..." not "Saved" (until it is).
|
||||
|
||||
## What Violates UI Copywriting
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| "Error code 500" to a user | P3, P1 |
|
||||
| "Are you sure?" with no consequence | P5 (no information) |
|
||||
| "Submit" on a delete button | P3 (wrong verb) |
|
||||
| 50-word tooltip | P9 Simplicity |
|
||||
| "Awesome!" on a routine save | P1 (not for the user) |
|
||||
@@ -0,0 +1,115 @@
|
||||
# UI / UX — First Principles
|
||||
|
||||
**Version:** 1.0.0
|
||||
**Status:** Foundational
|
||||
**Audience:** AI agents and humans designing user interfaces, components, pages, and flows.
|
||||
|
||||
## 1. Manifesto
|
||||
|
||||
A user interface is a contract between a system and a person. The cost of a bad interface is paid by every user, every time they use it. The highest quality interface is one that a stranger can use correctly without instructions, regardless of their abilities.
|
||||
|
||||
## 2. The Principles
|
||||
|
||||
### P1. User Primacy
|
||||
The user's goal is the system's goal. The system never optimizes for itself at the user's expense.
|
||||
|
||||
- A loading indicator exists because the user is waiting, not because the system is busy.
|
||||
- A default exists because the user would choose it, not because it is easiest to implement.
|
||||
- The system never makes the user do work the system could do.
|
||||
|
||||
### P2. Accessibility
|
||||
Every user can use the interface, regardless of ability or context.
|
||||
|
||||
- Accessibility is not a feature; it is a property of correct interfaces.
|
||||
- Failure to be accessible is disqualifying — see `domains/uiux/accessibility.md`.
|
||||
- Every interactive element is keyboard-reachable and screen-reader announced.
|
||||
|
||||
### P3. Clarity
|
||||
The interface communicates what it does, what it did, and what will happen next.
|
||||
|
||||
- Labels are nouns. Actions are verbs. States are adjectives.
|
||||
- The user should never wonder "what will this do?"
|
||||
- A confused user is a defect, not a user error.
|
||||
|
||||
### P4. Feedback
|
||||
Every user action produces an immediate, visible response.
|
||||
|
||||
- The system always acknowledges input, even before it processes it.
|
||||
- Feedback is for the user, not the developer. A console log is not feedback.
|
||||
- See `domains/observability/` for the system-side complement.
|
||||
|
||||
### P5. Forgiveness
|
||||
User actions are reversible. Mistakes are recoverable.
|
||||
|
||||
- Destructive actions require confirmation. Irreversible actions require double confirmation.
|
||||
- Undo is a first-class operation, not an afterthought.
|
||||
- The system never traps the user in a state they did not choose.
|
||||
|
||||
### P6. Performance
|
||||
The interface responds in the time the user expects, not the time the system takes.
|
||||
|
||||
- Perceived performance beats measured performance when they diverge.
|
||||
- A 200ms response with feedback feels faster than a 50ms response without.
|
||||
- See `domains/performance/frontend.md` for the technical complement.
|
||||
|
||||
### P7. Hierarchy
|
||||
The interface communicates priority through structure, not decoration.
|
||||
|
||||
- The most important thing is the most visible.
|
||||
- Hierarchy is visual: size, weight, position, contrast. Not noise.
|
||||
- A flat interface hides priority. A cluttered interface invents false priority.
|
||||
|
||||
### P8. Consistency
|
||||
The same action has the same result, the same name, and the same location, everywhere.
|
||||
|
||||
- Consistency serves predictability: the user learns once, applies everywhere.
|
||||
- Inconsistency is a tax on the user's attention.
|
||||
- See `domains/uiux/components.md` for component-level consistency rules.
|
||||
|
||||
### P9. Simplicity
|
||||
The interface shows the user what they need, when they need it, and nothing more.
|
||||
|
||||
- Progressive disclosure: show the common path, hide the rare path.
|
||||
- A simple interface is complete for its purpose. A simpler-than-necessary interface is not.
|
||||
- Simplicity serves clarity: a cluttered interface is unclear.
|
||||
|
||||
### P10. Reversibility
|
||||
The user can always go back, undo, or cancel.
|
||||
|
||||
- Navigation is reversible. Data changes are reversible. Sessions are resumable.
|
||||
- The "back" button always works. The "cancel" button always cancels.
|
||||
- Reversibility serves forgiveness (P5): the cost of a mistake is bounded.
|
||||
|
||||
## 3. Conflict Resolution
|
||||
|
||||
1. Accessibility (P2) — never sacrificed.
|
||||
2. Clarity (P3) — never sacrificed.
|
||||
3. User Primacy (P1) — never sacrificed.
|
||||
4. Forgiveness (P5) — sacrificed only when an action is genuinely irreversible by domain.
|
||||
5. Feedback (P4) — sacrificed only for Performance (P6) with perceived-performance evidence.
|
||||
6. Consistency (P8) — sacrificed for Clarity (P3) when a context demands a different pattern.
|
||||
7. Hierarchy (P7) — sacrificed for Simplicity (P9) when an interface is simple enough to need no hierarchy.
|
||||
8. Simplicity (P9) — sacrificed for Clarity (P3) when simplifying would obscure.
|
||||
9. Performance (P6) — sacrificed for Feedback (P4) when the user needs to know the system is working.
|
||||
10. Reversibility (P10) — sacrificed only for genuinely irreversible operations (e.g., account deletion), with explicit confirmation.
|
||||
|
||||
Three of ten principles are non-tradeable: Accessibility, Clarity, User Primacy. These derive from core C1 (Correctness) and C2 (Clarity).
|
||||
|
||||
## 4. What Violates These Principles
|
||||
|
||||
| Violation | Principle Breached |
|
||||
|-----------|-------------------|
|
||||
| A button that does nothing on click | P4 Feedback |
|
||||
| An image without alt text | P2 Accessibility |
|
||||
| A "delete" with no confirmation | P5 Forgiveness |
|
||||
| A 5-second spinner with no progress | P6 Performance, P4 Feedback |
|
||||
| Two "save" buttons that do different things | P8 Consistency |
|
||||
| A settings page with 50 options visible at once | P9 Simplicity |
|
||||
| A form that cannot be navigated by keyboard | P2 Accessibility |
|
||||
| An action that cannot be undone or cancelled | P10 Reversibility |
|
||||
| A label that says "Submit" when it deletes | P3 Clarity |
|
||||
| A system that optimizes its own load time over the user's wait | P1 User Primacy |
|
||||
|
||||
## 5. Relationship to Core
|
||||
|
||||
Subordinate to `core/first-principles.md`. The three non-tradeable principles (P2, P3, P1) are promoted to C1-equivalent. See `matrix/principles-matrix.md` for the full derivation. Sibling to `domains/uiux/components.md` and `domains/uiux/accessibility.md`.
|
||||
@@ -0,0 +1,48 @@
|
||||
# Design Tokens — Derived Rules
|
||||
|
||||
> Derives from `domains/uiux/first-principles.md` P7 (Hierarchy), P8 (Consistency), P9 (Simplicity).
|
||||
|
||||
## What is a Token
|
||||
|
||||
A design token is the smallest unit of a design system: a color, a spacing, a font size, a radius. It has a name and a value. The name is the API; the value is the implementation.
|
||||
|
||||
## The Token Hierarchy
|
||||
|
||||
```
|
||||
Global tokens (e.g., --color-blue-500)
|
||||
→ Alias tokens (e.g., --color-button-primary)
|
||||
→ Component tokens (e.g., --button-primary-bg)
|
||||
```
|
||||
|
||||
- Global tokens are the raw palette. They have no semantic meaning.
|
||||
- Alias tokens have meaning: "this is the button background." They reference global tokens.
|
||||
- Component tokens are scoped to a component. They reference alias tokens.
|
||||
- Components reference component or alias tokens, never global tokens directly.
|
||||
|
||||
## No Raw Values (P8 Consistency)
|
||||
|
||||
- A component never hardcodes `#3b82f6` or `16px`. It references `--color-button-primary` or `--space-4`.
|
||||
- A hardcoded value is a token that should exist but does not. It is design debt.
|
||||
- The token is the API to the design system. Bypassing it bypasses the system.
|
||||
|
||||
## Naming (P3 Clarity)
|
||||
|
||||
- Tokens are semantic, not visual: `--color-text-primary`, not `--color-dark-gray`.
|
||||
- Visual names couple to the implementation. Semantic names survive a redesign.
|
||||
- `--color-button-primary-bg` is clear. `--color-blue` is not (which button? what state?).
|
||||
|
||||
## Theming via Tokens (P5 Forgiveness, core C5 Reversibility)
|
||||
|
||||
- Themes are sets of token values. Switch theme = switch token values, not switch CSS.
|
||||
- Dark mode: `--color-text-primary: #fff` instead of `#000`. The component code does not change.
|
||||
- A component that hardcodes colors cannot be themed. A component that uses tokens can.
|
||||
|
||||
## What Violates Token Discipline
|
||||
|
||||
| Violation | Principle |
|
||||
|-----------|-----------|
|
||||
| `color: #3b82f6` in a component | P8 Consistency |
|
||||
| `--color-blue-500` referenced by a component directly | P9 (skip alias) |
|
||||
| `--color-dark-gray` (visual name) | P3 Clarity |
|
||||
| A component that cannot be themed | P5, core C5 |
|
||||
| No global tokens, every component invents its own palette | P8 |
|
||||
Reference in New Issue
Block a user