Documentation
¶
Overview ¶
Package lesson parses and scaffolds canonical directory Lessons at spec/lessons/<slug>/README.md and compatibility flat Lessons at spec/lessons/<slug>.md.
A Lesson records a gap in process — a missing check, gate, convention, or review step that let a defect ship unnoticed — and climbs a three-rung enforcement ladder (Recorded -> Stated -> Enforced) as the gap is closed by an increasingly binding mechanism. It is deliberately the flattest kind in the spec tree: no hierarchy, no source-Feature dependency, a single status line. Canonical READMEs keep the durable rule compact while immutable child occurrences hold manifestation history; compatibility flat files retain the historical four-section prose contract until explicit migration.
Package lesson — lifecycle transition orchestration for the Lesson kind.
This file hosts ChangeStatus, the kind-specific orchestrator invoked by `specscore lesson change-status`. It composes pkg/lifecycle/ primitives (state-machine validation, status-line rewrite, rollback) and adds the Lesson-specific structured `**Superseded By:**` successor reference — mirroring pkg/plan/transitions.go exactly, minus the execution band (a Lesson has none: every rung of the ladder is human-authored).
LINT INVOCATION lives in the cobra adapter (internal/cli/lesson.go), NOT here, to avoid an import cycle: pkg/lint imports pkg/lesson for the lesson-* lint rules, so pkg/lesson cannot depend back on pkg/lint. The adapter passes a PostMutationHook callback into ChangeStatus; this package only knows "run the post-mutation hook, and roll back if it fails".
Index ¶
- Constants
- Variables
- func AddRelation(lessonsDir, from, typ, to string) error
- func AddRelationTransaction(lessonsDir, from, typ, to string, hooks RelationTransactionHooks) (bool, error)
- func AddRelationWithPostMutation(lessonsDir, from, typ, to string, postMutation RelationPostMutationHook) error
- func CompensatePublication(removeAndSync func() error, cause error) error
- func FinalizeFlatMigration(opts FlatMigrationOptions, eventUUID string) error
- func FlatMigrationEventUUID(sourceSHA, slug string) string
- func IsSingleFileLessonPath(lessonsDir, filePath string) bool
- func LegalChangeStatusTargetNames() []string
- func LegalTransitionMatrix() string
- func PreflightLegacyApply(lessonsDir string, allowedClassifications []string, inv LegacyInventory, ...) error
- func Recur(path, note string) (int, error)
- func RecurWithPostMutation(path, note string, postMutation func(int) error) (int, error)
- func RelationToken(from, typ, to string) string
- func RemoveOccurrence(path string) error
- func RequiredSectionsFor(l *Lesson) []string
- func ResolveLessonFile(lessonsDir, slug string) (string, error)
- func RewriteFileAtomic(path string, data []byte) error
- func Scaffold(opts ScaffoldOptions) ([]byte, error)
- func ScaffoldCanonical(opts ScaffoldOptions, classifications []string) ([]byte, error)
- func ValidateOccurrence(o Occurrence) error
- func ValidateRelation(from, typ, to string) error
- func ValidateSafeContent(field, value string) error
- func ValidateSlug(slug string) error
- func WithMutationLock(projectRoot, slug string, mutate func() error) error
- func WithMutationLocks(projectRoot string, slugs []string, mutate func() error) error
- type AddOccurrenceOptions
- type ChangeStatusOptions
- type ChangeStatusResult
- type Evidence
- type FlatMigrationOptions
- type FlatMigrationPreflight
- type FlatMigrationResult
- type LegacyApplyInspection
- type LegacyApplyResult
- type LegacyCandidate
- type LegacyCollision
- type LegacyEntry
- type LegacyInventory
- type LegacyMapping
- type LegacyMappingEntry
- type LegacySourceRef
- type LegacyStatusDecision
- type Lesson
- type MutationError
- type MutationOutcome
- type Occurrence
- type PostMutationHook
- type Relation
- type RelationPostMutationHook
- type RelationTransactionHooks
- type ScaffoldOptions
Constants ¶
const FormatURL = "https://specscore.md/lesson-specification"
FormatURL is the canonical spec URL for the Lesson document type. It is carried verbatim in both the frontmatter `format:` field and the adherence-footer line, per the artifact-frontmatter-convention.
const OccurrenceStoreKeepFile = ".gitkeep"
OccurrenceStoreKeepFile preserves an otherwise empty canonical occurrence store in Git. It is not an occurrence and must remain empty.
Variables ¶
var RequiredSections = []string{"Incident", "Process gap", "Check", "Enforcement"}
RequiredSections is the closed, ordered set of H2 section headings every Lesson body MUST declare. Lint checks presence ONLY — never content, length, or wording — so a section holding nothing but a `<!-- TODO -->` prompt is lint-clean. Process gap and Enforcement are the load-bearing pair: a Lesson describing an Incident but naming no gap in the process is the useless entry the Process-gap requirement exists to refuse, and a Lesson proposing no enforcement path is the other half of that same refusal.
Functions ¶
func AddRelation ¶ added in v0.34.0
func AddRelationTransaction ¶ added in v0.34.0
func AddRelationTransaction(lessonsDir, from, typ, to string, hooks RelationTransactionHooks) (bool, error)
AddRelationTransaction returns false without invoking BeforeMutation when the exact reviewed relation is already present. This lets CLI adapters avoid creating a second event for a completed idempotent command while still reconciling a retained prepared event through ReconcileNoop.
func AddRelationWithPostMutation ¶ added in v0.34.0
func AddRelationWithPostMutation(lessonsDir, from, typ, to string, postMutation RelationPostMutationHook) error
AddRelationWithPostMutation holds both endpoint locks (lexical slug order), then the project relation lock, across artifact publication and bounded index/durability reconciliation.
func CompensatePublication ¶ added in v0.34.0
CompensatePublication runs the caller's exact inverse mutation and its durability fence. It returns a typed outcome so a prepared event is aborted only after full compensation is proven.
func FinalizeFlatMigration ¶ added in v0.34.0
func FinalizeFlatMigration(opts FlatMigrationOptions, eventUUID string) error
FinalizeFlatMigration retires the durable transaction marker only after the caller has durably committed the prepared event. It independently verifies the canonical artifacts, manifest, exact index projection, and removal of the flat source. A crash before this call leaves a marker that makes the CLI replay the same deterministic event and narrow index upsert.
func FlatMigrationEventUUID ¶ added in v0.34.0
func IsSingleFileLessonPath ¶
IsSingleFileLessonPath reports whether filePath looks like a single-file Lesson candidate location — directly under lessonsDir, with a `.md` extension, and not named README.md (the index file). It does NOT read the file; callers still must validate the title prefix via Parse().
func LegalChangeStatusTargetNames ¶
func LegalChangeStatusTargetNames() []string
LegalChangeStatusTargetNames returns the canonical-titled names of the legal --to values (every To column in the KindLesson matrix). Filtering lifecycle.LegalStatuses — itself already alphabetically sorted — preserves that order, so no separate sort step is needed.
func LegalTransitionMatrix ¶
func LegalTransitionMatrix() string
LegalTransitionMatrix returns a human-readable, ANSI-free rendering of the Lesson legal-transition matrix, suitable for cobra `Long` help text.
func PreflightLegacyApply ¶ added in v0.34.0
func PreflightLegacyApply(lessonsDir string, allowedClassifications []string, inv LegacyInventory, mapping LegacyMapping) error
PreflightLegacyApply validates the complete reviewed mapping, configured classification vocabulary, immutable source bytes, targets, and manifest collisions without writing. CLI adapters call it before preparing an event; ApplyLegacy calls the same function again immediately before publication.
func Recur ¶
Recur records that a Lesson's gap manifested again: it increments the `**Recurred:** N` header count (inserting the field, defaulted to 0 then incremented to 1, when a pre-existing Lesson predates it) and appends a dated bullet — with the optional note — to a `## Recurrences` section (created immediately before the adherence footer when absent). It does NOT change `**Status:**`: a recurrence is a signal that a lesson needs to graduate, not a graduation itself — the decision to promote stays a deliberate `change-status` call. Returns the new recurrence count.
func RecurWithPostMutation ¶ added in v0.34.0
RecurWithPostMutation holds the per-Lesson lifecycle lock across the body rewrite and its bounded reconciliation hook. The hook may acquire the shared Lesson-index lock; the total lock order is therefore always per-Lesson first, shared index second.
func RelationToken ¶ added in v0.34.0
func RemoveOccurrence ¶ added in v0.34.0
RemoveOccurrence performs an explicit caller-requested deletion and fences its parent directory. It is never safe as automatic post-publication compensation: a path returned by AddOccurrence is not an ownership token, because another writer can replace that path before a later unlink.
func RequiredSectionsFor ¶ added in v0.34.0
RequiredSectionsFor preserves legacy compatibility while exposing the canonical compact Lesson's actual contract to readers.
func ResolveLessonFile ¶
ResolveLessonFile resolves a canonical directory Lesson first, then a legacy flat file during the compatibility window. Both forms for the same slug are a conflict: picking one would make lifecycle changes nondeterministic.
func RewriteFileAtomic ¶ added in v0.34.0
RewriteFileAtomic replaces an existing Lesson artifact with a same-directory temp, file fsync, atomic rename, and parent-directory fsync. Callers that coordinate with lifecycle writers must hold WithMutationLock across their ownership check, this rewrite, and any shared-index reconciliation.
func Scaffold ¶
func Scaffold(opts ScaffoldOptions) ([]byte, error)
Scaffold returns a lint-clean flat Lesson file body: the artifact-frontmatter-convention frontmatter (`format:` + `status:` mirroring the body `**Status:** Recorded`), the `# Lesson:` title, the body-metadata header, the four required sections (Incident, Process gap, Check, Enforcement) with HTML-comment prompts, and the adherence footer whose URL agrees with `format:`.
A freshly scaffolded Lesson is immediately lint-clean: every required section exists (lint checks presence only, never content), so recording a lesson is a single command with no required flags beyond the slug — the friction-near-zero design this artifact kind depends on for agents to actually use it under time pressure.
func ScaffoldCanonical ¶ added in v0.34.0
func ScaffoldCanonical(opts ScaffoldOptions, classifications []string) ([]byte, error)
ScaffoldCanonical returns the compact directory-form Lesson README. The incident diary lives in immutable child occurrences, so the README contains only the durable lesson and its enforceable control.
func ValidateOccurrence ¶ added in v0.34.0
func ValidateOccurrence(o Occurrence) error
func ValidateRelation ¶ added in v0.34.0
func ValidateSafeContent ¶ added in v0.34.0
ValidateSafeContent enforces the occurrence schema's content policy for all committed Lesson strings. Errors deliberately identify only the field and policy class; unsafe values are never echoed into stderr or logs.
func ValidateSlug ¶
ValidateSlug returns nil when slug is a lowercase, hyphen-separated, URL-safe identifier with no `/`.
func WithMutationLock ¶ added in v0.34.0
WithMutationLock serializes one caller-owned Lesson transaction with every lifecycle, recurrence, relation, and force-update writer. Callers that also mutate the shared index must do so inside mutate: the total order is per-Lesson locks (lexical slug order), the optional relation-project lock, then the shared Lesson-index lock.
func WithMutationLocks ¶ added in v0.34.0
WithMutationLocks serializes a caller-owned transaction that spans more than one Lesson. Locks are acquired in lexical slug order and duplicate slugs are collapsed, preserving the package-wide lock order before the shared index lock is acquired by mutate.
Types ¶
type AddOccurrenceOptions ¶ added in v0.34.0
type AddOccurrenceOptions struct {
LessonPath string
// ID is optional for ordinary writes. Deterministic importers may supply a
// UUID v4-shaped content-derived identity to make replay idempotent.
ID string
Summary string
Context map[string]any
Evidence Evidence
Redactions []string
Now time.Time
}
type ChangeStatusOptions ¶
type ChangeStatusOptions struct {
// SpecRoot is the project root that contains the `spec/` subtree (NOT the
// `spec/` directory itself). The Lesson is resolved at
// SpecRoot/spec/lessons/<slug>.md.
SpecRoot string
// Slug is the Lesson slug. Caller is expected to have validated it via
// lesson.ValidateSlug.
Slug string
// To is the canonical (title-case) target status.
To lifecycle.Status
// Note is the optional free-form markdown transition note, written as a
// `## Resolution` section. REQUIRED (enforced by the cobra adapter) for
// the Withdrawn and Superseded dispositions.
Note string
// Successor is the slug of the lesson that supersedes this one. REQUIRED
// (enforced by the cobra adapter) for --to=Superseded, rejected
// otherwise. Written as a `**Superseded By:** <slug>` header line.
Successor string
// PostMutation is the post-rewrite hook (typically a spec-lint pass).
// Required; ChangeStatus returns exit 10 if nil.
PostMutation PostMutationHook
}
ChangeStatusOptions packages the inputs to ChangeStatus.
type ChangeStatusResult ¶
ChangeStatusResult is the success payload returned on exit 0. The cobra adapter formats it as the `<slug>: <from> → <to>` success line.
func ChangeStatus ¶
func ChangeStatus(opts ChangeStatusOptions) (ChangeStatusResult, error)
ChangeStatus performs a Lesson-kind lifecycle transition end-to-end.
Flow:
- Resolve <slug> to an existing Lesson file. A missing file returns exit 3.
- lifecycle.Validate against the KindLesson matrix. Illegal transitions return exit 4.
- lifecycle.Rewrite the **Status:** line.
- Optionally write the `**Superseded By:**` successor reference and the `## Resolution` note.
- Invoke the PostMutation hook. Any failure after step 3 is uncertain and retained for durable recovery; this function never destructively restores a snapshot after publication.
type FlatMigrationOptions ¶ added in v0.34.0
type FlatMigrationPreflight ¶ added in v0.34.0
type FlatMigrationPreflight struct {
Source LegacySourceRef
EventUUID string
Classifications []string
AlreadyMigrated bool
PendingTransaction bool
}
func PreflightFlatMigration ¶ added in v0.34.0
func PreflightFlatMigration(opts FlatMigrationOptions) (FlatMigrationPreflight, error)
PreflightFlatMigration validates source identity and the complete generated transaction without publishing committed artifacts. A retry reads the durable marker so the CLI reuses the original event UUID and timestamp.
type FlatMigrationResult ¶ added in v0.34.0
type FlatMigrationResult struct {
CanonicalPath string `json:"canonical_path"`
ManifestPath string `json:"manifest_path"`
CreatedOccurrences []string `json:"created_occurrences"`
RedactedSections []string `json:"redacted_sections,omitempty"`
AlreadyMigrated bool `json:"already_migrated"`
PendingFinalize bool `json:"pending_finalize"`
}
func MigrateFlat ¶ added in v0.34.0
func MigrateFlat(opts FlatMigrationOptions) (FlatMigrationResult, error)
MigrateFlat migrates exactly one spec/lessons/<slug>.md artifact. A repository-scoped marker makes an interrupted publication inspectable and resumable. All writes are exclusive and every existing path must be either absent or byte-identical before any missing path is published.
type LegacyApplyInspection ¶ added in v0.34.0
type LegacyApplyInspection struct {
MutationRequired bool
Result LegacyApplyResult
}
LegacyApplyInspection is the write-free result of validating a reviewed import against its current canonical targets. MutationRequired is false only when every Lesson, occurrence, and manifest is already present and owned by the exact reviewed import.
func InspectLegacyApply ¶ added in v0.34.0
func InspectLegacyApply(lessonsDir string, allowedClassifications []string, inv LegacyInventory, mapping LegacyMapping) (LegacyApplyInspection, error)
InspectLegacyApply performs the complete reviewed-import validation and classifies a completed second run without writing even private staging data. Callers must hold every affected per-Lesson lock from inspection through any subsequent ApplyLegacy call.
type LegacyApplyResult ¶ added in v0.34.0
type LegacyApplyResult struct {
CreatedLessons []string `json:"created_lessons"`
CreatedOccurrences []string `json:"created_occurrences"`
StatusDecisions []LegacyStatusDecision `json:"status_decisions"`
Manual []string `json:"manual"`
Skipped []string `json:"skipped"`
Manifest string `json:"manifest"`
}
func ApplyLegacy ¶ added in v0.34.0
func ApplyLegacy(lessonsDir string, allowedClassifications []string, inv LegacyInventory, mapping LegacyMapping) (LegacyApplyResult, error)
type LegacyCandidate ¶ added in v0.34.0
type LegacyCollision ¶ added in v0.34.0
type LegacyEntry ¶ added in v0.34.0
type LegacyEntry struct {
Key string `json:"key"`
Kind string `json:"kind"`
ParentKey string `json:"parent_key,omitempty"`
LegacyID string `json:"legacy_id"`
Ordinal int `json:"ordinal"`
Title string `json:"title"`
StartLine int `json:"start_line"`
EndLine int `json:"end_line"`
StartByte int `json:"start_byte"`
EndByte int `json:"end_byte"`
RawStatus string `json:"raw_status,omitempty"`
BytesSHA256 string `json:"bytes_sha256"`
Raw string `json:"-"`
SuggestedSlug string `json:"suggested_slug"`
Warnings []string `json:"warnings,omitempty"`
}
type LegacyInventory ¶ added in v0.34.0
type LegacyInventory struct {
Source LegacySourceRef `json:"source"`
LessonCount int `json:"lesson_count"`
RecurrenceMarkerCount int `json:"recurrence_marker_count"`
EntryProjectionSHA256 string `json:"entry_projection_sha256"`
Collisions []LegacyCollision `json:"collisions"`
UnmatchedCandidates []LegacyCandidate `json:"unmatched_candidates"`
Warnings []string `json:"warnings,omitempty"`
Entries []LegacyEntry `json:"entries"`
// contains filtered or unexported fields
}
func InventoryLegacy ¶ added in v0.34.0
func InventoryLegacy(source string) (LegacyInventory, error)
type LegacyMapping ¶ added in v0.34.0
type LegacyMapping struct {
Source LegacySourceRef `json:"source"`
Entries []LegacyMappingEntry `json:"entries"`
}
type LegacyMappingEntry ¶ added in v0.34.0
type LegacyMappingEntry struct {
Key string `json:"key"`
Action string `json:"action"`
Slug string `json:"slug,omitempty"`
Title string `json:"title,omitempty"`
Lesson string `json:"lesson,omitempty"`
ProcessGap string `json:"process_gap,omitempty"`
Status string `json:"status,omitempty"`
Classifications []string `json:"classifications,omitempty"`
}
type LegacySourceRef ¶ added in v0.34.0
type LegacySourceRef struct {
Repository string `json:"repository"`
Path string `json:"path"`
Revision string `json:"revision"`
CommittedAt string `json:"committed_at"`
SHA256 string `json:"sha256"`
ByteCount int `json:"byte_count"`
}
LegacySourceRef identifies the exact immutable Git blob from which an inventory was derived. Committed migration artifacts retain this reference, byte ranges, and hashes; they never copy the historical prose itself.
type LegacyStatusDecision ¶ added in v0.34.0
type Lesson ¶
type Lesson struct {
Path string // absolute path on disk
Slug string // filename without `.md`
// Canonical is true when Path is spec/lessons/<slug>/README.md.
Canonical bool
// OccurrencesDir is non-empty only for canonical directory lessons.
OccurrencesDir string
HasLessonTitle bool // first H1 line was `# Lesson: <title>`
TitleLine int // 1-based line number of the title (0 when absent)
Title string // the `<title>` portion after `# Lesson: `
Status string // value of `**Status:**` (empty when missing)
StatusLine int // 1-based line of the field; 0 when absent
Date string // value of `**Date:**` (empty when missing)
DateLine int // 1-based line of the field; 0 when absent
Owner string // value of `**Owner:**` (empty when missing)
OwnerLine int // 1-based line of the field; 0 when absent
Classifications []string
ClassificationsLine int
LegacyProvenance string
LegacyProvenanceLine int
DuplicateOf string
DuplicateOfLine int
Supersedes string
SupersedesLine int
Recurred int // parsed `**Recurred:**` count; 0 when absent or unparsable
RecurredRaw string // raw value as written
RecurredLine int // 1-based line of the field; 0 when absent
RecurredValid bool // true when RecurredRaw parsed cleanly as a non-negative integer
SupersededBy string // value of `**Superseded By:**` (empty when missing)
SupersededByLine int // 1-based line of the field; 0 when absent
// PromotesTo is the optional `**Promotes To:** rule:<slug>` pointer a
// Lesson gains once its durable rule has been promoted into a Rule
// artifact — the Lesson half of the strict lesson<->rule pair, mirroring
// the Idea's promotion pointer at its own Feature. It is optional and
// appended after the canonical relation block, so an existing Lesson stays
// lint-clean without it.
PromotesTo string
PromotesToLine int
Control string
ControlLine int
Verification string
VerificationLine int
Evidence string
EvidenceLine int
FrontmatterStatus string
FrontmatterStatusLine int
FieldCounts map[string]int
SectionSequence []string
// SectionLines maps a present H2 section title to its 1-based heading
// line. Only sections found in the body appear here; callers check
// RequiredSections membership against this map's keys to find gaps.
SectionLines map[string]int
}
Lesson is a parsed single-file Lesson artifact.
func Discover ¶
Discover walks the direct children of lessonsDir and returns the parsed single-file Lessons found there, sorted alphabetically by Slug.
It selects candidates via IsSingleFileLessonPath (which excludes README.md and anything not directly under lessonsDir), Parses each, and keeps only files whose first H1 was `# Lesson: <title>` (HasLessonTitle == true).
An absent lessonsDir is not an error: Discover returns an empty slice and nil.
func Parse ¶
Parse reads a candidate Lesson file. It returns a populated Lesson even when the file is not actually a Lesson (HasLessonTitle == false in that case) so callers can distinguish "not a Lesson" from "malformed Lesson".
func (*Lesson) HasSection ¶
HasSection reports whether title is present in the parsed body as an H2 heading.
func (*Lesson) MissingRequiredSections ¶
MissingRequiredSections returns the subset of RequiredSections absent from the parsed body, in RequiredSections order. Returns nil when none are missing.
func (*Lesson) MissingRequiredSectionsForLayout ¶ added in v0.34.0
type MutationError ¶ added in v0.34.0
type MutationError struct {
Outcome MutationOutcome
Err error
}
MutationError preserves a normal failure while carrying the only safe decision a transaction coordinator may make about its prepared event.
func (*MutationError) Error ¶ added in v0.34.0
func (e *MutationError) Error() string
func (*MutationError) Unwrap ¶ added in v0.34.0
func (e *MutationError) Unwrap() error
type MutationOutcome ¶ added in v0.34.0
type MutationOutcome uint8
MutationOutcome says what this invocation can prove about an artifact write. It deliberately describes the writer's own publication, not whether a path happens to exist: a competing writer may already own that path.
const ( // MutationPrePublication proves this invocation did not publish an artifact. MutationPrePublication MutationOutcome = iota // MutationCompensated proves a published artifact was removed and that // removal was durably synced. MutationCompensated // MutationUncertain means publication, removal, or a required durability // fence may have happened but cannot be proved either way. Callers must // retain their prepared event for explicit reconciliation. MutationUncertain )
func MutationOutcomeOf ¶ added in v0.34.0
func MutationOutcomeOf(err error) MutationOutcome
MutationOutcomeOf is conservative for errors from older writers: absent an explicit proof, a caller must assume publication may have survived.
type Occurrence ¶ added in v0.34.0
type Occurrence struct {
SchemaVersion int `json:"schema_version"`
ID string `json:"id"`
OccurredAt time.Time `json:"occurred_at"`
Summary string `json:"summary"`
Context map[string]any `json:"context"`
Evidence Evidence `json:"evidence"`
Redactions []string `json:"redactions"`
Path string `json:"-"`
}
Occurrence is the v1 on-disk JSON contract. Context intentionally remains a typed generic map so the CLI can preserve the format's vendor-neutral shape.
func AddOccurrence ¶ added in v0.34.0
func AddOccurrence(opts AddOccurrenceOptions) (Occurrence, error)
AddOccurrence writes exactly one new immutable child JSON file.
func DiscoverOccurrences ¶ added in v0.34.0
func DiscoverOccurrences(lessonPath string) ([]Occurrence, error)
DiscoverOccurrences validates every child file and returns deterministic chronological order. A malformed child is an error, never silently ignored.
func FindOccurrence ¶ added in v0.34.0
func FindOccurrence(lessonPath, id string) (Occurrence, error)
func ValidateOccurrenceFile ¶ added in v0.34.0
func ValidateOccurrenceFile(path string) (Occurrence, error)
ValidateOccurrenceFile validates one child independently so lint can report every malformed child instead of aborting a directory scan at the first.
type PostMutationHook ¶
type PostMutationHook func() error
PostMutationHook is the callback the cobra adapter wires to its bounded index synchronization, durability fence, and read-only validation. Once the status rewrite is visible, a hook failure is retained as MutationUncertain; restoring a whole-file snapshot could erase a concurrent foreign edit.
type Relation ¶ added in v0.34.0
func ListRelations ¶ added in v0.34.0
ListRelations returns both metadata relations and directional fields. Its stable ordering makes this also safe for scripting and snapshot tests.
type RelationPostMutationHook ¶ added in v0.34.0
type RelationPostMutationHook func() error
RelationPostMutationHook reconciles derived state while every affected per-Lesson lock and the relation-project lock remain held. A CLI hook may acquire only the shared Lesson-index lock, which is last in the total order.
type RelationTransactionHooks ¶ added in v0.34.0
type RelationTransactionHooks struct {
BeforeMutation func() error
PostMutation RelationPostMutationHook
ReconcileNoop bool
}
RelationTransactionHooks place event preparation immediately before the first visible relation write and reconciliation after it while all endpoint and relation-project locks remain held. ReconcileNoop is reserved for a retry that found the relation complete but still has its original prepared event to finish.