application

package
v0.18.0-alpha.1 Latest Latest
Warning

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

Go to latest
Published: Sep 12, 2026 License: MIT Imports: 40 Imported by: 0

Documentation

Overview

Package application owns SDD's protocol-neutral runtime, public request and result types, and the infrastructure ports implemented by consumers.

Acquired graph reads

Each application read acquires its source through SnapshotReader when the graph store implements it. Branch selection never calls TargetAcquirer. An empty branch selects the runtime's current authority, independently of DefaultBranch. Show, View and Search preserve their request signatures; ReadAttachmentRequest also carries Branch and BranchFromSession. MCP forwards the session branch for attachment pages. Each page is an independent read.

AcquiredSnapshot.Config is the sole source of effective read configuration. A nonnil value supplies committed language and dependency declarations from the acquired revision. Application copies those settings for the operation; it never mutates the source configuration or the shared ProjectRuntime. SnapshotData.Config is retained document data, not an alternative effective configuration input. Hosts populating both must derive them from one source; application does not interpret SnapshotData.Config as runtime settings.

Nil Config explicitly retains runtime configuration, including local overrides. It never represents a configuration read failure. Revision-backed hosts read their selected tree with ReadProjectConfigFS and return missing or malformed configuration as an acquisition error. Credentials, providers, index configuration and write routing remain runtime composition. Read declarations never grant access: each project and dependency is authorized independently.

Stores lacking SnapshotReader retain unpinned current-authority reads only. Named-branch, exact and causal selections fail rather than silently weakening their guarantees. Acquisition failures never trigger a Current fallback. Search preparation still requires pinned sources for all selected projects.

Application owns acquired resources until their last source-dependent read, joining release failures with operation errors. Returned canonical snapshots are materialized immutable values and need no live lease. Show/workflow dependency graphs are materialized before they escape acquisition. Discovery iterators instead borrow the caller's lease until iteration finishes or stops. SearchTarget must be consumed within PrepareSearch; retain descriptors for jobs, never a target or its iterator. Exact indexing retains its existing publication shortcut and fails when the recorded source cannot be acquired.

Writes retain the existing GraphStore.Apply contract, fresh-read revalidation, retry limit and recovery behavior. The preparation revision remains provenance, not a newly imposed write precondition. Acquired read configuration introduces no new write validations or changes to write-time configuration precedence.

InfoRequest.Branch selects the read authority; an empty branch intentionally selects current authority. MCP forwards the session binding for Info.

AttachmentPage.LocalPath is optional source-provided metadata for clients sharing the adapter filesystem. Filesystem sources return their own checkout path under attachment immutability; the hint does not retain that checkout. MCP forwards it only to local clients and never constructs filesystem paths. Compositions using mcpapp.Options.LocalAttachmentPath must remove that callback and supply paths from their attachment reader instead.

Local composition

FilesystemGraphStore pins graph and attachment bytes in memory and deliberately returns nil Config: language/dependency settings and local overrides continue to come from runtime composition. Its revision covers the graph directory, not committed repository configuration. Named local branches resolve registered worktrees through GitWorktreeAcquirer.ReadFactory without mutation acquisition; the local CLI uses its existing configuration resolver for graph-directory overrides and validates project identity. No local configuration precedence changes. An unscoped FilesystemGraphStore still rejects a nonempty branch. Local exact sources survive while retained by that store instance, not process restart. Durable consumers must provide their own reproducible source access.

SearchRequest.SyncMode is required. Without ApplicationOptions.PrepareSearch, SearchSyncNone skips maintenance, SearchSyncLocal reconciles the selected home snapshot, and SearchSyncAll also reconciles searched dependencies. A supplied PrepareSearch callback owns preparation policy for the complete authorized SearchTarget. Its error-only result never asserts coverage. See the SearchTarget examples for synchronous and external composition.

Semantic search derives coverage from published entry versions after preparation. This hashes the target's eligible entries and attachments even with SearchSyncNone. Retrieval verifies returned candidates against those same snapshots. Legacy adapters without SearchIndexEntryStore retain candidate-only verification without coverage metadata or custom preparation. Text-only search requires a mode but skips preparation and embedding coverage.

ProjectRuntime.DiscoverSearchEntries streams revision-bound requirements; ProjectRuntime.IndexSearchEntry publishes one exact-source version atomically. ProjectRuntime.ReconcileSearchIndex remains a synchronous convenience. Consumers own authorization, durable source retention, scheduling and retries. Reconciliation adds versions; it does not watch for subsequent graph changes.

Consumer adoption

Compose authorized project runtimes through the existing access resolver and register PrepareSearch once. MCP uses the same application. Every selected project needs SnapshotReader and SearchIndexEntryStore for custom preparation. For branch-aware hosts, implement SnapshotReader on the runtime's GraphStore for current, named, exact and causal selections; no read factory belongs in the mutation port. Return immutable Config from the same selected source. Acquire graph and attachment access together; an operation's release must not evict resources used by another lease. Cached graphs may be shared by project/revision, but authorization and operation configuration stay separate. Changes to loader settings require a distinct cache identity. Keep source availability for queued work independently of active leases. Preserve SDD's Coverage and readable Notice in the consumer's search response.

In the mutation finalizer or graph-write/recovery adapter, call AppliedMutation.AffectedEntryIDs. An empty result means no discovery job. Before enqueueing, durably retain a reproducible source and its attachments. This may be the finalized Git revision, rather than AppliedMutation.Revision from an earlier workspace apply. AffectedEntryIDs establishes no such guarantee. The consumer's write/recovery protocol must close any crash gap between commit, finalization and durable scheduling; a best-effort finalizer alone is insufficient.

Queue selected IDs for write-triggered discovery and nil for cold search, periodic reconciliation or configuration changes. Acquire the exact retained source, then call DiscoverSearchEntries for either scope. Persist each cursor atomically with durable enqueueing or the record that published work needs no enqueue. Deduplicate indexing by full SearchEntryVersion, and run IndexSearchEntry with source retention through retries. Queue state never establishes coverage.

Consumers own concurrent document batching behind embed.Embedder. Compose query routing separately and provider deadlines and observation per provider call. embed.Batched splits oversized requests; it does not combine callers. Local CLI and MCP indexing share incremental synchronous packing and publish complete entries as batches finish. Chunk preparation retains only active work.

Deploy publication-aware retrieval before asynchronous writers. The derivation schema participates in entry hashes, so prior rows can remain stored while current entries require fresh publication. Embedding configuration changes must change the fingerprint. Existing retention/rebuild tools own old-row cleanup.

Index

Examples

Constants

View Source
const (
	DefaultShowUpDepth   = query.DefaultUpDepth
	DefaultShowDownDepth = query.DefaultDownDepth
)

Default show expansion depths favor grounding context while keeping the typically wider consumer side shallow.

View Source
const (
	SearchSyncNone  = query.SearchSyncNone
	SearchSyncLocal = query.SearchSyncLocal
	SearchSyncAll   = query.SearchSyncAll
)
View Source
const (
	LegacyPreparedTransitionVersion uint32 = 1
	PreparedTransitionVersion       uint32 = 2
)
View Source
const (
	WorkflowEventCode = "workflow_event"

	BranchBoundEventCode   = "branchBound"
	BranchClearedEventCode = "branchCleared"
	DefaultShellCanonical  = "user-dialogue"
	WorkflowMaxLabelLength = 120
	ExecutionForkPreferred = "fork-preferred"
)
View Source
const FirstSessionCodecVersion uint32 = 1

FirstSessionCodecVersion is the oldest persisted session codec this binary still reads.

View Source
const NewSessionNote = "" /* 219-byte string literal not displayed */

NewSessionNote is the single statement of the way on from a dialogue that has ended: concluding is terminal, so continuing means a new session under a new handle rather than a revival of the spent one (d-tac-k4q). It is composed into the conclude serve and into every refusal an ended session answers with, so each surface names the same one path that works.

View Source
const SessionCodecVersion uint32 = 1
View Source
const SessionRecencyWindow = 15 * time.Minute

SessionRecencyWindow is the single threshold separating an active session from an idle one in listings — a hint derived from the last-activity stamp, never a gate. Erring long is cheap, so it is generous.

Variables

View Source
var ErrNotAnSDDProject = errors.New("sdd: no .sdd/config.yaml in the tree")

ErrNotAnSDDProject reports a tree without a committed .sdd/config.yaml.

View Source
var ErrSessionNotFound = errors.New("sdd: session not found")

Functions

func AttachmentDirRelPath

func AttachmentDirRelPath(entryID string) (string, error)

AttachmentDirRelPath returns the graph-relative attachment directory for an entry ID.

func MutationBatchDigest

func MutationBatchDigest(batch MutationBatch) (string, error)

MutationBatchDigest returns the SDD-owned digest over a storage-neutral batch. The Digest field itself is excluded.

func OwnerOnly

func OwnerOnly(_ context.Context, request SessionAccessRequest) error

OwnerOnly is the shipped continuation policy: only the principal who opened a session may continue it. Compositions that share sessions replace it.

func ProcedureRegistry

func ProcedureRegistry() (*engine.Registry, error)

ProcedureRegistry returns the engine registration procedure specs load and size against — the same bounds the runtime serves with (d-tac-rzi). Composition helper for the transitional CLI write path and `sdd lint`; the application's own flows wire it internally.

func SupportedSessionCodecVersion

func SupportedSessionCodecVersion(version uint32) bool

