Initial Commit

This commit is contained in:
2026-05-27 23:29:53 +01:00
commit fbc70f96e6
10000 changed files with 1102152 additions and 0 deletions

View File

@@ -0,0 +1,50 @@
# `@suaveplan/api-logger-worker-proxy` — worker-side proxy to main-process logger
**Package**: `@suaveplan/api-logger-worker-proxy`
**Status**: PROPOSED
**Type**: new-package
**Stage**: 3 (Worker isolation infrastructure)
## Why
Per design doc §5.2a, every capability ships a proxy surface. Logging is special in two ways: (a) it's bidirectional — the worker emits log calls that travel to the main-process logger AND the main host MAY disable worker logs during shutdown so the worker doesn't keep producing records after the sink closes; (b) it's high-volume — even with batching, a sustained worker producing a record per request can quickly saturate the proxy. So this package combines an outbound proxy (worker → main) with a control channel (main → worker, "stop logging now"), plus batching + backpressure to bound the wire cost.
## What changes
Net-new package.
**ADDED exports:**
- `./client` — `createLoggerProxyClient(port): WorkerProxyOf<LoggerLike>`. `info` / `warn` / `error` / `debug` / `trace` calls marshalled to main; pre-tagged with plugin name + version per design doc §5.1.
- `./batching` — batched send (`flushIntervalMs` default 50, `maxBatchSize` default 100); flush on `error` / `fatal` level immediately. Bounded outstanding-batch budget enforces backpressure.
- `./control-channel` — main → worker `disable` / `enable` messages. After `disable`, worker-side `log.info(...)` becomes a no-op (records dropped); after `enable`, normal flow resumes. Used by main host during plugin shutdown so the worker stops producing during dispose.
- `./error-codes` — `LOGGER_PROXY_*` codes (`LOGGER_PROXY_PORT_CLOSED`, `LOGGER_PROXY_BACKPRESSURE_DROP`, `LOGGER_PROXY_DISABLED`).
- `./testing` — paired `MessageChannel` + stub main-side sink.
## Out of scope
- **Concrete log sinks** — `@suaveplan/logger`'s job (and `api-audit-*` for audit records).
- **Trace correlation** — span/trace ids are populated main-side; this package preserves them but doesn't compute them.
- **Log format** — main-side logger owns the wire/disk format.
## Source mapping
Genesis-original. Design doc sections: §5.2a (worker-RPC variants), §5.1 (pre-tagged logger in `PluginContext`), §12.3 (logs emitted on span close).
| Requirement | Design doc section |
|---|---|
| Proxy over `LoggerLike` from `@suaveplan/logger` | §5.1 |
| Bidirectional control channel (main can disable worker logs) | Implied by §5.5 disposal (logger disposed last) |
| Batching to bound wire cost | §5.2a (backpressure) |
| Pre-tagged with plugin name + version | §5.1 |
## Verification approach
- `src/client/client.test.ts` — every level round-trips; record carries plugin tags.
- `src/batching/batching.test.ts` — batch fills before flush interval → flush; idle → flush at interval; `error` level flushes immediately.
- `src/batching/backpressure.test.ts` — bounded budget enforced; over-budget records emit `LOGGER_PROXY_BACKPRESSURE_DROP` counter rather than throwing (logging should be best-effort, not application-breaking).
- `src/control-channel/control-channel.test.ts` — main sends `disable` → worker `log.info` becomes no-op; `enable` restores.
## Migration impact
None — additive only.

View File

