/** * The automation door's lane: author one automation for an app, land the app's * own note of it, and arm it. * * This is a DOOR, not a rung. It used to hang off the escalation ladder as * `` — the same ladder that reaches for a * machine — which meant "run this every morning" travelled the road built for * "this needs a server". Escalation now means the box and nothing else, and * authoring an automation is its own small entry point: no machine, no * sandbox, seconds rather than minutes. * * An automation is a RECORD of its own, owned by the principal who asked for it * and carrying no reference back to any app. What the app keeps is a list of * ids, maintained here and nowhere else, resolved on read with dead ids dropped. * So deleting the app does not stop the automation: it fires and fails loudly at * tool resolution, in the run ledger, which is the designed behavior. */ import { type AppId, type ApprovalRequest, type AutomationId, type AutomationRecord, type AutomationTask, type RunContext } from "@vendoai/core"; import { type AppDocument } from "../../contract/index.js"; import type { Finding } from "../checking/types.js"; import type { GeneratedAppDocument, GenerationDependencies } from "../generation/engine.js"; import type { AppsRuntimeContext } from "../runtime/runtime-context.js"; import type { AppsRuntime, AutomationsSeam } from "../runtime/types.js"; import { type AutomationPlan } from "./plan.js"; /** The two ways an automation runs: fixed steps, or a judgment call per run. It * IS `AutomationTask["kind"]` — one vocabulary from the door to the record. */ export type AutomationMode = AutomationTask["kind"]; /** * WHICH automation a plan lands on: the existing record it is a new version of, * or nothing at all — a new record beside them. * * The create operation replaces a record whose id it is given, so this is the * whole of create-vs-edit. {@link ADDS_ANOTHER} outranks the planner: an ask * that says "another one" can never overwrite something the person already has. */ export declare const replacedAutomationId: (plan: AutomationPlan, /** The person's own words, when the caller has them. */ ask?: string) => AutomationId | undefined; /** Put the automation onto the document: the app's note of the id. The results * TABLE is not declared anywhere — an app's database has no manifest; the * table either exists or it does not, and `ensureResults` below makes it. */ export declare const applyAutomationPlan: >(document: Doc, _plan: AutomationPlan, automationId: AutomationId) => Doc; /** The rewire that makes an away run VISIBLE: the app's screen reads the store * rows the automation publishes, so without this the automation fires into * nothing the person can see. * * Read by the ONE screen builder, so it speaks the component dialect and only * its own delta from it — the builder's brief already teaches the file. */ export declare const automationResultsInstruction: (input: { appId: string; mode: AutomationMode; /** The automation's own name, when it has one. */ name?: string; resultsCollection: string; }) => string; /** * Create the record and arm it. A seam that throws and a seam that answers * without arming are the SAME miss — an automation sitting silently disarmed is * one the person believes is running — so both come back as an honest sentence * naming the surface to use. */ export declare const armAutomation: (seam: AutomationsSeam, id: AutomationId, ctx: RunContext) => Promise<{ enabled: boolean; pendingGrants?: ApprovalRequest[]; issues: string[]; }>; export interface AutomationLaneDeps extends GenerationDependencies { /** The stored app's id: the automation's publish step and the results query * both name it literally. */ appId: AppId; ctx: RunContext; /** * The person's own words for this change. * * The instruction the planner reads may be composed (an escalated plan's * `why` above their ask); the planner decides whether this is one more * automation or a new version of one they already have, and * {@link replacedAutomationId} reads the same words as the mechanical floor * under that decision. */ request?: string; /** * Rewire the app to surface something new (the automation's results rows). * Wired to one turn of the screen assembler — the one builder — so the board * that appears is written by the same thing that writes every other screen. * Absent → the automation still arms and the missing board is a `warn`, * exactly as a failed rewire is. * * It takes the instruction and nothing else: the assembler opens the app's own * STORED row, so the document to rewire is never handed to it — which is why * this runs after `land`. */ rebind?: (instruction: string) => Promise<{ document?: GeneratedAppDocument; issues: string[]; }>; /** Make the automation's results table before anything reads or writes it — * an app's database has no manifest, so the table has to be created, and * neither the planner's step nor the rebound board is the place for DDL. */ ensureResults?: (table: string) => Promise; /** The ONE place the app's note of the automation reaches the stored row, and * the first write of the two — the rewire's own save comes after it. */ land: (document: GeneratedAppDocument) => Promise; /** Unset ⇒ no engine is composed and nothing can be created; the app stands * and the ask is refused in one sentence, never armed on paper. */ automations?: AutomationsSeam; } export interface AutomationLaneResult { /** What the store holds when the lane is done: the landed document, or — once * the rewire has saved over it — the row that save left behind. */ document: GeneratedAppDocument; findings: Finding[]; /** The automation that was created and armed. */ automation?: { record: AutomationRecord; /** What the arming actually produced — false when the engine left it * disarmed or arming threw (the issues entry says why). The thread's * automation card needs the true state, not an inference. */ enabled: boolean; resultsCollection?: string; /** Standing-grant approvals the enable flow surfaced. */ pendingGrants?: ApprovalRequest[]; }; /** What arming had to say, for the CALLER — not just the operator's log. An * automation the engine left disarmed (or failed to arm) is the person's * problem to act on, and the sentence names the surface that fixes it, so it * rides the edit result rather than only a findings line nobody reads. */ armingIssues?: string[]; } /** * Author one automation for this app, land the app's note of it, arm it, and * only THEN rewire the app around it. */ export declare const runAutomationLane: (input: { appName: string; instruction: string; mode: AutomationMode; }, document: GeneratedAppDocument, deps: AutomationLaneDeps) => Promise; /** * The automation door: author one automation for a STORED app. * * The ONE wiring — the public `automation.author` door and `vendo_make`'s * compound ask both come through here, so they can never create, arm or audit * differently. It lands the app's note through the ordinary edit persist, and * records the `automation-created` audit row: something that fires unattended is * exactly the kind of event an audit trail exists for. */ export declare const createAutomationLane: (deps: Pick) => (input: { appId: AppId; /** What the planner reads — the ask, or an escalated plan's composed brief. */ instruction: string; mode: AutomationMode; /** The person's own words, for the create-vs-replace decision and the version row. */ request: string; document: AppDocument; }, ctx: RunContext, generation: GenerationDependencies, automations?: AutomationsSeam) => Promise; /** * `AppsRuntime.automation` — the public door. * * The same wiring `vendo_make`'s compound ask uses, so an automation authored by * asking for one directly and one that came along with an app create, arm and * audit identically. */ export declare const createAutomationDoor: (deps: Pick) => AppsRuntime["automation"]; //# sourceMappingURL=lane.d.ts.map