SupportedSessionCodecVersion reports whether a persisted codec version is one this binary can read. Read-compatibility with every shape sdd has written is permanent (d-cpt-i2x), so the whole range through the current version is accepted and superseded shapes are converted at decode; only a version this binary predates is a migration error.

Types

type Access

type Access string

Access is the permission required for the current operation.

const (
	AccessRead  Access = "read"
	AccessWrite Access = "write"
)

type AccessResolver

type AccessResolver interface {
	ResolvePrincipal(context.Context, RequestIdentity) (Principal, error)
	// ResolveParticipant names the graph participant the principal appears as
	// in the project — the name framing, authorship, and WIP markers carry. It
	// is asked once the project is known, because a person may appear under a
	// different name per project (s-cpt-ny6).
	ResolveParticipant(context.Context, Principal, ProjectID) (string, error)
	ListProjects(context.Context, Principal) (ProjectList, error)
	ResolveProject(context.Context, Principal, ProjectID, Access) (*ProjectRuntime, error)
	// ResolveDependency maps one dependency the project declares — a repo ID
	// from its committed configuration — to the runtime of the project that
	// carries it, or refuses. The declared string and the resolved project's
	// ID coincide only in the local composition. The application asks per
	// declared dependency, on every view over the horizon and on every
	// dependency-closure walk; a composition whose answer is costly caches it
	// itself, since only it knows when a mapping goes stale.
	ResolveDependency(context.Context, Principal, ProjectID, string) (*ProjectRuntime, error)
	// AuthorizeSession answers whether the actor may continue the session.
	// Membership in the session's home project is asked separately, so a
	// shared session never admits anyone into a project they cannot read.
	AuthorizeSession(context.Context, SessionAccessRequest) error
}

AccessResolver is the single identity, project-access, session-continuation, and dependency authorization boundary. Implementations must resolve current authorization from ctx on every call; previously returned principals and runtimes are not proof of current access. Every call arrives inside a request: the application never calls the resolver from a connection lifecycle.

type AcquiredSnapshot

type AcquiredSnapshot struct {
	Snapshot    *Snapshot
	Attachments AttachmentPageReader
	// Config is immutable committed configuration from this source revision.
	// Nil explicitly uses runtime configuration; configuration read failures
	// must be returned by the adapter, never converted to nil.
	Config  *ProjectConfig
	Release func() error
}

AcquiredSnapshot reuses the canonical snapshot and attachment paging types. Attachments must remain fixed at Snapshot.Revision until Release. Release is mandatory; the acquirer may share retained objects across many leases.

Example
package main

import (
	"context"
	"fmt"
	"io/fs"
	"testing/fstest"

	sdd "github.com/networkteam/sdd/pkg/application"
)

type exampleTreeAttachments struct {
	tree     fs.FS
	graphDir string
}

func (r exampleTreeAttachments) ReadAttachmentPage(_ context.Context, entry, name string, offset int64, limit int) (sdd.AttachmentPage, error) {
	return sdd.PageAttachment(r.tree, r.graphDir, entry, name, offset, limit)
}

func main() {
	// A revision-backed adapter obtains this immutable tree from its storage.
	tree := fstest.MapFS{
		".sdd/config.yaml":                                {Data: []byte("repo_id: example\nlanguage: en\n")},
		".sdd/graph/2026/01/01-100000-s-tac-aaa.md":       {Data: []byte("---\ntype: signal\nkind: fact\nlayer: tactical\nsummary: Source fixture.\n---\n\nSource fixture.")},
		".sdd/graph/2026/01/01-100000-s-tac-aaa/note.txt": {Data: []byte("Same revision.")},
	}
	config, err := sdd.ReadProjectConfigFS(tree)
	if err != nil {
		panic(err)
	}
	snapshot, err := sdd.LoadSnapshotFS(context.Background(), "example", "R1", tree, config.GraphDir)
	if err != nil {
		panic(err)
	}
	source := &sdd.AcquiredSnapshot{
		Snapshot: snapshot, Config: &config, Attachments: exampleTreeAttachments{tree: tree, graphDir: config.GraphDir},
		Release: func() error { return nil },
	}
	page, err := source.Attachments.ReadAttachmentPage(context.Background(), "20260101-100000-s-tac-aaa", "note.txt", 0, 100)
	if err != nil {
		panic(err)
	}
	if err := source.Release(); err != nil {
		panic(err)
	}
	fmt.Println(source.Snapshot.Revision(), source.Config.Language, string(page.Content))
}
Output:
R1 en Same revision.

type AcquiredTarget

type AcquiredTarget struct {
	Target     MutationTarget
	Graph      GraphStore
	Finalizers []MutationFinalizer
	Release    func() error
}

AcquiredTarget contains target-scoped adapters for one short operation. Release is mandatory and is called on every success and failure path.

type Application

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

Application resolves current access and dispatches protocol-neutral SDD operations. Every method resolves identity and project afresh; a session-addressed method resolves the session's home project from the session's own record.

func NewApplication

func NewApplication(options ApplicationOptions) (*Application, error)

func (*Application) AbandonWorkflowSession

func (a *Application) AbandonWorkflowSession(ctx context.Context, identity RequestIdentity, request WorkflowResumeRequest, reason string) (WorkflowAbandonResult, error)

AbandonWorkflowSession tears down a session by handle: it replays into a buffering sink (no stamp), abandons the instances, then records the terminal abandon in one final append. A mid-teardown failure returns before that append, so the session stays as it was — honest state, no phantom teardown. Ending a session is a participant act, and holding the handle is what authorizes it (d-cpt-aen).

func (*Application) ApplyPrepared

func (a *Application) ApplyPrepared(ctx context.Context, identity RequestIdentity, project ProjectID, binding SessionBinding, prepared PreparedTransition) (TransitionResult, error)

ApplyPrepared durably records intent before canonical apply and outcome afterward. Unknown apply outcomes retain staged blobs for reconciliation; definitive outcomes release them after all applied finalizers succeed.

func (*Application) CollectSessions

func (a *Application) CollectSessions(ctx context.Context, cmd CollectSessionsCmd) (CollectSessionsResult, error)

CollectSessions removes the sessions that are safe to remove and drains the pending declarations that can never converge. It is an operator act on the composition's stores, without identity or project (d-cpt-yjc); who may trigger it is the composition's to gate.

The pass is lock-free and optimistic. An ended session is never written to again, so two processes starting at once derive the same target set and both simply delete; an already-deleted target is success. The target set is recomputed from scratch every run, so a partially removed session is finished by the next pass and there is nothing to reconcile after an interruption.

What it protects, and the whole of it: it never removes a session that has not ended, one inside its retention window, one an in-flight declaration still claims, or one this binary cannot read — an unreadable log may belong to a newer version, so it is left alone rather than treated as garbage.

One pass walks one page of each store from the cursor; a session the pass keeps does not starve later pages because the cursor moves past it. The two enumerations share the cursor, so Next is the earlier of where they stopped: the later one re-enumerates a few areas next pass, which is idempotent.

func (*Application) CreateEntry

func (a *Application) CreateEntry(ctx context.Context, identity RequestIdentity, project ProjectID, binding SessionBinding, draft EntryDraft) (CreateEntryResult, error)

CreateEntry runs SDD-owned validation and pre-flight, prepares canonical document/blob facts, then enters the durable transition protocol.

func (*Application) CurrentSnapshot

func (a *Application) CurrentSnapshot(ctx context.Context, identity RequestIdentity, project ProjectID) (*Snapshot, error)

CurrentSnapshot resolves current read access and returns the opaque canonical snapshot for protocol adapters that host SDD's engine.

func (*Application) FinishWIP

func (a *Application) FinishWIP(ctx context.Context, identity RequestIdentity, project ProjectID, binding SessionBinding, target MutationTarget, markerID string) (MutationResult, error)

func (*Application) Info

func (a *Application) Info(ctx context.Context, identity RequestIdentity, project ProjectID, request InfoRequest) (InfoResult, error)

func (*Application) Lint

func (a *Application) Lint(ctx context.Context, identity RequestIdentity, project ProjectID, request types.LintQuery) (result *types.LintResult, err error)

Lint runs the categorized lint providers over the project's graph — the composition-root lint query (d-cpt-xc3): shells render the findings by category and derive their exit state from LintResult.Errors alone. Index findings need shell-resolved inputs (IndexLintQuery) and stay a shell concern.

func (*Application) ListRecoveries

func (a *Application) ListRecoveries(ctx context.Context, identity RequestIdentity, project ProjectID, includeClosed bool) (RecoveryList, error)

ListRecoveries is a free read projection. Closed terminal history is included only when requested; actionable states never perform acquisition or replay.

func (*Application) ListWorkflowSessions

func (a *Application) ListWorkflowSessions(ctx context.Context, identity RequestIdentity, project ProjectID) ([]WorkflowSessionSummary, error)

func (*Application) LoadWorkflow

func (a *Application) LoadWorkflow(ctx context.Context, identity RequestIdentity, request WorkflowResumeRequest) (*WorkflowSession, error)

LoadWorkflow loads an existing session by ID: the session's own record names its home project, the composition's continuation policy and current membership in that project gate the load, and an ended session is refused. Possession of the ID is the authorization — no consent, no takeover (d-cpt-aen). Loading stamps which client attached and when, the record staleness is derived from.

func (*Application) OpenStagedBlob

func (a *Application) OpenStagedBlob(ctx context.Context, identity RequestIdentity, project ProjectID, ref SessionRef, blobID string) (io.ReadCloser, error)

