Documentation
¶
Overview ¶
Package postgres is the PostgreSQL store: it writes finalized records and their initial event, reads records back, runs the contract's text search, records injections, and (contract 1.6, with pgvector) writes and searches embeddings, all as the application role inside a transaction that first declared its bounds.
It runs against a schema the .NET migrator applied (ExperienceSchemaMigrator.MigrateAsync, then ApplyApplicationRolePrivilegesAsync with row-level security on, or the contract's two privileges files) and never creates or alters a schema object. The bounds, the search and the injection record are the contract's SQL files, embedded and run as written: only their @name placeholders are spelled the way the driver spells them ($1, $2, ...).
Index ¶
- Constants
- Variables
- func ContractSQL(name string) (string, error)
- func ContractStatements(name string) (map[string]string, error)
- type Beginner
- type Statement
- type Store
- func (s *Store) Close()
- func (s *Store) Get(ctx context.Context, bounds ae.AuthorizationBounds, scope ae.Scope, id ae.UUID) (*ae.ExperienceRecord, error)
- func (s *Store) InsertFinalized(ctx context.Context, bounds ae.AuthorizationBounds, ...) error
- func (s *Store) InsertRecord(ctx context.Context, bounds ae.AuthorizationBounds, ...) error
- func (s *Store) ListLessonRecords(ctx context.Context, bounds ae.AuthorizationBounds, scope ae.Scope, ...) (ae.LessonRecords, error)
- func (s *Store) RecordInjection(ctx context.Context, bounds ae.AuthorizationBounds, scope ae.Scope, ...) error
- func (s *Store) Search(ctx context.Context, bounds ae.AuthorizationBounds, ...) ([]ae.SearchRow, error)
- func (s *Store) SearchVectors(ctx context.Context, bounds ae.AuthorizationBounds, query ae.VectorQuery) (ae.VectorSearchResult, error)
- func (s *Store) WriteEmbedding(ctx context.Context, bounds ae.AuthorizationBounds, write ae.EmbeddingWrite) (ae.EmbeddingWriteResult, error)
Constants ¶
const ( DeclareBoundsSQL = "declare-bounds.sql" SearchSQL = "search-experience-text.sql" RecordInjectionSQL = "record-injection.sql" // WriteEmbeddingSQL and SearchVectorSQL are contract 1.6's: each holds several statements, each starting at its // "-- statement: <name>" line (ContractStatements). WriteEmbeddingSQL = "write-experience-embedding.sql" SearchVectorSQL = "search-experience-vector.sql" // ListLessonsSQL is contract 1.7's: four statements (page, page-with-statuses, page-without-injections, // page-without-injections-with-statuses), each starting at its "-- statement: <name>" line (ContractStatements). ListLessonsSQL = "list-experience-lessons.sql" )
The contract's SQL files, as this package embeds them.
Variables ¶
var ErrSealed = errors.New("the record is sealed (payload_version 2) and a contract 1.x port cannot read it")
ErrSealed is Get's answer for a record that exists in the scope but is sealed (payload_version 2, crypto-shredding), which a contract 1.x port cannot read. A record that does not exist is (nil, nil).
Functions ¶
func ContractSQL ¶
ContractSQL is one of the contract's SQL files, as this package embeds it (DeclareBoundsSQL, SearchSQL, RecordInjectionSQL). A test keeps the embedded copies identical to contract/sql/.
func ContractStatements ¶
ContractStatements are the statements of a contract SQL file that holds several, by name: each starts at its "-- statement: <name>" line and runs to the next one (or the end); the header before the first is not a statement.
Types ¶
type Beginner ¶
Beginner starts the transactions the store runs in. *pgxpool.Pool, *pgx.Conn and pgx.Tx (each transaction is then a savepoint inside it) all satisfy it. Every connection it hands out must be the application role's.
type Statement ¶
Statement is a SQL text with its @name placeholders numbered for the driver, and the parameter each number stands for.
func ToDriverPlaceholders ¶
ToDriverPlaceholders numbers the contract's @name placeholders as $1, $2, ... (one number per distinct name). Only code is rewritten: a -- or /* */ comment, a single-quoted literal (E” escapes included), a double-quoted identifier and a dollar-quoted string are kept as written, so a parameter named only inside one of them is never asked for. An @ followed by a letter or _ in code is always a placeholder, so PostgreSQL's prefix @ operator must be written with a space after it.
type Store ¶
type Store struct {
// contains filtered or unexported fields
}
Store keeps records in PostgreSQL through pgx. Each operation runs one transaction, begun on the connection source the host gave it. A Store is safe for concurrent use exactly when its source is: a *pgxpool.Pool is, a single *pgx.Conn or pgx.Tx is not (use it from one goroutine at a time).
func NewStore ¶
NewStore is a store that runs its transactions on db, which the host controls (and closes).
func Open ¶
Open is a store on a new connection pool for a connection string (as the application role). Close it with Close.
func (*Store) Close ¶
func (s *Store) Close()
Close closes the pool Open made. It does nothing for a store on a source the host gave NewStore.
func (*Store) Get ¶
func (s *Store) Get(ctx context.Context, bounds ae.AuthorizationBounds, scope ae.Scope, id ae.UUID) (*ae.ExperienceRecord, error)
Get reads one record in exactly scope (every scope field equal, an unset one unset), within bounds. It returns (nil, nil) when there is none (or it was deleted), and ErrSealed when it is sealed (payload_version 2, which a contract 1.x port cannot read).
func (*Store) InsertFinalized ¶
func (s *Store) InsertFinalized(ctx context.Context, bounds ae.AuthorizationBounds, finalized *ae.FinalizedExperience, actor string, recordedAt *time.Time) error
InsertFinalized writes a finalized record in its final state at revision 1, and the initial lifecycle event that took it there, in one transaction. actor is the authorizing principal; recordedAt defaults to now. The event must be the record's own (its experience ID, its status and its revision); anything else is refused before writing.
func (*Store) InsertRecord ¶
func (s *Store) InsertRecord(ctx context.Context, bounds ae.AuthorizationBounds, record *ae.ExperienceRecord) error
InsertRecord writes one record's row exactly as given, with no lifecycle event. It is for seeding and tests: a record this package finalizes is written with InsertFinalized, or it has no history.
func (*Store) ListLessonRecords ¶
func (s *Store) ListLessonRecords(ctx context.Context, bounds ae.AuthorizationBounds, scope ae.Scope, statuses []ae.ExperienceStatus, limit int) (ae.LessonRecords, error)
ListLessonRecords is the lessons view's read (contract 1.7): in one transaction that declared bounds, "page" (or "page-with-statuses") from sql/list-experience-lessons.sql, the scope's live records newest first, each with its last injection. When that fails with undefined_table (42P01) or insufficient_privilege (42501), the injections cannot be read: the transaction is rolled back, a new one declares the same bounds and runs "page-without-injections" (or "...-with-statuses"), and the answer says its injections are not tracked. Any other failure is the call's. A scope outside bounds is ErrAuthorizationDenied and runs no statement; a limit outside 1 to 500 and empty or undefined statuses are refused first. A sealed row (payload_version 2) is left out. It never writes.
func (*Store) RecordInjection ¶
func (s *Store) RecordInjection(ctx context.Context, bounds ae.AuthorizationBounds, scope ae.Scope, experienceIDs []ae.UUID, injectedAt ae.Instant) error
RecordInjection records that experienceIDs were injected in scope at injectedAt: it runs sql/record-injection.sql (contract 1.4) once, in one transaction that declared bounds. Only a record live in exactly scope gets a row (one borrowed through a grant, or in another scope, is skipped by the statement); the instant is truncated to whole microseconds and clamped to the database clock. It refuses more than agentexperience.MaxRecordInjectionIDs IDs or a nil one, and a scope outside the bounds (agentexperience.ErrAuthorizationDenied); otherwise a failure is the driver's (insufficient_privilege, 42501, until the role holds the grants of sql/application-role-privileges-1.4.sql). The agent integration calls it in the background, fail-open.
func (*Store) Search ¶
func (s *Store) Search(ctx context.Context, bounds ae.AuthorizationBounds, parameters ae.SearchParameters) ([]ae.SearchRow, error)
Search runs sql/search-experience-text.sql and returns its rows in the order returned, each relevance widened from real. A sealed row (payload_version 2) is skipped. Rank them with agentexperience.Rank.
func (*Store) SearchVectors ¶
func (s *Store) SearchVectors(ctx context.Context, bounds ae.AuthorizationBounds, query ae.VectorQuery) (ae.VectorSearchResult, error)
SearchVectors is the nearest-neighbour search (contract 1.6): in one transaction that declared bounds, "search" (or "search-excluding-model-authored") from sql/search-experience-vector.sql with {dim} the query vector's length and, when it returns no row, the matching compatibility probe. Hits come back nearest first, ties by experience ID, NaN last; a sealed row (payload_version 2) is skipped. The query is validated first (an error, and nothing runs).
func (*Store) WriteEmbedding ¶
func (s *Store) WriteEmbedding(ctx context.Context, bounds ae.AuthorizationBounds, write ae.EmbeddingWrite) (ae.EmbeddingWriteResult, error)
WriteEmbedding stores a record's embedding (contract 1.6): in one transaction that declared bounds, "write" from sql/write-experience-embedding.sql and, when it wrote no row, "probe-revision". Written; Stale with the record's current revision; Missing for an unknown ID, a record in another scope or an erased one. The write is validated first (an error, and nothing runs). Needs the grants of sql/application-role-privileges-1.6.sql and the vectors migrations.