// inject(boundary[0]) → observe(boundary[1..n]) → verdict: pass | break_at(k)
chain: place-order
links:
- boundary: storefront-ui
signal: POST /api/orders
role: injection-point
transport: http
- boundary: order-service
signal: order.validated
expect_within: 200ms
transport: nats
- boundary: inventory-service
signal: inventory.reserved
expect_within: 500ms
transport: nats
- boundary: payment-service
signal: payment.charged
expect_within: 2000ms
transport: nats
- boundary: notification-service
signal: confirmation.sent
expect_within: 3000ms
transport: smtp chain: kyc-verification
links:
- boundary: onboarding-ui
signal: POST /api/kyc/submit
role: injection-point
transport: http
- boundary: document-service
signal: document.scanned
expect_within: 5000ms
transport: nats
payload_match:
status: [valid, invalid]
- boundary: risk-engine
signal: risk.scored
expect_within: 3000ms
transport: nats
payload_match:
score_range: [0, 100]
- boundary: compliance-service
signal: kyc.decision
expect_within: 1000ms
transport: nats
payload_match:
decision: [approved, rejected, manual_review]
- boundary: notification-service
signal: kyc.result.sent
expect_within: 2000ms
transport: smtp chain: sensor-alert
links:
- boundary: edge-gateway
signal: sensor.reading.ingested
role: injection-point
transport: mqtt
- boundary: anomaly-detector
signal: anomaly.detected
expect_within: 500ms
transport: nats
payload_match:
severity: [low, medium, high, critical]
- boundary: alert-router
signal: alert.routed
expect_within: 200ms
transport: nats
- boundary: operator-dashboard
signal: alert.displayed
expect_within: 1000ms
transport: websocket
- boundary: escalation-service
signal: escalation.logged
expect_within: 3000ms
transport: nats
condition: severity == critical The chain definition names boundaries and signals — not protocols. HTTP, NATS, SMTP are adapters. The chain is the port.
chain: place-order links: - boundary: storefront-ui signal: POST /api/orders transport: http - boundary: order-service signal: order.validated transport: nats
Each link declares a time window. The chain doesn't prescribe how — it asserts when. Miss the window, and the observer knows exactly which link broke.
- boundary: inventory-service signal: inventory.reserved expect_within: 500ms - boundary: payment-service signal: payment.charged expect_within: 2000ms
Every link makes one promise: "If I receive X at my boundary, I will produce Y at my boundary within T." No upstream knowledge. No downstream coupling.
// each link is a contract: receive(X) → produce(Y) ≤ T // chained: A→B→C→D→E // one artifact, three functions: // spec · test · monitor