/** * Layer 0 — ArtifactSummary derivation (design §6.1, PR 1 / task 3.2). * * Pure logic only: no I/O, no fs, no DB, no network. Follows the Core vs * Plugin boundary (AGENTS.md `antipattern-core-io`). * * `deriveArtifactSummary` derives a small, deterministic, size-bounded * `ArtifactSummary` from a runner's already-validated structured output. * It never inspects raw prompt text, never calls an LLM, and never widens * any output schema, validator, or prompt — the summary is a read-only * projection of fields the production validator has already accepted * (design §4.1, Requirement 1.7 / 1.15). * * rc-1 / rc-2: `validatedOutput` is received as `unknown` and narrowed only * via `typeof` / `Array.isArray` — never via `as` casts. * rc-5: object keys are read with `Object.hasOwn`, not `in` (ERR-013 — `in` * would match inherited properties like `toString`/`constructor`). * rc-9: every field the mapping table cannot resolve is recorded in * `omittedFields`, never silently dropped. * * The per-stage field mapping mirrors the mapping already exercised (and * gated) in `__tests__/progressive-disclosure-spike.test.ts` for the three * diagnostic stages — that mapping went through the real Phase 0 LLM value * -validation gate (four rounds), so this implementation intentionally * matches it rather than inventing a second, divergent mapping. */ import { type HashFn } from './artifact-content-hash.js'; /** * 8 kinds: 3 diagnostic stages + 5 peer stages. * The three diagnostic stages only participate in writer-side summary and * forwarding (design §4.7.1) — they do not own a `ContextManifest`. */ export type SummaryRunnerKind = 'diag_rootcause' | 'diag_distiller' | 'diag_router' | 'dreamer' | 'philosopher' | 'scribe' | 'artificer' | 'evaluator'; export declare const SUMMARY_RUNNER_KINDS: readonly SummaryRunnerKind[]; export declare const ARTIFACT_SUMMARY_SCHEMA_VERSION: 1; export declare const SUMMARY_HEADLINE_MAX_CHARS = 200; export declare const SUMMARY_FIELD_MAX_CHARS = 600; /** Self-summary: deterministic derivation, never contains newly-generated LLM content. */ export interface ArtifactSummary { readonly schemaVersion: typeof ARTIFACT_SUMMARY_SCHEMA_VERSION; readonly runnerKind: SummaryRunnerKind; /** tier0: single-line headline, length <= SUMMARY_HEADLINE_MAX_CHARS. */ readonly headline: string; /** tier1: structured field extraction, stable key names (design §6.6 manifest field paths). */ readonly fields: Readonly>; /** Derivation source, always 'structured_output' — reserved for future source kinds. */ readonly derivedFrom: 'structured_output'; /** Fields skipped during derivation (missing/empty) — rc-9: degradation must carry a reason. */ readonly omittedFields: readonly string[]; } /** Direct-predecessor reference: exactly one level, no recursion. */ export interface PredecessorSummaryRef { readonly artifactId: string; readonly runnerKind: SummaryRunnerKind; /** Hash of the predecessor's canonical contentJson, used for staleness detection. */ readonly contentHash: string; readonly summary: ArtifactSummary; } /** Additive envelope merged into an artifact's contentJson (all fields optional). */ export interface ArtifactSummaryEnvelope { readonly summary: ArtifactSummary; readonly predecessorSummary?: PredecessorSummaryRef; } /** * The predecessor artifact as already loaded by the runner's buildContext * (design F3: zero additional store reads). Shared type of the runner * context predecessor fields; moved here from the retired Layer 0 * attach-summary-envelope module (PRI-819 R-06). */ export interface LoadedPredecessorArtifact { readonly artifactId: string; readonly runnerKind: SummaryRunnerKind; readonly contentJson: unknown; } export type DeriveSummaryFailureReason = 'unsupported_runner_kind' | 'output_not_object' | 'no_derivable_field'; export type DeriveSummaryResult = { readonly ok: true; readonly value: ArtifactSummary; } | { readonly ok: false; readonly reason: DeriveSummaryFailureReason; readonly detail: string; }; /** * Pure function. Input is a runner's own already-validated output, but is * still treated as `unknown` (rc-1) and read via `Object.hasOwn` (rc-5). * * Preconditions: `runnerKind` belongs to `SummaryRunnerKind`. * Postconditions: * - never throws (writer paths must not fail because of summary derivation) * - `ok === true` implies `headline.length <= SUMMARY_HEADLINE_MAX_CHARS` * and every `fields` value has `length <= SUMMARY_FIELD_MAX_CHARS` * - identical input always produces byte-identical output (determinism, * supports golden replay) * - `ok === false` carries a `reason`; the caller must emit a degradation * event (rc-9) */ export declare function deriveArtifactSummary(runnerKind: SummaryRunnerKind, validatedOutput: unknown): DeriveSummaryResult; export type SummaryFreshness = { readonly fresh: true; } | { readonly fresh: false; readonly reason: 'content_hash_mismatch' | 'predecessor_missing'; }; /** * Freshness is judged solely by content-hash comparison — `updatedAt` is * never read (Requirements 3.2 / 3.9): a pending→validated timestamp * refresh must not be mistaken for a content change. * * No artifact-store read happens here — `loadedPredecessorContentJson` must * already be in memory (it is the same object `buildContext` loaded for the * current runner invocation, design F3). `hash` is the same injected * `HashFn` used by `computeContentHash` (core does not import * `node:crypto`, `antipattern-core-io`). */ export declare function checkPredecessorSummaryFreshness(ref: PredecessorSummaryRef | undefined, loadedPredecessorContentJson: unknown | undefined, hash: HashFn): SummaryFreshness; //# sourceMappingURL=artifact-summary.d.ts.map