103 lines
4.9 KiB
Go
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 }
|