/**
* modules/logs.ts — the `log` domain as a registered Papyrus-native module.
*
* Deliberately self-contained: does not import artifact/task/rule/playbook infrastructure --
* logs never touch the Artifact graph directly (see src/domain/log-entry.ts's own module
* comment on why `log` is not an Artifact kind).
*/
import type { JsonValue, LogLevel } from "../log/log-entry.ts";
import type { Logs } from "../log/log-service.ts";
import type { OperationDefinition } from "../module-registry.ts";
import { type OperationInput, optionalNumber, optionalString, string } from "./operation-input.ts";
const MODULE_ID = "logs";
function isJsonValue(value: unknown): value is JsonValue {
return (
value === null ||
typeof value === "string" ||
typeof value === "number" ||
typeof value === "boolean" ||
Array.isArray(value) ||
typeof value === "object"
);
}
function optionalFields(input: OperationInput, key: string): JsonValue | undefined {
const value = input[key];
if (value === undefined) return undefined;
if (!isJsonValue(value)) throw new Error(`${key} must be JSON-serializable`);
return value;
}
/** This module's own operation names, the single source of truth src/service.ts's EXPECTED_OPERATION_NAMES spreads in rather than re-listing by hand. */
export const LOGS_OPERATION_NAMES = ["logs.append", "logs.query"] as const;
/** Registers every logs.* operation against one Logs instance. */
export function logsOperations(logs: Logs): OperationDefinition[] {
const define = (name: string, execute: (input: Input) => Output): OperationDefinition => ({
name,
moduleId: MODULE_ID,
execute,
});
return [
define("logs.append", (input: OperationInput) =>
logs.append({
sourceId: string(input, "source_id"),
sourceLabel: optionalString(input, "source_label"),
projectRoot: optionalString(input, "project_root") ?? null,
level: string(input, "level") as LogLevel,
message: string(input, "message"),
fields: optionalFields(input, "fields"),
operationId: string(input, "operation_id"),
sessionId: optionalString(input, "session_id"),
occurredAt: optionalString(input, "occurred_at"),
}),
),
define("logs.query", (input: OperationInput) =>
logs.query({
sourceId: string(input, "source_id"),
since: optionalString(input, "since"),
level: optionalString(input, "level") as LogLevel | undefined,
limit: optionalNumber(input, "limit"),
}),
),
];
}