Move docs/ package mirror into document-navigator plugin

Relocate the ~6k-file docs tree from the repo root into
plugins/document-navigator/docs/ so the plugin is self-contained and
portable. docs/superpowers/ (specs and plans) stays at the repo root.

Moved via cp+rm rather than git mv because directory rename() fails with
EPERM on this WSL /mnt DrvFs mount; git rename-detection pairs the
identical-content files at commit time.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
2026-05-30 01:10:00 +01:00
parent fe3fedf42e
commit 9bf56a806b
6022 changed files with 12437 additions and 0 deletions

View File

@@ -0,0 +1,76 @@
# `in-memory-bus` — capture-all `PipelineEventsBus` for tests
## Purpose
`InMemoryPipelineEventsBus` is the canonical fixture wired by `createTestHost` (Stage-5 keystone) and consumed by every smoke / integration test that needs to assert what happened inside the §6 11-step request pipeline. Every emitted event is appended to an internal FIFO and broadcast synchronously to every registered subscriber.
The bus is intentionally simple — capture-all (no sampling), drainable (so multi-request tests don't leak), with `byRequest` and `byStage` slicers for the common assertion patterns. It is NOT suitable for production observability (unbounded memory growth, single-process); production bridges to OTel via a separate adapter in `@suaveplan/api-host-otel-pipeline` (follow-up change).
## Features
- FIFO capture of every emitted event, accessible via `events`, `length`, `byRequest(id)`, `byStage(name)`.
- Synchronous in-order fan-out to subscribers (registration order).
- Subscriber exception isolation — a throwing subscriber does not halt fan-out or prevent FIFO append.
- Idempotent `subscribe` return — calling the returned unsubscribe twice is a no-op.
- `drain()` returns and clears the captured FIFO in one call for between-request reset.
## Usage: Basic
```ts
import { InMemoryPipelineEventsBus } from "@suaveplan/api-host";
const bus = new InMemoryPipelineEventsBus();
bus.emit({ requestId: "r1", stage: "auth", outcome: "entered", at: 1 });
bus.emit({ requestId: "r1", stage: "auth", outcome: "completed", at: 2, durationMs: 1 });
expect(bus.byStage("auth")).toHaveLength(2);
```
## Usage: Advanced
Drain between requests so each request's assertions stay isolated:
```ts
const bus = new InMemoryPipelineEventsBus();
host.adapters.pipelineEvents = bus; // wired automatically by createTestHost
await host.fetch(new Request("http://h/probe"));
const first = bus.drain();
expect(first.filter((e) => e.outcome === "entered")).toHaveLength(11);
await host.fetch(new Request("http://h/probe"));
const second = bus.drain();
expect(second).not.toEqual(first); // fresh requestId, fresh timestamps
```
Subscribe a live bridge in addition to the FIFO capture:
```ts
const bus = new InMemoryPipelineEventsBus();
const off = bus.subscribe((event) => console.log(event.stage, event.outcome));
// ...
off();
expect(bus.subscriberCount).toBe(0);
```
## API Reference
```ts
export class InMemoryPipelineEventsBus implements PipelineEventsBus {
emit(event: PipelineStageEvent): void;
subscribe(listener: PipelineEventListener): PipelineEventUnsubscribe;
readonly events: readonly PipelineStageEvent[];
readonly length: number;
drain(): PipelineStageEvent[];
byRequest(requestId: string): PipelineStageEvent[];
byStage(stage: PipelineStage): PipelineStageEvent[];
readonly subscriberCount: number;
}
```
## Implementation Notes
`events` returns a copy on every call so mutating the snapshot does not corrupt the internal FIFO. Test authors who hot-loop on the assertion path should cache the result in a local variable to avoid the O(n) copy on every read.
Subscriber exceptions are swallowed silently in the test fixture. Production bridges (e.g. the OTel bridge) MUST log via the configured boundary logger — but the in-memory fixture deliberately stays silent so tests aren't drowning in `console.error` noise from intentionally-throwing subscribers used to verify the isolation invariant.
The bus does NOT enforce ordering across `requestId`s — interleaved requests produce interleaved events in the FIFO. Use `byRequest(id)` to slice; the conformance harness does this for the per-request order invariant.

View File

@@ -0,0 +1,59 @@
# `noop-bus` — zero-cost default `PipelineEventsBus`
## Purpose
`NoopPipelineEventsBus` is the default value the host installs on `HostAdapters.pipelineEvents` when no operator-supplied bus is configured. Production deployments that don't care about per-stage observability incur zero overhead — `emit` is a no-op that allocates nothing, and `subscribe` returns a cached unsubscribe so callers do not allocate per registration.
The pipeline emits 22 events per successful request (entered + completed for each of the 11 §6 stages); on a noop bus that is 22 method calls that immediately return. With listeners present it becomes 22 synchronous listener invocations. The split avoids forcing every host to pay observability cost when nobody is listening.
## Features
- Implements `PipelineEventsBus` from `@suaveplan/api-types/pipeline-events`.
- Zero per-call allocation — `emit` neither reads the event nor allocates.
- Module-scoped `NOOP_UNSUBSCRIBE` shared across every `subscribe` call.
- Stateless — the exported `noopPipelineEventsBus` singleton is safe to share across hosts.
## Usage: Basic
```ts
import { NoopPipelineEventsBus } from "@suaveplan/api-host";
const bus = new NoopPipelineEventsBus();
bus.emit({ requestId: "x", stage: "auth", outcome: "entered", at: Date.now() });
// — no-op, no listeners, nothing observable.
```
## Usage: Advanced
Use the shared singleton when constructing host configs to avoid per-host allocation. This is what `createHost` does internally when `adapters.pipelineEvents` is omitted:
```ts
import { noopPipelineEventsBus } from "@suaveplan/api-host";
const host = await createHost({
adapters: {
// ...
pipelineEvents: noopPipelineEventsBus,
},
plugins: [],
});
```
Production builds that wire an OTel exporter (`@suaveplan/api-host-otel-pipeline`, follow-up change) replace this with a bridging bus; the host code path is identical.
## API Reference
```ts
export class NoopPipelineEventsBus implements PipelineEventsBus {
emit(event: PipelineStageEvent): void; // no-op
subscribe(listener: PipelineEventListener): PipelineEventUnsubscribe; // cached no-op unsubscribe
}
export const noopPipelineEventsBus: NoopPipelineEventsBus;
```
## Implementation Notes
The unused parameter is bound with a `void` reference so static analyzers do not flag it as dead code AND so the structural type compatibility check passes verbatim against `PipelineEventsBus.emit`. The class form (rather than a frozen object literal) is required so consumers can `instanceof NoopPipelineEventsBus` when they need to bail out of observability bridging — the foundation closure smoke does this to confirm the default is in place when no explicit bus is wired.
`NOOP_UNSUBSCRIBE` is module-scoped (not a method-local closure) because returning the same function from every `subscribe` call lets V8 keep a single shared hidden class and avoids per-call closure allocation. The benchmark in `noop-bus.test.ts` measures both paths and asserts the zero-allocation invariant.

View File

@@ -0,0 +1,55 @@
# `pipeline-events-constants` — runtime constants for `PipelineEventsBus`
## Purpose
`@suaveplan/api-types` is types-only — adding a runtime export there breaks the byte-size invariant CI enforces. The `PipelineEventsBus` contract still needs runtime constants for the canonical stage order tuple (so consumers can iterate / assert without re-listing the names) and for the per-stage event-name strings (so OTel bridges can name events without re-deriving them). Those constants live here, in `@suaveplan/api-host`, where runtime exports are expected.
## Features
- `PIPELINE_STAGE_ORDER` frozen tuple — canonical §6 execution order matching the `PipelineStage` union.
- `PIPELINE_EVENT_NAME` literal — log-grep friendly namespace prefix (`api-host.pipeline.stage`).
- `PIPELINE_STAGE_EVENT_NAMES` per-stage map — concatenated `${PIPELINE_EVENT_NAME}.<stage>` strings for OTel span-event naming.
## Usage: Basic
```ts
import { PIPELINE_STAGE_ORDER } from "@suaveplan/api-host";
for (const stage of PIPELINE_STAGE_ORDER) {
console.log(stage); // request-id, route-match, tenant, auth, ...
}
```
## Usage: Advanced
Build a per-stage histogram dashboard by indexing the per-stage event-name map:
```ts
import { PIPELINE_STAGE_EVENT_NAMES } from "@suaveplan/api-host";
import type { PipelineStage } from "@suaveplan/api-types/pipeline-events";
const histograms = new Map<PipelineStage, number[]>();
for (const stage of Object.keys(PIPELINE_STAGE_EVENT_NAMES) as PipelineStage[]) {
histograms.set(stage, []);
}
bus.subscribe((event) => {
if (event.outcome === "completed" || event.outcome === "rejected") {
histograms.get(event.stage)?.push(event.durationMs ?? 0);
}
});
```
## API Reference
```ts
export const PIPELINE_STAGE_ORDER: readonly PipelineStage[];
export const PIPELINE_EVENT_NAME: "api-host.pipeline.stage";
export const PIPELINE_STAGE_EVENT_NAMES: Readonly<Record<PipelineStage, string>>;
```
## Implementation Notes
The constants are typed as `readonly PipelineStage[]` (tuple) and `Readonly<Record<PipelineStage, string>>` (map). The map's value type is `string` rather than the specific literal because TypeScript widens nested string literals through `Readonly<Record<...>>`. Consumers that need the literal narrowing import the `PIPELINE_STAGE_EVENT_NAMES` constant directly — TypeScript preserves the literal types on the const expression.
The tuple MUST stay in lock-step with the `PipelineStage` union in `@suaveplan/api-types/pipeline-events`. A `satisfies` check at the type-test level catches any drift between the two; the conformance harness's order invariant also catches drift at runtime by comparing the actual emit order against this tuple.