Skip to content

Integration Patterns

3. Inter-Component Interfaces and Contracts

Document status: Draft v0.1 Last updated: 2026-03-31 Depends on: sec2_components.md, Hippo sec4 (REST API), Bridge sec3 (unified API) Feeds into: Platform test strategy (sec5), deployment docs, contract tests


3.1 Integration Philosophy

BASS components integrate through well-defined, tested interfaces. The integration contract for each component pair is:

  1. Interface definition — what API/SDK calls cross the boundary
  2. Contract tests — automated tests that verify the contract is honoured from both sides
  3. Failure mode — what happens when one side of the interface is unavailable

Components do not call each other directly except through published interfaces. There are no internal shared modules, shared databases (except Hippo as the canonical store), or shared in-process state between components.


3.2 Integration Map

┌─────────────────────────────────────────────────────────────────────┐
│                        BASS Platform                                 │
│                                                                      │
│  ┌──────────────────────────────────────────────────────────────┐   │
│  │                          Bridge                               │   │
│  │   Auth enforcement, routing, actor injection, sync check     │   │
│  └──────────┬──────────────┬───────────────────┬───────────────┘   │
│             │              │                   │                     │
│       ┌─────▼────┐  ┌──────▼──────┐  ┌────────▼───────┐           │
│       │  Hippo   │  │   Cappella  │  │     Canon      │           │
│       │          │◀─┤             │  │                │           │
│       │          │──▶  reads /    │  │  resolves via  │           │
│       │          │  │  writes     │  │  Hippo entities│           │
│       └──────────┘  └─────────────┘  └────────────────┘           │
│             ▲                                                        │
│             │ (all data reads/writes)                                │
│       ┌─────┴────┐                                                  │
│       │ Aperture │                                                   │
│       │  (CLI)   │                                                   │
│       └──────────┘                                                   │
└─────────────────────────────────────────────────────────────────────┘

3.3 Hippo ↔ Cappella Interface

This is the most critical integration in the platform. Cappella is Hippo's primary data producer; Hippo is Cappella's sole persistent store.

3.3.1 Read Interface

Cappella reads entity state from Hippo at trigger execution time via HippoClient (SDK) or Hippo REST API (when running as a separate service).

Operation Interface Contract
Entity lookup by ExternalID client.get_by_external_id(type, source, id) Returns entity or raises NotFound
Filtered entity list client.query(type, filters) Returns list, possibly empty
Entity change detection (poll) client.query_updated_since(type, since) Returns entities with updated_at > since
Fuzzy entity search client.search(type, query) Returns ScoredMatch list sorted by score

3.3.2 Write Interface

Cappella writes all entity data via HippoClient with an explicit actor parameter.

Operation Interface Contract
Create entity client.put(type, data, actor=actor) Returns entity with UUID; validates schema + validators
Update entity client.put(type, data, actor=actor) (upsert by ExternalID) Creates if absent, updates if changed, skips if unchanged
Availability change client.set_availability(id, False, actor=actor) Records supersession event in provenance
Relationship creation client.create_relationship(from_id, rel_name, to_id, actor=actor) Validates both entities exist; records provenance

3.3.3 Actor Convention

Cappella writes always use a meaningful actor value: - Human-triggered syncs: the authenticated user's actor identity - Scheduled/automated syncs: service:cappella-<adapter-name> (e.g. service:cappella-starlims) - Pipeline output ingestion: service:cappella-pipeline-<run-id>

This convention ensures provenance records are attributable to the correct source.

3.3.4 Failure Mode

If Hippo is unavailable, Cappella's write operations fail. Cappella does not buffer writes or retry autonomously. Operators should configure Cappella's trigger retry behavior (retry_on_failure: true, max_retries: 3, backoff: exponential).


3.4 Hippo ↔ Canon Interface

Canon resolves file artifact paths from Hippo entity data.

3.4.1 Read Interface

Canon reads entity state from Hippo at resolution time. This is always a fresh read (no caching of entity state in Canon).

Operation Interface Contract
Entity fetch for resolution HippoClient.get(type, entity_id) Required fields must be non-null
Batch entity fetch HippoClient.batch_get(type, ids) Returns in-order list

3.4.2 Write Interface (Provenance Write-Back)

After resolving or producing an artifact, Canon writes a provenance event to Hippo.

Operation Interface Contract
Artifact resolved client.record_event(entity_id, "artifact_resolved", meta, actor) Records path, status, checksum
Artifact produced client.record_event(entity_id, "artifact_produced", meta, actor) Records CWL run ID, output path
Cache eviction client.record_event(entity_id, "artifact_evicted", meta, actor) Records reason

3.4.3 Failure Mode

If Hippo is unavailable, Canon cannot resolve artifacts (it needs entity fields to evaluate path templates). Canon returns 503 upstream_unavailable from its REST API. Provenance write-back failures are logged but do not fail the resolution response — the file was still found/produced; the audit gap is recovered on next resolution.


