pub struct Ticket {Show 20 fields
pub slug: String,
pub title: String,
pub priority: u8,
pub repo_refs: Vec<String>,
pub schedule: Schedule,
pub max_budget_usd: Option<f64>,
pub goal: String,
pub context: String,
pub scoping_answers: Vec<String>,
pub acceptance_hints: Vec<String>,
pub blocked_by: Vec<String>,
pub task_class: Option<String>,
pub review_artifact: Option<String>,
pub review_output: Option<String>,
pub trigger: Option<String>,
pub traced_from_mission: Option<String>,
pub defer_until: Option<DateTime<Utc>>,
pub lifecycle: Option<TicketLifecycle>,
pub state_note: Option<String>,
pub raw_body: String,
}Expand description
A parsed ticket: frontmatter fields plus body sections.
Fields§
§slug: String§title: String§priority: u8§repo_refs: Vec<String>§schedule: Schedule§max_budget_usd: Option<f64>§goal: String§context: String§scoping_answers: Vec<String>§acceptance_hints: Vec<String>§blocked_by: Vec<String>Slugs of tickets that must reach a Complete mission before this one
can be approved (blocked-by: [a, b] frontmatter).
task_class: Option<String>Backlog task class (task-class: execution-class frontmatter), used
to route the executor to a tier via crate::config::task_class_to_tier.
review_artifact: Option<String>Tracked text artifact reviewed by spec-review / incident-review.
It is context, never a writable deliverable.
review_output: Option<String>Required review deliverable. Review tickets default this to
reviews/<slug>.md; non-review tickets carry neither field.
trigger: Option<String>External trigger provenance (trigger: ci-failure|pr-comment
frontmatter) — set on webhook-drafted tickets (design D-F,
crate::hooks); None on human-authored tickets.
traced_from_mission: Option<String>Defect→mission link (traced-from-mission: m-xxxx frontmatter) — the
ONE data addition of the flight-surgeon console (ticket
flight-surgeon-dashboard): a defect ticket traces back to the mission
that shipped it. Seeded by kranz draft --from-mission or added by
hand; absent means “not a traced defect” (no false positives).
defer_until: Option<DateTime<Utc>>Deferral (defer-until: <RFC 3339> frontmatter, D-BW-3 adopted from
beads): present but NOT ready until the timestamp passes. Evaluated
against the clock at listing/admission time — no scheduler machinery;
None means ready now.
lifecycle: Option<TicketLifecycle>Operator-declared lifecycle (state: frontmatter; see the module
docs). None = the key is absent, so the .status sidecar governs
exactly as before this schema existed (backcompat); Some(Open) = an
explicit open, which defers to the sidecar the same way.
state_note: Option<String>The free-text state-note: frontmatter carried alongside a lifecycle
state (e.g. “superseded by the flight-surgeon console”). Never parsed
for meaning — notes are discussion, not a second state channel.
raw_body: StringThe full markdown body (everything after the frontmatter block).
Implementations§
Source§impl Ticket
impl Ticket
Sourcepub fn tickets_dir(repo_root: &Path) -> PathBuf
pub fn tickets_dir(repo_root: &Path) -> PathBuf
Directory holding ticket markdown for a repo: .kranz/tickets/.
Sourcepub fn valid_slug(slug: &str) -> bool
pub fn valid_slug(slug: &str) -> bool
A slug is a bare file stem, never a path: reject separators, ..,
leading dots, and empties BEFORE any join — a slug like ../x must
not escape .kranz/tickets/ (review P3).
Sourcepub fn ensure_valid_slug(slug: &str) -> Result<()>
pub fn ensure_valid_slug(slug: &str) -> Result<()>
valid_slug as an error for write/scaffold paths.
Sourcepub fn ticket_template(
title: &str,
goal: Option<&str>,
context: Option<&str>,
) -> String
pub fn ticket_template( title: &str, goal: Option<&str>, context: Option<&str>, ) -> String
The scaffolded body of a new ticket. Frontmatter carries the title;
the body is the four sections the orchestrator expects (## Goal,
## Context, ## Scoping answers, ## Acceptance hints), pre-seeded
with the goal/context when supplied. The result parses back cleanly
through Ticket::parse.
Sourcepub fn scaffold(
repo_root: &Path,
slug: &str,
title: &str,
goal: Option<&str>,
context: Option<&str>,
) -> Result<PathBuf>
pub fn scaffold( repo_root: &Path, slug: &str, title: &str, goal: Option<&str>, context: Option<&str>, ) -> Result<PathBuf>
Scaffold .kranz/tickets/<slug>.md from the template. Shared by the
CLI (kranz ticket new) and the REST POST /api/tickets handler so
the template lives in exactly one place. EngineError::Config for an
invalid slug, EngineError::InvalidState (→ 409 over REST) if a
ticket with that slug already exists. Returns the written path.
Sourcepub fn create_markdown(
repo_root: &Path,
slug: &str,
body: &str,
) -> Result<PathBuf>
pub fn create_markdown( repo_root: &Path, slug: &str, body: &str, ) -> Result<PathBuf>
Create a complete ticket without following links or replacing an existing entry. Retain directory capabilities through the write so a concurrent parent rename cannot redirect it outside the ticket tree.
Sourcepub fn parse(slug: &str, markdown: &str) -> Result<Ticket>
pub fn parse(slug: &str, markdown: &str) -> Result<Ticket>
Parse ticket markdown. slug is supplied by the caller (usually the
file stem). Malformed frontmatter is an EngineError::Config.
Sourcepub fn load(path: &Path) -> Result<Ticket>
pub fn load(path: &Path) -> Result<Ticket>
Load and parse a ticket file; the slug is the file stem.
The read is NO-FOLLOW (crate::paths::open_read_nofollow): tickets
live in a worker-writable tree and this loader runs unsandboxed in
both the CLI and kranz serve, so a symlinked <slug>.md would
otherwise hand a reader any file the process can open (audit: server
leaf reads follow symlinks).
Sourcepub fn list(repo_root: &Path) -> Vec<Ticket>
pub fn list(repo_root: &Path) -> Vec<Ticket>
Parse every *.md under .kranz/tickets/, skipping (with a warning) any
file that fails to parse. Sorted by (priority, slug).
Sourcepub fn is_ready_at(&self, now: DateTime<Utc>) -> bool
pub fn is_ready_at(&self, now: DateTime<Utc>) -> bool
Whether the ticket is ready at now: a defer-until timestamp in the
future parks it (D-BW-3); anything else — absent, or past — is ready.
Callers supply the clock so the check is explicit at each listing /
admission site (there is no scheduler flipping state).
Sourcepub fn mission_goal(&self) -> String
pub fn mission_goal(&self) -> String
Fold the whole ticket into one readable-markdown message: the goal plus
a compact appendix carrying scoping answers, acceptance hints, context,
and (when set) the task class — enough for a draft driver to seed the
orchestrator in one go, and the one channel that carries the task
class into crate::orchestrator::MissionEngine::create (which
recovers it via parse_task_class_from_goal) since every seed path —
CLI, REST, Slack — creates the mission from this folded string, not
the Ticket itself.
Sourcepub fn resolve_state(repo_root: &Path, slug: &str) -> ResolvedTicketState
pub fn resolve_state(repo_root: &Path, slug: &str) -> ResolvedTicketState
Resolve the effective ticket state under FRONTMATTER PRECEDENCE
(design ticket-state-frontmatter): a terminal state: key in the
committed .md wins over the .status sidecar — the sidecar is a
write-through cache, never the truth. A PRESENT sidecar that
disagrees is reported as a StateDivergence (a missing sidecar is
a cold cache, not a divergence). An absent key or an explicit open
defers to the sidecar; no sidecar at all is TicketState::New.
Sourcepub fn read_state(repo_root: &Path, slug: &str) -> TicketState
pub fn read_state(repo_root: &Path, slug: &str) -> TicketState
Read the effective pipeline state; a missing or unreadable status file
is TicketState::New, and an invalid slug never touches the
filesystem. Frontmatter precedence per Self::resolve_state: a
terminal state: key wins, and a diverging sidecar cache is logged —
never a silent divergence (design rule 2).
Sourcepub fn write_state(
repo_root: &Path,
slug: &str,
state: TicketState,
note: Option<String>,
) -> Result<()>
pub fn write_state( repo_root: &Path, slug: &str, state: TicketState, note: Option<String>, ) -> Result<()>
Write the pipeline state (plus an optional note) as JSON. Nothing time-based is recorded, so the file is a pure function of its inputs.
Sourcepub fn write_lifecycle(
repo_root: &Path,
slug: &str,
state: TicketLifecycle,
note: Option<String>,
) -> Result<()>
pub fn write_lifecycle( repo_root: &Path, slug: &str, state: TicketLifecycle, note: Option<String>, ) -> Result<()>
The ONE lifecycle write path (design ticket-state-frontmatter, rule 2:
the .status sidecar is a write-through cache of the committed
frontmatter state: — every lifecycle change writes BOTH, so no
reader of either file can observe them apart, and a fresh clone loses
only the cache, never the verdict).
Upserts the state: (and state-note:, replacing or removing it)
lines inside the ticket .md’s frontmatter block — every other byte
preserved — then mirrors the terminal pipeline projection into the
sidecar via Self::write_state.
Terminal states only: Open is the ABSENCE of a terminal claim, so
there is nothing to cache — un-close a ticket by removing the state:
key and resetting the pipeline by hand. Pipeline transitions
(Drafting/Review/Queued/…) keep using Self::write_state, which
never touches the committed .md.
Sourcepub fn record_mission(
repo_root: &Path,
slug: &str,
mission_id: &str,
) -> Result<()>
pub fn record_mission( repo_root: &Path, slug: &str, mission_id: &str, ) -> Result<()>
Durably link the ticket to the mission kranz draft created for it.
kranz ticket approve <slug> resolves through this link — goal-text
matching breaks the moment plan.approved rewrites the mission goal
to the orchestrator’s refined phrasing (observed live on the first
drafted batch).
Sourcepub fn mission_for(repo_root: &Path, slug: &str) -> Option<String>
pub fn mission_for(repo_root: &Path, slug: &str) -> Option<String>
The mission recorded by Self::record_mission, if any.
Sourcepub fn slug_for_mission(repo_root: &Path, mission_id: &str) -> Option<String>
pub fn slug_for_mission(repo_root: &Path, mission_id: &str) -> Option<String>
Reverse of Self::mission_for: find the ticket whose .status
records this mission id. Used when a mission-id approve/queue path
(e.g. Slack /kranz approve m-…) must still advance the linked
ticket’s pipeline state. Tickets without a mission link are skipped.
Slugs are scanned in sorted order so the result is deterministic (the
event-sourced engine must not depend on read_dir order) if two
tickets ever record the same mission id — an unexpected state, so a
duplicate link is also logged.
Sourcepub fn append_needs_context(
repo_root: &Path,
slug: &str,
questions: &[String],
) -> Result<()>
pub fn append_needs_context( repo_root: &Path, slug: &str, questions: &[String], ) -> Result<()>
Append the orchestrator’s verbatim clarifying questions to the ticket
.md under a ## Needs context (from orchestrator) heading, and set
the state to TicketState::NeedsContext.
Sourcepub fn append_wrong_plan(
repo_root: &Path,
slug: &str,
reason: &str,
) -> Result<()>
pub fn append_wrong_plan( repo_root: &Path, slug: &str, reason: &str, ) -> Result<()>
Append the planner’s wrong-plan escalation reason to the ticket .md
under a ## Wrong plan (from orchestrator) heading, and set the state
to TicketState::WrongPlan with the .status note carrying the
reason prefixed WRONG-PLAN: . Mirrors Self::append_needs_context’s
shape (bounded, scrubbed, atomic).
Sourcepub fn seed_traced_from_mission(
repo_root: &Path,
slug: &str,
mission_id: &str,
) -> Result<bool>
pub fn seed_traced_from_mission( repo_root: &Path, slug: &str, mission_id: &str, ) -> Result<bool>
Seed or update the ticket’s traced-from-mission frontmatter link
(the flight-surgeon console’s defect→mission join). kranz draft --from-mission calls this; hand-edited tickets need nothing here —
their field parses through Ticket::parse like any other. Only the
frontmatter block is rewritten (an existing link line in place, or a
new line right after the opening fence; a frontmatter-less ticket
gains a two-line block above its body) — body bytes are preserved.
Returns Ok(true) when the file changed, Ok(false) when the link
already named this mission.