graph

package
v0.27.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Jul 31, 2026 License: Apache-2.0 Imports: 14 Imported by: 0

Documentation

Overview

Package graph implements discovery, parsing, validation, scaffolding, and navigation for GraphSpec artifact trees consumed by the `specscore graph` command group.

GraphSpec defines the language (see the specscore spec repo, features graphspec/*, and decisions 0004-0007); this package only consumes it. A consumer graph tree lives at a single graph root per repository (default spec/graph/) with modules at modules/<module-id>/README.md, artifacts in plural collection directories (entities/, relationships/, commands/, events/, policies/) as unsuffixed <id>.md files, and ModelSpec sources at modules/<module-id>/models/*.hcl (decision 0006).

Index

Constants

View Source
const (
	KindModule       = "module"
	KindEntity       = "entity"
	KindRelationship = "relationship"
	KindCommand      = "command"
	KindEvent        = "event"
	KindPolicy       = "policy"
)

Kind tokens — the six GraphSpec kinds (decision 0004, amended by decision 0013 which adds policy). Value objects and enums are ModelSpec concepts, never GraphSpec kinds.

View Source
const DefaultGraphRoot = "spec/graph"

DefaultGraphRoot is the default repository-level graph root relative to the repo root (decision 0005). Per decision 0009 a repository's graph is the UNION of this root plus one per configured SpecScore module.

View Source
const ModelspecScheme = "modelspec://"

ModelspecScheme is the reference scheme for graph-to-ModelSpec references.

Variables

View Source
var ArtifactCollections = []string{"entities", "relationships", "commands", "events", "policies"}

ArtifactCollections lists the five artifact-bearing collection directories, in canonical order (models/ excluded — it holds HCL, not graph artifacts).

View Source
var CollectionDirs = []string{"entities", "relationships", "commands", "events", "policies", "models"}

CollectionDirs lists the six directories a full module scaffold carries (five artifact collections plus models/ for ModelSpec sources).

Kinds lists the six GraphSpec kinds in canonical order.

View Source
var ModelspecKindTokens = map[string]string{
	"entities":    "entity",
	"components":  "component",
	"enums":       "enum",
	"collections": "collection",
	"recordsets":  "recordset",
}

ModelspecKindTokens maps the plural kind segment of a modelspec:// reference (decision 0011) to the singular ModelSpec concept kind it addresses. The kind segment is OPTIONAL: the two-segment form modelspec:///<module>.<Name> resolves against the flat entity/component/enum trio; the three-segment form modelspec:///<module>.<kind>.<Name> names one of these five kinds explicitly.

View Source
var ReservedConceptNames = map[string]bool{
	"entities":    true,
	"components":  true,
	"enums":       true,
	"collections": true,
	"recordsets":  true,
}

ReservedConceptNames is the set of tokens (decision 0011 / ModelSpec decision 0015) forbidden as ModelSpec concept names in any scope: they are the kind segments of consumer reference syntax, so reserving them keeps kind-explicit references unambiguous without lookahead.

Functions

func DeriveID

func DeriveID(name string) string

DeriveID derives a bare kebab-case id from a display name (decision 0005): `CreateBooking` and `teamMember` both derive dash-separated ids (`create-booking`, `team-member`); non-alphanumerics become dashes.

func FindGraphRoot

func FindGraphRoot(repoRoot, rootRel string) (string, bool)

FindGraphRoot resolves the repository-level graph root for repoRoot. rootRel overrides the default spec/graph when non-empty. It returns the absolute path and whether the directory exists.

func GraphRootDirs

func GraphRootDirs(repoRoot, rootRel string) []string

GraphRootDirs enumerates the union of existing graph roots for repoRoot: the repository-level root (rootRel override or spec/graph) plus one per configured SpecScore module (<module.path>/<specs_dir>/graph), resolved through the existing repo-config machinery (decision 0009). Absent roots are omitted; duplicates (same cleaned path) are collapsed. Order is repo-level first, then module roots in configuration order.

func GraphRuleNames

func GraphRuleNames() []string

GraphRuleNames returns the sorted set of valid graph lint rule names.

func IsKebab

func IsKebab(s string) bool

IsKebab reports whether s is a bare lowercase kebab-case identifier.

func ResolveRef

func ResolveRef(g *Graph, ref string) (qid string, candidates []string, found bool)

ResolveRef resolves a <ref> argument to a single qualified id. It matches a qualified id first, then a shorthand (bare id or display name). candidates is populated (sorted) when the shorthand is ambiguous; found is false when nothing matches.

func ValidateRuleNames

func ValidateRuleNames(names []string) error

ValidateRuleNames returns an error naming the first unknown graph rule.

Types

type Artifact

type Artifact struct {
	Path          string // absolute path
	Stem          string // filename stem (README for modules, <id> otherwise)
	Module        string // owning module id, derived from placement
	CollectionDir string // "entities"/"relationships"/... or "" for a module README

	// Raw frontmatter presence — a key being present (even if empty) matters
	// for several rules (owner, fields/properties, sources, lifecycle).
	HasFrontmatter bool
	FMError        string

	// Decoded scalar fields.
	Kind    string
	ID      string
	Name    string
	Status  string
	Summary string

	// module
	DependsOn []string

	// entity
	Model                  string
	LifecycleStates        []string
	LifecycleStatesPresent bool

	// relationship. From/To always carry the endpoint reference; FromRole/
	// ToRole carry the optional decision-0012 role labels of the map form.
	From        string
	FromRole    string
	To          string
	ToRole      string
	Cardinality string
	Metadata    []MetaEntry

	// command / event. Participants carries the references (scalar view);
	// ParticipantItems carries the role-labeled per-item view (decision 0012).
	Subject          string
	Actors           []string
	Participants     []string
	ParticipantItems []ParticipantItem
	Inputs           []InputItem

	PossibleEvents []string
	Sources        []string

	// RoleIssues collects endpoint/participant shape violations (decision
	// 0012) for the graph-role-labels rule.
	RoleIssues []RoleIssue

	// Rules carries the Tier-1 rules: list of entity/relationship/command
	// artifacts (decision 0013); rulesMalformed records a non-list container
	// or non-map items, mirroring the inputs pattern.
	Rules []RuleItem

	// policy (decision 0013). AppliesKind/AppliesRef are set when the applies:
	// block carries exactly one of command|entity|relationship; the raw clause
	// structures back the graph-policy-shape rule.
	AppliesKind string
	AppliesRef  string
	// contains filtered or unexported fields
}

Artifact is a parsed GraphSpec artifact (module README or a collection artifact). Parse is resilient: a non-nil Artifact is returned for every readable file. FMError is set when the file has no leading frontmatter block or the block is malformed YAML; callers treat that as a hard violation.

func ParseArtifact

func ParseArtifact(path, module, collectionDir string) (*Artifact, error)

ParseArtifact reads a GraphSpec artifact file and decodes its frontmatter. module and collectionDir are supplied by discovery (placement-derived).

func (*Artifact) HasKey

func (a *Artifact) HasKey(key string) bool

HasKey reports whether the frontmatter declared the given top-level key.

func (*Artifact) KeyLine

func (a *Artifact) KeyLine(key string) int

KeyLine returns the 1-based file line of the named top-level frontmatter key, or 0 when the key is absent or its line is unknown.

func (*Artifact) QualifiedID

func (a *Artifact) QualifiedID() string

QualifiedID returns the computed <module>.<id> form (decision 0005). When the artifact has no owning module the bare id is returned.

type Concept

type Concept struct {
	Name       string
	Kind       string // "entity", "component", "enum", "collection", or "recordset"
	File       string // absolute path to the .hcl file
	Line       int
	EnumValues []string // populated for enum concepts only
	Properties []string // property/field member names (entity/component concepts; decision 0013 actor-is.model-role)
}

Concept is one ModelSpec concept declared in a module's HCL sources: an entity, component, enum, collection, or recordset (decision 0006; ModelSpec core-model; decision 0011 addressable concepts).

type Graph

type Graph struct {
	Root     string   // absolute repo-level graph root (may not exist)
	Roots    []string // absolute paths of every existing graph root in the union
	RepoRoot string   // absolute repo root (contains specscore.yaml)
	Modules  []*Module
}

Graph is a fully discovered graph: the union of the repository's graph roots and their modules (decision 0009).

func Load

func Load(repoRoot, rootRel string) (g *Graph, ok bool, err error)

Load discovers the union of graph roots for repoRoot (with an optional rootRel override on the repository-level root). ok is false when NO graph root exists — callers treat that as the "no graph root" notice case, not an error.

func (*Graph) AllArtifacts

func (g *Graph) AllArtifacts() []*Artifact

AllArtifacts returns every collection artifact across all modules, plus each module README, in a deterministic order (module id, then path).

func (*Graph) ModuleByID

func (g *Graph) ModuleByID(id string) *Module

ModuleByID returns the first module with the given id, or nil.

type InputItem

type InputItem struct {
	Name      string
	Ref       string
	Model     string
	HasName   bool
	HasRef    bool
	HasModel  bool
	ExtraKeys []string
	Line      int
}

InputItem is one entry of a CommandSpec `inputs:` list. Each item carries a name plus exactly one of ref: (qualified graph ref) or model: (modelspec:// ref). Presence flags and ExtraKeys let the inputs-shape rule report items with both, neither, or embedded structure.

type LintOptions

type LintOptions struct {
	RepoRoot string   // absolute repo root (contains specscore.yaml)
	Root     string   // graph-root override relative to RepoRoot; "" = spec/graph
	Rules    []string // enabled rules; nil = all
	Ignore   []string // disabled rules
	Severity string   // minimum severity: error|warning|info; "" = no filter
	Fix      bool     // when true, apply the specified graph fixers on disk
}

LintOptions configures a graph lint pass.

type LintResult

type LintResult struct {
	Violations  []lint.Violation
	NoGraphRoot bool
	Fixed       []string
}

LintResult is the outcome of a graph lint pass. NoGraphRoot is true when the repository has no graph root — a notice case, not a violation. Fixed lists the repo-root-relative paths of files changed by the fix pass (only when opts.Fix is true).

func Lint

func Lint(opts LintOptions) (LintResult, error)

Lint discovers the graph root and runs every enabled graph rule.

type ListItem

type ListItem struct {
	ID    string `json:"id" yaml:"id"`
	Kind  string `json:"kind" yaml:"kind"`
	Name  string `json:"name" yaml:"name"`
	Owner string `json:"owner" yaml:"owner"`
	Path  string `json:"path" yaml:"path"`
}

ListItem is one row of `graph list`. Owner is the owning module id, derived from placement (a module row owns itself).

func List

func List(g *Graph, kind, module string) []ListItem

List returns every GraphSpec artifact (modules plus collection artifacts) as list rows, filtered by kind and module when non-empty, sorted by id. owner and module are derived from placement.

type MetaEntry

type MetaEntry struct {
	Key    string
	Value  string // scalar text when Scalar is true
	Scalar bool
	Line   int
}

MetaEntry is one key of a RelationshipSpec `metadata:` flat map. Scalar records whether the value is a scalar (string/number/bool); when false the value was a nested map or list, which the metadata-shape rule rejects.

type ModelDiag

type ModelDiag struct {
	File    string
	Line    int
	Message string
}

ModelDiag is a located diagnostic produced while parsing HCL sources.

type ModelModule

type ModelModule struct {
	ID          string
	Dir         string
	Concepts    []*Concept
	Refs        []*ModelRef
	ParseErrors []ModelDiag
}

ModelModule is the parsed ModelSpec module formed by all *.hcl files under a graph module's models/ directory (decision 0006: one models/ dir = one ModelSpec module whose short name is the graph module id).

func LoadModelModule

func LoadModelModule(dir, id string) (*ModelModule, error)

LoadModelModule parses every *.hcl file under dir into one ModelModule.

func (*ModelModule) HasConcept

func (m *ModelModule) HasConcept(name string) bool

HasConcept reports whether the module declares a concept with the given name in any kind (used for bare same-module HCL references, which are attribute-typed and therefore kind-unambiguous at the source level).

type ModelRef

type ModelRef struct {
	Target string
	Attr   string // "entity", "component", "enum", or "use"
	Owner  string // declaring concept name
	File   string
	Line   int
}

ModelRef is one module-qualified or bare reference inside HCL sources — a property/field type reference (entity/component/enum) or an entity `use` entry (decision 0014). Target is bare (<Name>) for same-module references or qualified (<module>.<Name>) for cross-module references. Owner is the name of the concept whose body declares the reference — navigation uses it to derive graph edges from model structure (association-object visibility).

type ModelspecParseError

type ModelspecParseError struct {
	Rewrite string
	// contains filtered or unexported fields
}

ModelspecParseError is the typed error returned by ParseModelspecRef. Rewrite is set only for the legacy-form class and carries the exact corrected reference the --fix rewriter applies.

func (*ModelspecParseError) Error

func (e *ModelspecParseError) Error() string

type ModelspecRef

type ModelspecRef struct {
	Module   string
	Kind     string // singular: entity|component|enum|collection|recordset; "" = two-segment (trio)
	Name     string
	Repo     string // "{host}/{org}/{repo}" for cross-repo, or "" for local
	Ref      string // advisory ?ref= git pin (branch/tag/commit), or ""
	Fragment string // '#<value>' enum-value address (decision 0013), or ""
}

ModelspecRef is a parsed modelspec:// reference (decisions 0010/0011). The local form (modelspec:///<...>) carries an empty Repo; the cross-repo form (modelspec://{host}/{org}/{repo}/<...>) fills Repo with "host/org/repo". Kind is the singular concept kind for the three-segment form, or "" for the two-segment (trio) form. Ref carries an advisory ?ref= git pin, if any. Fragment carries a '#<value>' enum-value address (decision 0013), if any: it resolves only when the referenced concept is an enum declaring that value.

func ParseModelspecRef

func ParseModelspecRef(ref string) (ModelspecRef, error)

ParseModelspecRef parses a modelspec:// reference into its parts. On failure it returns a *ModelspecParseError whose class the linter inspects (legacy form carries the exact --fix rewrite; other classes carry a clear message).

type Module

type Module struct {
	ID        string
	Root      string      // absolute path to the module directory
	Readme    *Artifact   // parsed README.md frontmatter, or nil when absent
	DependsOn []string    // declared outbound dependencies (from README)
	Artifacts []*Artifact // collection artifacts owned by this module
	ModelDir  string      // absolute path to models/, or "" when absent
	Model     *ModelModule
}

Module is one GraphSpec module discovered under a graph root's modules/<id>/ directory. Identity is by placement: ID is the directory name (decision 0006).

type ParticipantItem added in v0.17.0

type ParticipantItem struct {
	Ref  string
	Role string
	Line int
}

ParticipantItem is one entry of an EventSpec `participants:` list — either a scalar qualified reference or a role-labeled {ref, role} map (decision 0012).

type QualifiedRef

type QualifiedRef struct {
	Module string
	Local  string
}

QualifiedRef is a parsed graph-to-graph reference of the form <module>.<local-id> (decision 0005).

func ParseQualifiedRef

func ParseQualifiedRef(ref string) (QualifiedRef, bool)

ParseQualifiedRef splits a <module>.<local-id> reference. ok is false when the value is not exactly module + one dot + local, both non-empty, or carries a modelspec:// scheme.

type RefItem

type RefItem struct {
	ID   string `json:"id" yaml:"id"`
	Kind string `json:"kind" yaml:"kind"`
	Path string `json:"path" yaml:"path"`
}

RefItem is one referencing artifact in `graph refs` output.

func Refs

func Refs(g *Graph, targetQID string, transitive bool) []RefItem

Refs returns the artifacts that reference targetQID (inbound), including edges derived from ModelSpec structure (an entity whose model carries an entity-reference property references the entity whose model is the target concept — association-object visibility). With transitive, it returns the full set of artifacts that can reach targetQID through reference chains, cycle-safe.

type Resolver

type Resolver struct {
	// contains filtered or unexported fields
}

Resolver resolves module short names to ModelSpec modules across the three decision-0007 steps: local graph root, configured `projects:` local paths, then explicit @{host}/{org}/{repo} suffixes for locally-available repos.

func BuildResolver

func BuildResolver(repoRoot string, g *Graph) *Resolver

BuildResolver constructs the resolver for graph g rooted at repoRoot, reading repoRoot/specscore.yaml for configured `projects:` local paths. A missing or unreadable config yields a local-only resolver — never an error, so lint stays deterministic and offline.

type RoleIssue added in v0.17.0

type RoleIssue struct {
	Line    int
	Message string
}

RoleIssue is a shape violation found while decoding a role-labeled endpoint or participant (decision 0012); the graph-role-labels rule reports them.

type RuleItem added in v0.18.0

type RuleItem struct {
	ID        string
	Text      string
	Refs      []string
	HasID     bool
	HasText   bool
	ExtraKeys []string
	Line      int
	// contains filtered or unexported fields
}

RuleItem is one entry of a Tier-1 `rules:` list (decision 0013): {id, text, refs?}. Presence flags and ExtraKeys let the graph-rules-shape rule report missing keys and unknown structure; refsMalformed records a refs value that is not a list of scalar references.

type ScaffoldOptions

type ScaffoldOptions struct {
	Kind     string
	Name     string
	ID       string // optional; derived from Name when empty
	Module   string // required for non-module kinds
	RepoRoot string // absolute repo root
	Root     string // repo-level graph-root override (relative); "" = spec/graph
	Status   string // default "draft"
	Summary  string
	From     string // relationship
	To       string // relationship
	Subject  string // command / event
	Bare     bool   // module: skip collection scaffolding
}

ScaffoldOptions configures `graph new`.

type ScaffoldResult

type ScaffoldResult struct {
	Created []string
	Target  string
}

ScaffoldResult reports the created files (repo-relative, sorted) and the primary artifact path.

func Scaffold

func Scaffold(opts ScaffoldOptions) (ScaffoldResult, error)

Scaffold writes a new GraphSpec artifact and returns the created files. It performs the collision check before any write (no partial writes) and returns exitcode-carrying errors for the documented failure modes.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL