package graph import ( "context" "time" "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 only the seams above; every field of // the schema is answered by calling through them. type Resolver struct { reader Reader searcher Searcher proposals Proposals }