package graph
import (
"context"
"time"
"github.com/99designs/gqlgen/graphql"
"sourcecraft.dev/bigbes/sr-ht-spec/core"
"sourcecraft.dev/bigbes/sr-ht-spec/doc"
"sourcecraft.dev/bigbes/sr-ht-spec/search"
"sourcecraft.dev/bigbes/sr-ht-spec/service"
)
// Reader is the part of the orchestration layer this schema reads through.
// *service.Service satisfies it.
//
// It is declared the way mcpsrv/ and web/ declare theirs, and every method is
// one service/ already has: the design's "MCP and GraphQL are not competitors —
// the MCP tools call the same resolver layer, not a parallel implementation" is
// only true if all three surfaces call the same functions. A method here that
// service/ does not have would be the beginning of a second implementation, and
// a rule this schema resolved for itself would be a rule the other two could
// disagree with.
type Reader interface {
ListSpaces(ctx context.Context) ([]*service.Space, error)
OpenSpace(ctx context.Context, ref core.SpaceRef) (*service.Space, error)
ResolveRev(ctx context.Context, sp *service.Space, rev string) (string, error)
// Archive is the whole of the document read path here: the addressing rule
// — a document is named by its frontmatter id when that is well-formed and
// unique in the space, and by its path otherwise — lives in doc/ behind a
// constructor that takes git objects, so a surface that resolved ids for
// itself would either reach past service/ into gitx or keep a second copy
// of the rule. Neither is allowed to happen a third time.
Archive(ctx context.Context, sp *service.Space, rev string) (*doc.Archive, map[string][]byte, error)
GetProject(ctx context.Context, ref core.ProjectRef) (*service.Project, error)
ListProjects(ctx context.Context) ([]*service.Project, error)
// ProjectSpaces is the listing counterpart of service.ResolveProject, and
// the difference is not cosmetic: a filter is never enumerated, whereas a
// list is enumerated by definition. The meta-project therefore lists every
// space here — a snapshot of the corpus as of the call — while a query
// restricted to it must go through the filter, which excludes nothing and
// so cannot go stale.
ProjectSpaces(ctx context.Context, ref core.ProjectRef) ([]*service.Space, error)
}
// Searcher is the query side of the one global index. *search.Index satisfies
// it.
type Searcher interface {
Search(ctx context.Context, q search.Query) (search.Results, error)
}
// Proposal is one proposal as the read schema needs it: service/-shaped, in
// core types, so the port below stays free of anything GraphQL.
type Proposal struct {
ID int
Space core.SpaceRef
Title string
Rationale string
BaseRev string
Branch string
State core.ProposalState
Approval core.Approval
MergedRev string
Agent string
AgentSession string
Created time.Time
Resolved *time.Time
}
// Proposals is the read side of the proposal store.
//
// It is a port with no production implementation yet, and that is a real gap
// rather than a design choice of this package: the design puts "proposal
// listing" in Phase 2's read schema, but service/ exposes no proposal read at
// all, and db/'s only listing is by state across every space. Nothing above
// service/ may touch db/ directly — that rule is what keeps the three
// agent-facing surfaces behaviourally identical — so this schema declares what
// it needs and waits for service/ to supply it.
//
// Until then Options.Proposals is nil and the `proposals` field fails loudly
// with that explanation. It does not return an empty list: "this space has no
// open proposals" and "this build cannot answer the question" are different
// answers, and quietly giving the first would tell a reviewer their queue is
// clear when it is unread.
type Proposals interface {
ListProposals(ctx context.Context, space core.SpaceRef, state core.ProposalState) ([]Proposal, error)
}
// Compile-time assertions that the production types satisfy the interfaces this
// package is written against. They are here rather than in a test so that a
// signature change in service/ or search/ breaks the build of the package that
// depends on them, not of a test somebody may not run.
var (
_ Reader = (*service.Service)(nil)
_ Searcher = (*search.Index)(nil)
)
// Resolver is the root resolver. It holds the seams above — every read field of
// the schema is answered by calling through them — and the schema it is itself
// part of.
type Resolver struct {
reader Reader
searcher Searcher
proposals Proposals
// schema is this service's executable schema, set by newSchema once the
// resolver it is built from exists. The webhook management resolvers need
// it: createUserWebhook validates a subscriber's stored query against it.
// It used to be read off core-go's server context, which is installed on
// the authenticated router and which /query no longer runs behind.
schema graphql.ExecutableSchema
}