@@ -0,0 +1,70 @@
<!-- markdownlint-disable MD024 -->
# Capability Spec Delta: api-logger-worker-proxy (api-logger-worker-proxy)
**Package**: `@suaveplan/api-logger-worker-proxy`
**Status**: PROPOSED
> Every requirement below carries a `**Verified by:**` line at authoring time.
## ADDED Requirements
### Requirement: Proxy preserves LoggerLike surface with plugin pre-tagging
The package SHALL expose `createLoggerProxyClient(port, { plugin: { name, version } })` returning a `WorkerProxyOf<LoggerLike>`. Every emitted record SHALL carry the plugin's `name` and `version` as pre-tagged fields per design doc §5.1. Every log level (`trace`, `debug`, `info`, `warn`, `error`, `fatal`) MUST round-trip through the proxy.
#### Scenario: every level round-trips with plugin tags
- **WHEN** the worker calls `log.info({ msg: "x" })`
- **THEN** the main-side stub sink receives a record with `plugin === { name, version }` AND `msg === "x"` AND `level === "info"`
**Verified by:** `src/client/client.test.ts::"every level round-trips with plugin tags"`
---
### Requirement: Batched send flushes on size, idle, or error level
The package SHALL batch outbound log records. A batch SHALL flush when ANY of three conditions hold: (a) `maxBatchSize` records buffered (default 100); (b) `flushIntervalMs` elapsed since the first buffered record (default 50); (c) the record's level is `error` or `fatal` (immediate flush). Buffered records MUST persist across the immediate flush — flushing for an `error` record does NOT discard lower-level records buffered before it.
#### Scenario: batch flushes at max size
- **WHEN** 100 records are buffered
- **THEN** the batch is sent on the next microtask
#### Scenario: error level flushes immediately AND preserves earlier records
- **WHEN** 5 `info` records are buffered AND a `log.error(...)` is called
- **THEN** the batch flushes within 1 microtask AND the main side receives all 6 records in order
**Verified by:** `src/batching/batching.test.ts::"batch flushes at max size"` AND `src/batching/batching.test.ts::"error flushes immediately and preserves earlier records"`
---
### Requirement: Backpressure drops records and emits a counter rather than throwing
The package SHALL enforce a bounded outstanding-batch budget per port. When the budget is exhausted, new records MUST be dropped silently AND a `LOGGER_PROXY_BACKPRESSURE_DROP` counter MUST be incremented. The proxy MUST NOT throw on log calls — logging is best-effort by design, and an application MUST NOT break because the log sink is slow.
#### Scenario: over-budget records drop without throwing
- **WHEN** the stub sink is paused AND 10000 records are emitted
- **THEN** no exception is thrown AND `LOGGER_PROXY_BACKPRESSURE_DROP` counter is incremented above 0
**Verified by:** `src/batching/backpressure.test.ts::"over-budget records drop without throwing"`
---
### Requirement: Control channel can disable and re-enable worker logs
The package SHALL accept `disable` and `enable` messages on the control channel from the main process. After `disable`, every level method on the worker side MUST become a no-op (records discarded without buffer or batch). After `enable`, normal flow resumes. The main host calls `disable` during plugin shutdown so a worker that is being torn down stops producing records after the main-side sink has been disposed (per design doc §5.5 disposal-in-reverse-order).
#### Scenario: disable makes level methods no-op
- **WHEN** the main sends `disable` AND the worker subsequently calls `log.info(...)`
- **THEN** no record reaches the main-side sink AND no batch is queued
#### Scenario: enable restores normal flow
- **WHEN** the main sends `enable` after a prior `disable`
- **THEN** subsequent `log.info(...)` calls reach the sink normally
**Verified by:** `src/control-channel/control-channel.test.ts::"disable makes level methods no-op"` AND `src/control-channel/control-channel.test.ts::"enable restores normal flow"`

View File

