import { type SqliteDatabase } from '../sqlite/sqlite-driver.js'; import { z } from 'zod'; import { type CrystallizationEvent, type CrystallizationEventInput, type CrystallizationEventStatus, type EvidenceBrief } from './crystallization-types.js'; import { listCrystallizationEvents } from './crystallization-store.js'; type LoopReleaseZodSchema = { parse: (input: unknown) => unknown; }; type LoopBeeRelationZodSchema = { parse: (input: unknown) => unknown; omit: (keys: Record) => { parse: (input: unknown) => unknown; }; }; type InsertLoopReleaseFn = (db: SqliteDatabase, row: unknown) => void; type InsertLoopBeeRelationFn = (db: SqliteDatabase, row: unknown) => unknown; /** * CrystallizationService — spec §5 (post-run crystallization) * + §4.5 + §4.7 + §10 RL-2 / RL-3 / RL-7. * * Hard rules enforced at THIS layer (defense in depth; the Zod * schemas above and the brief refine guard add the second wall): * * candidate task has `task_status === 'completed'` AND * `gates_passed === true` AND `evidence_collected === true`. * Any pre-run attempt (status !== 'completed') is rejected * with `CRYSTALLIZATION_PRE_RUN`. This is the pre-run block * slice. * loop_bee_relation) pair happens in a SINGLE TRANSACTION. The * brief persist on crystallization_event also lives inside * the same transaction (the brief is the durable evidence; * dropping either side would break consistency). * JSON / manifest / CLI verb; all inputs are typed payloads * the LLM submits on the user's behalf. * `EvidenceBriefSchema.refine` guard rejects partial briefs * at the parse boundary. * (what_happened / why_it_matters / what_learned / * what_action). The service throws `BriefSectionError` if the * payload fails the guard. * * Sub-domain error codes: * * - CRYSTALLIZATION_PRE_RUN: task_status / gates_passed / * evidence_collected precondition failed. * - CRYSTALLIZATION_INVALID_TASK_STATE: a task input field is * malformed (e.g. unknown task_status). * - MISSING_BRIEF_SECTION: a brief is missing at least one of * its 4 sections (raised from the evidence-brief-builder * refine; surfaced as-is by the service). * - CRYSTALLIZATION_TX_FAILED: a transaction-side effect failed * (FK constraint, duplicate key, …). The service re-raises * richer domain errors first; this is the catch-all for * unexpected SQL errors. */ export declare const CRYSTALLIZATION_TASK_STATUSES: readonly ["completed", "in_progress", "scratch", "blocked", "failed"]; export type CrystallizationTaskStatus = (typeof CRYSTALLIZATION_TASK_STATUSES)[number]; /** * The pre-run task state the service requires. Crystallization is * GATED on these three flags. The validator rejects any input that * does not match. Adding a new flag means a schema-version bump; for * now the three are required. */ export declare const CrystallizationTaskStateSchema: z.ZodObject<{ task_id: z.ZodString; task_status: z.ZodLiteral<"completed">; gates_passed: z.ZodLiteral; evidence_collected: z.ZodLiteral; }, z.core.$strip>; export type CrystallizationTaskState = z.infer; export type CrystallizationIntegrityErrorCode = 'CRYSTALLIZATION_PRE_RUN' | 'CRYSTALLIZATION_INVALID_TASK_STATE' | 'CRYSTALLIZATION_TX_FAILED' | 'MISSING_BRIEF_SECTION'; export declare class CrystallizationIntegrityError extends Error { readonly code: CrystallizationIntegrityErrorCode | 'MISSING_BRIEF_SECTION'; readonly findings: ReadonlyArray<{ path: string; message: string; }>; constructor(code: CrystallizationIntegrityErrorCode, message: string, findings?: ReadonlyArray<{ path: string; message: string; }>); } export interface CrystallizePayload { /** The candidate task state. Must satisfy the pre-run gate. */ task: CrystallizationTaskState; /** The loop_release to create (new). Must include main bee via relation. */ loop_input: unknown; /** * The main bee_release to create. Note: bee_release requires a * manifest row to keep the existing 4.x schema consistent; * `bee_input` here carries ONLY the manifest-level fields the * service uses. A future slice will swap in the full * retainRelease() flow. */ bee_input: BeeCrystallizeInput; /** * Optional NL reason the LLM authored; surfaced on the * loop_bee_relation row (spec §4.6 `reason`). */ bee_relation_reason: string; /** * The 4-section evidence brief (spec §4.7). */ evidence_brief: EvidenceBrief; /** Optional structured bullets (RL-7: counts may support the brief, not replace it). */ evidence_bullets?: string[]; /** Source trace pointers for the brief (spec §4.5). */ source_trace_pointers?: string[]; /** Optional evaluator summary (independent scorers — spec §4.5). */ evaluator_summary?: string; /** Optional user decision summary (spec §4.5). */ user_decision_summary?: string; /** Trigger classification (spec §5.4). */ trigger: CrystallizationEventInput['trigger']; } /** * Minimum bee_release payload the service needs to create a main bee * alongside a loop. The full bee_release schema (see * `src/services/skillhub/release-retain.ts`) carries manifest rows, * segment refs, file rows, etc.; M5 only writes the bee_release header * + a single bee_manifest row sufficient to keep the 4.x FK * constraints green. Full bee retaining lands in a future slice. * * Schema definition is hoisted above the interface so the typed * interface can reference the inferred Zod type without forward- * reference gymnastics. */ declare const BeeNameSchema: z.ZodString; export interface BeeCrystallizeInput { bee_name: z.infer; version: string; description: string; user_intent_raw?: string; parent_version?: string; changelog?: string; } /** * The crystallization result. The brief is returned AS PERSISTED * (re-parsed through the schema) so callers receive the canonical * shape, not the loose payload. */ export interface CrystallizationResult { loop_release_id: string; bee_release_id: number; loop_bee_relation_id: number; crystallization_event_id: string; loop_release_lifecycle_status: CrystallizationEventStatus; } /** * Service interface for the post-run crystallization flow. */ export interface CrystallizationOptions { loopReleaseSchema: LoopReleaseZodSchema; loopBeeRelationSchema: LoopBeeRelationZodSchema; insertLoopRelease: InsertLoopReleaseFn; insertLoopBeeRelation: InsertLoopBeeRelationFn; } export declare class CrystallizationService { private readonly db; private readonly opts; constructor(db: SqliteDatabase, opts: CrystallizationOptions); /** * Read a CrystallizationEvent by id. */ read(id: string): CrystallizationEvent | undefined; /** * List events. Filter by lifecycle_status and/or the FK pointer * columns. When no filters are supplied, lists ALL events * (newest-first). */ list(opts?: Parameters[1]): { trigger: "user_explicit" | "llm_suggested" | "success_default_prompt" | "similar_task_recurrence"; evidence_brief: { what_happened: string; why_it_matters: string; what_learned: string; what_action: string; }; evidence_bullets: string[]; source_trace_pointers: string[]; evaluator_summary: string; user_decision_summary: string; lifecycle_status: "candidate" | "stable" | "retired"; id: string; schema_version: "peaks.crystallization/1"; created_at: string; created_loop_release_id?: string | undefined; updated_loop_release_id?: string | undefined; created_bee_release_id?: number | undefined; updated_bee_release_id?: number | undefined; }[]; /** * Update a crystallization event's lifecycle_status. Always allowed * (no schema-version gate). Returns the updated row or undefined if * the id is absent. * * A `stable` transition is the natural post-crystallization state; * the service does NOT enforce user-confirmation here (per spec * §5.6 promotion happens on a separate `peaks loop promote` path). * `retired` is the dispose path (`peaks asset dispose`). */ updateStatus(id: string, next: CrystallizationEventStatus): CrystallizationEvent | undefined; /** * Pre-run gate: throws if any of `task_status`, `gates_passed`, * `evidence_collected` is not as required. Called from * `crystallize` BEFORE the transaction begins. * * Reachable as a public method so the CLI / tests can probe the * gate in isolation. */ assertReady(task: CrystallizationTaskState): CrystallizationTaskState; /** * Run the post-run crystallization flow. * * Steps (all in ONE transaction through the sqlite seam, atomic): * * 1. Pre-run gate (task must be completed / gates_passed / * evidence_collected). * 2. Re-validate the 4-section brief (EvidenceBriefSchema.parse, * EvidenceBriefSchema.refine). * 3. Insert loop_release. * 4. Insert bee_release header (+ a single bee_manifest row to * keep the 4.x FK contract). * 5. Insert loop_bee_relation row with role='main'. * 6. Insert crystallization_event with the brief persisted. * * Returns the four ids + the new loop's lifecycle_status. On any * failure the transaction is rolled back and the partial rows * disappear. * * Throws: * - CrystallizationIntegrityError(CRYSTALLIZATION_PRE_RUN) on * the pre-run gate. * - BriefSectionError or ZodError if the brief is malformed; * surfaced as * CrystallizationIntegrityError(MISSING_BRIEF_SECTION). * - CrystallizationIntegrityError(CRYSTALLIZATION_TX_FAILED) * on any SQL transaction failure. */ crystallize(payload: CrystallizePayload): CrystallizationResult; } export {};