OpenStagedBlob resolves read access and session ownership, then streams a staged blob's bytes — the read-side counterpart of StageBlob.

func (*Application) OpenWorkflow

func (a *Application) OpenWorkflow(ctx context.Context, identity RequestIdentity, project ProjectID, request WorkflowOpenRequest) (*WorkflowSession, *WorkflowServe, error)

func (*Application) Procedures

func (*Application) Projects

func (a *Application) Projects(ctx context.Context, identity RequestIdentity) (ProjectList, error)

Projects lists the projects the request's principal can reach, with per-project access. It resolves no project: it is the read a shell needs before a project is chosen (d-tac-1z6).

func (*Application) ReadAttachment

func (a *Application) ReadAttachment(ctx context.Context, identity RequestIdentity, project ProjectID, request ReadAttachmentRequest) (result ReadAttachmentResult, err error)

func (*Application) ReconcileMutation

func (a *Application) ReconcileMutation(ctx context.Context, identity RequestIdentity, request RecoveryReconcileRequest) (result RecoveryResult, err error)

ReconcileMutation refreshes one actionable recovery projection without choosing a terminal or graph-affecting verb. It exists for interactive clients that must present actions from current target evidence instead of guessing from a durable projection that may predate reconciliation.

func (*Application) RecoverMutation

func (a *Application) RecoverMutation(ctx context.Context, identity RequestIdentity, request RecoveryRequest) (result RecoveryResult, err error)

RecoverMutation performs exactly one explicitly authorized verb. It always reconciles a freshly acquired concrete target before any graph-affecting or terminal action and never runs from startup, resume, or read surfaces.

func (*Application) ReplaceSummary

func (a *Application) ReplaceSummary(ctx context.Context, identity RequestIdentity, project ProjectID, binding SessionBinding, target MutationTarget, entryID, summary string) (MutationResult, error)

func (*Application) ResumeWorkflow

ResumeWorkflow loads a session and serves its current position: every running instance at its step with the schema to continue it.

func (*Application) Search

func (a *Application) Search(ctx context.Context, identity RequestIdentity, project ProjectID, request SearchRequest) (result SearchResult, err error)

func (*Application) Show

func (a *Application) Show(ctx context.Context, identity RequestIdentity, project ProjectID, request ShowRequest) (result ShowResult, err error)

func (*Application) StageBlob

func (a *Application) StageBlob(ctx context.Context, identity RequestIdentity, project ProjectID, ref SessionRef, filename string, content []byte) (StagedBlob, error)

StageBlob resolves current read access before placing immutable bytes in session-scoped scratch. Canonical write access is checked later at the mutation gate, so read-only principals can still conduct dialogue.

func (*Application) StartWIP

func (a *Application) StartWIP(ctx context.Context, identity RequestIdentity, project ProjectID, binding SessionBinding, target MutationTarget, entryID, description string) (string, MutationResult, error)

func (*Application) View

func (a *Application) View(ctx context.Context, identity RequestIdentity, project ProjectID, request ViewRequest) (result ViewResult, err error)

func (*Application) Vocabulary

func (a *Application) Vocabulary(ctx context.Context, identity RequestIdentity, project ProjectID) (string, error)

func (*Application) WritingGuideCheck

func (a *Application) WritingGuideCheck(ctx context.Context, identity RequestIdentity, project ProjectID, draft EntryDraft) ([]GuideFinding, error)

WritingGuideCheck runs the writing guide against a draft in isolation — the pre-playback half of the capture-guide architecture (d-cpt-20r). The draft's own fields feed the prompt and refs stay unresolved, because the guide's scope is the entry as a stranger reads it. The single exception is the draft's closure edges, described one summary sentence deep: which act an entry performs is not inferable from the body alone, and a guide that cannot see it misreads correct entries (s-tac-fu8). Findings are drafting input for the dialogue, never a gate.

type ApplicationError

type ApplicationError struct {
	Code       ErrorCode
	Message    string
	Project    ProjectRef
	Action     *ProjectAction
	ApplyState ApplyState
	Revision   string
	Version    uint32
	Cause      error
	// Ended carries the act that ended the session on an ErrorSessionEnded, so
	// the caller can be told who/when/why.
	Ended *SessionEnd
}

func (*ApplicationError) Error

func (e *ApplicationError) Error() string

func (*ApplicationError) Unwrap

func (e *ApplicationError) Unwrap() error

type ApplicationOptions

type ApplicationOptions struct {
	Access      AccessResolver
	Sessions    SessionStore
	StagedBlobs StagedBlobStore
	Clock       Clock
	// PrepareSearch runs once after authorized semantic-search snapshots are
	// selected. nil preserves SyncMode preparation. Returning nil claims no
	// coverage; SDD reads publication afterwards. Callback failures propagate.
	PrepareSearch func(context.Context, SearchTarget) error
}

ApplicationOptions composes the application. Sessions and staged blobs are scaffolding of the composition as a whole, keyed by session ID and namespaced by subject, not of any project (d-cpt-yjc); a nil Clock is the system clock.

type AppliedMutation

type AppliedMutation struct {
	Project  ProjectID
	BatchID  string
	Revision string
	Batch    MutationBatch
}

func (AppliedMutation) AffectedEntryIDs

func (m AppliedMutation) AffectedEntryIDs() ([]string, error)

AffectedEntryIDs returns entry selections for post-write discovery, including deleted documents and attachment owners. It does not establish durable source: queue discovery only after the consumer can reacquire its exact finalized revision and attachments. An empty result means no discovery job.

Example
package main

import (
	"context"
	"fmt"

	sdd "github.com/networkteam/sdd/pkg/application"
)

type indexingDiscoveryFinalizer struct {
	retainFinalizedSource func(context.Context, sdd.AppliedMutation) (string, error)
	enqueueDiscovery      func(context.Context, sdd.ProjectID, string, []string) error
}

func (indexingDiscoveryFinalizer) Name() string { return "index-discovery" }
func (f indexingDiscoveryFinalizer) Finalize(ctx context.Context, mutation sdd.AppliedMutation) error {
	ids, err := mutation.AffectedEntryIDs()
	if err != nil {
		return err
	}
	if len(ids) == 0 {
		return nil
	}
	revision, err := f.retainFinalizedSource(ctx, mutation)
	if err != nil {
		return err
	}
	if revision == "" {
		return fmt.Errorf("finalized source revision is required")
	}
	return f.enqueueDiscovery(ctx, mutation.Project, revision, ids)
}

func main() {
	mutation := sdd.AppliedMutation{Project: "example", Revision: "workspace-revision", Batch: sdd.MutationBatch{
		Attachments: []sdd.AttachmentMaterialization{{LogicalPath: "2026/01/01-100000-s-tac-aaa/evidence.md"}},
	}}
	// The consumer's durable write/recovery protocol makes these effects
	// idempotent and retries a crash between source finalization and enqueueing.
	finalizer := indexingDiscoveryFinalizer{
		retainFinalizedSource: func(context.Context, sdd.AppliedMutation) (string, error) { return "retained-git-revision", nil },
		enqueueDiscovery: func(_ context.Context, project sdd.ProjectID, revision string, ids []string) error {
			fmt.Println(project, revision, ids)
			return nil
		},
	}
	if err := finalizer.Finalize(context.Background(), mutation); err != nil {
		panic(err)
	}
}
Output:
example retained-git-revision [20260101-100000-s-tac-aaa]

type ApplyResult

type ApplyResult struct {
	State    ApplyState
	Revision string
}

type ApplyState

type ApplyState string
const (
	MutationNotApplied ApplyState = "not_applied"
	MutationApplied    ApplyState = "applied"
	MutationUnknown    ApplyState = "unknown"
)

type Attachment

type Attachment struct {
	Subject       string
	ClientName    string
	ClientVersion string
	LastActivity  time.Time
}

Attachment is the stamp of the client that last attached to the session and when the session was last acted on. It is a record, not a lock: the handle is the capability, integrity comes from CAS on append, and staleness is derived from LastActivity (d-cpt-aen).

type AttachmentMaterialization

type AttachmentMaterialization struct {
	BlobID      string
	Digest      BlobDigest
	Size        int64
	SourceName  string
	LogicalPath string
}

type AttachmentPage

type AttachmentPage struct {
	// LocalPath is an optional absolute path supplied by the attachment source
	// for clients sharing its filesystem. It does not extend source retention.
	LocalPath  string
	Filename   string
	Content    []byte
	Offset     int64
	NextOffset int64
	TotalSize  int64
	More       bool
	Digest     BlobDigest
}

func PageAttachment

func PageAttachment(fsys fs.FS, graphDir, entryID, filename string, offset int64, maxBytes int) (AttachmentPage, error)

PageAttachment reads one page of an entry's attachment from a graph directory. An empty filename selects the entry's only attachment and fails when there are several. A caller's mistake — bad range, bad name, no attachment to infer — is an ApplicationError with ErrorInvalidArgument; a missing entry directory or file surfaces as the filesystem's fs.ErrNotExist. Paths never leave graphDir: the filename must be a bare name and the resulting path must be valid for fs.FS. Locking and recovery around the read belong to the store that owns the directory.

type AttachmentPageReader

type AttachmentPageReader interface {
	ReadAttachmentPage(context.Context, string, string, int64, int) (AttachmentPage, error)
}

AttachmentPageReader reads attachment bytes from one acquired source.

type Author

type Author struct {
	Name  string
	Email string
}

type BlobDigest

type BlobDigest struct {
	Algorithm string
	Value     string
}

