Per head's parallel-prep brief while m/mBrian#73 (migration script + [schema] node) is being built mBrian-side. NO mBrian-MCP-backed implementation yet — the migration worker may refine the landed node/edge shape and building the impl now risks rework. Built ONLY the parts stable regardless of mBrian internals: 1. CONSUMER INVENTORY (docs/plans/slice-b-adapter-contract.md §1) - Every *store.Store read method (15 methods) with signature + semantics - Every call site across web/, internal/aggregate/, mcp/ — table form - Item / ItemLink field-by-field shape contract: which fields come direct from node columns, which from edge-walk, which from metadata-unpack - Direct pgxpool access flagged out-of-scope (admin counts, bulk tx, links event-date update — slice C reworks those) - Views (5j) explicitly NOT in scope per m's Q5=(a) 2. INTERFACE CONTRACT (store/adapter.go) - ItemReader Go interface — 15 methods, pure projax-shaped structs in/out, zero mBrian type leakage - var _ ItemReader = (*Store)(nil) compile-time assertion proving the existing pgx-backed *Store satisfies the contract today 3. SKELETON (store/adapter.go MBrianReader) - Empty struct (mBrian client choice deferred to slice B impl) - All 15 methods stubbed, return errNotImplementedSliceB - var _ ItemReader = (*MBrianReader)(nil) keeps the stubs in lockstep with the interface as slice B grows - Each stub carries a one-line comment naming the §3 gap(s) it resolves at impl time - `go build ./...` green; `go vet ./store/` green 4. GAP FLAGS (docs/plans/slice-b-adapter-contract.md §3) - item_links.rel free-form annotation → mBrian edge.note (add to m/mBrian#73 §1 for the migration script) - ItemLink.RefID per-rel-type extraction rule (caldav URL vs gitea owner/repo vs mai project uuid) - paths[] recomputation cost (per-request memoisation) - AllTags aggregation (full-scan ok at m's scale; tag-graph deferred per m's Q8) - Roots / MaiOrphans "no outbound child_of edge" predicate - ItemsCreatedInRange scoped to projax_origin marker - Item.Source / SourceRefID constant + mai-edge-derived fields - ItemLinkWithItem join shape (two queries + in-memory join vs bulk MCP helper) - Admin counts — recommend adding Counts(ctx) to ItemReader for cohesion Stays parked after this. Slice B IMPL (mBrian-MCP client wiring + per- method bodies + handler rename from s.Store.X to s.Items.X) waits on the migration completing and uuid map landing.
159 lines
6.6 KiB
Go
159 lines
6.6 KiB
Go
package store
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"time"
|
|
)
|
|
|
|
// ItemReader is the read-path contract every projax UI handler, the
|
|
// internal/aggregate fan-out engine, and the MCP read tools depend on.
|
|
// Pure projax-shaped structs in/out; the slice-B mBrian-backed
|
|
// implementation translates mBrian nodes/edges into the same shape
|
|
// without leaking mBrian types to consumers.
|
|
//
|
|
// Phase 6 Slice B prep — see docs/plans/slice-b-adapter-contract.md.
|
|
// The existing *Store already satisfies this interface (the compile-time
|
|
// assertion below pins that). Slice B impl ships a second satisfier
|
|
// (MBrianReader) once m/mBrian#73's migration completes and hands the
|
|
// uuid map over.
|
|
type ItemReader interface {
|
|
// --- item lookups ---
|
|
ListAll(ctx context.Context) ([]*Item, error)
|
|
GetByID(ctx context.Context, id string) (*Item, error)
|
|
GetByPath(ctx context.Context, path string) (*Item, error)
|
|
GetByPathOrSlug(ctx context.Context, key string) (*Item, error)
|
|
Roots(ctx context.Context) ([]*Item, error)
|
|
MaiOrphans(ctx context.Context) ([]*Item, error)
|
|
ListByFilters(ctx context.Context, f SearchFilters) ([]*Item, error)
|
|
Search(ctx context.Context, q string, limit int) ([]*Item, error)
|
|
ItemsCreatedInRange(ctx context.Context, from, to time.Time) ([]*Item, error)
|
|
AllTags(ctx context.Context) ([]string, error)
|
|
|
|
// --- link lookups ---
|
|
LinksByType(ctx context.Context, itemID, refType string) ([]*ItemLink, error)
|
|
LinksByRefType(ctx context.Context, refType string) ([]*ItemLink, error)
|
|
DatedLinks(ctx context.Context, itemID string) ([]*ItemLink, error)
|
|
DatedLinksRange(ctx context.Context, from, to time.Time) ([]*ItemLinkWithItem, error)
|
|
RecentDocuments(ctx context.Context, since time.Time, limit int) ([]*ItemLinkWithItem, error)
|
|
}
|
|
|
|
// Compile-time assertion that the existing pgx-backed *Store satisfies
|
|
// ItemReader. Drops in cleanly because every method in the interface is
|
|
// already part of *Store's public surface. If a future refactor removes
|
|
// or reshapes one of these methods on *Store, the compiler points at
|
|
// this line first.
|
|
var _ ItemReader = (*Store)(nil)
|
|
|
|
// errNotImplementedSliceB is the placeholder return from every method on
|
|
// the slice-B-prep stub. Slice B's impl replaces each return with the
|
|
// real mBrian-backed body.
|
|
var errNotImplementedSliceB = errors.New("not implemented: Phase 6 Slice B (mBrian-backed reader) — waits on m/mBrian#73 migration")
|
|
|
|
// MBrianReader is the slice-B implementation target. Every method body
|
|
// returns errNotImplementedSliceB during prep; slice B's coder fills
|
|
// each in once the migration completes and the uuid map lands. The type
|
|
// holds no mBrian client today — the client decision (MCP-over-stdio /
|
|
// direct pgxpool against mbrian.* / in-process submodule) is the first
|
|
// thing slice B's impl chooses, then this struct grows the
|
|
// corresponding field.
|
|
//
|
|
// Per-method comments name the §3 gaps in the contract doc each method
|
|
// will need to resolve at impl time.
|
|
type MBrianReader struct {
|
|
// Reserved for slice-B's mBrian client. Empty struct today so the
|
|
// compile-time assertion below stays meaningful.
|
|
}
|
|
|
|
// Compile-time assertion that MBrianReader satisfies ItemReader. Keeps
|
|
// the stubs in lockstep with the interface as slice B grows methods.
|
|
var _ ItemReader = (*MBrianReader)(nil)
|
|
|
|
// --- item lookups ---
|
|
|
|
// ListAll: §2.1 — edge-walk for Item.Paths + Item.ParentIDs per item;
|
|
// §2.2 — metadata-unpack across all returned items; §3 — Item.Source
|
|
// constant.
|
|
func (*MBrianReader) ListAll(ctx context.Context) ([]*Item, error) {
|
|
return nil, errNotImplementedSliceB
|
|
}
|
|
|
|
// GetByID: §2.2 metadata-unpack.
|
|
func (*MBrianReader) GetByID(ctx context.Context, id string) (*Item, error) {
|
|
return nil, errNotImplementedSliceB
|
|
}
|
|
|
|
// GetByPath: §2.1 — walks child_of edges from path's root segment to
|
|
// resolve a leaf node. Per-request cache reduces N+1.
|
|
func (*MBrianReader) GetByPath(ctx context.Context, path string) (*Item, error) {
|
|
return nil, errNotImplementedSliceB
|
|
}
|
|
|
|
// GetByPathOrSlug: GetByPath, fall back to slug lookup.
|
|
func (*MBrianReader) GetByPathOrSlug(ctx context.Context, key string) (*Item, error) {
|
|
return nil, errNotImplementedSliceB
|
|
}
|
|
|
|
// Roots: §2.1 — "no outbound child_of edge" predicate.
|
|
func (*MBrianReader) Roots(ctx context.Context) ([]*Item, error) {
|
|
return nil, errNotImplementedSliceB
|
|
}
|
|
|
|
// MaiOrphans: §2.1 — Roots ∩ metadata.projax.management ⊇ {'mai'}.
|
|
func (*MBrianReader) MaiOrphans(ctx context.Context) ([]*Item, error) {
|
|
return nil, errNotImplementedSliceB
|
|
}
|
|
|
|
// ListByFilters: structured search; status/management/has-link/paths-prefix
|
|
// dimensions map to metadata.projax.* predicates + edge existence checks.
|
|
func (*MBrianReader) ListByFilters(ctx context.Context, f SearchFilters) ([]*Item, error) {
|
|
return nil, errNotImplementedSliceB
|
|
}
|
|
|
|
// Search: mBrian already has trigram + FTS on title + content_md
|
|
// (idx_nodes_fts). Adapter narrows to projax-managed nodes.
|
|
func (*MBrianReader) Search(ctx context.Context, q string, limit int) ([]*Item, error) {
|
|
return nil, errNotImplementedSliceB
|
|
}
|
|
|
|
// ItemsCreatedInRange: direct over nodes.created_at, scoped to
|
|
// metadata.projax_origin IS NOT NULL (or whatever projax-managed marker
|
|
// the migration settles on).
|
|
func (*MBrianReader) ItemsCreatedInRange(ctx context.Context, from, to time.Time) ([]*Item, error) {
|
|
return nil, errNotImplementedSliceB
|
|
}
|
|
|
|
// AllTags: §3 — union over metadata.projax.tags[]. Full-scan at m's
|
|
// scale; tag-graph deferred to Phase 7 (m's Q8).
|
|
func (*MBrianReader) AllTags(ctx context.Context) ([]string, error) {
|
|
return nil, errNotImplementedSliceB
|
|
}
|
|
|
|
// --- link lookups ---
|
|
|
|
// LinksByType: §2.3 — WHERE source_id=$1 AND rel='projax-'||$2.
|
|
func (*MBrianReader) LinksByType(ctx context.Context, itemID, refType string) ([]*ItemLink, error) {
|
|
return nil, errNotImplementedSliceB
|
|
}
|
|
|
|
// LinksByRefType: §2.3 — WHERE rel='projax-'||$1.
|
|
func (*MBrianReader) LinksByRefType(ctx context.Context, refType string) ([]*ItemLink, error) {
|
|
return nil, errNotImplementedSliceB
|
|
}
|
|
|
|
// DatedLinks: §2.3 — one item's edges with metadata ? 'event_date'.
|
|
func (*MBrianReader) DatedLinks(ctx context.Context, itemID string) ([]*ItemLink, error) {
|
|
return nil, errNotImplementedSliceB
|
|
}
|
|
|
|
// DatedLinksRange: §2.3 — metadata->>'event_date' BETWEEN $1 AND $2,
|
|
// joined with source node for the ItemLinkWithItem shape.
|
|
func (*MBrianReader) DatedLinksRange(ctx context.Context, from, to time.Time) ([]*ItemLinkWithItem, error) {
|
|
return nil, errNotImplementedSliceB
|
|
}
|
|
|
|
// RecentDocuments: dated links since $1 ORDER BY event_date DESC LIMIT $2.
|
|
func (*MBrianReader) RecentDocuments(ctx context.Context, since time.Time, limit int) ([]*ItemLinkWithItem, error) {
|
|
return nil, errNotImplementedSliceB
|
|
}
|