Architecture Decision Records (ADRs)¶
ADRs (Architecture Decision Records) document important technical decisions: what was decided, why, and which alternatives were discarded.
The full files are in docs/es/adr/.
Index¶
| ADR | Title | Status |
|---|---|---|
| ADR-001 | Microservices architecture with FastAPI | Accepted |
| ADR-002 | Multi-tenancy with schema isolation in PostgreSQL | Accepted |
| ADR-003 | Direct asyncpg instead of SQLAlchemy ORM | Accepted |
| ADR-005 | Batch operations API | Accepted |
| ADR-006 | Configurable AI provider (multi-provider, global) for the knowledge layer | Proposed |
| ADR-007 | Document metadata with template-validated JSONB | Proposed |
| ADR-008 | Immutable audit with hash-chained audit_log |
Proposed |
| ADR-009 | Flow history as append-only events + projection | Proposed |
| ADR-010 | Shared library orpycamcp_common (audit and events) |
Accepted |
| ADR-011 | SvelteKit frontend (SSR) as the presentation app in the monorepo | Proposed |
| ADR-012 | Per-tenant migrations (runner global/ + tenant/) |
Accepted |
| ADR-013 | Authorization resolved in the DB per request (Keycloak authenticates, the DB authorizes) | Accepted |
| ADR-014 | Search with PostgreSQL FTS (tsvector + pg_trgm + JSONB), not Elasticsearch | Accepted |
| ADR-015 | TRD/CCD model: series↔subseries hierarchy, two-phase retention, AGN disposition (CT/E/S/M) | Accepted |
| ADR-016 | Native electronic signature (SHA-256 hash + identity + timestamp), PKI/PDF deferred | Accepted |
| ADR-017 | Physical archive: location (recursive address) ≠ conservation unit (mobile container), ArchivesSpace pattern | Accepted |
| ADR-018 | Outgoing interoperability via signed HTTP webhooks (HMAC-SHA256) over the event bus, subscribed per tenant | Accepted |
| ADR-019 | MCP layer as a thin gateway-client facade (declarative tool catalog, no logic or DB) | Accepted |
| ADR-020 | Orpyca design system (Sass tokens + custom properties, per-tenant branding) and conversational assistant (text/voice) as a translator to MCP tools with local opt-in AI | Proposed |
| ADR-021 | PEL reclamation and per-consumer-group dead-letter in Redis Streams (signature/workflow/notification-service) | Accepted |
| ADR-022 | Electronic index v2 profile — RT-15 metadata per document (format, size, foliation) | Accepted |
| ADR-023 | WORM / Object-Lock for preservation (MinIO) and the AIP/PREMIS roadmap | Accepted (Increment A) |
| ADR-024 | USUA_PERM_ROOT is never granted through the API — an entity administrator is not the ceiling of their own entity; ROOT is provisioned out of band only |
Accepted |
| ADR-025 | The TRD is a convalidated instrument, not a configuration table: append-only versioning and pinning of the version that governs each expediente | Implemented (Increments 1+2) |
| ADR-026 | The TVD is the same instrument with different content: a tipo_instrumento discriminator on trd_series, not its own table (registration and convalidation circuit for an accumulated fonds) |
Implemented (Increment 1) |
| ADR-027 | MinIO archived its open-source project and pulled its images: the object store moves to RustFS, and the engine is chosen by a WORM conformance battery against a real S3 (RustFS 7/7, MinIO 7/7, Garage 0/7), not by a datasheet | Implemented |
| ADR-028 | The workflow moves the radicado's status: one pure function written in the same transaction as the step, closing only via an explicit "Finish processing", closed/anulado terminal (409) and holder-only marcar_leido; audit remediation: clearance and holder gates on assign/transfer, annulment cancels the step, immutable backfill evidence |
Implemented |
| ADR-029 | Current location of the radicado: three flujo_* columns written by workflow-service in the same UPDATE as the status, two generated ubicacion_* columns (computed as pairs), one pure derivar_ubicacion function (last step by when it closed, not by its number), code only when the name is unique, backfill with insert-only evidence, and kept out of everything public |
Implemented |
| ADR-031 | Authorization record: gateway-signed IP on public routes, per-entity data-processing policy served and re-read by the server, and a delivery receipt with author and no reuse | Accepted |
| ADR-032 | The dependencia is correlated by code, not by name: immutable codigo (trigger + 422), a single resolver_codigo_dependencia function with name history, code-canonical contract with the name as deprecated alias, fail-closed backfill on ambiguity, deactivate/delete by code |
Accepted |
| ADR-033 | Production entry: containerised nginx (edge) as the only service with ports, TRUSTED_PROXIES without the network's gateway so the sealed IP cannot be forged, Keycloak published only on /realms/ and /resources/, and third-party images pinned by tag and digest |
Accepted |
| ADR-034 | The XAdES signature is declared as it is applied: explicit Transforms on the references signxml leaves without them, SignedInfo signed after the patch (seal and personal signature), fail-closed self-verification with a default XAdESVerifier(), no canonicalisation shortcut when verifying, and ArchiveTimeStamp input per EN 319 132-1 §5.5.2; validated against EU DSS |
Accepted |
| ADR-035 | Entity state is enforced at the edge: /auth/validate reports tenant_estado, the api-gateway vetoes (403 tenant_inactivo, 503 tenant_no_provisionado/tenant_estado_no_disponible, fail-closed) within its 15 s cache TTL, every get_tenant_conn re-checks it before the search_path, a suspended entity keeps its public reads (verification, OAI, CMIS, disposal publicity) and closes its writes with 503 canal_no_disponible, while non-existent, deactivated and unprovisioned entities answer an identical 404; login issues no token to a non-operational entity and the signed identity expires after 120 s; event consumers and background tasks are deliberately excluded |
Accepted |
| ADR-036 | The signed identity is bound to the request method and path: both enter the HMAC base (v2) without travelling as headers, ruta_canonica (no query, decoded to a fixed point) is identical on signer and verifier, the API has no defaults and an AST guardian requires the signed path to be the called one; an identity replayed on another operation gets 401 identidad_no_autentica and deployment is an atomic cut-over |
Accepted |
How to propose a new ADR¶
- Create the file
docs/es/adr/ADR-{NNN}-{slug}.md - Use the standard format: Context, Decision, Consequences, Alternatives
- Update this page with the new ADR
- Open an MR for discussion before marking it as "Accepted"