using InvercargillSql.Orm; namespace ClankerSaddle.Data { /** * A software project worked on by saddle agents, stored in the * `projects` table. */ public class Project : Object { public int64 id { get; set; } public string name { get; set; default = ""; } public string git_url { get; set; default = ""; } public string default_branch { get; set; default = "main"; } public string? summary { get; set; default = null; } public DateTime? created_at { get; set; default = null; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("projects") .column("id", o => o.id, (o, v) => o.id = v) .column("name", o => o.name, (o, v) => o.name = v) .column("git_url", o => o.git_url, (o, v) => o.git_url = v) .column("default_branch", o => o.default_branch, (o, v) => o.default_branch = v) .column("summary", o => o.summary, (o, v) => o.summary = v) .column("created_at", o => o.created_at, (o, v) => o.created_at = v); } } /** * A directed relationship between two projects ('co-developed' or * 'reference'), stored in the `project_links` table. */ public class ProjectLink : Object { public int64 id { get; set; } public int64 parent_project_id { get; set; } public int64 linked_project_id { get; set; } public string relationship { get; set; default = "reference"; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("project_links") .column("id", o => o.id, (o, v) => o.id = v) .column("parent_project_id", o => o.parent_project_id, (o, v) => o.parent_project_id = v) .column("linked_project_id", o => o.linked_project_id, (o, v) => o.linked_project_id = v) .column("relationship", o => o.relationship, (o, v) => o.relationship = v); } } /** * A unit of work for a saddle agent: created in {@link * TaskService.STATUS_DRAFT}, queued by its creator (human or goal * agent), dispatched to a saddle, worked, possibly blocked on * questions, and eventually done or failed (completion is * agent-only). The plan-12 shape: goals own the task graph — a task * links its {@link goal_id}, runs as a plain worker or the goal * agent itself ({@link kind}), carries a work {@link mode} and its * {@link creator}, and human tasks may carry an auto-enqueue * predecessor ({@link enqueue_after_task_id}). Stored in the * `tasks` table. Named `TaskEntity` (not `Task`) to stay clear of * `GLib.Task` in files that import Gio transitively. */ public class TaskEntity : Object { public int64 id { get; set; } public int64 project_id { get; set; } public string title { get; set; default = ""; } public string? description { get; set; default = null; } public string status { get; set; default = "draft"; } public int64 priority { get; set; default = 5; } public string? summary { get; set; default = null; } public string? task_branch { get; set; default = null; } public string? branch_from { get; set; default = null; } public DateTime? created_at { get; set; default = null; } public DateTime? started_at { get; set; default = null; } public DateTime? completed_at { get; set; default = null; } public DateTime? suspended_at { get; set; default = null; } /** * Goal 4 task 1 (Force Wake): the human pressed Force wake on * the task page — this suspended plain task becomes * parked-offerable again with the explicit wake notice. * Consumed (cleared) by the allocation that resumes it. */ public bool force_wake { get; set; default = false; } /** The goal this task belongs to (0 = standalone). */ public int64 goal_id { get; set; default = 0; } /** "task" (a worker) or "goal_run" (the goal agent's own run). */ public string kind { get; set; default = "task"; } /** implement / investigate / review / test (orchestrate for goal runs). */ public string mode { get; set; default = "implement"; } /** "human" or "goal" — enqueueing is creator-only. */ public string creator { get; set; default = "human"; } /** Human-created tasks only: auto-enqueue when this predecessor is done. */ public int64 enqueue_after_task_id { get; set; default = 0; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("tasks") .column("id", o => o.id, (o, v) => o.id = v) .column("project_id", o => o.project_id, (o, v) => o.project_id = v) .column("title", o => o.title, (o, v) => o.title = v) .column("description", o => o.description, (o, v) => o.description = v) .column("status", o => o.status, (o, v) => o.status = v) .column("priority", o => o.priority, (o, v) => o.priority = v) .column("summary", o => o.summary, (o, v) => o.summary = v) .column("task_branch", o => o.task_branch, (o, v) => o.task_branch = v) .column("branch_from", o => o.branch_from, (o, v) => o.branch_from = v) .column("created_at", o => o.created_at, (o, v) => o.created_at = v) .column("started_at", o => o.started_at, (o, v) => o.started_at = v) .column("completed_at", o => o.completed_at, (o, v) => o.completed_at = v) .column("suspended_at", o => o.suspended_at, (o, v) => o.suspended_at = v) .column("force_wake", o => o.force_wake, (o, v) => o.force_wake = v) .column("goal_id", o => o.goal_id, (o, v) => o.goal_id = v ?? 0) .column("kind", o => o.kind, (o, v) => o.kind = v) .column("mode", o => o.mode, (o, v) => o.mode = v) .column("creator", o => o.creator, (o, v) => o.creator = v) .column("enqueue_after_task_id", o => o.enqueue_after_task_id, (o, v) => o.enqueue_after_task_id = v ?? 0); } } /** * A named bundle of reference/co-developed projects (goal 8 task * 2): applied one-click to a project's or a goal's link set so a * fleet's common reference collection needs one click, not N. * Stored in the `stacks` table. */ public class Stack : Object { public int64 id { get; set; } public string name { get; set; default = ""; } public string? summary { get; set; default = null; } public DateTime? created_at { get; set; default = null; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("stacks") .column("id", o => o.id, (o, v) => o.id = v) .column("name", o => o.name, (o, v) => o.name = v) .column("summary", o => o.summary, (o, v) => o.summary = v) .column("created_at", o => o.created_at, (o, v) => o.created_at = v); } } /** * One project of a {@link Stack} with the relationship it is * applied as ('co-developed' or 'reference'). Stored in the * `stack_projects` table. */ public class StackProject : Object { public int64 id { get; set; } public int64 stack_id { get; set; } public int64 project_id { get; set; } public string relationship { get; set; default = "reference"; } public DateTime? created_at { get; set; default = null; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("stack_projects") .column("id", o => o.id, (o, v) => o.id = v) .column("stack_id", o => o.stack_id, (o, v) => o.stack_id = v) .column("project_id", o => o.project_id, (o, v) => o.project_id = v) .column("relationship", o => o.relationship, (o, v) => o.relationship = v) .column("created_at", o => o.created_at, (o, v) => o.created_at = v); } } /** * One row of the plan-17 unified event ledger (`events`): daemon * events carry their session and its per-session monotonic `seq` * (the idempotency key), web-originated events carry neither. The * payload is a JSON object whose shape depends on `kind` (see * Services.EventService). */ public class EventRecord : Object { public int64 id { get; set; } public int64 session_id { get; set; default = 0; } public int64 seq { get; set; default = 0; } public string kind { get; set; default = ""; } public string actor { get; set; default = ""; } public string scope_type { get; set; default = ""; } public int64 scope_id { get; set; } public string payload { get; set; default = "{}"; } public DateTime? created_at { get; set; default = null; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("events") .column("id", o => o.id, (o, v) => o.id = v) .column("session_id", o => o.session_id > 0 ? (int64?) o.session_id : (int64?) null, (o, v) => o.session_id = v ?? 0) .column("seq", o => o.seq > 0 ? (int64?) o.seq : (int64?) null, (o, v) => o.seq = v ?? 0) .column("kind", o => o.kind, (o, v) => o.kind = v) .column("actor", o => o.actor, (o, v) => o.actor = v) .column("scope_type", o => o.scope_type, (o, v) => o.scope_type = v) .column("scope_id", o => o.scope_id, (o, v) => o.scope_id = v ?? 0) .column("payload", o => o.payload, (o, v) => o.payload = v) .column("created_at", o => o.created_at, (o, v) => o.created_at = v); } } /** A remark on a task, authored either by a human or by the agent ('agent'). */ public class TaskComment : Object { public int64 id { get; set; } public int64 task_id { get; set; } public string author { get; set; default = "agent"; } public string body { get; set; default = ""; } public string? quote { get; set; default = null; } public DateTime? created_at { get; set; default = null; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("task_comments") .column("id", o => o.id, (o, v) => o.id = v) .column("task_id", o => o.task_id, (o, v) => o.task_id = v) .column("author", o => o.author, (o, v) => o.author = v) .column("body", o => o.body, (o, v) => o.body = v) .column("quote", o => o.quote, (o, v) => o.quote = v) .column("created_at", o => o.created_at, (o, v) => o.created_at = v); } } /** * A blocking prompt raised by an agent mid-task — a multiple-choice * {@link block_type} question, a request for comment or a review * request — answered by a human in the web UI; the answer is collected * by the saddle on its next * {@link ClankerSaddle.Services.SaddlePollState} delivery ledger poll. */ public class TaskBlock : Object { public int64 id { get; set; } public int64 task_id { get; set; } public string question { get; set; default = ""; } public string? answer { get; set; default = null; } public string block_type { get; set; default = "question"; } public string? options { get; set; default = null; } public string? body { get; set; default = null; } public DateTime? answered_at { get; set; default = null; } public string? answered_by { get; set; default = null; } public DateTime? created_at { get; set; default = null; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("task_blocks") .column("id", o => o.id, (o, v) => o.id = v) .column("task_id", o => o.task_id, (o, v) => o.task_id = v) .column("question", o => o.question, (o, v) => o.question = v) .column("answer", o => o.answer, (o, v) => o.answer = v) .column("type", o => o.block_type, (o, v) => o.block_type = v) .column("options", o => o.options, (o, v) => o.options = v) .column("body", o => o.body, (o, v) => o.body = v) .column("answered_at", o => o.answered_at, (o, v) => o.answered_at = v) .column("answered_by", o => o.answered_by, (o, v) => o.answered_by = v) .column("created_at", o => o.created_at, (o, v) => o.created_at = v); } } /** * A saddle container registered with this app: one daemon managing one * bash session at a time, controlled over HTTP at {@link host}. The * per-saddle token ({@link token}, plan 10) authorises every * daemon→web call; removing the row revokes it. Plan 16 item 23: * sessions are the source of truth for "who runs what" — busy/idle * are DERIVED (busy iff an open session on an assigned/working task * exists; see {@link Services.SaddleService.current_work_view}) and * the row's `status` column is only the OFFLINE probe state * ('offline' vs 'idle'): the poller's unreachable stamp, cleared by * a successful probe or a proof-of-life ask. The former * `current_task_id`/`allocated_at` cache columns are gone. */ public class Saddle : Object { public int64 id { get; set; } public string name { get; set; default = ""; } public string host { get; set; default = ""; } public string status { get; set; default = "idle"; } public string? git_name { get; set; default = null; } public string? git_email { get; set; default = null; } /** * The saddle admin's standing prompt note (goal 8 task 5): * a message about THIS machine (e.g. "You have root and may * install development tooling via DNF") served verbatim inside * every assignment's system prompt; null when unset. */ public string? prompt_note { get; set; default = null; } public string? ssh_public_key { get; set; default = null; } public string? token { get; set; default = null; } /** * The workspace root this saddle's daemon reported at * /register (its SADDLE_WORKSPACE_ROOT), or null for daemons * that predate the field — assignment paths fall back to * SADDLE_PROJECTS_ROOT, then "/workspace". */ public string? workspace_root { get; set; default = null; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("saddles") .column("id", o => o.id, (o, v) => o.id = v) .column("name", o => o.name, (o, v) => o.name = v) .column("host", o => o.host, (o, v) => o.host = v) .column("status", o => o.status, (o, v) => o.status = v) .column("git_name", o => o.git_name, (o, v) => o.git_name = v) .column("git_email", o => o.git_email, (o, v) => o.git_email = v) .column("prompt_note", o => o.prompt_note, (o, v) => o.prompt_note = v) .column("ssh_public_key", o => o.ssh_public_key, (o, v) => o.ssh_public_key = v) .column("token", o => o.token, (o, v) => o.token = v) .column("workspace_root", o => o.workspace_root, (o, v) => o.workspace_root = v); } } /** * One run of a task on a saddle: opened when the dispatcher assigns * the task, closed with an outcome when the session ends. */ public class Session : Object { public int64 id { get; set; } public int64 task_id { get; set; } public int64 saddle_id { get; set; } public DateTime? started_at { get; set; default = null; } public DateTime? ended_at { get; set; default = null; } public string? outcome { get; set; default = null; } /** * What a resumed session's agent first received (plan 14 B4): * the resume_notice the allocation built, stored on the row at * resume-allocation; null for fresh sessions. */ public string? resume_notice { get; set; default = null; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("sessions") .column("id", o => o.id, (o, v) => o.id = v) .column("task_id", o => o.task_id, (o, v) => o.task_id = v) .column("saddle_id", o => o.saddle_id, (o, v) => o.saddle_id = v) .column("started_at", o => o.started_at, (o, v) => o.started_at = v) .column("ended_at", o => o.ended_at, (o, v) => o.ended_at = v) .column("outcome", o => o.outcome, (o, v) => o.outcome = v) .column("resume_notice", o => o.resume_notice, (o, v) => o.resume_notice = v); } } /** * A reusable instruction the dispatched agent must follow, stored in * the `rules` table and attached to projects and/or tasks. * `updated_at` stamps the last body/name edit (null until edited) * — the resume-time context_update delta keys on it. */ public class Rule : Object { public int64 id { get; set; } public string name { get; set; default = ""; } public string body { get; set; default = ""; } public DateTime? created_at { get; set; default = null; } public DateTime? updated_at { get; set; default = null; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("rules") .column("id", o => o.id, (o, v) => o.id = v) .column("name", o => o.name, (o, v) => o.name = v) .column("body", o => o.body, (o, v) => o.body = v) .column("created_at", o => o.created_at, (o, v) => o.created_at = v) .column("updated_at", o => o.updated_at, (o, v) => o.updated_at = v); } } /** An attachment of a rule to a project (`rule_projects`). */ public class RuleProject : Object { public int64 id { get; set; } public int64 rule_id { get; set; } public int64 project_id { get; set; } public DateTime? created_at { get; set; default = null; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("rule_projects") .column("id", o => o.id, (o, v) => o.id = v) .column("rule_id", o => o.rule_id, (o, v) => o.rule_id = v) .column("project_id", o => o.project_id, (o, v) => o.project_id = v) .column("created_at", o => o.created_at, (o, v) => o.created_at = v); } } /** An attachment of a rule to a task (`task_rules`). */ public class TaskRule : Object { public int64 id { get; set; } public int64 rule_id { get; set; } public int64 task_id { get; set; } public DateTime? created_at { get; set; default = null; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("task_rules") .column("id", o => o.id, (o, v) => o.id = v) .column("rule_id", o => o.rule_id, (o, v) => o.rule_id = v) .column("task_id", o => o.task_id, (o, v) => o.task_id = v) .column("created_at", o => o.created_at, (o, v) => o.created_at = v); } } /** * An LLM-authored remark about a project (`saddle note` via the * callback endpoint), curated from the project page. The source task * is null for notes without one. */ public class ProjectNote : Object { public int64 id { get; set; } public int64 project_id { get; set; } public int64 source_task_id { get; set; default = 0; } public string body { get; set; default = ""; } public DateTime? created_at { get; set; default = null; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("project_notes") .column("id", o => o.id, (o, v) => o.id = v) .column("project_id", o => o.project_id, (o, v) => o.project_id = v) .column("source_task_id", o => o.source_task_id, (o, v) => o.source_task_id = v ?? 0) .column("body", o => o.body, (o, v) => o.body = v) .column("created_at", o => o.created_at, (o, v) => o.created_at = v); } } /** * One historical revision of a project's Understanding (goal 24): * a markdown document summarising the project's structure and * anything unusual worth knowing, stamped with the commit hash it * was written against. Rows are immutable — a refresh INSERTS a * new revision — and the project's "current" understanding is its * newest row (see {@link ClankerSaddle.Services.UnderstandingService}). * Stored in the `project_understandings` table. */ public class ProjectUnderstanding : Object { public int64 id { get; set; } public int64 project_id { get; set; } public string body { get; set; default = ""; } /** The commit the document was written against ("" when unknown). */ public string commit_hash { get; set; default = ""; } /** The understanding task that wrote the row (0 = human/unknown). */ public int64 task_id { get; set; default = 0; } public DateTime? created_at { get; set; default = null; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("project_understandings") .column("id", o => o.id, (o, v) => o.id = v) .column("project_id", o => o.project_id, (o, v) => o.project_id = v) .column("body", o => o.body, (o, v) => o.body = v) .column("commit_hash", o => o.commit_hash, (o, v) => o.commit_hash = v) .column("task_id", o => (int64?) o.task_id, (o, v) => o.task_id = v ?? 0) .column("created_at", o => o.created_at, (o, v) => o.created_at = v); } } /** * One row of the discoveries ledger (goal 24): the fleet's global, * EDITABLE knowledge base of non-project facts — compiler quirks, * library behaviours, tool flags, protocol gotchas. Unlike the * immutable Understanding history, a discovery is LIVING: agents * refine it in place through the update callback (the body is * replaced, {@link updated_at} bumped) and humans edit or delete * rows from the Discoveries page, so nuance accumulates in one * note instead of stacking near-duplicates. Stored in the * `discoveries` table. */ public class Discovery : Object { public int64 id { get; set; } public string body { get; set; default = ""; } /** The task whose agent wrote the row (0 = human/unknown). */ public int64 source_task_id { get; set; default = 0; } /** When the row was first written — frozen. */ public DateTime? created_at { get; set; default = null; } /** When the body was last refined (equals created_at until then). */ public DateTime? updated_at { get; set; default = null; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("discoveries") .column("id", o => o.id, (o, v) => o.id = v) .column("body", o => o.body, (o, v) => o.body = v) .column("source_task_id", o => (int64?) o.source_task_id, (o, v) => o.source_task_id = v ?? 0) .column("created_at", o => o.created_at, (o, v) => o.created_at = v) .column("updated_at", o => o.updated_at, (o, v) => o.updated_at = v); } } /** * A dashboard scratchpad note (goal 8 task 3): a personal * todo/idea jotted down before a batch of changes is ready to be * a full goal. Promoting ({@link ClankerSaddle.Services.NoteService.promote_to_goal}) * creates a DRAFT goal from the body and sets {@link goal_id}, * which links the created goal and freezes the note. Stored in * the `notes` table. */ public class Note : Object { public int64 id { get; set; } public string body { get; set; default = ""; } public bool done { get; set; default = false; } public DateTime? done_at { get; set; default = null; } /** The goal this note promoted into (0 = not promoted). */ public int64 goal_id { get; set; default = 0; } public DateTime? created_at { get; set; default = null; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("notes") .column("id", o => o.id, (o, v) => o.id = v) .column("body", o => o.body, (o, v) => o.body = v) .column("done", o => o.done, (o, v) => o.done = v) .column("done_at", o => o.done_at, (o, v) => o.done_at = v) .column("goal_id", o => o.goal_id > 0 ? (int64?) o.goal_id : (int64?) null, (o, v) => o.goal_id = v ?? 0) .column("created_at", o => o.created_at, (o, v) => o.created_at = v); } } /** * A named tag used for dispatch routing (`capabilities`): saddles * declare the tags they carry, tasks the tags they require. */ public class Capability : Object { public int64 id { get; set; } public string name { get; set; default = ""; } public DateTime? created_at { get; set; default = null; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("capabilities") .column("id", o => o.id, (o, v) => o.id = v) .column("name", o => o.name, (o, v) => o.name = v) .column("created_at", o => o.created_at, (o, v) => o.created_at = v); } } /** A tag declaration on a saddle (`saddle_capabilities`). */ public class SaddleCapability : Object { public int64 id { get; set; } public int64 capability_id { get; set; } public int64 saddle_id { get; set; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("saddle_capabilities") .column("id", o => o.id, (o, v) => o.id = v) .column("capability_id", o => o.capability_id, (o, v) => o.capability_id = v) .column("saddle_id", o => o.saddle_id, (o, v) => o.saddle_id = v); } } /** A required tag on a task (`task_capabilities`). */ public class TaskCapability : Object { public int64 id { get; set; } public int64 capability_id { get; set; } public int64 task_id { get; set; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("task_capabilities") .column("id", o => o.id, (o, v) => o.id = v) .column("capability_id", o => o.capability_id, (o, v) => o.capability_id = v) .column("task_id", o => o.task_id, (o, v) => o.task_id = v); } } /** * A user's Web Push subscription endpoint (plan 11 task E shape): * `p256dh`/`auth` are the browser's application server keys; the * endpoint URL is unique per browser. `user_id` references the * Spry users table by value (no FK — the auth schema owns that * table). */ public class PushSubscription : Object { public int64 id { get; set; } public int64 user_id { get; set; } public string endpoint { get; set; default = ""; } public string p256dh { get; set; default = ""; } public string auth { get; set; default = ""; } public DateTime? created_at { get; set; default = null; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("push_subscriptions") .column("id", o => o.id, (o, v) => o.id = v) .column("user_id", o => o.user_id, (o, v) => o.user_id = v) .column("endpoint", o => o.endpoint, (o, v) => o.endpoint = v) .column("p256dh", o => o.p256dh, (o, v) => o.p256dh = v) .column("auth", o => o.auth, (o, v) => o.auth = v) .column("created_at", o => o.created_at, (o, v) => o.created_at = v); } } /** * A user's per-task "Notify me" opt-in (plan 11 task E shape): the * task's first start, failure and completion notify the opted-in * user; block events notify all push-subscribed users globally. */ public class TaskNotifyPref : Object { public int64 id { get; set; } public int64 task_id { get; set; } public int64 user_id { get; set; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("task_notify_prefs") .column("id", o => o.id, (o, v) => o.id = v) .column("task_id", o => o.task_id, (o, v) => o.task_id = v) .column("user_id", o => o.user_id, (o, v) => o.user_id = v); } } /** * A user-created goal (plan 12): an agent-orchestrated unit of * work over one primary project plus optional links. The brief * locks once started; the lifecycle runs draft → planning → plan * review → active → completion review → complete / failed / * cancelled, with paused freezing supervision in between. Stored * in the `goals` table. */ public class Goal : Object { public int64 id { get; set; } public int64 project_id { get; set; } public string title { get; set; default = ""; } public string brief { get; set; default = ""; } public string branch_name { get; set; default = ""; } public string? base_branch { get; set; default = null; } public string status { get; set; default = "draft"; } public int64 priority { get; set; default = 3; } public string? plan { get; set; default = null; } public string? completion_report { get; set; default = null; } public string creator { get; set; default = "human"; } /** * Plan 15 C3 (was last_seen_event_id): the timestamp through * which the goal agent has consumed its tasks' terminal * transitions — resume_due fires on transitions newer than * it, and a resume advance it. The narrative itself is * derived at read time. */ public DateTime? last_terminal_watermark { get; set; default = null; } public DateTime? created_at { get; set; default = null; } public DateTime? started_at { get; set; default = null; } public DateTime? completed_at { get; set; default = null; } public DateTime? paused_at { get; set; default = null; } /** * Goal 4 task 1 (Force Wake): the human pressed Force wake on * the goal page — the parked run becomes offerable immediately * and the resume notice leads with the explicit wake line. * Consumed (cleared) by the resume that carries it. */ public bool force_wake { get; set; default = false; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("goals") .column("id", o => o.id, (o, v) => o.id = v) .column("project_id", o => o.project_id, (o, v) => o.project_id = v) .column("title", o => o.title, (o, v) => o.title = v) .column("brief", o => o.brief, (o, v) => o.brief = v) .column("branch_name", o => o.branch_name, (o, v) => o.branch_name = v) .column("base_branch", o => o.base_branch, (o, v) => o.base_branch = v) .column("status", o => o.status, (o, v) => o.status = v) .column("priority", o => o.priority, (o, v) => o.priority = v) .column("plan", o => o.plan, (o, v) => o.plan = v) .column("completion_report", o => o.completion_report, (o, v) => o.completion_report = v) .column("creator", o => o.creator, (o, v) => o.creator = v) .column("last_terminal_watermark", o => o.last_terminal_watermark, (o, v) => o.last_terminal_watermark = v) .column("created_at", o => o.created_at, (o, v) => o.created_at = v) .column("started_at", o => o.started_at, (o, v) => o.started_at = v) .column("completed_at", o => o.completed_at, (o, v) => o.completed_at = v) .column("paused_at", o => o.paused_at, (o, v) => o.paused_at = v) .column("force_wake", o => o.force_wake, (o, v) => o.force_wake = v); } } /** A project link on a goal (`goal_links`): co-developed or reference. */ public class GoalLink : Object { public int64 id { get; set; } public int64 goal_id { get; set; } public int64 project_id { get; set; } public string relationship { get; set; default = "reference"; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("goal_links") .column("id", o => o.id, (o, v) => o.id = v) .column("goal_id", o => o.goal_id, (o, v) => o.goal_id = v) .column("project_id", o => o.project_id, (o, v) => o.project_id = v) .column("relationship", o => o.relationship, (o, v) => o.relationship = v); } } /** * One advisor chat (goal 27): a private conversation between a * user and the read-only AI advisor loop. `status` is the loop's * coarse state — 'idle', 'running' (a turn is in flight) or * 'awaiting_answer' (the loop parked on an ask_question). Stored * in the `advisor_chats` table. */ public class AdvisorChat : Object { public int64 id { get; set; } public int64 user_id { get; set; } public string title { get; set; default = ""; } public string status { get; set; default = "idle"; } public DateTime? created_at { get; set; default = null; } public DateTime? updated_at { get; set; default = null; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("advisor_chats") .column("id", o => o.id, (o, v) => o.id = v) .column("user_id", o => o.user_id, (o, v) => o.user_id = v) .column("title", o => o.title, (o, v) => o.title = v) .column("status", o => o.status, (o, v) => o.status = v) .column("created_at", o => o.created_at, (o, v) => o.created_at = v) .column("updated_at", o => o.updated_at, (o, v) => o.updated_at = v); } } /** * One row of an advisor chat's transcript (goal 27) — the * conversation stored IN THE WIRE FORMAT so the OpenAI messages * array reconstructs from rows alone on every turn (no separate * transcript cache; a restart loses nothing). `kind` is 'user', * 'assistant', 'tool', 'error' (a failed turn's message) or * 'system' (an advisor-external notice such as a task completion * callback). A tool row carries the call's name/arguments/result * in the JSON `payload`; a question is an assistant row whose * payload holds `{"options": [...]}` and whose `answer` is filled * when the human replies. Stored in the `advisor_messages` table. */ public class AdvisorMessage : Object { public int64 id { get; set; } public int64 chat_id { get; set; } public string kind { get; set; default = "user"; } public string content { get; set; default = ""; } /** JSON: a tool row's name/arguments/result; a question's options. */ public string? payload { get; set; default = null; } /** The human's answer when this row is a parked question. */ public string? answer { get; set; default = null; } public DateTime? answered_at { get; set; default = null; } public DateTime? created_at { get; set; default = null; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("advisor_messages") .column("id", o => o.id, (o, v) => o.id = v) .column("chat_id", o => o.chat_id, (o, v) => o.chat_id = v) .column("kind", o => o.kind, (o, v) => o.kind = v) .column("content", o => o.content, (o, v) => o.content = v) .column("payload", o => o.payload, (o, v) => o.payload = v) .column("answer", o => o.answer, (o, v) => o.answer = v) .column("answered_at", o => o.answered_at, (o, v) => o.answered_at = v) .column("created_at", o => o.created_at, (o, v) => o.created_at = v); } } /** * One advisor suggestion (goal 27): a task or goal the advisor * proposed mid-chat. INERT while 'pending' — nothing is created * until the chat's user approves it in the UI ('approved' / * 'declined'); an approved row keeps the created * {@link task_id}/{@link goal_id} so the completion callback finds * the chat again. `delivered_at` marks the callback delivered. * `payload` is the JSON the approve action creates from (title, * mandate/brief, mode, project_id, priority, branch_name…). * Stored in the `advisor_suggestions` table. */ public class AdvisorSuggestion : Object { public int64 id { get; set; } public int64 chat_id { get; set; } public int64 user_id { get; set; } /** "task" or "goal". */ public string kind { get; set; default = "task"; } public string payload { get; set; default = "{}"; } public string status { get; set; default = "pending"; } public int64 task_id { get; set; default = 0; } public int64 goal_id { get; set; default = 0; } public DateTime? created_at { get; set; default = null; } public DateTime? decided_at { get; set; default = null; } public DateTime? delivered_at { get; set; default = null; } public static void entity_mapping(EntityMapperBuilder cfg) { cfg.table("advisor_suggestions") .column("id", o => o.id, (o, v) => o.id = v) .column("chat_id", o => o.chat_id, (o, v) => o.chat_id = v) .column("user_id", o => o.user_id, (o, v) => o.user_id = v) .column("kind", o => o.kind, (o, v) => o.kind = v) .column("payload", o => o.payload, (o, v) => o.payload = v) .column("status", o => o.status, (o, v) => o.status = v) .column("task_id", o => (int64?) o.task_id, (o, v) => o.task_id = v ?? 0) .column("goal_id", o => (int64?) o.goal_id, (o, v) => o.goal_id = v ?? 0) .column("created_at", o => o.created_at, (o, v) => o.created_at = v) .column("decided_at", o => o.decided_at, (o, v) => o.decided_at = v) .column("delivered_at", o => o.delivered_at, (o, v) => o.delivered_at = v); } } }