package mcpsrv import ( "context" "sourcecraft.dev/bigbes/sr-ht-dolt/browse" "sourcecraft.dev/bigbes/sr-ht-dolt/core" ) // The seams this package calls, declared consumer-side — the house style // web/deps.go sets — so that the tools can be driven by an in-process MCP client // over fakes, with no Postgres and no store on disk (docs/DESIGN.mcp.md §12). // // They name exactly what the tools call, and what is *absent* from them is the // design of docs/DESIGN.mcp.md §2 made structural rather than remembered: // // - No StoreManager. Nothing on this surface creates, initialises, evicts or // deletes an on-disk store, so no handler here can. A write tool added later // would have to widen this file first, which is a diff a reviewer sees. // - No ACL mutation, no repository create/update/delete. Repos below is the // read half of web's RepoStore and nothing more: a resolution, two listings // and the caller's effective grant. // - No key management. Dolt keys are a credential plane, and a surface reached // with one credential has no business enumerating another. // - No UserResolver. Resolving a username to a mirrored account is a write to // the local user table on first sight; this surface reads. // - No SQL engine anywhere. BrowseSession is the bare-store reader of // browse/, which is the only reading of a hosted database that exists // without a working set (docs/DESIGN.mcp.md §2, browse/open.go). // // The nil contract is the one every seam here shares and is not restated per // method: a nil error means a usable result, no method returns (nil, nil) except // EffectiveAccess — whose (nil, nil) *is* the answer "this caller holds no ACL // entry", exactly as db.Store.EffectiveAccess defines it. // Repos is the metadata store as this surface reads it: the repository rows and // the caller's ACL grant on one of them. // // It is spelled with db.Store's own signatures, so *db.Store satisfies it as // written and the production wiring binds it through the same request-scoped // adapter web uses. There is no compile-time assertion here because that adapter // is web's and unexported; the wiring commit is where the two meet. // // Visibility is *not* applied by these methods and must not be assumed from // them. GetRepoByOwnerAndName answers about any repository that exists, and the // caller here reproduces the browse dance — EffectiveAccess, core.Allowed, // core.NotFoundForPrivate — exactly as web/router.go's loadRepoForBrowse does // (docs/DESIGN.mcp.md §4.3). The two listings are the exception and say so // themselves. type Repos interface { // GetRepoByOwnerAndName resolves a database by its owner's username (without // the "~") and name, or reports db.ErrNotFound. It applies no visibility rule // whatsoever: what the caller may see is decided afterwards, by core.Allowed // over the grant EffectiveAccess returns. GetRepoByOwnerAndName(ctx context.Context, ownerUsername, name string) (*core.Repo, error) // ListReposByOwner lists one owner's databases that viewer may *list*, which // is a narrower rule than "may read": PUBLIC to everyone including anonymity, // plus anything viewer owns or holds an ACL entry on. An UNLISTED database of // somebody else is therefore absent from the listing and still readable by // direct address, which is the rule the dashboard already implements. // // viewer is nil for an anonymous caller, and anonymous is a normal caller // here — it sees the PUBLIC ones. ListReposByOwner(ctx context.Context, ownerUsername string, viewer *core.Caller) ([]*core.Repo, error) // ListReposForDashboard lists every database userID owns or holds an ACL // entry on, whatever its visibility. It is the signed-in caller's own view of // the instance and takes no viewer argument because the user *is* the viewer. ListReposForDashboard(ctx context.Context, userID int) ([]*core.Repo, error) // ListReposForViewer lists every database viewer may be shown, across all // owners. It is ListReposByOwner's rule minus the owner filter, and it is the // only enumeration on this seam that can answer an instance-wide question — // which is what ready_work with no database named asks (docs/DESIGN.mcp.md // §9.2, docs/DESIGN.views.md ch. 4). // // Listing is not authorization: the caller here still asks core.Allowed for // OpBrowse per database, exactly as web/handlers_ready.go does, before a // single store is opened. viewer is nil for an anonymous caller, and // anonymous is a normal caller — it gets the PUBLIC set. ListReposForViewer(ctx context.Context, viewer *core.Caller) ([]*core.Repo, error) // EffectiveAccess resolves the caller's ACL grant on a repository, or // (nil, nil) when there is none. Feed the result to core.Allowed; a nil grant // is not a denial, it is a fall-through to visibility. EffectiveAccess(ctx context.Context, userID, repoID int) (*core.AccessMode, error) } // BrowseSession is the read-only view of one bare store: the method set // web/deps.go declares plus TableHash, so a *browse.DB satisfies both and the // production adapter is the same one web uses. // // Everything here reads committed roots. There is no Write, no Commit and no // working set to hold one — a bare NBS store has none (browse/open.go), which is // what the whole pure-Go build stands on (docs/DESIGN.mcp.md §2). type BrowseSession interface { Branches(ctx context.Context) ([]browse.Branch, error) Log(ctx context.Context, refStr, fromHash string, limit int) ([]browse.CommitInfo, string, error) Tables(ctx context.Context, refStr string) ([]browse.TableInfo, error) TableHash(ctx context.Context, refStr, table string) (string, bool, error) Rows(ctx context.Context, refStr, table string, offset, limit int) (*browse.RowPage, error) CommitSummary(ctx context.Context, hashStr string) (*browse.CommitDiff, error) Close() error } // BrowseOpener opens a session over the bare store at diskPath. // // Open is paired with Close by the handler that called it (defer sess.Close()), // which is the browse discipline and not an ornament: a session is one fresh // read of the on-disk manifest, so a handler sees commits the push writer landed // since the last call and nothing is cached between calls (browse/open.go). type BrowseOpener interface { Open(ctx context.Context, diskPath string) (BrowseSession, error) }