ADR-020: Runtime Data Governance β Control Plane vs Git
| Status | Accepted |
|---|---|
| Date | 2026-01-23 |
| Decision Makers | Christophe ABOULICAM |
| Related Tickets | CAB-850, CAB-849, CAB-848 |
Contextβ
STOA Platform uses GitOps for infrastructure and configuration management. However, a critical question arises: where should API runtime metadata live?
Current State (Anti-Pattern)β
Developer modifies category in stoa-catalog/*.yaml β Git push β GitLab CI β Deployment β Runtime updated
Problems Identifiedβ
| Issue | Impact |
|---|---|
| No business validation | Invalid categories accepted |
| No audit log | Who changed what, when, why? |
| No RBAC | Anyone with Git access can modify |
| No approval workflow | Changes go live without review |
| No stakeholder notification | API owners not informed |
| Complex rollback | Git revert vs API call |
Decisionβ
Split data governance by type:
| Data Type | Source of Truth | Modification Method |
|---|---|---|
| OpenAPI Spec | Git (stoa-catalog) | PR + Code Review |
| Infrastructure Config | Git (stoa-gitops) | PR + Code Review |
| Runtime Metadata | PostgreSQL | Control Plane API |
What is Runtime Metadata?β
Data that changes independently of the API contract:
category/tagsβ Classificationvisibilityβ Community, AD groupsstatusβ draft, published, deprecatedowner/teamβ Ownership assignmentsla/rate_limitsβ Custom policies
What Stays in Git?β
Data that defines the API contract:
- OpenAPI specification (endpoints, schemas)
- API name and base description
- Version (major.minor.patch)
- Protocol bindings (REST, GraphQL, gRPC)
Architectureβ
Data Modelβ
-- Runtime metadata (editable via Control Plane)
CREATE TABLE api_metadata (
api_id UUID PRIMARY KEY REFERENCES apis(id),
category VARCHAR(100),
tags TEXT[],
status VARCHAR(20) DEFAULT 'draft',
visibility JSONB, -- {community_ids: [], ad_groups: []}
owner_team_id UUID REFERENCES teams(id),
custom_rate_limit INTEGER,
updated_at TIMESTAMPTZ DEFAULT NOW(),
updated_by UUID REFERENCES users(id)
);
-- Automatic audit log
CREATE TABLE api_metadata_audit (
id UUID PRIMARY KEY DEFAULT gen_random_uuid(),
api_id UUID NOT NULL,
field_name VARCHAR(50) NOT NULL,
old_value JSONB,
new_value JSONB,
changed_by UUID REFERENCES users(id),
changed_at TIMESTAMPTZ DEFAULT NOW(),
reason TEXT
);
API Endpointsβ
GET /v1/apis/{api_id}/metadata β Read metadata
PATCH /v1/apis/{api_id}/metadata β Update metadata (RBAC enforced)
GET /v1/apis/{api_id}/metadata/audit β View change history
Bootstrap Sync (Git β DB)β
async def sync_catalog_to_db():
"""
Import initial metadata from Git.
Does NOT overwrite if already present in DB.
"""
for api_yaml in git_catalog.list_apis():
if not await db.api_metadata_exists(api_yaml.id):
await db.create_api_metadata(api_yaml)
# else: DB is source of truth, Git ignored for metadata
Consequencesβ
Positiveβ
- Audit trail β Complete history of who changed what
- RBAC β Only authorized users can modify metadata
- Validation β Business rules enforced at API level
- Notifications β Stakeholders informed of changes
- Simple rollback β API call vs git revert
Negativeβ
- Two sources β Spec in Git, metadata in DB
- Sync complexity β Bootstrap logic needed
- Migration β Existing YAML metadata must migrate to DB
Neutralβ
- Console UI required β Need UI for non-technical users
- API versioning β Metadata API needs versioning strategy
Implementationβ
See CAB-850 for implementation details.
Phasesβ
- Phase 1 β Data model + Alembic migration
- Phase 2 β API endpoints + RBAC
- Phase 3 β Console UI
- Phase 4 β Sync + migration script
Referencesβ
- Kong Declarative vs DB Mode
- GitOps Principles
- Discussion: Claude Chat 2026-01-23