type BranchValidator

type BranchValidator interface {
	ValidateBranch(context.Context, MutationTarget) error
}

BranchValidator resolves branch authority without opening graph adapters or finalizers. It is the declare-time half of TargetAcquirer: local compositions use the same live checkout rule for both.

type BranchValidatorFunc

type BranchValidatorFunc func(context.Context, MutationTarget) error

BranchValidatorFunc adapts a function to BranchValidator.

func (BranchValidatorFunc) ValidateBranch

func (f BranchValidatorFunc) ValidateBranch(ctx context.Context, target MutationTarget) error

type CanonicalChunk

type CanonicalChunk = types.CanonicalChunk

type ChooserKind

type ChooserKind string

ChooserKind classifies who answers a pending chooser, mirrored from the engine for the served workflow response.

type Clock

type Clock interface{ Now() time.Time }

Clock is the application's time source, replaceable for tests.

type ClockFunc

type ClockFunc func() time.Time

ClockFunc adapts a function to Clock.

func (ClockFunc) Now

func (f ClockFunc) Now() time.Time

type CollectSessionsCmd

type CollectSessionsCmd struct {
	Retention time.Duration
	Limit     int
	After     SessionID
}

CollectSessionsCmd asks for one reclamation pass over the composition's session store. Retention is how long an ended session is kept; zero means remove as soon as it has ended. Limit bounds the page one pass processes (zero: everything) and After is the cursor a previous pass returned as Next, so the sweep converges over repeated calls instead of loading every session.

type CollectSessionsResult

type CollectSessionsResult struct {
	RemovedSessions []SessionID
	RemovedStaged   []SessionRef
	DrainedIntents  int
	Skipped         []SessionID
	Next            SessionID
}

CollectSessionsResult reports what one pass did. Nothing here is actionable — the pass either removed something or deliberately left it, and the skips say which sessions it could not read so a caller can log them. Next is where the pass stopped: pass it back as After, and stop when it is empty.

type CreateEntryResult

type CreateEntryResult struct {
	Project  ProjectRef
	Binding  SessionBinding
	EntryID  string
	Summary  string
	Findings []Finding
}

type DiscoverSearchEntriesQuery

type DiscoverSearchEntriesQuery struct {
	// The caller owns the lease and releases it after consuming the iterator.
	Source *AcquiredSnapshot
	Cursor SearchDiscoveryCursor
	// EntryIDs selects canonical full IDs. Nil means all; nonnil empty is invalid.
	EntryIDs []string
}

type DocumentChange

type DocumentChange struct {
	LogicalPath    string
	Document       *EntryDocument
	CanonicalBytes []byte
	Delete         bool
}

DocumentChange is storage-neutral. CanonicalBytes are rendered once by SDD; Document is present when the logical artifact has structured entry form.

type DocumentIssue

type DocumentIssue struct {
	LogicalPath string
	Message     string
}

DocumentIssue names one document a store could not decode: its logical path and the decode error message.

type EntryDocument

type EntryDocument struct {
	LogicalPath string
	Frontmatter map[string]any
	Body        string
	Attachments []string
}

EntryDocument is the storage-neutral form of an entry. Frontmatter carries the canonical graph schema as structured values; SDD validates and normalizes it before constructing a Snapshot.

type EntryDraft

type EntryDraft struct {
	Target            MutationTarget
	Kind              string
	Layer             string
	Intent            string
	Body              string
	Refs              []EntryRef
	Closes            []string
	Supersedes        []string
	Participants      []string
	Confidence        string
	Topics            []string
	Index             *FactIndex
	AttachmentHandles []string
	// Canonical and Aliases carry a kind: actor signal's identity; Actor carries
	// a kind: role decision's bound actor canonical; Class carries a
	// kind: procedure decision's execution role. Mirrors the CLI-side
	// NewEntryCmd fields — a value on the wrong kind is a blocking finding at
	// the construction boundary.
	Canonical string
	Aliases   []string
	Actor     string
	Class     string
	// ProcedureSpec carries a kind: procedure decision's workflow declaration
	// as one structured document — {params?, state?, steps, framing?} —
	// converted strictly at draft-to-entry assembly. Interpretation stays
	// with the engine.
	ProcedureSpec map[string]any
	// FocusActors, FocusWhen, and Involvement carry a kind: focus decision's
	// advances list and its focus-level defaults. Mirrors the CLI-side
	// NewEntryCmd fields — ignored on other kinds, written onto the entry so
	// the model-layer validator sees the required involvement frontmatter.
	FocusActors   []string
	FocusWhen     *types.FocusWhen
	Involvement   []types.Involvement
	SkipPreflight bool
}

type EntryRef

type EntryRef struct {
	ID   string
	Kind string
	Desc string
}

type ErrorCode

type ErrorCode string
const (
	ErrorAuthenticationRequired ErrorCode = "authentication_required"
	ErrorInvalidArgument        ErrorCode = "invalid_argument"
	ErrorProjectRequired        ErrorCode = "project_required"
	ErrorProjectUnavailable     ErrorCode = "project_unavailable"
	ErrorActionRequired         ErrorCode = "action_required"
	ErrorReadDenied             ErrorCode = "read_denied"
	ErrorWriteDenied            ErrorCode = "write_denied"
	ErrorBranchUnavailable      ErrorCode = "branch_unavailable"
	ErrorSessionOwnership       ErrorCode = "session_ownership_mismatch"
	ErrorSessionConflict        ErrorCode = "session_conflict"
	ErrorSessionEnded           ErrorCode = "session_ended"
	ErrorGraphConflict          ErrorCode = "graph_conflict"
	ErrorMigrationRequired      ErrorCode = "migration_required"
	ErrorRecoveryRequired       ErrorCode = "recovery_required"
)

type FactIndex

type FactIndex struct {
	Title string `json:"title"`
	Topic string `json:"topic"`
}

type FactIndexRow

type FactIndexRow struct {
	ID    string
	Title string
	Topic string
}

FactIndexRow is the application-boundary shape of an indexed fact: plain, serializable strings only. Topic carries the canonical slash-joined form (e.g. "cli/view"). ID and Title match the template keys the user-dialogue procedure renders from the factIndex inject result.

type FinalizerOutcome

type FinalizerOutcome struct {
	Name      string
	Succeeded bool
	Message   string
}

type Finding

type Finding struct {
	Severity    string
	Category    string
	Observation string
}

type FixedTargetAcquirer

type FixedTargetAcquirer struct {
	Target     MutationTarget
	Graph      GraphStore
	Finalizers []MutationFinalizer
}

FixedTargetAcquirer is a small composition adapter for stores whose one configured runtime already represents a concrete target. It exact-matches the target and never interprets cwd.

func (FixedTargetAcquirer) Acquire

type GraphStore

type GraphStore interface {
	Current(context.Context) (*Snapshot, error)
	Apply(context.Context, string, MutationBatch, StagedBlobReader) (ApplyResult, error)
	Reconcile(context.Context, string, string) (ApplyResult, error)
	ReadAttachmentPage(context.Context, string, string, int64, int) (AttachmentPage, error)
}

GraphStore is the canonical graph authority: snapshot reads, atomic mutation, reconciliation, and canonical attachment bytes.

type GuideFinding

type GuideFinding struct {
	Reasoning string
	Axis      string
	Quote     string
	Repair    string
	Severity  string
}

GuideFinding mirrors query.GuideFinding at the API boundary.

type IndexNamespace

type IndexNamespace = types.IndexNamespace

type IndexSearchEntryCmd

type IndexSearchEntryCmd = command.IndexSearchEntryCmd

type IndexedChunk

type IndexedChunk = types.IndexedChunk

type InfoRequest

type InfoRequest struct {
	Branch            string
	BranchFromSession bool
}

InfoRequest selects current read authority when Branch is empty. A selected branch that cannot be acquired returns an error.

type InfoResult

type InfoResult struct {
	Project     ProjectRef
	Participant string
	Language    string
	Search      string
	Recovery    string
}

type MutationBatch

type MutationBatch struct {
	ID          string
	Digest      string
	Changes     []DocumentChange
	Attachments []AttachmentMaterialization
	Message     string
	Author      Author
}

type MutationFinalizer

type MutationFinalizer interface {
	Name() string
	Finalize(context.Context, AppliedMutation) error
}

MutationFinalizer is a named, idempotent post-apply effect. It cannot redefine or roll back the canonical MutationBatch.

type MutationResult

type MutationResult struct {
	Project ProjectRef
	Binding SessionBinding
}

type MutationTarget

type MutationTarget struct {
	Project ProjectID `json:"project"`
	Branch  string    `json:"branch"`
}

MutationTarget is the immutable canonical authority for one graph mutation. Project identifies the project; Branch names its logical write authority. Adapters resolve that authority to their storage.

func (MutationTarget) Validate

func (t MutationTarget) Validate(project ProjectID) error

type PreparedTransition

type PreparedTransition struct {
	Version uint32
	Target  MutationTarget
	// ExpectedGraphRevision is prepare-time provenance only. The apply CAS
	// operand is the freshly revalidated revision (see applyOnAcquired), so a
	// concurrent unrelated append merges cleanly instead of failing the pin.
	ExpectedGraphRevision string
	Batch                 MutationBatch
	// Staged keeps its persisted name so an in-flight intent stays replayable
	// across an upgrade.
	Staged  SessionRef `json:"BlobOwner"`
	BlobIDs []string
}

