// Package definition reads Krow agent and skill definitions — Markdown with // YAML frontmatter — the way the frontend reads them. // // # Why this exists // // A definition is authored in the browser and stored by the server, so two // parsers see it: the JavaScript in src/lib/skills and src/lib/agents, and // this one. If they disagree, one of two things happens, and both are silent: // // - A definition the editor accepts and this package rejects looks valid // while it is being written and fails when it is saved. // - A definition this package accepts and the editor rejects is stored and // then cannot be rendered by the product that owns it. // // Compatibility is therefore a contract rather than an aspiration, and it is // enforced by a conformance suite (conformance_test.go) that replays the // ACTUAL output of the JavaScript parser — captured from the real frontend // module graph — against this one, over all 37 shipped definitions and every // adversarial case in testdata/oracle.json. The corpus count is pinned by a // test; the case count is deliberately not restated here, because a number // kept in a comment is a number that goes stale. // // # What is in the contract // // - The document layer: byte-order mark, line endings, leading blank lines, // fence recognition, body extraction. See frontmatter.go. // - The YAML subset: block maps and sequences, scalars, quoting, comments. // A port of yaml.js, with no YAML dependency, deliberately — see yaml.go. // - Definition-level normalization and validation: id, name, description, // status, version, pages, icons, reasoning, permissions, starters, // knowledge, subagents. // // Those cover every column migration 000005 projects out of a definition: // definition_id, status, version, name, description, pages. // // # What is deferred, and why // // A skill may carry a `ui:` block (declarative page sections) or an `owliver:` // block (assistant capabilities). Validating those means reproducing roughly // 1,500 lines of closed vocabulary describing what the FRONTEND can render — // placements, data sources, section types, periods — none of which the backend // stores, projects, or acts on. // // This package therefore does not check them. It records their presence on // Skill.Deferred instead, so the gap is a value a caller can see rather than // an assumption. The one consequence is stated exactly: // // skill-examples/board-invalid-context.md is rejected by the frontend, on a // rule about which placement can supply which data source, and accepted // here. It is the only definition in the corpus where the two disagree, and // the conformance suite asserts that it stays the only one. // // # What is not the parser's job // // Normalization never rewrites what is stored. Migration 000005 keeps // `markdown` verbatim and every other column is derived from it; this package // only ever reads. The Markdown handed in is the Markdown that goes to the // database, byte for byte, and a test asserts it. // // Visibility (personal or organization) is deliberately absent. It is not a // frontmatter field — the frontend ignores `visibility:` in a definition // entirely — it is a storage tier chosen by the request and checked by the // visibility CHECK in migration 000005. A definition cannot name its own // tenancy. package definition // MaxMarkdownLength is the markdown_size CHECK from migration 000005, in // CHARACTERS — `length()` in PostgreSQL counts characters, not bytes. // // The frontend does NOT enforce this, so a definition longer than this is one // the editor accepts and the database refuses. This package refuses it first, // which turns a constraint violation into a message an author can act on. const MaxMarkdownLength = 65536 // MaxVersion is the range of agent_definitions.version, a PostgreSQL // `integer`. // // The frontend accepts any integer of 1 or more, so a version above this is // another value the editor accepts and the database cannot store. const MaxVersion = 2147483647 // maxExactInteger is 2^53-1, the largest integer a float64 names exactly and so // the largest a JavaScript number carries without loss. It bounds the version // conversion in ParseAgent; it is not a rule about what may be stored, which is // MaxVersion's job. const maxExactInteger = 1<<53 - 1 // Rejection is a definition that parses but may not be stored. // // Message is the frontend's own wording wherever the rule is shared, so the // editor and the API describe the same problem the same way. type Rejection struct { Message string // BackendOnly marks a rule the frontend does not have — a bound the // database imposes that the editor never checks. These are the only // messages that can differ from what an author would see in the browser, // and each one is listed in docs/phase-4d-parser-contract.md. BackendOnly bool } func (r *Rejection) Error() string { return r.Message }