apps/runner scaffold (app-tier, walking-skeleton story 04): WS server speaking @repo/core-runner-protocol. Every inbound/outbound frame is envelope-wrapped and zod-parsed; hello/ready handshake gates on the workspace-scoped token (constant-time compare, redacted token on rejection replies); named error events for version/schema/auth rejections. Config via env only (token never argv); port announced on stdout for the story-06 provisioner. Runtime deps: ws (the standard Node WS server; ADR-022 traces do not apply to app-tier) and zod. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_016j8z4VHjedXDTjEDNg7qHK
35 lines
1.1 KiB
TypeScript
35 lines
1.1 KiB
TypeScript
/**
|
|
* Minimal JSON-lines logger for the runner process.
|
|
*
|
|
* The runner logs to stdout so the provisioner (story 06) and the
|
|
* integration tests can read a structured stream. The sink is injectable
|
|
* so in-process tests can capture every emitted line and assert
|
|
* credential-leak absence (spec §15): nothing in this module — and by
|
|
* convention nothing passed to it — may ever contain a secret. Callers
|
|
* log event names and safe fields only, never raw protocol payloads.
|
|
*/
|
|
export type LogSink = (line: string) => void;
|
|
|
|
export interface Logger {
|
|
info(event: string, fields?: Record<string, unknown>): void;
|
|
error(event: string, fields?: Record<string, unknown>): void;
|
|
}
|
|
|
|
const stdoutSink: LogSink = (line) => {
|
|
process.stdout.write(`${line}\n`);
|
|
};
|
|
|
|
export function createLogger(sink: LogSink = stdoutSink): Logger {
|
|
const emit = (
|
|
level: "info" | "error",
|
|
event: string,
|
|
fields?: Record<string, unknown>,
|
|
) => {
|
|
sink(JSON.stringify({ level, event, ...fields }));
|
|
};
|
|
return {
|
|
info: (event, fields) => emit("info", event, fields),
|
|
error: (event, fields) => emit("error", event, fields),
|
|
};
|
|
}
|