Documentation
¶
Index ¶
- Constants
- Variables
- func DefaultMetamodelYAML() string
- func FiniteOrder(v any) (float64, bool)
- func IsBuiltinType(t string) bool
- func NextRrule(s string, after time.Time) (time.Time, error)
- func ParseBooleanValue(val any) (bool, error)
- func ParseDateValue(s string, propDef *PropertyDef) (time.Time, error)
- func ParseIntegerValue(val any) (int, error)
- func RenameEntityType(path, oldType, newType string, fs storage.FS) error
- func ValidateIDPrefix(prefix string) error
- func ValidateRrule(s string) error
- func ValidateSchemaName(name string) error
- type ACLBypass
- type AttachmentPolicy
- type AttachmentsConfig
- type AutomationAction
- type AutomationCheck
- type AutomationDef
- type AutomationTrigger
- type Capabilities
- type ChecklistRule
- type CircularIncludeError
- type ConflictingIDPrefixError
- type ContentRule
- type CreateEntityAction
- type CreateRelationAction
- type CustomType
- type DuplicateDefinitionError
- type EntityDef
- func (e *EntityDef) DisplayProperties() []string
- func (e *EntityDef) DisplayTitle(id string, properties map[string]any) string
- func (e *EntityDef) GetAliases() []string
- func (e *EntityDef) GetBorderColor() string
- func (e *EntityDef) GetColor() string
- func (e *EntityDef) GetDefaultStatus(m *Metamodel) string
- func (e *EntityDef) GetIDCaps() string
- func (e *EntityDef) GetIDPatterns() []stringdeprecated
- func (e *EntityDef) GetIDPrefixes() []string
- func (e *EntityDef) GetIDType() string
- func (e *EntityDef) GetLabel() string
- func (e *EntityDef) GetLabelPlural() string
- func (e *EntityDef) GetPlural(typeName string) string
- func (e *EntityDef) GetPrimaryProperty() string
- func (e *EntityDef) GetProperties() any
- func (e *EntityDef) GetPropertyOrder() []string
- func (e *EntityDef) GetRDFType() string
- func (e *EntityDef) HasContent() bool
- func (e *EntityDef) HasPattern(pattern string) bool
- func (e *EntityDef) IsManualID() bool
- func (e *EntityDef) IsSequentialID() bool
- func (e *EntityDef) IsShortID() bool
- func (e *EntityDef) MatchesID(id string) bool
- func (e *EntityDef) PropertyDefs() map[string]PropertyDef
- type EntityProjection
- type EntityShape
- type FSLoader
- type HeaderCheck
- type ImageStep
- type IncludeHasRootFieldError
- type IncludeNotFoundError
- type InvalidIDCapsError
- type InvalidIDPrefixError
- type InvalidIDTypeError
- type InvalidRelationError
- type InverseDef
- type Loader
- type Metamodel
- func (m *Metamodel) DisplayTitle(id, entityType string, properties map[string]any) string
- func (m *Metamodel) EntityTypes() []string
- func (m *Metamodel) GetEntities() any
- func (m *Metamodel) GetEntityDef(entityType string) (*EntityDef, bool)
- func (m *Metamodel) GetNamespace() string
- func (m *Metamodel) GetPropertyDefault(entityType, property string) string
- func (m *Metamodel) GetPropertyType(entityType, property string) string
- func (m *Metamodel) GetRelationDef(name string) (*RelationDef, bool)
- func (m *Metamodel) GetRelationFrom(relation string) []string
- func (m *Metamodel) GetRelationLabel(relation string) string
- func (m *Metamodel) GetRelationTo(relation string) []string
- func (m *Metamodel) GetRelations() any
- func (m *Metamodel) GetTypeDefault(typeName string) string
- func (m *Metamodel) GetTypes() any
- func (m *Metamodel) GetVersion() string
- func (m *Metamodel) HasEntityType(entityType string) bool
- func (m *Metamodel) HasValidationRule(ruleName string) bool
- func (m *Metamodel) InferEntityType(id string) string
- func (m *Metamodel) InitAliases()
- func (m *Metamodel) InverseOwner(inverseName string) (string, bool)
- func (m *Metamodel) IsEnumType(typeName string) bool
- func (m *Metamodel) IsPropertyRequired(entityType, property string) bool
- func (m *Metamodel) RelationTypes() []string
- func (m *Metamodel) RenderProjection() RenderProjection
- func (m *Metamodel) ResolveAlias(alias string) string
- func (m *Metamodel) ResolveWidgetFromType(propType string) string
- func (m *Metamodel) ShapeProjection() ShapeProjection
- func (m *Metamodel) ValidateEntity(id, entityType string, properties map[string]any) []*ValidationError
- func (m *Metamodel) ValidateProperties(props map[string]any, schema PropertySchema) []*ValidationError
- func (m *Metamodel) ValidatePropertyValue(propName string, propDef *PropertyDef, val any) error
- func (m *Metamodel) ValidateRelation(relationType, fromType, toType string) error
- func (m *Metamodel) ValidateRelationProperties(relationType string, properties map[string]any) []*ValidationError
- type OrderableMode
- type PropertyDef
- type PropertyProjection
- type PropertySchema
- type PropertyShape
- type RelationDef
- func (r *RelationDef) GetDescription() string
- func (r *RelationDef) GetFrom() []string
- func (r *RelationDef) GetInverse() any
- func (r *RelationDef) GetLabel() string
- func (r *RelationDef) GetMaxIncoming() *int
- func (r *RelationDef) GetMaxOutgoing() *int
- func (r *RelationDef) GetMinIncoming() *int
- func (r *RelationDef) GetMinOutgoing() *int
- func (r *RelationDef) GetTo() []string
- func (r *RelationDef) HasAdvancedFeatures() bool
- func (r *RelationDef) HasContent() bool
- func (r *RelationDef) IncomingOrderProperty() string
- func (r *RelationDef) IsSymmetric() bool
- func (r *RelationDef) OutgoingOrderProperty() string
- func (r *RelationDef) PropertyDefs() map[string]PropertyDef
- type RelationNotFoundError
- type RelationShape
- type RenderProjection
- type ReservedPropertyError
- type ReservedTypeNameError
- type ScanPolicy
- type SchemaValidationError
- type ShapeDelta
- type ShapeProjection
- type ShapeReport
- type ShapeTier
- type SortSpec
- type StringOrSlice
- type Subscriber
- type TransformDef
- type TransformStep
- type TransitionDef
- type TypeValidation
- type ValidationError
- type ValidationErrorType
- type ValidationRule
- type WhitespacePropertyError
Constants ¶
const ( PropertyTypeString = "string" PropertyTypeDate = "date" PropertyTypeDatetime = "datetime" PropertyTypeInteger = "integer" PropertyTypeBoolean = "boolean" PropertyTypeEnum = "enum" PropertyTypeFile = "file" PropertyTypeRrule = "rrule" )
Built-in property types
const ( IDTypeShort = "short" // IDs are random base36 strings (e.g., REQ-a3f8) - default IDTypeSequential = "sequential" // IDs are auto-generated with numeric suffix (e.g., REQ-001) IDTypeManual = "manual" // IDs are manually specified strings (e.g., auth-module) // Deprecated alias (still accepted for backwards compatibility) IDTypeString = "string" // Deprecated: use "manual" instead )
ID types for entities
const ( IDCapsUpper = "upper" // Random suffix is uppercase (e.g., REQ-A3F8) - default IDCapsLower = "lower" // Random suffix is lowercase (e.g., REQ-a3f8) )
ID capitalization modes for short IDs
const ( OrderPropertyOut = "_order_out" OrderPropertyIn = "_order_in" )
Reserved relation-property names that hold the user-controlled order value. The names are stable across mode changes so that promoting a relation from outgoing-only to both (or vice versa) keeps existing values on disk valid.
const DefaultDateFormat = "2006-01-02"
DefaultDateFormat is the default format for date properties (ISO 8601)
const DefaultDatetimeFormat = time.RFC3339
DefaultDatetimeFormat is the default format for datetime properties (RFC3339, a time-bearing ISO 8601 instant). Unlike date, a datetime value carries a time-of-day and (canonically) a UTC offset.
Variables ¶
var ErrRruleExhausted = errors.New("rrule exhausted")
ErrRruleExhausted marks "this rule has no further occurrence" — a COUNT that has been reached or an UNTIL that has passed. It is distinct from a malformed rule so a caller can tell a finished schedule from an operator typo.
var ReservedPropertyNames = map[string]bool{ "id": true, "type": true, }
ReservedPropertyNames contains property names that cannot be used in metamodel definitions because they conflict with built-in entity fields.
Functions ¶
func DefaultMetamodelYAML ¶
func DefaultMetamodelYAML() string
DefaultMetamodelYAML returns the default metamodel as YAML
func FiniteOrder ¶
FiniteOrder converts a JSON- or YAML-decoded relation-property value to a finite float64. Returns (value, true) for any built-in Go numeric type (int*, uint*, float32, float64) that is finite; returns (0, false) for nil, non-numeric types, NaN, or +/-Inf.
All consumers that interpret managed order properties go through this helper: the entity manager's auto-assign and renumber paths, the data-entry sort and wire validators, the analyzer, and the CLI commands. Keep one canonical implementation to avoid drift; do not redefine variants in callers.
func IsBuiltinType ¶
IsBuiltinType returns true if the type is a built-in property type
func NextRrule ¶
NextRrule returns the first occurrence of s strictly after `after`, truncated to UTC midnight.
It lives here, beside ValidateRrule, so RRULE mechanics stay in one package: callers get validation and occurrence-stepping with identical prefix handling and identical error text, and no other package needs to depend on the rrule library.
A rule without DTSTART is anchored at `after`, so a bare rule stored in an `rrule` property is interpreted relative to the date the caller asked about. (ValidateRrule already rejects INTERVAL > 1 without DTSTART, since that combination drifts.)
Returns ErrRruleExhausted when the rule has no occurrence left.
func ParseBooleanValue ¶
ParseBooleanValue parses a boolean from various input types
func ParseDateValue ¶
func ParseDateValue(s string, propDef *PropertyDef) (time.Time, error)
ParseDateValue parses a date string using the property's format. It tries the specified format first, then falls back to common formats to handle dates stored with timestamps (e.g., from YAML parsing).
func ParseIntegerValue ¶
ParseIntegerValue parses an integer from various input types. A float64 is accepted only when it has no fractional part — truncating 3.5 to 3 would silently corrupt the value (matching the integer property-validation rule).
func RenameEntityType ¶
RenameEntityType performs an AST-level rename of an entity type in a metamodel YAML file using the given filesystem. It preserves comments, formatting, and key ordering.
Updates:
- The entity key under `entities:`
- All references in `relations:` `from:` and `to:` arrays
- All references in `validations:` `entity_type:` fields
func ValidateIDPrefix ¶
ValidateIDPrefix rejects id_prefix values whose generated IDs would fail entity ID validation (BUG-RHFHTH). Generated short/sequential IDs have the shape <base>-<suffix>, where base is the prefix with one trailing dash trimmed — so the base must be non-empty, contain only [A-Za-z0-9_-], and neither contain nor end in a dash run that would re-create the forbidden "--" sequence (reserved as the relation key separator). Enforced at metamodel load; entity.GenerateShortID assumes a load-validated prefix.
func ValidateRrule ¶
ValidateRrule validates an RRULE string. It strips the "RRULE:" prefix if present, parses the rule, and rejects INTERVAL > 1 without DTSTART (which would cause interval cadence drift).
This function is the single source of truth for RRULE validation, used by both the metamodel property validator and the Lua rrule_next helper.
func ValidateSchemaName ¶
ValidateSchemaName reports whether name is safe to interpolate into reconciler DDL (see [unsafeSchemaNameChar]). It is intentionally a blocklist of dangerous characters rather than an allowlist, because entity-type and property names in shipped metamodels legitimately use dashes and internal spaces (e.g. "review-response", "some property"); an allowlist would reject existing valid schemas. It also rejects a leading/trailing space, which is a likely typo and confuses the DDL literal. Exported so the reconciler can re-check as defense-in-depth before emitting DDL rather than trusting that load-time validation ran.
Types ¶
type ACLBypass ¶
type ACLBypass string
ACLBypass declares which ACL-bypassing capabilities a Lua surface may unlock through rela.bypass_acl (TKT-D8T148, TKT-Y3JVFK).
It is a ROUGH GUARD, not a permission model. Its job is to tell whoever deploys a script whether its bypass block needs reading carefully:
- absent ⇒ the script can only do what the invoking principal can. The ACL already bounds it, so it needs no special scrutiny.
- present ⇒ a human reads that closure before deployment.
The value says WHICH KIND of scrutiny — is this reading data the principal cannot see, or writing past their permissions? That is all the review decision needs, which is why the capability is not sliced by verb (`create`, `update`, `delete`). Verb granularity would add config surface without changing what the reviewer does. It would also name capabilities the elevated handle does not have: there is no elevated create_entity or update_entity today, and config naming a nonexistent capability is worse than a missing field because it appears to work (DEC-O59WM4).
If elevated entity creation ever lands, a set-valued form (`allow_acl_bypass: [read, write]`) would let verbs be added without a second migration. Do not grow this into a string-matched grammar.
const ( // ACLBypassNone is the zero value: no elevation. rela.bypass_acl is not // registered at all, so the script cannot elevate however it is written. ACLBypassNone ACLBypass = "" // ACLBypassRead unlocks the elevated READ methods only // (admin.get_entity / list_entities / get_relations). The admin table has // no write methods, so the surface is structurally unable to mutate. This // is what a document render uses to aggregate over rows its caller cannot // see. ACLBypassRead ACLBypass = "read" // ACLBypassWrite unlocks the elevated WRITE methods only // (admin.create_relation / delete_relation / delete_entity). ACLBypassWrite ACLBypass = "write" // ACLBypassReadWrite unlocks both. This is what `allow_acl_bypass: true` // meant before the enum existed, and what the migration rewrites it to. ACLBypassReadWrite ACLBypass = "read+write" )
func (ACLBypass) AllowsRead ¶
AllowsRead reports whether elevated reads are unlocked.
func (ACLBypass) AllowsWrite ¶
AllowsWrite reports whether elevated writes are unlocked.
func (*ACLBypass) UnmarshalYAML ¶
UnmarshalYAML accepts only the string forms. The legacy boolean `allow_acl_bypass: true` is REFUSED with a message naming the replacement, rather than silently reinterpreted.
Accepting the bool would be the easy path and is deliberately not taken. This field grants ACL bypass, and a parser that maps a legacy value to the BROADEST setting is the wrong default for a privilege field — if the two spellings ever drift, the shim resolves toward more access. A compatibility shim also has no forcing function that ever removes it, so two representations of one concept would persist indefinitely and every reader (validation, docs, tooling, the next capability added) would handle both. `rela migrate` rewrites `true` ⇒ `read+write`, which is the one-time cost that buys a single representation.
type AttachmentPolicy ¶
type AttachmentPolicy struct {
// contains filtered or unexported fields
}
AttachmentPolicy is a focused read-view over a metamodel's attachment-scan configuration. It is constructed with NewAttachmentPolicy rather than living as methods on Metamodel, keeping the scan accessors off the metamodel's wide public surface (the plimsoll god-object load line) — consumers depend on the narrow scan policy, not the whole schema.
func NewAttachmentPolicy ¶
func NewAttachmentPolicy(m *Metamodel) AttachmentPolicy
NewAttachmentPolicy returns the scan-policy view over m. m must be non-nil.
func (AttachmentPolicy) HasUnconfiguredScan ¶
func (p AttachmentPolicy) HasUnconfiguredScan() bool
HasUnconfiguredScan reports whether the metamodel declares at least one `file`-type property while no scan command is configured for it (no global command, no property command) and it has not explicitly opted out with `scan: off`. The composition root uses this to emit a single startup warning nudging the operator to wire a scanner or explicitly disable scanning.
func (AttachmentPolicy) ScanCommandFor ¶
func (p AttachmentPolicy) ScanCommandFor(prop PropertyDef) []string
ScanCommandFor resolves the scan command that should run for a file property, or nil when the property is not scanned. Scanning runs when a command is configured (property-level wins over global) and the property has not opted out with `scan: off`.
func (AttachmentPolicy) ScanSockets ¶
func (p AttachmentPolicy) ScanSockets() []string
ScanSockets returns the operator-configured extra socket paths to bind read-only into the scan command's sandbox (`attachments.scan_sockets`), on top of the always-bound well-known clamd locations. Nil when unset.
type AttachmentsConfig ¶
type AttachmentsConfig struct {
// Allow names the MIME allowlist preset (e.g. "default-safe") or, when it
// holds more than a preset name, an explicit list of allowed sniffed MIME
// types. Empty means the built-in default-safe preset.
Allow []string `yaml:"allow,omitempty"`
// ScanCmd is the global external scan command (array args). Its presence
// enables scanning for every `file` property that does not opt out with
// `scan: off`.
ScanCmd []string `yaml:"scan_cmd,omitempty"`
// ScanSockets are extra host paths bound read-only into the scan command's
// sandbox, on top of the well-known clamd socket locations that are always
// bound. The motivating case is a `clamd.conf` whose `LocalSocket` lives
// outside the defaults. A unix socket bound this way is reachable without
// opening network egress. Empty on the common path (stock ClamAV install).
ScanSockets []string `yaml:"scan_sockets,omitempty"`
}
AttachmentsConfig is the top-level `attachments:` block: the global safety floor applied to every `file` property unless a property overrides it.
type AutomationAction ¶
type AutomationAction struct {
Set string `yaml:"set,omitempty"`
Value string `yaml:"value,omitempty"`
CreateRelation *CreateRelationAction `yaml:"create_relation,omitempty"`
CreateEntity *CreateEntityAction `yaml:"create_entity,omitempty"`
Lua string `yaml:"lua,omitempty"` // Inline Lua code to execute
LuaFile string `yaml:"lua_file,omitempty"` // Path to Lua script in scripts/ directory
// AllowACLBypass unlocks rela.bypass_acl in this Lua action (TKT-D8T148).
// Operator-only (lives in the schema file). When set, the script may call
// rela.bypass_acl(fn) to obtain a closure-scoped elevated handle whose
// access skips the ACL deny (still audited, real principal preserved).
// Ignored for non-Lua actions.
//
// Since TKT-Y3JVFK this is an enum, not a bool: `read`, `write` or
// `read+write` select which methods the handle carries. The legacy
// `true` is refused at parse time with a message naming `read+write`;
// `rela migrate` rewrites it.
AllowACLBypass ACLBypass `yaml:"allow_acl_bypass,omitempty"`
// Capabilities declares which ambient capabilities this Lua action may
// reach: http, ai, write_file and named secrets (TKT-YH52OM). Omitting it
// grants NONE of them — automations run on the write path of any HTTP
// request, so they are not an operator-shell surface and do not get the
// trusted default. Ignored for non-Lua actions.
Capabilities Capabilities `yaml:"capabilities,omitempty"`
}
AutomationAction specifies an operation to perform.
type AutomationCheck ¶
type AutomationCheck struct {
Check string `yaml:"check"`
Severity string `yaml:"severity,omitempty"`
Message string `yaml:"message"`
}
AutomationCheck specifies a validation condition.
type AutomationDef ¶
type AutomationDef struct {
Name string `yaml:"name"`
Description string `yaml:"description,omitempty"`
On AutomationTrigger `yaml:"on"`
Do []AutomationAction `yaml:"do,omitempty"`
Validate []AutomationCheck `yaml:"validate,omitempty"`
}
AutomationDef defines a trigger-action automation rule.
type AutomationTrigger ¶
type AutomationTrigger struct {
Entity StringOrSlice `yaml:"entity,omitempty"`
Property string `yaml:"property,omitempty"`
Becomes string `yaml:"becomes,omitempty"`
From string `yaml:"from,omitempty"`
Created bool `yaml:"created,omitempty"`
RelationCreated string `yaml:"relation_created,omitempty"`
RelationRemoved string `yaml:"relation_removed,omitempty"`
When []string `yaml:"when,omitempty"` // Property conditions that must match (AND logic)
// Condition is a predicate EXPRESSION that must hold for the
// automation to fire, ANDed with every When clause.
//
// When and Condition are separate keys because their syntaxes
// overlap without erroring: filter.Parse accepts
// "days_between(entity.due, today()) <= 7" as a filter on a property
// literally named "days_between(entity.due, today())", which then
// matches nothing, silently. Sniffing which dialect a string is
// written in would guess, and guess quietly — so the operator says
// which one they meant by choosing the key.
//
// `when:` is filter syntax (`status=todo`) transpiled to predicate on
// load; `condition:` is predicate source evaluated as written, so it
// gets boolean composition and the host-function stdlib — notably the
// date arithmetic (today/days_between/date_add/rrule_next) that a
// property filter cannot express.
Condition string `yaml:"condition,omitempty"`
}
AutomationTrigger specifies conditions that activate an automation.
type Capabilities ¶
type Capabilities struct {
// HTTP grants the `http` global (outbound requests).
HTTP bool `yaml:"http,omitempty"`
// AI grants the `ai` global. Note ai.* calls are billable.
AI bool `yaml:"ai,omitempty"`
// WriteFile grants rela.write_file (already confined to output/).
WriteFile bool `yaml:"write_file,omitempty"`
// Secrets names the keys from .rela/secrets.yaml this script may read.
// A key not listed is absent from rela.secrets entirely.
Secrets []string `yaml:"secrets,omitempty"`
}
Capabilities is the operator-authored declaration of which ambient, non-graph capabilities a Lua script may reach (TKT-YH52OM). It is the YAML face of lua.Capabilities; the wiring site translates one to the other.
It appears under a `capabilities:` key on the config blocks that name a script — automation actions, data-entry actions, and documents:
actions:
notify_slack:
script: notify.lua
capabilities:
http: true
secrets: [slack_webhook_url]
Fail-closed ¶
Omitting the block grants nothing. That is deliberate and is the whole point of the ticket: before this existed, every script on every surface — including read-only document renders and validation rules — held `http`, `ai` and the ENTIRE contents of .rela/secrets.yaml, which is a two-call exfiltration path. A default of "closed" means a capability is present only where an operator wrote it down.
Secrets is a list ¶
Capabilities.Secrets names individual keys rather than being a boolean, because a boolean grants the whole file: an action needing one Slack webhook would also receive the database DSN. There is deliberately no "all" spelling here — the broad grant exists only as a Go-side wiring choice (lua.TrustedCapabilities), so it cannot be reached from a config file.
func (Capabilities) Any ¶
func (c Capabilities) Any() bool
Any reports whether the block grants anything at all.
Note this deliberately has no AllSecrets term, unlike lua.Capabilities.Any: there is no "all secrets" spelling in YAML, so a config block can only grant via the named list. See the AllSecrets field on lua.Capabilities.
func (Capabilities) Fields ¶
func (c Capabilities) Fields() (http, ai, writeFile bool, secrets []string)
Fields returns the grant as plain values.
This is the SINGLE translation seam between the YAML type and every runtime consumer (TKT-YH52OM). It exists because the obvious alternative — each consumer copying the struct field-by-field — is what produced the defect this method was added to prevent: the grant was hand-copied at five sites, and a sixth path dropped it silently.
It returns loose values rather than a lua.Capabilities because metamodel must not import lua, and autocascade may import neither (see .go-arch-lint.yml). Each consumer converts at its own boundary, but they all read the fields from here, so adding a capability means changing this signature — a COMPILE error at every consumer rather than a silent per-surface omission.
func (*Capabilities) UnmarshalYAML ¶
func (c *Capabilities) UnmarshalYAML(unmarshal func(any) error) error
UnmarshalYAML decodes the mapping form and REFUSES a bare boolean.
`capabilities: true` is rejected rather than read as "grant everything", following the same reasoning ACLBypass.UnmarshalYAML records for allow_acl_bypass: for a privilege field, a parser that maps a loose value to the BROADEST setting is the wrong default. Here it is sharper still, since "everything" would include the entire secrets file — precisely the grant this type exists to make impossible to write by accident.
type ChecklistRule ¶
type ChecklistRule struct {
// AllChecked requires all checklist items to be checked
AllChecked bool `yaml:"all-checked,omitempty"`
// AllowSkipped treats strikethrough items as complete (e.g., "- [x] ~~task~~ (N/A: reason)")
AllowSkipped bool `yaml:"allow-skipped,omitempty"`
}
ChecklistRule defines validation rules for markdown checklists.
type CircularIncludeError ¶
type CircularIncludeError struct {
Chain []string // e.g., ["a.yaml", "b.yaml", "a.yaml"]
}
CircularIncludeError is returned when a circular include chain is detected.
func (*CircularIncludeError) Error ¶
func (e *CircularIncludeError) Error() string
type ConflictingIDPrefixError ¶
type ConflictingIDPrefixError struct {
EntityType string
}
ConflictingIDPrefixError is returned when both id_prefix and id_prefixes are specified.
func (*ConflictingIDPrefixError) Error ¶
func (e *ConflictingIDPrefixError) Error() string
type ContentRule ¶
type ContentRule struct {
// RequiredHeaders specifies headers that must appear in the content
RequiredHeaders []HeaderCheck `yaml:"required-headers,omitempty"`
// Checklist specifies validation rules for markdown checklists (task lists)
Checklist *ChecklistRule `yaml:"checklist,omitempty"`
}
ContentRule defines validation rules for markdown body content.
type CreateEntityAction ¶
type CreateEntityAction struct {
Type string `yaml:"type"` // Entity type to create
Template string `yaml:"template,omitempty"` // Optional: template variant, supports interpolation (e.g., "{{new.kind}}")
Properties map[string]string `yaml:"properties,omitempty"` // Properties (values support interpolation)
Relation string `yaml:"relation,omitempty"` // Optional: relation FROM trigger TO created entity
IfExists string `yaml:"if_exists,omitempty"` // Behavior when relation already exists: skip (default), error, replace
}
CreateEntityAction specifies parameters for creating a new entity.
type CreateRelationAction ¶
CreateRelationAction specifies parameters for creating a relation.
type CustomType ¶
type CustomType struct {
Values []string `yaml:"values,omitempty"` // Allowed values (makes this an enum type)
Labels map[string]string `yaml:"labels,omitempty"` // Optional display labels keyed by value (display-only; value stays the identity)
// Descriptions is optional per-value prose keyed by value: what each value
// MEANS, as opposed to Labels (the short display text). Display-only,
// surfaced by the generated documentation (FEAT-G4VO53) so a reader
// understands e.g. what "blocked" or "in-review" signifies. A value with no
// entry simply has no description. Distinct from the type-level Description
// scalar below (which documents the type as a whole).
Descriptions map[string]string `yaml:"descriptions,omitempty"`
Default string `yaml:"default,omitempty"` // Default value
Description string `yaml:"description,omitempty"` // Documentation for the type
Validations []TypeValidation `yaml:"validations,omitempty"` // Regex validations with error messages
// Transitions declares the legal value→value moves for this enum,
// making it a state machine (TKT-E4LW2). This is declarative source
// data only — the metamodel does not enforce it. At startup
// internal/statemachine.Compile reads these into an executable machine
// that the entitymanager runs on the write path (legality 422, guard
// 403, precondition 422). Empty means "any value may change to any
// other" (the historical, unconstrained behavior). Only meaningful on a
// named type — inline `type: enum` properties carry no transitions.
Transitions []TransitionDef `yaml:"transitions,omitempty"`
// Initial names the only legal entry value on entity create when this
// type is a state machine. Empty falls back to Default. Consumed by
// internal/statemachine at compile time.
Initial string `yaml:"initial,omitempty"`
}
CustomType defines a reusable type with optional enum values and/or regex validations.
type DuplicateDefinitionError ¶
type DuplicateDefinitionError struct {
Kind string // "type", "entity", "relation", or "validation"
Name string
File1 string
File2 string
}
DuplicateDefinitionError is returned when the same name is defined in multiple included files.
func (*DuplicateDefinitionError) Error ¶
func (e *DuplicateDefinitionError) Error() string
type EntityDef ¶
type EntityDef struct {
Label string `yaml:"label"`
LabelPlural string `yaml:"label_plural,omitempty"`
Description string `yaml:"description,omitempty"` // Documentation explaining intent/usage
Plural string `yaml:"plural,omitempty"` // Used for directory names (e.g., "policies" for "policy")
Aliases []string `yaml:"aliases,omitempty"`
IDType string `yaml:"id_type,omitempty"` // "short" (default), "sequential", or "manual"
IDCaps string `yaml:"id_caps,omitempty"` // "upper" (default) or "lower" - capitalization for short ID suffix
IDPrefix string `yaml:"id_prefix,omitempty"` // Single ID prefix (sugar for single-element id_prefixes)
IDPrefixes []string `yaml:"id_prefixes,omitempty"` // Multiple ID prefixes
RDFType string `yaml:"rdf_type,omitempty"`
Properties map[string]PropertyDef `yaml:"properties"`
PropertyOrder []string `yaml:"-"` // Order of properties as defined in YAML (computed at load)
DefaultSort []SortSpec `yaml:"default_sort,omitempty"` // Default sort order for this entity type
Color string `yaml:"color,omitempty"`
BorderColor string `yaml:"border_color,omitempty"`
// DisplayProperty names the property whose value renders as the
// human-readable display name in lists, cards, link text, etc.
// When empty, GetPrimaryProperty() falls back to the autoderivation
// (priority list title/name/label, then alphabetical fallback).
// Validated at metamodel-load time: must reference a defined
// property and must not have leading/trailing whitespace.
DisplayProperty string `yaml:"display_property,omitempty"`
}
EntityDef defines an entity type in the metamodel
TODO(TKT-N0IKN9): 24 exported methods, over the 20 exported-method line. Schema value type; ratchet candidate alongside Metamodel. DisplayProperties (TKT-NJTBQX) is the 24th — it reports the property set backing the display title so the ACL locked-title guard can gate on templated display_property (see internal/dataentry mentions); ratchet back down when this type is decomposed.
func (*EntityDef) DisplayProperties ¶
DisplayProperties returns every property whose value backs the display title, so callers can reason about the title's data sources — notably to decide whether the title is unreadable when one of those properties is access-controlled (see internal/dataentry mentions).
- Templated display_property: all placeholder property names, in order.
- Bare display_property or an autoderived primary: that single name.
- No display name source (autoderivation found nothing): empty.
Unlike GetPrimaryProperty (which returns "" for a template because a template names no single *writable* target), this reports the full read set. The template was validated at load, so parsing here cannot fail; a malformed template (only reachable via the no-validate migration path) yields no names rather than an error.
func (*EntityDef) DisplayTitle ¶
DisplayTitle returns the display title for an entity using its type's primary property. Behavior:
- String value: returned verbatim (the common case).
- Non-string value (number, boolean, enum stored as a typed value): stringified via fmt.Sprintf("%v", val) so an explicit display_property: status (an enum) shows the value and not the ID. nil values fall through to the ID — `%v` on nil yields "<nil>" which would be a worse display name than the ID.
- Missing or empty-after-stringification: falls back to the ID.
The non-string stringification is what makes the explicit display_property override pay off for enum-typed fields. See review-response RR-9CW5N.
When display_property is a template (contains `{`), the placeholders are substituted from properties, whitespace is collapsed, and the result is returned — falling back to the ID when it renders empty.
func (*EntityDef) GetAliases ¶
GetAliases returns the entity aliases
func (*EntityDef) GetBorderColor ¶
GetBorderColor returns the border color
func (*EntityDef) GetDefaultStatus ¶
GetDefaultStatus returns the default status value for this entity type. It checks the entity's status property definition for a custom type or inline values. If no explicit default exists, returns the first valid value, or "draft" as final fallback.
func (*EntityDef) GetIDCaps ¶
GetIDCaps returns the ID capitalization mode for short IDs, defaulting to "upper".
func (*EntityDef) GetIDPatterns
deprecated
func (*EntityDef) GetIDPrefixes ¶
GetIDPrefixes returns the effective ID prefixes for this entity type. It normalizes id_prefix (singular) and id_prefixes (plural) into a single list.
func (*EntityDef) GetLabelPlural ¶
GetLabelPlural returns the human-readable plural label for an entity type (used in UI strings, e.g. "List of Features").
func (*EntityDef) GetPlural ¶
GetPlural returns the slug-form plural for an entity type (used as URL segments, fsstore directory names, OpenAPI paths). Falls back to naive pluralization of the type name when not explicitly set.
func (*EntityDef) GetPrimaryProperty ¶
GetPrimaryProperty returns the name of the primary property used as the entity's display name.
Resolution order:
- Explicit `display_property` set on the entity definition. The name is returned verbatim — load-time validation already guaranteed it references a defined property.
- The first match in the priority list `title`/`name`/`label`, when defined as a required string.
- Any required string property (alphabetical for determinism).
- Empty string when no candidate exists.
A *templated* display_property (containing `{`) has no single primary property — it is a readonly, derived display string. GetPrimaryProperty returns "" for it; DisplayTitle renders the template directly. Callers that treat the result as a writable property key (e.g. a create title shortcut) therefore get "" and skip, which is correct: there is no single field to write into.
func (*EntityDef) GetProperties ¶
GetProperties returns the entity properties for JSON output. Note: This returns interface{} to satisfy the SchemaEntityDef interface. For typed access, use PropertyDefs() which implements PropertySchema.
func (*EntityDef) GetPropertyOrder ¶
GetPropertyOrder returns the property names in their definition order. If PropertyOrder was not populated during loading, returns nil. Returns a copy to prevent external modification.
func (*EntityDef) GetRDFType ¶
GetRDFType returns the RDF type
func (*EntityDef) HasContent ¶
HasContent implements PropertySchema for EntityDef. Entities always support markdown body content.
func (*EntityDef) HasPattern ¶
HasPattern checks if the entity type matches a given ID pattern
func (*EntityDef) IsManualID ¶
IsManualID returns true if this entity type uses manually-specified IDs
func (*EntityDef) IsSequentialID ¶
IsSequentialID returns true if this entity type uses auto-generated sequential IDs
func (*EntityDef) PropertyDefs ¶
func (e *EntityDef) PropertyDefs() map[string]PropertyDef
PropertyDefs implements PropertySchema for EntityDef.
type EntityProjection ¶
type EntityProjection struct {
// DisplayProperty is the explicit display-name property (may be empty, in
// which case rendering falls back to the title/name/label autoderivation).
DisplayProperty string `json:"display_property,omitempty"`
// PropertyOrder is the property order as defined in the metamodel YAML,
// preserved because display and diff rendering present properties in order.
PropertyOrder []string `json:"property_order,omitempty"`
// Properties maps each property name to its render-relevant definition.
Properties map[string]PropertyProjection `json:"properties"`
}
EntityProjection is the render-relevant projection of one entity type.
type EntityShape ¶
type EntityShape struct {
Properties map[string]PropertyShape `json:"properties"`
}
EntityShape is the data-shape projection of one entity type.
type FSLoader ¶
type FSLoader struct {
// contains filtered or unexported fields
}
FSLoader loads a metamodel from a file on the given filesystem. It performs a migration-detection pre-check: if the file uses deprecated syntax the current code can't safely interpret, Load returns a *migration.Error telling the user to run `rela migrate`.
func NewFSLoader ¶
NewFSLoader constructs a filesystem-backed metamodel loader.
func (*FSLoader) Load ¶
Load runs migration detection and then parses the metamodel. Includes are resolved recursively; the returned file list covers the main file plus every included file.
type HeaderCheck ¶
type HeaderCheck struct {
// Header is an exact header string to match (e.g., "## Context")
Header string `yaml:"header,omitempty"`
// Pattern is a regex pattern to match headers (e.g., "## (Alternative|Alternatives)")
Pattern string `yaml:"pattern,omitempty"`
}
HeaderCheck specifies a header to check for in markdown content. Can be unmarshaled from either a simple string (exact match) or an object with pattern field.
func (*HeaderCheck) GetMatchString ¶
func (h *HeaderCheck) GetMatchString() string
GetMatchString returns the pattern or header string to match against
func (*HeaderCheck) IsPattern ¶
func (h *HeaderCheck) IsPattern() bool
IsPattern returns true if this is a regex pattern match
func (*HeaderCheck) UnmarshalYAML ¶
func (h *HeaderCheck) UnmarshalYAML(unmarshal func(any) error) error
UnmarshalYAML allows HeaderCheck to be unmarshaled from either a string or an object. String form: "## Context" (exact header match) Object form: { pattern: "## (Alternative|Alternatives)" }
type ImageStep ¶
type ImageStep struct {
// Reencode is the canonical output format: "jpeg" (default) or "png". Both
// are always within the default-safe MIME allowlist, so the re-encoded
// output is safe by construction.
Reencode string `yaml:"reencode,omitempty"`
// Quality is the JPEG quality (1..100) when Reencode is "jpeg". Zero uses
// the package default. Ignored for PNG.
Quality int `yaml:"quality,omitempty"`
}
ImageStep is a native, in-process image transform: it decodes the upload with a memory-safe pure-Go decoder, bakes in EXIF orientation, and re-encodes to a canonical format, dropping all metadata. Supported inputs are PNG, JPEG, GIF, and WebP (decode); the output is always PNG or JPEG (there is no pure-Go WebP encoder). Because decoding is memory-safe, this needs no external tool and no OS sandbox. Resize/thumbnail is a later phase and deliberately absent here.
func (ImageStep) ImageReencode ¶
ImageReencode returns the effective re-encode target for the step, applying the "jpeg" default when unset.
type IncludeHasRootFieldError ¶
IncludeHasRootFieldError is returned when an included file contains version or namespace.
func (*IncludeHasRootFieldError) Error ¶
func (e *IncludeHasRootFieldError) Error() string
type IncludeNotFoundError ¶
IncludeNotFoundError is returned when an included file does not exist.
func (*IncludeNotFoundError) Error ¶
func (e *IncludeNotFoundError) Error() string
type InvalidIDCapsError ¶
InvalidIDCapsError is returned when an entity has an invalid id_caps value.
func (*InvalidIDCapsError) Error ¶
func (e *InvalidIDCapsError) Error() string
type InvalidIDPrefixError ¶
InvalidIDPrefixError is returned when an id_prefix would generate IDs that fail entity ID validation (BUG-RHFHTH).
func (*InvalidIDPrefixError) Error ¶
func (e *InvalidIDPrefixError) Error() string
type InvalidIDTypeError ¶
InvalidIDTypeError is returned when an entity has an invalid id_type value.
func (*InvalidIDTypeError) Error ¶
func (e *InvalidIDTypeError) Error() string
type InvalidRelationError ¶
InvalidRelationError is returned when a relation is not valid between two entity types.
func (*InvalidRelationError) Error ¶
func (e *InvalidRelationError) Error() string
type InverseDef ¶
type InverseDef struct {
// ID is the identifier for the inverse relation (e.g., "addressedBy")
ID string `yaml:"id,omitempty"`
// Label is the display label for the inverse relation (e.g., "addressed by").
// If not specified, the raw ID is displayed — labels are authored, never
// derived (DEC-6C1NAA).
Label string `yaml:"label,omitempty"`
}
InverseDef defines the inverse of a relation. Can be unmarshaled from either a simple string (inverse identifier only) or an object with id and label fields.
func (*InverseDef) GetID ¶
func (i *InverseDef) GetID() string
GetID returns the inverse relation identifier
func (*InverseDef) GetLabel ¶
func (i *InverseDef) GetLabel() string
GetLabel returns the display label, falling back to the raw ID.
A label is authored, never derived (DEC-6C1NAA). This used to convert camelCase to space-separated lowercase ("addressedBy" → "addressed by"), which bakes an English orthographic convention into a language-neutral metamodel. Write an explicit `label:` to control the display text.
func (*InverseDef) UnmarshalYAML ¶
func (i *InverseDef) UnmarshalYAML(unmarshal func(any) error) error
UnmarshalYAML allows InverseDef to be unmarshaled from either a string or an object. String form: "addressedBy" (ID only; the ID doubles as the display label) Object form: { id: "addressedBy", label: "addressed by" }
type Loader ¶
Loader is the top-level service for loading a project's metamodel. Implementations cover different sources (filesystem, remote API, database) behind a uniform contract.
The returned []string lists the source files that were read when the loader has a filesystem-like layout. Sources that don't (remote APIs) return nil — the value is informational for consumers like watchers, not part of the load contract.
type Metamodel ¶
type Metamodel struct {
Version string `yaml:"version"`
Namespace string `yaml:"namespace"`
// Description is optional end-user prose describing what this deployment /
// application is for. Display-only, surfaced by the generated documentation
// (the `rela docs` generator — FEAT-G4VO53). Empty by default; the metamodel
// does not otherwise consult it.
Description string `yaml:"description,omitempty"`
Includes []string `yaml:"includes,omitempty"`
Types map[string]CustomType `yaml:"types"`
Entities map[string]EntityDef `yaml:"entities"`
Relations map[string]RelationDef `yaml:"relations"`
Validations []ValidationRule `yaml:"validations,omitempty"`
Automations []AutomationDef `yaml:"automations,omitempty"`
// Attachments holds the global attachment safety floor (MIME allowlist,
// scan policy) applied to every `file` property unless overridden.
Attachments *AttachmentsConfig `yaml:"attachments,omitempty"`
// Transforms is the view-export registry: named markdown -> format
// conversions run via an external command (e.g. pandoc). Registering a
// transform here makes it available to every markdown-producing surface
// (entity view, list view, Lua document) as an "Export as ..." format. Lives
// in the metamodel (not data-entry config) so the CLI can reach it too. See
// internal/transform.
Transforms map[string]TransformDef `yaml:"transforms,omitempty"`
// contains filtered or unexported fields
}
Metamodel represents the full metamodel configuration
TODO(TKT-N0IKN9): 30 exported methods, over the 20 exported-method line. This is the schema accessor — wide read-API by nature — but a ratchet candidate: group the type/relation/property lookups behind focused accessors (the attachment-scan accessors moved behind AttachmentPolicy this way).
32nd exported method is ShapeProjection (TKT-0C57FS), the data-shape sibling of RenderProjection.
func DefaultMetamodel ¶
func DefaultMetamodel() *Metamodel
DefaultMetamodel returns a minimal default metamodel
func Load ¶
Load reads and parses a metamodel from a YAML file using the given filesystem. If the metamodel contains an `includes:` key, included files are recursively loaded and merged. Include paths are resolved relative to the directory containing the metamodel file.
The returned []string contains the absolute paths of all files that were read: the main metamodel.yaml path plus all include files.
func LoadWithoutMigrationCheck ¶
LoadWithoutMigrationCheck loads a metamodel without checking for migrations. This is used by the migrate command itself to avoid chicken-and-egg issues. Returns nil if loading fails (caller should handle gracefully).
The returned []string contains the absolute paths of all files that were read.
func (*Metamodel) DisplayTitle ¶
DisplayTitle returns the display title for an entity using its type's primary property. Falls back to entity ID if no entity definition found or no primary property value is set.
func (*Metamodel) EntityTypes ¶
EntityTypes returns all entity type names
func (*Metamodel) GetEntities ¶
GetEntities returns the entities map for JSON output
func (*Metamodel) GetEntityDef ¶
GetEntityDef returns the entity definition for a type (resolving aliases)
func (*Metamodel) GetNamespace ¶
GetNamespace returns the metamodel namespace
func (*Metamodel) GetPropertyDefault ¶
GetPropertyDefault returns the default value for a property.
func (*Metamodel) GetPropertyType ¶
GetPropertyType returns the type of a property for an entity type (empty if not found).
func (*Metamodel) GetRelationDef ¶
func (m *Metamodel) GetRelationDef(name string) (*RelationDef, bool)
GetRelationDef returns the relation definition
func (*Metamodel) GetRelationFrom ¶
GetRelationFrom returns the "from" entity types for a relation.
func (*Metamodel) GetRelationLabel ¶
GetRelationLabel returns the label for a relation (empty if not found).
func (*Metamodel) GetRelationTo ¶
GetRelationTo returns the "to" entity types for a relation.
func (*Metamodel) GetRelations ¶
GetRelations returns the relations map for JSON output
func (*Metamodel) GetTypeDefault ¶
GetTypeDefault returns the default value for a custom type.
func (*Metamodel) GetVersion ¶
GetVersion returns the metamodel version
func (*Metamodel) HasEntityType ¶
HasEntityType returns true if the entity type exists in the metamodel.
func (*Metamodel) HasValidationRule ¶
HasValidationRule returns true if a validation rule with the given name exists.
func (*Metamodel) InferEntityType ¶
InferEntityType tries to determine the entity type from an ID
func (*Metamodel) InitAliases ¶
func (m *Metamodel) InitAliases()
InitAliases initializes the alias map from entity definitions. Call this after programmatically constructing a Metamodel (e.g., via testutil builders) to enable alias resolution. Not needed when using Parse() which calls this automatically.
func (*Metamodel) InverseOwner ¶
InverseOwner returns the canonical relation type that declares the given inverse name, if any. Populated at load time by the metamodel loader after rejecting collisions and canonical-name shadowing. The data-entry unified-PATCH wire format uses this to resolve inverse body keys without scanning the relation map on every request.
For a relation declared `symmetric: true` with `inverse: <self>`, the inverse maps back to the same canonical relation — callers that care about direction should consult `RelationDef.Symmetric` and treat the path entity as source regardless of the body key.
func (*Metamodel) IsEnumType ¶
IsEnumType returns whether a type is an enum-like type (has values).
func (*Metamodel) IsPropertyRequired ¶
IsPropertyRequired returns whether a property is required.
func (*Metamodel) RelationTypes ¶
RelationTypes returns all relation type names
func (*Metamodel) RenderProjection ¶
func (m *Metamodel) RenderProjection() RenderProjection
RenderProjection returns the render-relevant projection of the metamodel. The result is deterministic: map iteration is never observed (all output is keyed maps and the hash sorts keys), so two calls on an equal metamodel produce an equal projection and hash.
func (*Metamodel) ResolveAlias ¶
ResolveAlias returns the canonical entity type name for an alias
func (*Metamodel) ResolveWidgetFromType ¶
ResolveWidgetFromType returns the canonical widget for a property type. This is the single source of truth for the type→widget mapping used by the data entry app and the migration system.
func (*Metamodel) ShapeProjection ¶
func (m *Metamodel) ShapeProjection() ShapeProjection
ShapeProjection returns the data-shape projection of the metamodel. The result is deterministic: all output is keyed maps plus slices copied in declaration order, and the hash sorts every map key.
func (*Metamodel) ValidateEntity ¶
func (m *Metamodel) ValidateEntity(id, entityType string, properties map[string]any) []*ValidationError
ValidateEntity validates an entity's type, properties, and ID prefix against the metamodel.
func (*Metamodel) ValidateProperties ¶
func (m *Metamodel) ValidateProperties(props map[string]any, schema PropertySchema) []*ValidationError
ValidateProperties validates a properties map against a PropertySchema. This is shared between entity and relation validation.
func (*Metamodel) ValidatePropertyValue ¶
func (m *Metamodel) ValidatePropertyValue(propName string, propDef *PropertyDef, val any) error
ValidatePropertyValue validates a single property value against its definition. Returns a plain error for backward compatibility with existing callers.
Note: The explicit nil check is required because returning a nil *ValidationError directly as error creates a non-nil interface with nil value (Go interface gotcha).
func (*Metamodel) ValidateRelation ¶
ValidateRelation checks if a relation is valid between two entity types
func (*Metamodel) ValidateRelationProperties ¶
func (m *Metamodel) ValidateRelationProperties( relationType string, properties map[string]any, ) []*ValidationError
ValidateRelationProperties validates a relation's properties against the metamodel.
type OrderableMode ¶
type OrderableMode string
OrderableMode controls which side(s) of a relation type are user-orderable.
const ( OrderableNone OrderableMode = "" OrderableOutgoing OrderableMode = "outgoing" OrderableIncoming OrderableMode = "incoming" OrderableBoth OrderableMode = "both" )
func (OrderableMode) IsValid ¶
func (m OrderableMode) IsValid() bool
IsValid reports whether the value is a recognized OrderableMode (including the empty "not orderable" value).
type PropertyDef ¶
type PropertyDef struct {
Type string `yaml:"type"`
Required bool `yaml:"required,omitempty"`
Values []string `yaml:"values,omitempty"` // For inline enum types
Labels map[string]string `yaml:"labels,omitempty"` // Optional display labels keyed by value (display-only; value stays the identity)
Default string `yaml:"default,omitempty"`
Description string `yaml:"description,omitempty"` // Documentation for the property
Format string `yaml:"format,omitempty"` // Date format (Go layout, e.g., "2006-01-02")
List bool `yaml:"list,omitempty"` // True for multi-select properties (allows multiple values)
// Computed is a pure Lua-compatible scalar expression evaluated from the
// entity's other properties on every write. Computed properties are
// materialized but never caller-authored. Entity-local dependencies are
// inferred from the compiled expression.
Computed string `yaml:"computed,omitempty" json:"Computed,omitempty"`
// Unique constrains the property to a natural key: no two entities of
// the same type may carry the same non-empty value. Enforced at write
// time by the entitymanager (a colliding create/update is rejected as
// a validation error → 422). Empty values are exempt (a property is
// unique among the entities that set it). Ignored on `list` properties
// (a natural key is a scalar).
//
// Guarantee level is not uniform across backends. The write-path check
// is a check-then-write, not an atomic constraint, so under concurrent
// writers two racing creates with the same value can both commit on
// ANY backend. For race-free enforcement an operator adds a store-level
// unique index (a partial unique index on pgstore), which is the only
// mechanism that makes the constraint atomic. Uniqueness is therefore
// NOT part of the store conformance contract — a new store.Store
// implementation is not required to enforce it. See
// `internal/entitymanager` checkUniqueProperties and the ACL
// `principal_property` gate, which requires the referenced property to
// be unique (and non-list).
Unique bool `yaml:"unique,omitempty"`
// Max caps how many attachments a `file`-type property may hold.
// Zero/unset means 1 (the default, single-attachment). When > 1 the
// property holds a list of attachment paths and the data-entry UI
// switches from replace-mode to multi-file add-mode. Only meaningful
// for `type: file`.
Max int `yaml:"max,omitempty"`
// Accept narrows the MIME allowlist for this `file` property to these
// sniffed MIME types (e.g. ["application/pdf"]). Empty means inherit the
// global allowlist. Only meaningful for `type: file`.
Accept []string `yaml:"accept,omitempty"`
// Scan overrides the global virus-scan policy for this `file` property.
// ScanUnset (the zero value) means inherit the global policy. Only
// meaningful for `type: file`.
Scan ScanPolicy `yaml:"scan,omitempty"`
// ScanCmd is the external scan command (array args) run when the effective
// scan policy is `required`. Empty inherits the global scan command. Only
// meaningful for `type: file`. See [AttachmentsConfig.ScanCmd].
ScanCmd []string `yaml:"scan_cmd,omitempty"`
// Transform is the ordered list of byte transforms (each an external
// command) applied to this `file` property's uploads. Only meaningful for
// `type: file`.
Transform []TransformStep `yaml:"transform,omitempty"`
}
PropertyDef defines a property on an entity or relation
func (PropertyDef) FileMax ¶
func (p PropertyDef) FileMax() int
FileMax returns the effective attachment cap for a file property: Max when set (>0), otherwise 1. Callers should use this rather than reading Max directly so the unset-means-one default lives in one place.
func (*PropertyDef) GetDateFormat ¶
func (p *PropertyDef) GetDateFormat() string
GetDateFormat returns the date format for a property, defaulting to ISO 8601. For datetime properties the default is RFC3339 (time-bearing); an explicit Format still overrides.
type PropertyProjection ¶
type PropertyProjection struct {
Type string `json:"type"`
Required bool `json:"required"`
List bool `json:"list"`
Format string `json:"format,omitempty"`
// Values is the inline enum value list (for properties whose type is an
// inline enum rather than a named custom type).
Values []string `json:"values,omitempty"`
}
PropertyProjection is the render-relevant projection of one property definition: the fields that affect how a value is titled, typed, stringified, or diffed. Attachment/scan/transform config, description, and default are omitted — they don't change how a stored value renders.
type PropertySchema ¶
type PropertySchema interface {
// PropertyDefs returns the property definitions map
PropertyDefs() map[string]PropertyDef
// HasContent returns true if markdown body content is supported
HasContent() bool
}
PropertySchema abstracts property definitions for entities and relations. Both EntityDef and RelationDef implement this interface, allowing shared validation and form generation logic.
type PropertyShape ¶
type PropertyShape struct {
Type string `json:"type"`
Required bool `json:"required,omitempty"`
List bool `json:"list,omitempty"`
Format string `json:"format,omitempty"`
Values []string `json:"values,omitempty"` // inline enum value list
// Default is included even though it only affects future creates:
// the generator offers backfill steps from it, and the classifier
// tiers default-only changes additive (amendment A7).
Default string `json:"default,omitempty"`
// Computed changes affect already-materialized values and therefore take
// part in shape identity even though bulk recomputation is operator-driven.
Computed string `json:"computed,omitempty"`
}
PropertyShape is the data-shape projection of one property definition: the fields that determine whether a stored value conforms and how it would be coerced. Display labels, descriptions, uniqueness and attachment/scan config are omitted — they constrain writes or presentation, not the shape of values already stored.
type RelationDef ¶
type RelationDef struct {
Label string `yaml:"label"`
Description string `yaml:"description,omitempty"`
From []string `yaml:"from"`
To []string `yaml:"to"`
Inverse *InverseDef `yaml:"inverse,omitempty"`
Symmetric bool `yaml:"symmetric,omitempty"`
MinOutgoing *int `yaml:"min_outgoing,omitempty"`
MaxOutgoing *int `yaml:"max_outgoing,omitempty"`
MinIncoming *int `yaml:"min_incoming,omitempty"`
MaxIncoming *int `yaml:"max_incoming,omitempty"`
// Properties defines typed properties that can be attached to relations of this type.
// Uses the same PropertyDef structure as entity properties.
Properties map[string]PropertyDef `yaml:"properties,omitempty"`
// Content indicates whether relations of this type support markdown body content.
// When true, the data-entry UI will show a content editor for the relation.
Content bool `yaml:"content,omitempty"`
// Orderable declares which side(s) of this relation type are user-orderable.
// When set, the data-entry UI offers drag-to-reorder controls on the enabled
// side(s); the API returns relations sorted by the corresponding managed
// order property (OrderPropertyOut / OrderPropertyIn).
Orderable OrderableMode `yaml:"orderable,omitempty"`
}
RelationDef defines a relation type in the metamodel
func (*RelationDef) GetDescription ¶
func (r *RelationDef) GetDescription() string
GetDescription returns the relation description
func (*RelationDef) GetFrom ¶
func (r *RelationDef) GetFrom() []string
GetFrom returns the source entity types
func (*RelationDef) GetInverse ¶
func (r *RelationDef) GetInverse() any
GetInverse returns the inverse definition for JSON output
func (*RelationDef) GetLabel ¶
func (r *RelationDef) GetLabel() string
GetLabel returns the relation label
func (*RelationDef) GetMaxIncoming ¶
func (r *RelationDef) GetMaxIncoming() *int
GetMaxIncoming returns the maximum incoming cardinality (to-side constraint)
func (*RelationDef) GetMaxOutgoing ¶
func (r *RelationDef) GetMaxOutgoing() *int
GetMaxOutgoing returns the maximum outgoing cardinality (from-side constraint)
func (*RelationDef) GetMinIncoming ¶
func (r *RelationDef) GetMinIncoming() *int
GetMinIncoming returns the minimum incoming cardinality (to-side constraint)
func (*RelationDef) GetMinOutgoing ¶
func (r *RelationDef) GetMinOutgoing() *int
GetMinOutgoing returns the minimum outgoing cardinality (from-side constraint)
func (*RelationDef) GetTo ¶
func (r *RelationDef) GetTo() []string
GetTo returns the target entity types
func (*RelationDef) HasAdvancedFeatures ¶
func (r *RelationDef) HasAdvancedFeatures() bool
HasAdvancedFeatures returns true if this relation type has properties or content, indicating that the data-entry UI should use the advanced cards+modal interface.
func (*RelationDef) HasContent ¶
func (r *RelationDef) HasContent() bool
HasContent implements PropertySchema for RelationDef.
func (*RelationDef) IncomingOrderProperty ¶
func (r *RelationDef) IncomingOrderProperty() string
IncomingOrderProperty returns the relation-property name that holds the incoming-side order value, or "" if incoming ordering is not enabled.
func (*RelationDef) IsSymmetric ¶
func (r *RelationDef) IsSymmetric() bool
IsSymmetric returns whether the relation is symmetric
func (*RelationDef) OutgoingOrderProperty ¶
func (r *RelationDef) OutgoingOrderProperty() string
OutgoingOrderProperty returns the relation-property name that holds the outgoing-side order value, or "" if outgoing ordering is not enabled.
func (*RelationDef) PropertyDefs ¶
func (r *RelationDef) PropertyDefs() map[string]PropertyDef
PropertyDefs implements PropertySchema for RelationDef.
type RelationNotFoundError ¶
type RelationNotFoundError struct {
Name string
}
RelationNotFoundError is returned when a relation type is not defined in the metamodel.
func (*RelationNotFoundError) Error ¶
func (e *RelationNotFoundError) Error() string
type RelationShape ¶
type RelationShape struct {
From []string `json:"from,omitempty"`
To []string `json:"to,omitempty"`
Symmetric bool `json:"symmetric,omitempty"`
MinOutgoing *int `json:"min_outgoing,omitempty"`
MaxOutgoing *int `json:"max_outgoing,omitempty"`
MinIncoming *int `json:"min_incoming,omitempty"`
MaxIncoming *int `json:"max_incoming,omitempty"`
Content bool `json:"content,omitempty"`
Properties map[string]PropertyShape `json:"properties,omitempty"`
}
RelationShape is the data-shape projection of one relation type.
type RenderProjection ¶
type RenderProjection struct {
// Entities maps each entity type name to its render projection. All types
// are included (not just the version's own type) so a diff/timeline can
// render related-entity titles as-of.
Entities map[string]EntityProjection `json:"entities"`
// Types maps each custom (enum) type name to its ordered value list — the
// values a property of that type may take, needed to render/validate an
// enum value in a historical snapshot.
Types map[string][]string `json:"types"`
}
RenderProjection is the render-relevant slice of a metamodel: exactly the schema facts needed to render or diff a stored entity version faithfully — property definitions, display configuration, and the enum value lists a property may reference. It deliberately EXCLUDES the churny, non-render parts of the metamodel (automations, validations, cascade rules, colors, id config), so its content hash stays stable across edits that don't change how an entity renders.
This is the unit the pgstore versioning feature (TKT-9INY0Y) content-addresses into schema_versions: an entity_version row references the hash of the projection in force when it was captured, so a historical version renders against the schema it was created under, not today's (possibly drifted) one.
Projecting to render-relevant fields — rather than hashing the whole metamodel — is a dedup-correctness win: hashing the full schema would churn the hash on every automation/validation edit, forcing a new schema_versions row (and a new pointer on every subsequent version) even though nothing render-relevant changed.
func (RenderProjection) Hash ¶
func (p RenderProjection) Hash() string
Hash returns the content-address of the projection: a hex-encoded SHA-256 over a length-prefixed, key-sorted encoding of every field. Length prefixes make the encoding unambiguous (a value cannot smuggle a delimiter to forge a different structure with the same bytes — the same defense internal/canonical uses for entity hashes), and sorting every map key makes the digest independent of Go map iteration order.
func (RenderProjection) JSON ¶
func (p RenderProjection) JSON() ([]byte, error)
JSON returns the projection serialized as deterministic JSON, for storage in schema_versions.projection. The bytes are content-addressed by RenderProjection.Hash (a separate length-prefixed digest), so this serialization is for storage and re-render, not identity — encoding/json with sorted map keys is sufficient.
It returns an error rather than panicking because it is called on the write path (the entitymanager version hook), where the contract is that versioning must never fail a write — the caller logs and swallows. In practice RenderProjection holds only strings, bools, and slices/maps of them, so an error is not reachable short of a runtime bug.
type ReservedPropertyError ¶
ReservedPropertyError is returned when a property name conflicts with a reserved name.
func (*ReservedPropertyError) Error ¶
func (e *ReservedPropertyError) Error() string
type ReservedTypeNameError ¶
type ReservedTypeNameError struct {
TypeName string
}
ReservedTypeNameError is returned when a custom type name conflicts with a built-in type.
func (*ReservedTypeNameError) Error ¶
func (e *ReservedTypeNameError) Error() string
type ScanPolicy ¶
type ScanPolicy int
ScanPolicy is the per-scope virus-scan switch for attachments. Scanning is driven by the *presence of a scan command*: if a `scan_cmd` is configured (globally or on the property) the upload is scanned, fail-closed. There is no separate "required" level — configuring a scanner is the intent to use it.
ScanPolicy exists only as an OPT-OUT: a property may set `scan: off` to skip scanning despite a global `scan_cmd`. The zero value (ScanDefault) means "scan when a command is configured."
const ( // ScanDefault (the zero value) means scan iff a scan command is configured // for this scope. No explicit policy was set. ScanDefault ScanPolicy = iota // ScanOff disables scanning for this property, even when a global scan // command exists. ScanOff )
func (ScanPolicy) MarshalYAML ¶
func (s ScanPolicy) MarshalYAML() (any, error)
MarshalYAML renders only the explicit `off`; the default is omitted so round-tripping a metamodel does not invent a key.
func (ScanPolicy) String ¶
func (s ScanPolicy) String() string
String renders the policy for diagnostics.
func (*ScanPolicy) UnmarshalYAML ¶
func (s *ScanPolicy) UnmarshalYAML(unmarshal func(any) error) error
UnmarshalYAML accepts the string `off` (case-insensitive). `required`/`on` are accepted as no-ops for forgiveness (scanning is already implied by configuring a command), mapping to ScanDefault. An absent key leaves ScanDefault.
type SchemaValidationError ¶
type SchemaValidationError struct {
Errors []string
}
SchemaValidationError collects multiple validation issues found in a metamodel.
func (*SchemaValidationError) Error ¶
func (e *SchemaValidationError) Error() string
type ShapeDelta ¶
type ShapeDelta struct {
Tier ShapeTier
// Kind is a stable machine-readable delta kind (e.g. "property_removed",
// "property_type_changed", "possible_property_rename"). The generator
// switches on it.
Kind string
// Subject names what changed: "task", "task.status", "rel:implements",
// "rel:implements.weight", or "type:status" for a named enum.
Subject string
// Detail is the human-readable explanation shown in gate notices.
Detail string
// Counterpart names the removed half of a possible-rename pair (the old
// subject), for possible_property_rename / possible_entity_type_rename.
Counterpart string
// Removed/Added carry the value diff for enum_values_* kinds, so the
// migration generator can draft a map_values stub without re-diffing.
Removed []string
Added []string
}
ShapeDelta is one classified difference between two shape projections.
type ShapeProjection ¶
type ShapeProjection struct {
// Entities maps each entity type name to its property shapes.
Entities map[string]EntityShape `json:"entities"`
// Relations maps each relation type name to its shape.
Relations map[string]RelationShape `json:"relations"`
// Types maps each named custom (enum) type to its ordered value list.
Types map[string][]string `json:"types"`
}
ShapeProjection is the data-shape slice of a metamodel: exactly the schema facts that determine whether STORED CONTENT conforms to the schema — entity properties (type, required, list, format, inline values, default), named enum value lists, and relation types (endpoints, cardinality, symmetry, content flag, relation properties).
It is the identity unit of the data-migration system (TKT-0C57FS): the hash of the projection in force is the "schema version" a store's content conforms to, recorded in the state.KV marker and referenced by migration files. Cosmetic schema edits (labels, descriptions, colors, views, automations, validations, display configuration, id prefixes) do not move the hash, so they never demand a migration.
This is a SIBLING of RenderProjection, not a replacement. The two answer different questions — "how does a stored version render?" versus "does the stored data still fit the schema?" — and have independent stability contracts: RenderProjection's hash content-addresses schema_versions rows for the pgstore versioning feature and must not churn, while ShapeProjection deliberately includes relation shape and property defaults that rendering never needs. Do not merge them.
id prefixes are deliberately EXCLUDED (TKT-0C57FS amendment A5): no v1 migration step can rewrite entity IDs, so including prefixes would let a prefix edit create a needs-migration state no migration could resolve.
func ShapeProjectionFromJSON ¶
func ShapeProjectionFromJSON(data []byte) (ShapeProjection, error)
ShapeProjectionFromJSON parses a projection previously serialized by ShapeProjection.JSON (from a migration file or the state marker).
func (ShapeProjection) Hash ¶
func (p ShapeProjection) Hash() string
Hash returns the content-address of the shape projection: a hex-encoded SHA-256 over a length-prefixed, key-sorted encoding of every field — the same writer discipline as RenderProjection.Hash and internal/canonical (length prefixes make the encoding unambiguous; sorted keys make it independent of map iteration order). The leading tag byte differs ('S' versus RenderProjection's 'P') so the two hash spaces can never collide even on structurally similar input.
func (ShapeProjection) JSON ¶
func (p ShapeProjection) JSON() ([]byte, error)
JSON returns the projection serialized as deterministic JSON, for embedding in migration files and the state.KV marker. Identity is ShapeProjection.Hash (a separate length-prefixed digest); this serialization is for storage and re-load.
type ShapeReport ¶
type ShapeReport struct {
Deltas []ShapeDelta
}
ShapeReport is the classified diff between two shape projections.
func CompareShapes ¶
func CompareShapes(from, to ShapeProjection) ShapeReport
CompareShapes classifies every difference between two shape projections. `from` is the shape the stored data conforms to; `to` is the live schema.
The one inherent blind spot: a rename is indistinguishable from a delete+add. When a removed and an added property in the same entity type share type/list/format, the report carries an explicit possible_property_rename delta (drift tier) so the operator is told — but it cannot be more than a warning. Same for entity types of similar property shape.
func (ShapeReport) ByTier ¶
func (r ShapeReport) ByTier(t ShapeTier) []ShapeDelta
ByTier returns the deltas of one tier, in report order.
func (ShapeReport) Compatible ¶
func (r ShapeReport) Compatible() bool
Compatible reports whether the target shape can be adopted without a migration (no needs-migration deltas).
func (ShapeReport) Tier ¶
func (r ShapeReport) Tier() ShapeTier
Tier reduces the report to a single verdict: the highest tier among the deltas. An empty report (identical shapes) is TierAdditive.
type ShapeTier ¶
type ShapeTier int
ShapeTier classifies one schema-shape delta by its impact on stored data. The ordering is meaningful: a higher tier subsumes a lower one when a report is reduced to a single verdict.
const ( // TierAdditive deltas cannot invalidate stored content (new types, new // optional properties, new enum values, widenings, default-only edits). // A store may adopt the new shape silently. TierAdditive ShapeTier = iota // TierDrift deltas leave stored content stale but never broken: // deletions orphan data (GC territory), a new required property yields // soft warnings. A store adopts the new shape but the operator is told. TierDrift // TierMigration deltas mean stored values no longer fit the schema // (type/format changes, list flips, enum replacements, narrowings). // The store must not adopt the new shape until a migration runs. TierMigration )
type SortSpec ¶
type SortSpec struct {
Property string `yaml:"property" json:"property"`
Direction string `yaml:"direction,omitempty" json:"direction,omitempty"` // "asc" (default) or "desc"
}
SortSpec describes a single sort criterion used in metamodel default_sort, queries, and data-entry config. Property can be a real entity property name or a virtual property: "id" (entity ID) or "modified" (file modification time).
func (SortSpec) IsDescending ¶
IsDescending returns true if direction is "desc".
type StringOrSlice ¶
type StringOrSlice []string
StringOrSlice is a YAML type that can be unmarshaled from either a string or []string.
func (*StringOrSlice) UnmarshalYAML ¶
func (s *StringOrSlice) UnmarshalYAML(unmarshal func(any) error) error
UnmarshalYAML allows StringOrSlice to be unmarshaled from either a string or a slice.
type Subscriber ¶
type Subscriber interface {
// Subscribe starts a watcher that invokes onChange whenever the
// underlying metamodel data changes. The returned stop function
// releases all watcher resources.
Subscribe(ctx context.Context, onChange func()) (stop func(), err error)
}
Subscriber is the optional change-notification interface on a Loader. Implementations that can detect external changes to the metamodel data satisfy Subscriber; consumers type-assert to subscribe. Backends with no change-detection capability (e.g. an embedded byte-slice loader) simply don't implement it.
type TransformDef ¶
type TransformDef struct {
From string `yaml:"from"`
Command []string `yaml:"command"`
Produces string `yaml:"produces"`
}
TransformDef is one entry in the top-level `transforms:` view-export registry. It converts From-format bytes (v1: always "markdown") to the Produces content-type by running Command as an argv array. Command may reference the {in}/{out} placeholders (temp paths owned by the runner); otherwise input is on stdin and output on stdout. Commands come from project config (this file), never from a request — a request may only name a registered transform.
The mirror of this in internal/transform is transform.Def; the metamodel keeps its own YAML-tagged type so internal/transform need not be imported here.
type TransformStep ¶
type TransformStep struct {
// Cmd is an external command (array args) that rewrites the bytes; it
// receives templated {in}/{out} paths owned by the runner.
Cmd []string `yaml:"cmd,omitempty"`
// Image is a native in-process image transform (decode, orient, re-encode).
// Mutually exclusive with Cmd.
Image *ImageStep `yaml:"image,omitempty"`
}
TransformStep is one entry in a `transform:` pipeline. A step is EITHER an external command (Cmd) OR a native, in-process image operation (Image) — exactly one must be set, enforced at load. The two kinds have very different trust models: a Cmd shells out to an operator-configured binary (sandboxed by internal/cmdexec), while an Image step runs a memory-safe pure-Go decoder in-process with no external tool and no sandbox. See the attachment-security guide.
func (TransformStep) Kind ¶
func (t TransformStep) Kind() string
Kind reports which of the two mutually-exclusive step kinds is set, or an empty string when the step is malformed (both/neither set).
type TransitionDef ¶
type TransitionDef struct {
From string `yaml:"from"` // Source value; must be one of CustomType.Values
To string `yaml:"to"` // Target value; must be one of CustomType.Values
// Label is optional display text for the MOVE (the action), not the
// destination state — e.g. "Start progress" for todo→doing rather than the
// state noun "Doing". Purely presentational: a machine-aware status control
// lists transitions as verbs. Empty falls back to the target value's display
// label (CustomType.Labels[To]) and then the raw To value. Display-only; the
// executable machine ignores it for enforcement.
Label string `yaml:"label,omitempty"`
// Help is optional longer prose explaining WHY or WHEN a user would make
// this move, beyond the short verb Label — e.g. "Send for review once the
// implementation is complete and tests pass." Display-only, surfaced by the
// generated documentation (FEAT-G4VO53); the executable machine ignores it.
Help string `yaml:"help,omitempty"`
// Guard names an ACL permission the acting principal must hold for this
// transition. Enforced only on served paths (a principal exists); inert
// on direct CLI writes. Empty means the transition is legal for anyone
// who may otherwise write the entity.
Guard string `yaml:"guard,omitempty"`
// When is an internal/predicate expression evaluated as a precondition
// against the entity + graph at write time. False rejects the transition
// (422). Empty means no precondition.
When string `yaml:"when,omitempty"`
}
TransitionDef is one edge in an enum state machine: a legal move from one value to another, optionally gated by an ACL permission (Guard) and/or a data precondition (When). This is declarative source data; the executable machine is built from it by internal/statemachine.Compile.
type TypeValidation ¶
type TypeValidation struct {
Pattern string `yaml:"pattern"` // Regex pattern that values must match
Error string `yaml:"error"` // User-friendly error message if pattern doesn't match
// contains filtered or unexported fields
}
TypeValidation defines a regex validation for a custom type.
func (*TypeValidation) Compiled ¶
func (tv *TypeValidation) Compiled() *regexp.Regexp
Compiled returns the pre-compiled regex pattern. Returns nil if the pattern hasn't been compiled yet.
func (*TypeValidation) SetCompiled ¶
func (tv *TypeValidation) SetCompiled(re *regexp.Regexp)
SetCompiled sets the pre-compiled regex pattern.
type ValidationError ¶
type ValidationError struct {
Type ValidationErrorType
Property string // The property name that failed validation (empty for entity-level errors)
Message string // Human-readable error message
}
ValidationError represents a structured validation error with field information.
func (*ValidationError) Error ¶
func (e *ValidationError) Error() string
Error implements the error interface.
func (*ValidationError) IsSoft ¶
func (e *ValidationError) IsSoft() bool
IsSoft reports whether the error describes a soft condition per DEC-HWZHA — a state a hand-edited markdown file can produce that the API should tolerate at write time and surface as a warning rather than reject with a 422.
Property-level mistakes (required-field-missing, type mismatch, invalid value such as out-of-enum / bad date / bad RRULE) are soft: the file already on disk likely contains them after a hand-edit, so rejecting them on the next API write would create a hostile asymmetry. Entity-level structural problems (unknown entity type, ID prefix that doesn't match the type) are hard: the storage layer can't construct a path to persist the entity at all.
The categorization lives next to the error type so every consumer (workspace, future per-edge endpoints, MCP, etc.) gets a single authoritative answer.
type ValidationErrorType ¶
type ValidationErrorType string
ValidationErrorType indicates the kind of validation error.
const ( ValidationErrorRequired ValidationErrorType = "required" ValidationErrorInvalidValue ValidationErrorType = "invalid_value" ValidationErrorInvalidType ValidationErrorType = "invalid_type" ValidationErrorUnknownType ValidationErrorType = "unknown_type" ValidationErrorIDPrefix ValidationErrorType = "id_prefix" // ValidationErrorUnique reports a duplicate value for a property // declared `unique: true`. It is a HARD error (IsSoft returns false): // a natural-key collision is a constraint violation the write path // must reject with a 422, not a tolerable hand-edit state. Unlike the // other types, this one is raised by the entitymanager write path // (which can query other entities), not by the pure per-entity // [Metamodel.ValidateEntity]. ValidationErrorUnique ValidationErrorType = "unique" )
type ValidationRule ¶
type ValidationRule struct {
// Name is a unique identifier for the validation rule
Name string `yaml:"name"`
// Description explains what this validation checks
Description string `yaml:"description"`
// EntityType limits the validation to a specific entity type (optional)
// If empty, the validation applies to all entity types
EntityType string `yaml:"entity_type,omitempty"`
// When specifies filter conditions that select which entities this rule applies to
// Uses the same syntax as --where filters (e.g., "status=approved")
// Multiple conditions are ANDed together
// If empty, the rule applies to all entities (of the specified type)
When []string `yaml:"when,omitempty"`
// Then specifies filter conditions that matching entities must satisfy
// Uses the same syntax as --where filters (e.g., "owner!=")
// Multiple conditions are ANDed together
Then []string `yaml:"then,omitempty"`
// WhenCondition is a predicate EXPRESSION selecting which entities the
// rule applies to, ANDed with every When clause. Expression syntax
// (unlike When's filter syntax) gets boolean composition and the
// host-function stdlib, including date arithmetic.
WhenCondition string `yaml:"when_condition,omitempty"`
// ThenCondition is a predicate EXPRESSION that matching entities must
// satisfy, ANDed with every Then clause.
//
// A rule may use any mix: `when:` + `then_condition:` is a filter
// selecting entities that an expression then asserts over.
ThenCondition string `yaml:"then_condition,omitempty"`
// Content specifies validation rules for markdown body content
Content *ContentRule `yaml:"content,omitempty"`
// Severity is the severity level of violations: "error" or "warning"
// Defaults to "warning" if not specified
Severity string `yaml:"severity,omitempty"`
// Lua specifies inline Lua code for custom validation logic.
// The code should return true if the entity is valid, or false/nil for a violation.
// The entity being validated is available as the `entity` global variable.
// Read-only workspace access is available via rela.get_entity(), rela.list_entities(), etc.
Lua string `yaml:"lua,omitempty"`
// LuaFile specifies a path to a Lua script file in the scripts/ directory.
// The script should return true if valid, or false/nil for a violation.
// Example: "validate-dates.lua" loads scripts/validate-dates.lua
LuaFile string `yaml:"lua_file,omitempty"`
// LuaArgs specifies arguments to pass to Lua validation scripts.
// Available as rela.args in the Lua runtime.
LuaArgs []string `yaml:"lua_args,omitempty"`
}
ValidationRule defines a custom validation rule for entities
func (*ValidationRule) GetSeverity ¶
func (v *ValidationRule) GetSeverity() string
GetSeverity returns the severity level, defaulting to "warning"
func (*ValidationRule) IsError ¶
func (v *ValidationRule) IsError() bool
IsError returns true if this validation has error severity
type WhitespacePropertyError ¶
WhitespacePropertyError is returned when a property name has leading or trailing whitespace.
func (*WhitespacePropertyError) Error ¶
func (e *WhitespacePropertyError) Error() string