feat(canon): add Interface Change Registry concept and workplan

Concept doc captures the design for coordinated API evolution in agent
ecosystems: InterfaceChange entity, draft→published→resolved lifecycle,
TPSC-derived dependency routing, inbox-based notifications, pre-change
coordination via planned_for, and deliberate deferral of webhooks.

CUST-WP-0033 workplan: 6 tasks (model, API, dispatch integration,
MCP tools, dashboard, webhook EP).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-26 15:13:11 +02:00
parent 8dd15efde1
commit 2cd3099ebf
2 changed files with 332 additions and 0 deletions

View File

@@ -0,0 +1,162 @@
---
id: CUST-CPT-CUST-2026-000002
type: concept
title: "Interface Change Registry — Coordinated API Evolution for Agent Ecosystems"
status: active
owners: ["Bernd", "Custodian"]
created: "2026-04-26"
updated: "2026-04-26"
scope:
domains: ["custodian"]
sensitivity: internal
tags: ["interface", "api-evolution", "agent-coordination", "ecosystem", "change-management"]
related_workplan: CUST-WP-0033
---
# Interface Change Registry — Coordinated API Evolution for Agent Ecosystems
## Problem
In a distributed ecosystem of closely coupled repos and services, APIs and interfaces
evolve continuously. Without coordination, breaking changes propagate silently: a
service updates its REST API, and dependent agents or services discover the breakage
only at runtime — typically as a 422, a 307 redirect, or a schema validation failure.
Human-operated systems address this with release notes and changelogs. Agent-operated
systems need something machine-readable and actionable: a record of what changed, who
depends on it, and a channel to trigger adaptation before the breakage hits.
The trailing-slash normalisation performed on 2026-04-26 is a concrete example: it
was a deliberate, coordinated breaking change. Without a registry, every consumer had
to be found by manual grep. With one, the change record names the affected repos,
their agents receive inbox notifications, and each can adapt autonomously.
## Core Abstraction: InterfaceChange
An `InterfaceChange` record describes a single, versioned mutation to a published
interface boundary. It carries:
| Field | Purpose |
|---|---|
| `repo_slug` | The repo that owns the interface |
| `interface_type` | What kind of interface: `rest_api`, `mcp_tool`, `cli`, `schema`, `capability` |
| `change_type` | Nature of change: `breaking`, `additive`, `deprecation`, `removal` |
| `title` | Short human-readable summary |
| `description` | Full description with before/after detail |
| `affected_paths` | Specific endpoints, tool names, CLI commands, or schema fields changed |
| `affected_repo_slugs` | Repos known to consume this interface |
| `status` | `draft``published``resolved` |
| `planned_for` | Optional date — set when change is announced before it ships |
| `published_at` | When the change became live |
| `resolved_at` | When all known dependents have adapted |
## Lifecycle
```
draft ──publish──▶ published ──resolve──▶ resolved
│ │
│ auto-notify affected
│ repo agents (inbox)
└── edit freely; no notifications yet
```
**draft** — change is being documented but not yet live or announced. Safe to edit.
Can be used to pre-announce a planned breaking change before it is merged.
**published** — the change is live (or imminently scheduled). On publish, the hub
automatically sends an inbox message to the agent of each `affected_repo_slug`.
The message contains enough context for the agent to identify what needs updating.
**resolved** — all known dependents have adapted and confirmed. Can be closed by
the originating agent or by any affected agent once it has updated its side.
## Dependency Routing
`affected_repo_slugs` can be populated in two ways:
1. **Explicit** — the author lists known consumers when creating the record.
2. **Derived** — the hub queries the TPSC graph: repos that have a TPSC snapshot
declaring a dependency on the originating repo's service are automatically
included as candidates. The author confirms or trims the list before publish.
This means the TPSC catalog (`tpsc.yaml` files) is the underlying dependency map
for routing interface change notifications. Keeping TPSC current is what makes
automatic routing accurate.
## Pre-Change Coordination
Setting `planned_for` and publishing in `draft` status (then moving to `published`
on merge) enables a coordination window:
```
day 0 — change drafted, planned_for set to day 7
day 0 — dependent agents receive notification, begin adapting
day 7 — change lands, status set to published
day 7+ — agents confirm adaptation, change resolved
```
This is the proactive adaptation model: the breaking change announcement travels
faster than the change itself, giving dependents time to prepare. At scale, this
enables the ecosystem to self-heal around planned migrations.
## Agent Session Integration
Two integration points keep agents aware of pending changes without requiring
active polling:
**Session start (pull):** The `/repos/{slug}/dispatch` endpoint includes a
`pending_interface_changes` field — published changes that affect this repo and
are not yet resolved. Agents reading dispatch at session start see what they need
to adapt to.
**Inbox notification (push):** On publish, the hub sends an inbox message to each
affected repo's agent. The message includes the change title, description, and
`affected_paths` so the agent can locate the relevant code without additional API
calls.
Together, these ensure no published breaking change is invisible to an agent
working in an affected repo.
## Relationship to Existing State-Hub Entities
| Entity | Relationship |
|---|---|
| `ManagedRepo` | InterfaceChange.repo_id FK; affected_repo_slugs reference repo slugs |
| `TPSC` / `TPSCSnapshot` | Source for derived affected_repo_slugs via service dependency graph |
| `Decision` | A planned breaking change is conceptually a pending decision; consider linking |
| `ProgressEvent` | Publishing a change auto-appends a progress event to the originating repo |
| `Message` | Publish action sends inbox messages to affected agents |
## Webhook Extension (Deferred)
Inbox messages cover agents that poll at session start. Services that need
real-time push — CI pipelines, external webhooks, non-Custodian agents — require
a separate subscription mechanism.
This is explicitly deferred. The design leaves room for it:
- `InterfaceChange` records are immutable once published (safe to deliver idempotently)
- `affected_repo_slugs` is the routing key; a subscription table maps slugs to URLs
- Delivery semantics: at-least-once with exponential backoff
A dedicated EP (`EP-CUST-ICR-001`) will track this when the inbox-first approach
proves insufficient.
## Long-term Vision
The Interface Change Registry is a first step toward a **self-healing ecosystem**:
1. A repo publishes a breaking change with `planned_for = T+7`.
2. The hub identifies all dependents via TPSC and notifies their agents.
3. Each dependent agent opens a task, locates the affected code, and creates an
adaptation PR before T+7.
4. At T+7, the change ships; all dependents are already adapted.
5. The originating agent marks the change resolved.
No human coordination is needed for routine interface evolution. Humans remain in
the loop for non-routine changes — architectural decisions, security-sensitive
migrations, or changes that require cross-domain agreement — via the Decision entity
and the existing escalation protocol.
At greater scale, the dependency graph enables **contract testing**: two repos can
register a formal interface contract, and the hub can detect when a proposed change
would violate it before the change is merged.