PreparedTransition is the storage-neutral write-gate output. It contains only pinned v1 facts; adapters never reconstruct application intent.

type Principal

type Principal struct {
	Subject string
}

Principal is the stable identity resolved from a current request. It is binding and audit data, never cached authorization. The participant the subject appears as is resolved separately, per project (s-cpt-ny6).

type ProcedureListRequest

type ProcedureListRequest struct{}

type ProcedureListResult

type ProcedureListResult struct {
	Project    ProjectRef
	Procedures string
}

type ProjectAction

type ProjectAction struct {
	ID          string
	DisplayName string
	State       ProjectState
	ActionURL   string
	Reason      string
}

type ProjectConfig

type ProjectConfig struct {
	RepoID        string
	Dependencies  []string
	DefaultBranch string
	Language      string
	// GraphDir is repository-relative, .sdd/graph when the file leaves it unset.
	GraphDir string
}

ProjectConfig is the committed .sdd/config.yaml as a composition reads it: the identity, horizon, and layout facts every checkout shares, never the machine-local overlay.

func ReadProjectConfigFS

func ReadProjectConfigFS(fsys fs.FS) (ProjectConfig, error)

ReadProjectConfigFS reads the committed configuration from a repository root. It shares the parser with the CLI's own config resolution, so both read one schema; a composition that also needs the local overlay or the tool settings is holding a checkout of its own and uses the CLI.

type ProjectConfigDocument

type ProjectConfigDocument struct {
	LogicalPath string
	Fields      map[string]any
}

type ProjectID

type ProjectID = types.ProjectID

ProjectID is a composition's stable project identity.

type ProjectList

type ProjectList struct {
	Actions  []ProjectAction
	Projects []ProjectSummary
}

type ProjectRef

type ProjectRef struct {
	ID          ProjectID
	DisplayName string
}

ProjectRef is the only project identity exposed in project-scoped results.

type ProjectRuntime

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

ProjectRuntime owns the immutable ports and project configuration resolved for one application operation.

func NewProjectRuntime

func NewProjectRuntime(options ProjectRuntimeOptions) (*ProjectRuntime, error)

func (*ProjectRuntime) DiscoverSearchEntries

DiscoverSearchEntries hashes at most the current entry and never prepares chunks. Stop iteration to stop I/O. A returned error ends the sequence. Save each cursor atomically with enqueueing its missing descriptor. Cursors bind revision, index namespace and the canonical selected ID set. Reordered or duplicated IDs preserve scope; nil differs from every explicit selection. Malformed IDs fail; valid absent or ineligible IDs yield no requirement. Missing attachment bytes and other source/read failures remain errors.

func (*ProjectRuntime) IndexSearchEntry

func (r *ProjectRuntime) IndexSearchEntry(ctx context.Context, cmd IndexSearchEntryCmd) (err error)

IndexSearchEntry indexes exact retained source, never the current branch. Hosts authorize calls and retry failures. Already published work requires neither a source lease nor embedding, including after a lost acknowledgement.

func (*ProjectRuntime) Project

func (r *ProjectRuntime) Project() ProjectRef

func (*ProjectRuntime) ReconcileSearchIndex

func (r *ProjectRuntime) ReconcileSearchIndex(ctx context.Context, cmd ReconcileSearchIndexCmd) (err error)

ReconcileSearchIndex maintains the runtime's current graph index. The host authorizes the call; this operation does not resolve a request identity.

type ProjectRuntimeOptions

type ProjectRuntimeOptions struct {
	Project       ProjectRef
	DefaultBranch string
	Language      string
	Dependencies  []string
	Graph         GraphStore
	Targets       TargetAcquirer
	Branches      BranchValidator
	Recovery      RecoveryAuthorizer
	// Embedder and LLM are the two model dependencies, each a pkg/llm port
	// injected as an instance that arrives already composed — observed,
	// bounded, and rate-limited by the host's decorators. Routing, deadlines,
	// and recording are the host's composition duty (20260830-234501-d-cpt-q6n,
	// 20260902-114838-d-tac-cov); application contributes only the facts it
	// alone holds: the Purpose and the prompts, or the texts.
	Embedder    embed.Embedder
	SearchIndex SearchIndexStore
	LLM         llm.Runner
	Finalizers  []MutationFinalizer
	// ExcludeEmbeddedFromIndex mirrors the CLI's excludeEmbedded semantics for
	// the vector index: connected-repo runtimes set it so binary-shipped base
	// entries embed once per machine (in the base store) rather than once per
	// connected repo. The base runtime leaves it false — its store includes
	// embedded entries. The rule is applied identically at index and read time.
	ExcludeEmbeddedFromIndex bool
}

type ProjectState

type ProjectState string

ProjectState describes whether a listed project can be used immediately.

const (
	ProjectReady          ProjectState = "ready"
	ProjectActionRequired ProjectState = "action_required"
	ProjectUnavailable    ProjectState = "unavailable"
)

type ProjectSummary

type ProjectSummary struct {
	ProjectRef
	SourceID string
	CanRead  bool
	CanWrite bool
	State    ProjectState
}

type ReadAttachmentRequest

type ReadAttachmentRequest struct {
	Branch            string
	BranchFromSession bool
	EntryID           string
	Filename          string
	Offset            int64
	MaxBytes          int
}

type ReadAttachmentResult

type ReadAttachmentResult struct {
	Project   ProjectRef
	Page      AttachmentPage
	Available []string
}

type ReconcileSearchIndexCmd

type ReconcileSearchIndexCmd = command.ReconcileSearchIndexCmd

type RecoveryAccessRequest

type RecoveryAccessRequest struct {
	Actor           Principal
	Target          MutationTarget
	Verb            RecoveryVerb
	OriginalSubject string
	OriginalSession SessionID
}

type RecoveryAuthorizer

type RecoveryAuthorizer interface {
	AuthorizeRecovery(context.Context, RecoveryAccessRequest) error
}

type RecoveryAuthorizerFunc

type RecoveryAuthorizerFunc func(context.Context, RecoveryAccessRequest) error

func (RecoveryAuthorizerFunc) AuthorizeRecovery

func (f RecoveryAuthorizerFunc) AuthorizeRecovery(ctx context.Context, request RecoveryAccessRequest) error

type RecoveryItem

type RecoveryItem struct {
	Session         SessionID
	MutationID      string
	Digest          string
	Target          MutationTarget
	OriginalSubject string
	State           RecoveryState
	// Reason qualifies State: what delivery waits on while pending, or which
	// decision ended it while abandoned. Empty when delivered.
	Reason RecoveryReason
	// Recovered records that recovery machinery touched this mutation — a
	// reconciliation or a verb. It is provenance, not state: a recovered write is
	// delivered exactly like one that never needed help.
	Recovered        bool
	LegacyUnroutable bool
	EntryIDs         []string
	LastEvidence     string
	// Cause is the terminal's structured cause (e.g. graph-contention for an
	// engine-recorded discard), empty for participant decisions and open items.
	Cause string
}

func (RecoveryItem) Actionable

func (i RecoveryItem) Actionable() bool

Actionable reports whether this item awaits a recovery decision. It is derived from State rather than stored beside it, so the two cannot disagree.

type RecoveryList

type RecoveryList struct {
	Project ProjectRef
	Items   []RecoveryItem
}

type RecoveryReason

type RecoveryReason string

RecoveryReason qualifies a state that does not explain itself: what delivery is waiting on, or which decision ended it.

const (
	RecoveryReasonOutcomeUnknown   RecoveryReason = "outcome-unknown"
	RecoveryReasonNotApplied       RecoveryReason = "not-applied"
	RecoveryReasonFinalizationOwed RecoveryReason = "finalization-owed"
	RecoveryReasonDiscarded        RecoveryReason = "discarded"
	RecoveryReasonAbandonedUnknown RecoveryReason = "abandoned-unknown"
)

type RecoveryReconcileRequest

type RecoveryReconcileRequest struct {
	Session    SessionID
	MutationID string
}

type RecoveryRequest

type RecoveryRequest struct {
	Session    SessionID
	MutationID string
	Verb       RecoveryVerb
	Reason     string
	Target     MutationTarget
}

type RecoveryResult

type RecoveryResult struct {
	Project    ProjectRef
	Item       RecoveryItem
	Transition TransitionResult
}

type RecoveryState

type RecoveryState string
const (
	// RecoveryDelivered means the write reached its desired state: the batch
	// applied and finalization is proven. Nothing is owed.
	RecoveryDelivered RecoveryState = "delivered"
	// RecoveryPending means delivery is not proven yet. Pending is exactly the
	// actionable condition, and Reason names what is owed.
	RecoveryPending RecoveryState = "pending"
	// RecoveryAbandoned means a participant decided to stop pursuing delivery.
	// Reason names the decision.
	RecoveryAbandoned RecoveryState = "abandoned"
)

State answers one question — has delivery been reached — so it carries the two durable conditions of the delivery contract plus the one outcome that is a participant's decision rather than a delivery result.

type RecoveryVerb

type RecoveryVerb string

RecoveryVerb is deliberately finer-grained than write access. Runtime compositions authorize each recovery action and the nonterminal reconcile refresh afresh.

const (
	RecoveryReconcile      RecoveryVerb = "reconcile"
	RecoveryApply          RecoveryVerb = "apply"
	RecoveryDiscard        RecoveryVerb = "discard"
	RecoveryFinalizeRetry  RecoveryVerb = "finalize-retry"
	RecoveryAbandonUnknown RecoveryVerb = "abandon-unknown"
	RecoveryBindTarget     RecoveryVerb = "bind-target"
)

