feat(graph): wire the proposals read to service.ListProposals (Phase 3) The Proposals port declared a proposal listing and left Options.Proposals nil, so the `proposals` query failed loudly with "service/ exposes no proposal listing yet". Phase 3 supplies it: an adapter maps service.Proposal onto graph.Proposal at the edge — the two structs are identical, but service/ must not import graph/, so the rename lives here beside web.NewReader's equivalent — and cmd wires graph.NewProposals(svc) into the schema. The `proposals` field now answers from service/.
feat(graph): the read-only GraphQL schema at /query Eight query fields over the service layer, no Mutation and no Subscription — the design defers mutations until the proposal state machine settles, and TestSchemaHasNoMutations stands guard on that. Access is fail-closed and gated before parse, matching web's ACL exactly, so introspection is treated as content too. A federating api.sr.ht must therefore present a token or skip us, which costs one log line. A malformed rev is reported as a GraphQL error rather than folded into null. service/ deliberately hides malformed-versus-absent from probing, but the caller here is already authenticated as the owner or its own agent, and a bare null for rev=proposals/42 is indistinguishable from an absent document — it reads as a silently dropped argument. Proposal listing declares its port but is unwired: service/ exposes no proposal read yet, and returning an empty list would tell a reviewer their queue is clear when it is merely unread.