View File

@@ -0,0 +1,170 @@
---
id: CUST-WP-0033
type: workplan
title: "Interface Change Registry — Coordinated API Evolution"
domain: custodian
repo: the-custodian
status: active
owner: custodian
topic_slug: custodian
created: "2026-04-26"
updated: "2026-04-26"
concept: canon/projects/custodian/interface_change_registry_v0.1.md
state_hub_workstream_id: "420a3981-abf5-4a8e-a94b-455964f1a0e5"
---
# CUST-WP-0033 — Interface Change Registry
## Goal
Add a first-class `InterfaceChange` entity to the state-hub. Agents producing
breaking changes can document them; agents consuming interfaces discover pending
changes at session start via dispatch, and receive inbox notifications on publish.
Webhook delivery is deferred and registered as an extension point.
Reference concept: `canon/projects/custodian/interface_change_registry_v0.1.md`
## T01: Data model and migration
```task
id: CUST-WP-0033-T01
status: todo
priority: high
state_hub_task_id: "6bc77d3c-78e0-485a-a3bc-b5987c4ccc53"
```
New table `interface_changes`. Fields: `id` (UUID PK), `repo_id` (FK
managed_repos), `interface_type` (String: rest_api / mcp_tool / cli / schema /
capability), `change_type` (String: breaking / additive / deprecation / removal),
`title` (String), `description` (Text), `affected_paths` (JSONB list of strings),
`affected_repo_slugs` (JSONB list of slugs), `status` (String: draft / published /
resolved), `planned_for` (Date nullable), `published_at` (DateTime nullable),
`resolved_at` (DateTime nullable), `author` (String), `created_at`, `updated_at`.
Index on `(repo_id, status)` and `(status)` for dispatch queries.
Acceptance: Alembic migration runs cleanly, model importable, no regressions in
existing test suite.
## T02: API endpoints
```task
id: CUST-WP-0033-T02
status: todo
priority: high
state_hub_task_id: "7664551e-0871-4e82-a9ba-b59be515c47c"
```
Router at `/interface-changes/` (prefix). Endpoints:
- `POST /interface-changes/` — create (status always `draft` on creation)
- `GET /interface-changes/` — list; filter params: `repo_slug`, `status`,
`change_type`, `affected_repo` (returns changes that affect the given slug)
- `GET /interface-changes/{change_id}` — single record
- `PATCH /interface-changes/{change_id}` — update mutable fields (title,
description, affected_paths, affected_repo_slugs, planned_for); only valid in
`draft` status
- `POST /interface-changes/{change_id}/publish` — transition draft → published;
sets `published_at`; fires inbox messages to agents of all `affected_repo_slugs`;
appends a progress event on the originating repo
- `POST /interface-changes/{change_id}/resolve` — transition published → resolved;
sets `resolved_at`
Acceptance: all endpoints return correct status codes; publish transitions send
inbox messages; 409 on invalid status transitions; tests cover happy path and
invalid transitions.
## T03: Dispatch integration
```task
id: CUST-WP-0033-T03
status: todo
priority: medium
state_hub_task_id: "8f8403a2-4444-4196-9845-ea9c66b674eb"
```
Extend `GET /repos/{slug}/dispatch` to include a `pending_interface_changes` field:
published `InterfaceChange` records where `affected_repo_slugs` contains `slug` and
status is not `resolved`. Each entry: `id`, `title`, `change_type`,
`interface_type`, `repo_slug` (origin), `affected_paths`, `planned_for`,
`published_at`.
Extend `DispatchWorkstream` schema with the new field. Update `RepoDispatch` schema.
Update the `get_repo_dispatch` endpoint accordingly.
Acceptance: `GET /repos/repo-registry/dispatch` returns `pending_interface_changes`
list (empty or populated); no regression on existing dispatch tests.
## T04: MCP tools
```task
id: CUST-WP-0033-T04
status: todo
priority: medium
state_hub_task_id: "d9135829-954e-41de-af9f-607768916478"
```
Four tools in `mcp_server/server.py`:
- `register_interface_change(repo_slug, interface_type, change_type, title,
description, affected_paths=None, affected_repo_slugs=None, planned_for=None)`
— creates a draft record
- `list_interface_changes(repo_slug=None, status=None, change_type=None,
affected_repo=None)` — returns formatted summary
- `publish_interface_change(change_id)` — publishes and triggers notifications
- `resolve_interface_change(change_id)` — marks resolved
Acceptance: tools callable from Claude Code; publish tool returns confirmation
of how many inbox messages were sent.
## T05: Dashboard page
```task
id: CUST-WP-0033-T05
status: todo
priority: low
state_hub_task_id: "d2fcbe83-c5a7-400c-a53b-ff1950795814"
```
New page `dashboard/src/interface-changes.md`. Shows:
- Table of published/draft changes grouped by repo, sorted by `published_at` desc
- Change type badge (breaking = red, deprecation = amber, additive = green)
- Affected repos column with count
- Filter by repo slug and change_type
- A "planned" section for changes with future `planned_for` dates, sorted
chronologically — effectively a migration calendar
Add to `observablehq.config.js` nav.
Acceptance: page renders; data loads from `GET /interface-changes/?status=published`
and `GET /interface-changes/?status=draft`.
## T06: Register webhook extension point
```task
id: CUST-WP-0033-T06
status: todo
priority: low
state_hub_task_id: "47d7bea8-b5fb-4fc4-9ec5-9ed5e0cdef72"
```
Register EP-CUST-ICR-001 in the state-hub:
```
interface_type: future_capability
ep_type: architecture
title: Webhook subscriptions for interface change notifications
description: |
Inbox messages cover polling agents. Real-time push to CI pipelines,
external webhooks, and non-Custodian agents requires a subscription table
(repo_slug → webhook_url) and delivery infrastructure (retry, dead-letter).
Defer until inbox-first approach proves insufficient for ≥1 real case.
status: open
priority: low
```
No implementation. Documents the deliberate deferral and records the design
direction so it is not re-invented later.
Acceptance: EP registered, retrievable via `GET /extension-points/?domain=custodian`.