type RegistryFunction

type RegistryFunction struct {
	Name   string
	Class  string
	Doc    string
	Reads  []string
	Writes []string
}

func WorkflowRegistryDocs

func WorkflowRegistryDocs(class string) ([]RegistryFunction, error)

type RequestIdentity

type RequestIdentity struct {
	Subject    string
	Scopes     []string
	Attributes map[string]any
}

RequestIdentity is current-request authentication material supplied by a transport composition. SDD treats Subject as opaque and does not interpret application-specific roles.

type ScoredChunkHit

type ScoredChunkHit = types.ScoredChunkHit

type SearchCoverage

type SearchCoverage struct {
	Project   ProjectID `json:"project"`
	Revision  string    `json:"revision"`
	Required  int       `json:"required"`
	Published int       `json:"published"`
	Complete  bool      `json:"complete"`
}

SearchCoverage reports SDD's post-preparation publication read for one fixed project snapshot. It contains no consumer queue or retry state.

type SearchDiscoveryCursor

type SearchDiscoveryCursor = query.SearchDiscoveryCursor

type SearchEntryDescriptor

type SearchEntryDescriptor = types.SearchEntryDescriptor

type SearchEntryRequirement

type SearchEntryRequirement = query.SearchEntryRequirement

type SearchEntryVersion

type SearchEntryVersion = types.SearchEntryVersion

type SearchIndexEntryManifest

type SearchIndexEntryManifest interface {
	IndexedEntries(context.Context, IndexNamespace) ([]StoredEntryRef, error)
}

SearchIndexEntryManifest is an optional capability a persistent store implements so the application can reconcile on entry presence (monotonic accumulation of immutable-entry chunks) instead of chunk-identity comparison. A store that does not implement it falls back to the compatibility reconciliation path in vector search.

type SearchIndexEntryStore

type SearchIndexEntryStore interface {
	EntryPublished(context.Context, SearchEntryVersion) (bool, error)
	PublishEntry(context.Context, SearchEntryVersion, []IndexedChunk) error
}

SearchIndexEntryStore publishes complete entry versions, including versions with no chunks. Unpublished chunks must be invisible to both EntryPublished and Nearest. Publication is atomic, durable on success, and idempotent under concurrent calls. A returned error may have committed; callers recheck. Implementations validate all identities and vectors before publication.

type SearchIndexStoreFuncs

type SearchIndexStoreFuncs struct {
	ManifestFunc  func(context.Context, IndexNamespace) ([]StoredChunkRef, error)
	ReconcileFunc func(context.Context, IndexNamespace, string, []IndexedChunk, []string) error
	NearestFunc   func(context.Context, []IndexNamespace, []float32, int) ([]ScoredChunkHit, error)
}

SearchIndexStoreFuncs adapts an index implementation while SDD retains chunking, embedding input, reconciliation decisions, filtering, and ranking.

func (SearchIndexStoreFuncs) Manifest

func (f SearchIndexStoreFuncs) Manifest(ctx context.Context, namespace IndexNamespace) ([]StoredChunkRef, error)

func (SearchIndexStoreFuncs) Nearest

func (f SearchIndexStoreFuncs) Nearest(ctx context.Context, namespaces []IndexNamespace, vector []float32, limit int) ([]ScoredChunkHit, error)

func (SearchIndexStoreFuncs) Reconcile

func (f SearchIndexStoreFuncs) Reconcile(ctx context.Context, namespace IndexNamespace, revision string, upserts []IndexedChunk, deletes []string) error

type SearchRequest

type SearchRequest struct {
	// IncludesRevision asks the source to observe a revision containing this write.
	IncludesRevision string
	// SyncMode is required, including for text-only requests.
	SyncMode          SearchSyncMode
	Terms             []string
	Phrase            string
	Branch            string
	BranchFromSession bool
	Type              string
	Layer             string
	Kind              string
	IncludeSuperseded bool
	Limit             int
	MaxCitations      int
	Repos             []string
	AllRepos          bool
}

type SearchResult

type SearchResult struct {
	Coverage []SearchCoverage
	Notice   string
	Project  ProjectRef
	Results  string
	EntryIDs []string
}

type SearchSyncMode

type SearchSyncMode = query.SearchSyncMode

type SearchTarget

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

SearchTarget is the fixed, authorized selection passed to PrepareSearch. Its read capabilities expire when Search returns; retain descriptors, not this value, for durable jobs. Source retention remains the consumer's duty.

Example (ExternalConsumer)
package main

import (
	"context"

	sdd "github.com/networkteam/sdd/pkg/application"
)

func main() {
	// These functions belong to the consumer's durable scheduling protocol.
	var enqueue func(context.Context, sdd.SearchEntryDescriptor, sdd.SearchDiscoveryCursor) error
	var waitWithinBudget func(context.Context, []sdd.SearchTargetProject) error
	options := sdd.ApplicationOptions{
		PrepareSearch: func(ctx context.Context, target sdd.SearchTarget) error {
			for requirement, err := range target.Entries(ctx) {
				if err != nil {
					return err
				}
				if requirement.Published {
					continue
				}
				if err := enqueue(ctx, requirement.Entry, requirement.Cursor); err != nil {
					return err
				}
			}
			// Ordinary budget expiry returns nil. Parent cancellation and preparation
			// failures return errors. SDD determines coverage after this returns.
			return waitWithinBudget(ctx, target.Projects())
		},
	}
	_ = options
}
Example (Local)
package main

import (
	"context"

	sdd "github.com/networkteam/sdd/pkg/application"
)

func main() {
	// The composition root supplies runtimes for the projects it authorizes.
	var runtimes map[sdd.ProjectID]*sdd.ProjectRuntime
	options := sdd.ApplicationOptions{
		PrepareSearch: func(ctx context.Context, target sdd.SearchTarget) error {
			for requirement, err := range target.Entries(ctx) {
				if err != nil {
					return err
				}
				if requirement.Published {
					continue
				}
				runtime := runtimes[requirement.Entry.Version.Namespace.Project]
				if err := runtime.IndexSearchEntry(ctx, sdd.IndexSearchEntryCmd{Entry: requirement.Entry}); err != nil {
					return err
				}
			}
			return nil
		},
	}
	_ = options // Pass to NewApplication with the other required capabilities.
}

func (SearchTarget) Entries

Entries lazily derives required versions, including attachments, and reads published presence. Errors terminate iteration and must be propagated by preparation callbacks. A Published hint may advance after it was yielded; SDD always reads publication again after preparation. Partial iteration does not narrow the target whose coverage SDD will check.

func (SearchTarget) Projects

func (t SearchTarget) Projects() []SearchTargetProject

func (SearchTarget) SyncMode

func (t SearchTarget) SyncMode() SearchSyncMode

type SearchTargetProject

type SearchTargetProject struct {
	Project  ProjectID
	Revision string
}

type SessionAccessRequest

type SessionAccessRequest struct {
	Actor   Principal
	Owner   Principal
	Session SessionID
	Project ProjectID
}

SessionAccessRequest carries what a continuation policy needs to answer whether Actor may continue the session Owner opened. The application has just loaded the session; the composition is not asked to load it again. It names no verb: continuing a dialogue is one act (d-cpt-yjc).

type SessionAppend

type SessionAppend struct {
	Metadata *SessionMetadata
	Events   []StoredEvent
}

type SessionBinding

type SessionBinding struct {
	SessionID SessionID
	Subject   string
	Project   ProjectID
	Version   uint64
}

SessionBinding is a loaded session's write token: identity plus the version it last observed. Append CAS on the version is the sole integrity mechanism; possession of the handle is the whole authorization (d-cpt-aen).

type SessionEnd

type SessionEnd struct {
	Act     SessionEndAct
	EndedAt time.Time
	Reason  string `json:",omitempty"`
}

SessionEnd records the participant act that ended a session, written once and never revised. Reason records the abandon note, so a displaced writer's next call can be told why. Who ended it is the session's own participant; the ending client's stamp is transport and does not enter the durable record.

type SessionEndAct

type SessionEndAct string

SessionEndAct is the closed set of participant acts that end a dialogue.

const (
	SessionConcluded SessionEndAct = "concluded"
	SessionAbandoned SessionEndAct = "abandoned"
)

type SessionFilter

type SessionFilter struct {
	Subject     string
	Project     ProjectID
	EndedBefore *time.Time
	After       SessionID
	Limit       int
}

SessionFilter selects the sessions List returns. Subject and Project match metadata; EndedBefore selects sessions whose recorded ending lies before the instant, from metadata alone. After and Limit page the result in session-ID order — IDs are time-prefixed and unique, so ID order is a cursor every store honors; a zero Limit means every match.

func (SessionFilter) Matches

func (f SessionFilter) Matches(m SessionMetadata) bool

Matches reports whether metadata passes the filter's selection — the part a store applies per session, leaving After and Limit to its enumeration.

type SessionID

type SessionID string

type SessionMetadata

type SessionMetadata struct {
	ID          SessionID
	Subject     string
	Project     ProjectID
	Participant string
	Label       string
	// Branch is the session's explicit branch binding. Empty means unbound;
	// compositions without a branch concept leave it empty.
	Branch     string `json:"branch,omitempty"`
	Attachment *Attachment
	// Ended is the session's single terminal record. Its presence is what makes
	// a session ended; nothing else about a session ends it (d-cpt-rw7).
	Ended     *SessionEnd `json:",omitempty"`
	UpdatedAt time.Time
}

