Files
2026-08-24 13:06:29 +05:30

103 lines
4.9 KiB
Go

// 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 }