// Package core holds the pure domain logic of spec.sr.ht: owner and space name // validation, safe document paths, the globally-unique document ID grammar, the // frontmatter schema contract, `.spec.yml` space policy, the proposal state // machine and branch naming, and [SpaceFilter] — what a project resolves to and // what a search is scoped by, which is here rather than in either of them // because it is the one thing they must agree about. // // It depends on nothing but the standard library and gopkg.in/yaml.v3, never // touches the network or the filesystem, and knows nothing about git, Postgres // or HTTP. Dependency direction is strictly downward: gitx, db, service, api and // web import core; core imports none of them. That is what keeps the three // agent-facing surfaces (REST, MCP, GraphQL) behaviourally identical — they // share these rules rather than each re-deriving them. package core import "errors" // Sentinel errors, one per failure class. Callers compare with errors.Is; // wrapping with %w adds the offending value without losing the class, which is // what lets the API layer map a failure to a status code (422 for a schema // violation, 400 for a malformed name) without string matching. var ( // ErrInvalidName is returned for a malformed owner or space name. ErrInvalidName = errors.New("invalid name") // ErrReservedName is returned for a name that is well-formed but not the // caller's to claim. The one instance today is the meta-project: it is an // address that resolves to a filter excluding nothing, so a stored project // of that name could only shadow it. ErrReservedName = errors.New("reserved name") // ErrInvalidPath is returned for a path that is not a safe relative path // inside a space: absolute, traversing, or carrying bytes that would be // unsafe in a git tree or misleading in the review UI. ErrInvalidPath = errors.New("invalid path") // ErrInvalidDocID is returned for a document ID that does not match the // PREFIX-DIGITS grammar. IDs are globally unique and are the anchor for // cross-space links, comments and staleness checks, so shape is enforced // at every door. ErrInvalidDocID = errors.New("invalid document id") // ErrMalformedFrontmatter marks a document whose YAML frontmatter block is // missing, unterminated, not a mapping, or not parseable. It is distinct // from ErrMissingField: this is "cannot read", not "read but incomplete". ErrMalformedFrontmatter = errors.New("malformed frontmatter") // ErrMissingField marks frontmatter that parsed but omits a key the space's // schema requires. This is the 422 an agent gets for forgetting `status:`. ErrMissingField = errors.New("missing required frontmatter field") // ErrInvalidStatus is returned for a `status:` value outside the allowed // enum. Note "approved" is deliberately not a status — approval is a // property of the branch a document is reachable from, never of authored // metadata. ErrInvalidStatus = errors.New("invalid status") // ErrInvalidPolicy is returned for a `.spec.yml` that parses as YAML but // does not describe a usable policy. ErrInvalidPolicy = errors.New("invalid .spec.yml policy") // ErrInvalidPattern is returned for a malformed auto_merge path pattern. // A pattern that silently matches nothing would quietly turn the bimodal // cadence back into "everything waits for a human", so it is rejected. ErrInvalidPattern = errors.New("invalid path pattern") // ErrInvalidState is returned for a proposal state string outside the // open/merged/rejected set, typically read back from Postgres. ErrInvalidState = errors.New("invalid proposal state") // ErrInvalidTransition is returned for a proposal state change the machine // does not allow, such as re-merging an already rejected proposal. ErrInvalidTransition = errors.New("invalid proposal state transition") // ErrInvalidApproval is returned for an approval kind outside human/policy. ErrInvalidApproval = errors.New("invalid approval kind") // ErrInvalidProposalID is returned for a proposal id that cannot name a // proposal: ids are Postgres sequence values starting at 1, so anything // else is an unwritten row or an unset field rather than a proposal. ErrInvalidProposalID = errors.New("invalid proposal id") // ErrInvalidCommentSide is returned for a comment side outside new/old. ErrInvalidCommentSide = errors.New("invalid comment side") )