SessionMetadata is structured routing and ownership data. Dialogue events remain opaque to the store. The type itself is the metadata contract: its evolution is the Go type's own, and how a store survives that is the adapter's concern — schema migrations, or format discrimination in its persisted record (d-tac-8js).

func (*SessionMetadata) UnmarshalJSON

func (m *SessionMetadata) UnmarshalJSON(data []byte) error

UnmarshalJSON decodes stored metadata, recovering the terminal record from the attachment history superseded shapes carried it in. Decoding stays lenient about every other field in both directions (d-cpt-i2x).

type SessionPage

type SessionPage struct {
	Sessions []StoredSession
	Next     SessionID
}

SessionPage is one List result. Next is the cursor to continue from, empty once the store is exhausted. A page may hold fewer sessions than Limit, or none, while Next is still set: the store stopped at Limit before the filter admitted enough, so a consumer loops until Next is empty rather than reading exhaustion off the page length.

type SessionRef

type SessionRef struct {
	Subject string
	Session SessionID
}

SessionRef addresses one session inside a subject's namespace. Staged blobs are scoped to a session, so this is what names their area — there is no owner entity, just the two fields that identify whose scaffolding this is.

func (SessionRef) AtOrBefore

func (r SessionRef) AtOrBefore(cursor SessionRef) bool

AtOrBefore reports whether r lies at or before cursor in enumeration order, which is what a paged StagedSessions skips. A cursor naming only a session covers every subject of that session.

func (SessionRef) Compare

func (r SessionRef) Compare(other SessionRef) int

Compare orders refs by session, then subject — the order StagedSessions enumerates in, so a session-ID cursor from the session store lines up.

type SessionStore

SessionStore persists structured metadata plus ordered opaque events. Append is the sole mutation primitive and must compare ExpectedVersion atomically.

Compositions must not run mixed engine versions against one session store: metadata carries no version guard (d-tac-8js), so an older engine reading metadata a newer one wrote is undetected there — only a session the newer engine actually advanced fails closed, through StoredEvent.CodecVersion.

List is also the enumeration collection sweeps over, paged by the filter's cursor so a sweep converges over repeated calls instead of loading every session, and Delete is what makes sweeps possible against any implementation rather than only the local one. Delete must be idempotent: removing a session that is already gone is success, since two sweeps may derive the same target set.

type ShowRequest

type ShowRequest struct {
	IDs               []string
	UpDepth           int
	DownDepth         int
	Branch            string
	BranchFromSession bool
	// Budget bounds each direction's chain expansion on the serve path; the
	// zero value is unbounded — explicit pulls arrive complete (d-tac-rzi).
	Budget types.ShowTreeBudget
}

type ShowResult

type ShowResult struct {
	Project    ProjectRef
	Entries    string
	FullIDs    []string
	SummaryIDs []string
}

type Snapshot

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

Snapshot is an immutable, validated SDD graph snapshot. Its indexed model remains private; structured and filesystem stores both enter through SnapshotData. The finder is the shared read authority over the snapshot's graph; the private graph field mirrors it so in-package seams (write-side resolution, the engine graph provider) keep reading a *model.Graph directly.

func BuildSnapshot

func BuildSnapshot(_ context.Context, data SnapshotData) (*Snapshot, error)

BuildSnapshot is the single in-memory graph construction path. It adapts the canonical documents into a storage-neutral source and hands them to the shared GraphFinder, which applies the one semantic gate (parse, embedded-base merge, partial-read load issues) and holds the resulting graph. A malformed entry document no longer aborts the build — it surfaces as a load issue on the snapshot's graph (Snapshot.Health). Structural failures stay hard: missing project/revision, a malformed logical path, a WIP document outside wip/, and base-entry assembly.

func LoadSnapshotFS

func LoadSnapshotFS(ctx context.Context, project ProjectID, revision string, fsys fs.FS, graphDir string) (*Snapshot, error)

LoadSnapshotFS parses canonical filesystem documents into SnapshotData and delegates all validation and indexing to BuildSnapshot.

func (*Snapshot) Health

func (s *Snapshot) Health() types.GraphHealth

Health reports the snapshot graph's integrity problems — parse-failed (unreadable) documents and per-entry validation warnings — so external hosts can render graph health without reaching into the private model. A clean graph reports zero of each.

func (*Snapshot) Project

func (s *Snapshot) Project() ProjectID

func (*Snapshot) Revision

func (s *Snapshot) Revision() string

type SnapshotData

type SnapshotData struct {
	Project  ProjectID
	Revision string
	// Config retains stored document data only. Effective read configuration
	// comes exclusively from AcquiredSnapshot.Config or explicit runtime compatibility.
	Config  ProjectConfigDocument
	Entries []EntryDocument
	WIP     []WIPDocument
	// Unreadable records documents a store could not decode into structured
	// form — a file whose YAML frontmatter would not parse, for example. They
	// are carried as data rather than aborting the load: BuildSnapshot turns
	// each into a graph load issue surfaced through Snapshot.Health, so one
	// malformed file no longer makes an entire graph (and every session over
	// it) unopenable.
	Unreadable []DocumentIssue
}

SnapshotData contains canonical stored document facts, never derived graph indexes, status, or traversal state.

type SnapshotReadQuery

type SnapshotReadQuery struct {
	Branch           string
	ExactRevision    string
	IncludesRevision string
}

SnapshotReadQuery separates exact job input from causal search freshness. ExactRevision selects precisely that revision. IncludesRevision selects a branch revision containing the write, which may be newer. They are exclusive.

type SnapshotReader

type SnapshotReader interface {
	AcquireSnapshot(context.Context, SnapshotReadQuery) (*AcquiredSnapshot, error)
}

SnapshotReader is an optional GraphStore capability for pinned reads. Hosts retain exact revisions independently of lease lifetime for durable jobs. IncludesRevision is a causal guarantee, never lexical revision comparison. Readers must honor Branch or reject it; an empty branch selects their current authority. Target-scoped readers must validate nonempty branch requests.

type StagedBlob

type StagedBlob struct {
	ID        string
	Session   SessionRef
	Digest    BlobDigest
	Size      int64
	Filename  string
	CreatedAt time.Time
}

type StagedBlobReader

type StagedBlobReader interface {
	Open(context.Context, string) (io.ReadCloser, error)
}

StagedBlobReader limits Apply to the blobs named by its prepared batch.

type StagedBlobStore

StagedBlobStore owns immutable session-scoped scratch bytes and the retentions holding them. Nothing here is durable: a staged blob lives as long as its session does, and durability is earned only by a captured entry.

StagedSessions and DeleteStaged put reclamation inside the published contract, so a sweep enumerates staging areas and drops the ones whose session is gone through this interface rather than through local-only code. StagedSessions pages in SessionRef order after the cursor (SessionRef.AtOrBefore), a zero limit meaning every area. DeleteStaged must be idempotent, and removes a session's blobs together with its retentions.

type StagedSessionPage

type StagedSessionPage struct {
	Sessions []SessionRef
	Next     SessionRef
}

StagedSessionPage is one StagedSessions result. Next is the cursor to continue from, empty once the store is exhausted.

type StoredChunkRef

type StoredChunkRef = types.StoredChunkRef

type StoredEntryRef

type StoredEntryRef = types.StoredEntryRef

type StoredEvent

type StoredEvent struct {
	CodecVersion uint32
	Code         string
	Payload      json.RawMessage
}

type StoredSession

type StoredSession struct {
	Metadata SessionMetadata
	Version  uint64
	Events   []StoredEvent
}

type TargetAcquirer

type TargetAcquirer interface {
	Acquire(context.Context, MutationTarget) (*AcquiredTarget, error)
}

TargetAcquirer resolves a concrete mutation authority to short-lived, target-scoped graph and finalizer adapters. Implementations rediscover the target on every call; checkout paths are not durable intent.

type TargetAcquirerFunc

type TargetAcquirerFunc func(context.Context, MutationTarget) (*AcquiredTarget, error)

TargetAcquirerFunc adapts a function to TargetAcquirer.

func (TargetAcquirerFunc) Acquire

type TransitionResult

type TransitionResult struct {
	Project    ProjectRef
	Binding    SessionBinding
	Apply      ApplyResult
	Finalizers []FinalizerOutcome
}

type ValidationError

type ValidationError struct {
	Warnings []types.Warning
}

ValidationError reports that model.ValidateEntry rejected a draft at the write gate. It carries the structural warnings so the workflow gate can re-serve them as actionable findings — naming the violated rule and the field — and route the instance back to a step that can fix it, rather than wedging behind an opaque hard error (closes half of s-prc-g0j).

func (*ValidationError) Error

func (e *ValidationError) Error() string

type ViewRequest

type ViewRequest struct {
	Layout            string
	Branch            string
	BranchFromSession bool
	Repos             []string
	AllRepos          bool
	// Budget bounds the view's scaling parts on the serve path; the zero
	// value is unbounded — explicit pulls arrive complete (d-tac-rzi).
	Budget types.ViewBudget
	// OmitRecovery skips the appended recovery notices — for injected lanes
	// whose session already carries them via sessionInfo, so a pending
	// recovery is served once, not once per lane.
	OmitRecovery bool
}

type ViewResult

