# @octaviaflow/resilience

Cockatiel-based resilience primitives for Octaviaflow Bun services. Wraps
timeout, retry, circuit breaker, bulkhead, and fallback into a single
per-dependency policy with a drop-in `resilientFetch`.

Shipped via the `Octaviaflow-System/` workspace alongside `@octaviaflow/eventbus`.

## Install

```bash
bun add @octaviaflow/resilience
```

## Quick start

```ts
import {
  makePolicy,
  resilientFetch,
  attachObserver,
  NonRetriable,
} from '@octaviaflow/resilience';
import { logger } from './config/logger';
import { meter } from './instrumentation';

// One singleton per remote dependency
const enginePolicy = makePolicy({
  name: 'engine',
  timeoutMs: 5_000,
  retry:    { attempts: 3, initialDelayMs: 100, maxDelayMs: 1_000 },
  breaker:  { threshold: 5, durationMs: 30_000 },
  bulkhead: { maxConcurrent: 50, maxQueue: 100 },
});

attachObserver(enginePolicy, { logger, meter });

export const engineFetch = resilientFetch(enginePolicy);

// Call site change is exactly: fetch(...) → engineFetch(...)
const res = await engineFetch(`${ENGINE_URL}/api/v1/engine/execute`, {
  method: 'POST',
  body: JSON.stringify(payload),
  headers: { 'Content-Type': 'application/json' },
});
```

## Fallback at call sites

```ts
import { BrokenCircuitError, TaskCancelledError } from 'cockatiel';
import { NonRetriable } from '@octaviaflow/resilience';

try {
  const res = await trqeFetch(`/token/${id}`);
  return await res.json();
} catch (err) {
  if (err instanceof BrokenCircuitError || err instanceof TaskCancelledError) {
    const cached = await readCachedToken(id);
    if (cached) return cached;
  }
  throw err;
}
```

Allowed fallback strategies, in preference order:

1. Serve cached value (Valkey / in-memory LRU).
2. Serve degraded response (e.g. flag the run as `pendingAuthRefresh`).
3. Reject with `503 Service Unavailable` and a `Retry-After` header.

Returning `200 OK` with stale or partial data without explicit caller opt-in
is forbidden.

## Per-dependency policy matrix

The values consumed by every Octaviaflow service are normative — see
`Octaviaflow-Docs/Infrastructure/resilience/octaviaflow-resilience-package.md`
section 5.4.

## Tests

```bash
bun test                # unit (no infra)
bun run test:integration  # against a Bun.serve fixture in-process
```

## References

- ADR: `Octaviaflow-Docs/Decisions/adr-resilience-cockatiel.md`
- Implementation spec: `Octaviaflow-Docs/Infrastructure/resilience/octaviaflow-resilience-package.md`
- Cockatiel: https://github.com/connor4312/cockatiel