3.5 Hippo ↔ Aperture Interface

Aperture is a read-heavy client of Hippo.

Operation Interface Contract
Entity list GET /hippo/entities/{type} (via Bridge) or client.query() Paginated; empty list if no matches
Entity get GET /hippo/entities/{type}/{id} 404 if not found
Entity create/update POST/PUT /hippo/entities/{type}/{id} Validates schema; returns 422 on validation failure
Provenance history GET /hippo/entities/{type}/{id}/history Returns event list, newest first
Schema inspection GET /hippo/schema Returns full schema definition

Aperture is always a client of Hippo — it never writes directly to Hippo's storage layer. In multi-user deployments, all Aperture requests pass through Bridge for auth enforcement.


3.6 Bridge ↔ All Components Interface

Bridge is the only component that communicates with all others. Its interface with each component is the component's REST API, augmented with injected headers.

3.6.1 Routing Contract

Bridge receives Bridge sends to
GET /api/v1/hippo/* Hippo REST API: GET /*
POST /api/v1/cappella/* Cappella REST API: POST /*
GET /api/v1/canon/* Canon REST API: GET /*
POST/GET /api/v1/bridge/* Bridge itself (auth endpoints, health, metrics)

Bridge strips the /api/v1/{component}/ prefix before forwarding.

3.6.2 Auth Middleware Contract

All components that receive requests via Bridge must implement the auth middleware contract. When Bridge is active, each component:

  1. Accepts X-Bass-Actor and X-Bass-Roles headers from Bridge's trusted network CIDR.
  2. Uses X-Bass-Actor as the actor parameter for all writes.
  3. Rejects X-Bass-Actor headers from sources outside the trusted network.
  4. Falls through to the component's own auth stub when the header is absent (local dev mode).

This is implemented via the BridgeAwareAuthMiddleware class in bridge.sdk.auth_middleware.

3.6.3 Failure Mode

If a component is unavailable, Bridge returns 503 component_unavailable to the caller. Requests to other components continue normally. Bridge does not cascade failures across components.


3.7 Cappella ↔ Canon Interface

Cappella calls Canon to resolve and produce file artifacts as part of pipeline execution.

Operation Interface Contract
Resolve artifact canon.resolve(rule, entity_id, entity_type) Returns path and status
Produce artifact canon.produce(rule, entity_id, entity_type) Blocks until CWL job completes; returns output path
Batch resolve canon.batch_resolve(rule, entity_ids) Returns path+status per entity

In SDK mode (components co-located), Cappella imports canon directly. In service mode, Cappella calls GET /api/v1/canon/resolve via Bridge.


3.8 Contract Tests

Each component pair has a corresponding contract test that verifies the interface from both sides. Contract tests use real instances (not mocks) because BASS's integration failures typically arise from assumption drift, not logic bugs.

Test file Contract verified
tests/contracts/test_cappella_expects_hippo.py Cappella can write entities and read them back via Hippo SDK
tests/contracts/test_cappella_expects_canon.py Cappella can resolve artifacts for entities in Hippo
tests/contracts/test_entity_loader_contract.py ExternalSourceAdapter implementations produce entities Hippo accepts
tests/platform/test_round_trip.py Full round-trip: external source → Cappella → Hippo → Canon → provenance write-back

See platform/design/sec5_integration_test_strategy.md for the full test strategy.


3.9 Data Flow: End-to-End Example

Full data flow for a Cappella-driven pipeline run:

1. External source (STARLIMS) → Cappella (webhook trigger)
2. Cappella adapter reads source data
3. Cappella calls HippoClient.put() for each entity (upsert by ExternalID)
   → Hippo validates schema + validators
   → Hippo writes entity + provenance event (actor: service:cappella-starlims)
4. Cappella trigger engine emits internal event "starlims.sync_complete"
5. Post-sync trigger fires → Cappella submits CWL pipeline via Canon
6. Canon resolves input artifacts (reads entity fields from Hippo)
7. Canon invokes cwltool, waits for output
8. Canon writes provenance event to Hippo (artifact_produced)
9. Cappella writes pipeline output entities to Hippo (actor: service:cappella-pipeline-<run-id>)
10. Bridge sync engine detects run completion, verifies outputs present in Hippo
11. Aperture user queries results: bass list Sample --filter cohort=CTE

3.10 Open Questions

Question Priority Status
Should Cappella call Canon via Bridge (for auth), or directly (for performance)? Medium Open — for v1.0 single-server deployments, direct is fine; Bridge path needed for multi-server
Event schema versioning — should component events carry a version field for forward compatibility? Low Open
Cappella → Hippo write failure recovery — should Cappella persist a retry queue? High Open — currently relies on operator-configured retries; more robust solution deferred