@@ -0,0 +1,68 @@
# Tasks — `@suaveplan/api-logger-worker-proxy`
> Every checkbox MUST reflect work actually done in this change.
## 0. Pre-flight (non-negotiable)
- [x] 0.1 Confirm clean working tree (`git status` reports nothing) and no in-flight worktrees with uncommitted work (`git worktree list` then `git status` in each)
- [x] 0.2 If any worktrees exist under `.claude/worktrees/`, account for each one: still-active agent, work-pending-merge, or stale-and-prune. Stale worktrees MUST be removed (`git worktree remove`) before opening a new one — code that has been lost twice on main is the failure mode this guards against.
- [x] 0.3 Confirm the package set in `MANIFEST.md` matches `find packages -maxdepth 3 -name package.json` count. Drift here means a prior change archived without regenerating the manifest.
## 1. Spec authoring
- [x] 1.1 Read design doc §5.1, §5.2a, §5.5, §12.3
- [x] 1.2 Read umbrella
- [x] 1.3 Read `openspec/AGENTS.md` Sections 13 and 14
- [x] 1.4 Draft `proposal.md`
- [x] 1.5 Draft `specs/api-logger-worker-proxy/spec.md`
- [x] 1.6 Every requirement carries `**Verified by:**`
## 2. Implementation
- [x] 2.1 Scaffold `packages/api/api-logger-worker-proxy/` (`0.1.0`, deps: `@suaveplan/api-worker-rpc`, `@suaveplan/api-types`, `@suaveplan/error`, `@suaveplan/logger`, `zod`)
- [x] 2.2 `src/client/` — typed proxy client; level methods marshalled
- [x] 2.3 `src/batching/` — batched send + backpressure budget
- [x] 2.4 `src/control-channel/` — main → worker disable / enable
- [x] 2.5 `src/error-codes.ts` — `LOGGER_PROXY_*` codes
- [x] 2.6 `src/testing/` — paired `MessageChannel` + stub sink
- [x] 2.7 `src/index.ts` — namespace re-exports
## 3. Tests
- [x] 3.1 `src/client/client.test.ts` — `info` / `warn` / `error` / `debug` / `trace` round-trip; pre-tagged with plugin name + version
- [x] 3.2 `src/batching/batching.test.ts` — flush on max batch size; flush on idle interval; `error` flushes immediately
- [x] 3.3 `src/batching/backpressure.test.ts` — over-budget records emit `LOGGER_PROXY_BACKPRESSURE_DROP` counter; no throw
- [x] 3.4 `src/control-channel/control-channel.test.ts` — `disable` → records dropped silently; `enable` → flow resumes
- [x] 3.5 Every `it(...)` carries `{ timeout: 5000 }`
## 4. Documentation
- [x] 4.1 Sibling `.md` per `.ts`
- [x] 4.2 README links every co-located `.md`; explicit note that logging is best-effort under backpressure
- [x] 4.3 Update CLAUDE.md
## 5. Pre-archive verification (the 5-gate triple)
- [x] 5.1 `bun run check` — Biome 0/0/0
- [x] 5.2 `bunx turbo lint typecheck test --filter=@suaveplan/api-logger-worker-proxy` — green
- [x] 5.3 `bunx turbo test:coverage --filter=@suaveplan/api-logger-worker-proxy` — 100/100/100/100
- [x] 5.4 `bun scripts/openspec-conformance.ts @suaveplan/api-logger-worker-proxy` — exit 0
- [x] 5.5 `bunx turbo build --filter=@suaveplan/api-logger-worker-proxy` — green
- [x] 5.6 **Stage-4 conformance gate**: `runLoggerConformance` (from `@suaveplan/logger/testing` — Stage-1 abstract logger sibling adds this if not yet present) MUST pass under both direct AND worker-proxy surfaces before Stage 4 can complete
## 6. Pre-archive 7-row checklist
- [x] 6.1 Generics — `LoggerLike` shape preserved
- [x] 6.2 Zod at wire boundary (record envelopes)
- [x] 6.3 Every `LOGGER_PROXY_*` code surfaced (counter or thrown)
- [x] 6.4 Strict-mode behaviors — backpressure drop vs throw configurable; default drop
- [x] 6.5 OTel — `LOGGER_PROXY_BACKPRESSURE_DROP` emitted as counter; asserted via `InMemoryMetricExporter`
- [x] 6.6 Lifecycle dedup — N/A
- [x] 6.7 No retroactive tick
## 7. Archive
- [x] 7.1 Fold spec delta into `openspec/specs/api-logger-worker-proxy/spec.md`
- [x] 7.2 Move to `openspec/archive/<yyyy-mm-dd>-api-logger-worker-proxy/`
- [x] 7.3 Tick Stage-3 row in umbrella
- [x] 7.4 Refresh `MANIFEST.md`