type ViewResult struct {
	Project  ProjectRef
	Sections string
	// MatchedCount is the total primary units the layout produced across the
	// local graph and any queried dependency repos. Zero means the pipeline
	// matched nothing — surfaces distinguish an empty result from a failure
	// (an agent over MCP cannot tell a blank string from a broken call).
	MatchedCount int
	// KnownParticipants names the graph's canonical participants, populated
	// only when the result was empty and the layout carried a participant
	// filter — so an empty participant() view can say what names exist rather
	// than leaving an exact-match miss indistinguishable from no data.
	KnownParticipants []string
}

type WIPDocument

type WIPDocument struct {
	LogicalPath string
	Content     string
}

type WorkflowAbandonResult

type WorkflowAbandonResult struct {
	Abandoned   bool
	Session     SessionID
	Label       string
	Discarded   []WorkflowInstanceSummary
	HeldMarkers []string
	Base        *WorkflowServe
}

type WorkflowAdvanceRequest

type WorkflowAdvanceRequest struct {
	Instance string
	Report   map[string]any
	Label    string
}

type WorkflowChooser

type WorkflowChooser struct {
	Chooser string
	Kind    ChooserKind
	Options []WorkflowChooserOption
}

type WorkflowChooserOption

type WorkflowChooserOption struct {
	Choice  string
	Collect []string
}

type WorkflowInstanceSummary

type WorkflowInstanceSummary struct {
	Instance  string
	Procedure string
	Step      string
}

type WorkflowOpenRequest

type WorkflowOpenRequest struct {
	ClientName    string
	ClientVersion string
	Shell         string
	Label         string
}

type WorkflowParkResult

type WorkflowParkResult struct {
	Instance  string
	Procedure string
	Step      string
	Base      *WorkflowServe
}

type WorkflowResumeRequest

type WorkflowResumeRequest struct {
	SessionID     SessionID
	ClientName    string
	ClientVersion string
}

WorkflowResumeRequest names the session to load by its ID and the client loading it. Possession of the ID within the principal's scope is the whole authorization (d-cpt-aen).

type WorkflowResumeResult

type WorkflowResumeResult struct {
	Session      SessionID
	Participant  string
	Label        string
	Branch       string
	Open         []WorkflowServe
	Instructions string
}

type WorkflowServe

type WorkflowServe struct {
	Session SessionID
	// Project is the project the served instance targets — the home project
	// unless the move was started in a dependency (d-cpt-yjc).
	Project        ProjectID
	Branch         string
	Instance       string
	Procedure      string
	Status         string
	Step           string
	Goal           string
	Instructions   string
	Missing        []string
	ReportSchema   map[string]any
	PendingChooser *WorkflowChooser
	Execution      string
	Produced       map[string]any
	Diagnostics    []string
	// InstructionLanes are the unit's rendered lanes in order — what the MCP
	// layer dedups independently; Instructions is their join plus diagnostics.
	InstructionLanes []types.ServeLane
	// Sizes is the engine's per-part byte accounting for this serve, read by
	// the serve-budget measurement (d-tac-qwc).
	Sizes []types.PartSize
	Base  *WorkflowServe
	// Collected is the instance's already-gathered param and state values,
	// projected only onto resume serves so a newly attached or reoriented
	// agent sees what this instance holds — the anchor, chosen scope, and
	// reported judgments that persist across a handover (d-cpt-0tm). Empty on
	// door, next, and base-junction serves, which stay unchanged.
	Collected map[string]any
}

func (*WorkflowServe) ComposeInstructions

func (s *WorkflowServe) ComposeInstructions(unitText string) string

ComposeInstructions joins host-recomposed unit text (e.g. the deduped lane subset) with this serve's diagnostics — the engine's one composition rule applied host-side.

func (*WorkflowServe) ReminderInstructions

func (s *WorkflowServe) ReminderInstructions() string

ReminderInstructions composes the short reminder used when a host has already served this instruction unit, while retaining any gate diagnostics. The stub assumes the caller still holds the earlier full text; if a context compaction dropped it, the breadcrumb names the one-shot escape so an amnesiac agent is not left following instructions it no longer has.

type WorkflowSession

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

WorkflowSession is a protocol-neutral, durable engine session. It stores no authorization proof: every operation receives the current request identity and resolves access again before touching project state.

func (*WorkflowSession) Abandon

func (w *WorkflowSession) Abandon(ctx context.Context, identity RequestIdentity, instance, reason string) (WorkflowAbandonResult, error)

func (*WorkflowSession) Advance

func (*WorkflowSession) BindBranch

func (w *WorkflowSession) BindBranch(ctx context.Context, identity RequestIdentity, branch string, clear bool) error

BindBranch changes the durable session-level branch declaration. Setting a binding resolves the branch against the runtime's live branch capability before the CAS append; clearing is store-only and works without that capability.

func (*WorkflowSession) Binding

func (w *WorkflowSession) Binding() SessionBinding

func (*WorkflowSession) Branch

func (w *WorkflowSession) Branch() string

func (*WorkflowSession) EditStagedAttachment

func (w *WorkflowSession) EditStagedAttachment(ctx context.Context, identity RequestIdentity, handle string, pairs []types.PatchPair) error

EditStagedAttachment applies ordered exact search-replace pairs to a staged file addressed by its handle and stages the result under the same handle, so a small correction costs neither a full re-stage nor a full re-read (20260826-120330-d-tac-8f8). Atomic: a failing pair names itself and the staged file stays unchanged.

func (*WorkflowSession) Finished

func (w *WorkflowSession) Finished() bool

Finished reports whether this dialogue is over: the shell has left running, which is the act that wrote the terminal record. A finished session is spent — the door opens a new one rather than re-serving it, and no move may carry it on.

func (*WorkflowSession) Framing

func (w *WorkflowSession) Framing(ctx context.Context, identity RequestIdentity) ([]string, error)

Framing composes the session framing as ordered, independently dedupable blocks: the engine-supplied info block (participant, language, search modes) first, then one block per declared shell lane, rendered through the injection mechanism. Returning the lanes as separate blocks — not one joined string — lets the host dedup each on its own, so a graph write that changes only the recent-moves lane re-serves that lane alone, never the stable aspirations or directives (I6, A1). A shell with no declared lanes yields the info block alone; there is no Go-constant fallback.

func (*WorkflowSession) ID

func (w *WorkflowSession) ID() SessionID

func (*WorkflowSession) IsShell

func (w *WorkflowSession) IsShell(instance string) bool

func (*WorkflowSession) LogRead

func (w *WorkflowSession) LogRead(ctx context.Context, identity RequestIdentity, tool string, full, summary []string) error

func (*WorkflowSession) OpenInstances

func (w *WorkflowSession) OpenInstances() []WorkflowInstanceSummary

func (*WorkflowSession) Park

func (w *WorkflowSession) Park(ctx context.Context, identity RequestIdentity, instance, note string) (WorkflowParkResult, error)

func (*WorkflowSession) Project

func (w *WorkflowSession) Project() ProjectID

func (*WorkflowSession) ReadScope

func (w *WorkflowSession) ReadScope(ctx context.Context, identity RequestIdentity, project ProjectID) (ProjectID, string, bool, error)

ReadScope resolves where a free read runs: the home project on the session's branch binding by default, or another project the session may work in — on that project's configured default, since the binding is a fact about the home checkout alone.

func (*WorkflowSession) ReadStagedAttachment

func (w *WorkflowSession) ReadStagedAttachment(ctx context.Context, identity RequestIdentity, handle string, offset int64, maxBytes int) (AttachmentPage, []string, error)

ReadStagedAttachment returns one bounded page of a staged file by handle, plus the session's staged handles as the discovery surface.

func (*WorkflowSession) RecordServed

func (w *WorkflowSession) RecordServed(ctx context.Context, identity RequestIdentity, hashes []string) error

RecordServed logs the content hashes of blocks just served in full. A finished session records nothing: its last serve is the one that ended it.

func (*WorkflowSession) Reorient

func (w *WorkflowSession) Reorient(ctx context.Context, identity RequestIdentity) error

Reorient records the consumer's request for the session's position and resets the served set, so the position re-serves in full.

func (*WorkflowSession) ServeAll

func (*WorkflowSession) ServeShell

func (w *WorkflowSession) ServeShell(ctx context.Context, identity RequestIdentity) (*WorkflowServe, error)

func (*WorkflowSession) ServedBefore

func (w *WorkflowSession) ServedBefore(hash string) bool

ServedBefore reports whether the session's consumer already holds a block with this content hash, derived from the session ledger (d-cpt-aen).

func (*WorkflowSession) StageAttachment

func (w *WorkflowSession) StageAttachment(ctx context.Context, identity RequestIdentity, filename string, content []byte) (string, error)

func (*WorkflowSession) StagedHandles

func (w *WorkflowSession) StagedHandles() []string

StagedHandles lists the session's staged file handles, sorted.

func (*WorkflowSession) Start

type WorkflowSessionSummary

type WorkflowSessionSummary struct {
	Session      SessionID
	Label        string
	Participant  string
	Branch       string
	Anchor       string
	Open         []WorkflowInstanceSummary
	LastActivity time.Time
	Attachment   *Attachment
	Active       bool
}

type WorkflowStartRequest

type WorkflowStartRequest struct {
	Canonical string
	Params    map[string]any
	Label     string
	Parent    string
	// Project pins the project the new instance targets. Empty leaves it to
	// the dispatching parent's project, else the home project.
	Project ProjectID
}

Directories

Path Synopsis
Package types holds the plain data types the public application surface shares with the internal packages that produce them.
Package types holds the plain data types the public application surface shares with the internal packages that produce them.

Jump to

Keyboard shortcuts

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