client

package
v0.9.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Nov 10, 2026 License: MIT Imports: 16 Imported by: 1

Documentation

Overview

Package client is raglit's Go client: how another program asks raglit about raglit's data.

It exists because the alternative was tried and failed. kgraph read relations.jsonl directly, raglit later moved rulings into a database projected from an audit trail, and kgraph went on reading a file nobody wrote any more — no error, no warning, just stale answers. Parsing another tool's storage makes every storage change a silent break in a program you did not edit.

So the contract is the HTTP API and never the files. raglit may reorganise its storage whenever it likes; a consumer only has to keep speaking this.

Availability is not an error. A consumer asking about rulings on a machine where raglit is not running should be told "unknown", not fail: kgraph must still scan a corpus when no daemon is up. Every call here returns ErrUnavailable for that case so the caller can tell "nothing ruled" from "could not ask" — a distinction that matters, because the first is a fact about the corpus and the second is a fact about this machine.

Index

Constants

View Source
const DefaultAddr = "http://127.0.0.1:7420"

DefaultAddr is the shared per-user daemon.

Variables

View Source
var ErrUnavailable = errors.New("raglit is not reachable")

ErrUnavailable means raglit could not be reached — NOT that it had nothing to say. Callers must not read it as an empty result.

Functions

This section is empty.

Types

type Client

type Client struct {
	BaseURL string
	HTTP    *http.Client
}

Client talks to a raglit daemon.

func New

func New(base string) *Client

New returns a client for base, defaulting to $RAGLIT_DAEMON then the shared per-user daemon. Discovery also consults the daemon's own state file, so a daemon on a non-default port is found instead of a dead default being probed.

func (*Client) Alive

func (c *Client) Alive(ctx context.Context) bool

Alive reports whether the daemon answers.

func (*Client) Document

func (c *Client) Document(ctx context.Context, index, path string) (Doc, error)

Document returns one document's text, per page where raglit knows the pages.

PAGES, not fragments. A fragment may span a page boundary and carries a single page number, so a hit inside one cannot be resolved to the page it is on — a page number right for the start of a fragment and wrong for the rest is worse than none, because a reader turns confidently to the wrong page. This returns the per-page text raglit produced at ingest.

An unreachable daemon is an error, for the reason on Search.

func (*Client) Documents

func (c *Client) Documents(ctx context.Context, index string) ([]DocSummary, error)

Documents lists what an index holds. Path is raglit's absolute doc id.

One call instead of one per document: a consumer asking "does this have readable text" for a whole corpus was otherwise a round trip per file, which turns a backlog listing into a network storm.

IT DECODED INTO THE WRONG TYPE AND THEREFORE ALWAYS FAILED. This returned []Doc, whose `pages` is a LIST of pages with their text; the endpoint sends `pages` as a COUNT. Every real response failed to unmarshal — "cannot unmarshal number into Go struct field Doc.documents.pages" — so this function had never once returned a document.

The consequence was silent and lived one layer up: kgraph's `docsWithText` maps an error to nil, meaning "could not ask", and its caller falls back to looking for a sidecar on disk. So the backlog was permanently answering the question the index was supposed to have answered, for every corpus, and the fallback made it look like it was working.

func (*Client) Relations

func (c *Client) Relations(ctx context.Context, project, doc string) ([]Relation, error)

Relations returns every ruling for a project. doc, when non-empty, narrows to rulings involving that document.

func (*Client) Search

func (c *Client) Search(ctx context.Context, index, q string, n int) ([]Hit, error)

Search runs one query against a named index and returns the best n fragments.

index is raglit's full index name, which for a project-scoped index is "<project>__<index>" — the daemon namespaces them, and passing a bare project name silently searches somebody else's corpus.

An unreachable daemon is an ERROR, never an empty result. A consumer deciding whether a corpus holds a document cannot be handed "no" when the truth is "not asked": that is the one answer that stops a person looking for evidence that is actually there.

func (*Client) Slices

func (c *Client) Slices(ctx context.Context, project, parent string) ([]Slice, error)

Slices returns declared sub-documents for a project, optionally one bundle's.

func (*Client) Withdrawals

func (c *Client) Withdrawals(ctx context.Context, index string) ([]Withdrawal, error)

Withdrawals lists what has been ruled out of an index, path order.

Read off the health report rather than a ledger endpoint of its own, because that report is already where a withdrawal is published — `kind=withdrawn` IS the list, and it is exempted there from the filter that hides withdrawn documents from every other kind.

AN UNREACHABLE DAEMON IS AN ERROR, and a caller must not read it as "nothing has been withdrawn". Those are the two answers this exists to keep apart: one says a ruling was made, the other says nobody could ask. `get` returns ErrUnavailable for both a stopped daemon and one too old to have the route.

type Doc

type Doc struct {
	Index string `json:"index"`
	Path  string `json:"path"`
	Title string `json:"title,omitempty"`
	Pages []Page `json:"pages,omitempty"`
	Text  string `json:"text,omitempty"`
	// Truncated reports that Text is not the whole document. A consumer checking
	// whether a quotation appears MUST NOT read a miss as absence when this is
	// set: the passage may be in the part that was cut.
	Truncated bool `json:"truncated,omitempty"`
}

Doc is a document's text as raglit holds it, per page and whole.

type DocSummary

type DocSummary struct {
	Index string `json:"index"`
	Path  string `json:"path"`
	Title string `json:"title,omitempty"`
	// Fragments is the indexed text: zero means nothing readable came out of it.
	Fragments int `json:"fragments"`
	// Pages is the page COUNT, not the pages. A document may have pages and no
	// fragments, which is exactly the un-OCR'd scan.
	Pages int `json:"pages"`
	// Vision counts what was read by a vision model rather than a text layer.
	Vision   int    `json:"vision"`
	FragMode string `json:"frag_mode,omitempty"`
}

DocSummary is one document as the INDEX holds it: what it is, and how much of it was actually read.

Fragments is the number that answers "does this have readable text". A row with zero is IN the index and was never read — a scan nobody OCR'd, an image nobody captioned — and that is a different state from a document the index has never heard of, which is a different state again from one on disk with a text layer. Collapsing any two of them produces a backlog that asks somebody to classify a document nothing can read.

type Hit

type Hit struct {
	Index   string  `json:"index"`
	Doc     string  `json:"doc_id"`
	Title   string  `json:"title,omitempty"`
	Page    int     `json:"page,omitempty"`
	Score   float64 `json:"score"`
	Snippet string  `json:"snippet,omitempty"`
	// Caveat is the one line to show beside the snippet when it cannot be read
	// as the record — a model's description of an image, or a read nobody
	// measured. EMPTY for an ordinary transcription, because a caveat on every
	// row is a caveat on none.
	//
	// A consumer that shows the snippet and drops this is showing a model's
	// account of a picture as though somebody wrote it down.
	Caveat string `json:"caveat,omitempty"`
	// Trust is the same fact structured: what produced the text, whether a
	// person has ruled on it, and the per-facet confidences. Nil when raglit
	// recorded nothing about how the document was read — which is NOT a claim
	// that it is reliable, and is why Caveat is non-empty in that case.
	Trust *HitTrust `json:"trust,omitempty"`
}

Hit is one search result: a fragment of a document raglit holds.

Doc is raglit's own document id, which is an ABSOLUTE path — a consumer whose own document paths are relative has to relativize it, and comparing the two unnormalized matches nothing while looking exactly like "found nothing".

type HitTrust

type HitTrust struct {
	// Method is what produced the text: text-layer, vision-ocr, oidio-asr, pandoc…
	Method string `json:"method,omitempty"`
	// Level is machine, adapted or attested — whether a person has been through it.
	Level string `json:"level,omitempty"`
	// DescribedPct is how much of the document a model DESCRIBED rather than
	// transcribed. -1 means it was never measured, which is distinct from 0.
	DescribedPct int `json:"described_pct,omitempty"`
	// Facets is the per-facet confidence, 0-100: text, speaker, layout, subject.
	// A facet that is ABSENT is not claimed, rather than distrusted.
	Facets map[string]int `json:"facets,omitempty"`
	// Summary renders Facets weakest-first for a reader ("subject 80%", "text 90%").
	Summary []string `json:"summary,omitempty"`
	// RuledBy is the person who ruled, when one has.
	RuledBy string `json:"ruled_by,omitempty"`
}

HitTrust is how far a hit's text can be relied on, and about WHAT.

Mirrored here rather than imported from raglit's root package, for the reason this package exists: the contract is the JSON, so raglit may reshape its internals and a consumer only has to keep speaking this.

type Page

type Page struct {
	Page int    `json:"page"`
	Text string `json:"text"`
}

Page is one page of a document, as raglit read it.

type Relation

type Relation struct {
	A          string  `json:"a"`
	B          string  `json:"b"`
	Kind       string  `json:"kind"` // copy | version | unrelated
	Supersedes string  `json:"supersedes,omitempty"`
	Note       string  `json:"note,omitempty"`
	By         string  `json:"by,omitempty"`
	At         string  `json:"at,omitempty"`
	Relation   string  `json:"relation,omitempty"`
	Coverage   float64 `json:"coverage,omitempty"`
}

Relation is a ruling on a pair of documents.

func (Relation) Other

func (r Relation) Other(doc string) (string, bool)

Other returns the side of the pair that is not doc.

type Slice

type Slice struct {
	ID     string `json:"id"`
	Parent string `json:"parent"`
	From   int    `json:"from"`
	To     int    `json:"to"`
	Title  string `json:"title,omitempty"`
	Note   string `json:"note,omitempty"`
	By     string `json:"by,omitempty"`
	At     string `json:"at,omitempty"`
}

Slice is a declared sub-document: a page range of a bundle.

type TokenSource

type TokenSource struct {
	// contains filtered or unexported fields
}

TokenSource yields the bearer token. The zero value and nil have no token.

func EnvTokenSource

func EnvTokenSource(configCmd string) *TokenSource

EnvTokenSource reads $RAGLIT_TOKEN and $RAGLIT_TOKEN_COMMAND, falling back to configCmd (config auth.token_command). Nil when none is set.

func LocalAdminTokenSource added in v0.4.0

func LocalAdminTokenSource() *TokenSource

LocalAdminTokenSource reads the admin token of a daemon on this machine.

func (*TokenSource) Token

func (s *TokenSource) Token(renew bool) (string, error)

Token returns the current token; renew discards a cached one and runs the command again. A static token is returned as is either way.

type Transport

type Transport struct {
	Base  http.RoundTripper
	Src   *TokenSource
	Allow func(host string) bool
}

Transport adds the bearer token to requests for the hosts Allow accepts, and retries once with a renewed token after a 401. Every other request passes through untouched — a daemon token must never reach a model endpoint.

func (*Transport) RoundTrip

func (t *Transport) RoundTrip(req *http.Request) (*http.Response, error)

type Withdrawal

type Withdrawal struct {
	Path   string `json:"path"`
	Reason string `json:"reason"`
}

Withdrawal is a document ruled OUT of the corpus, with the grounds recorded.

Withdrawing and deleting are different acts, and the difference is the whole point of reading this: a delete removes rows and the next sweep puts the document back, so it is a preference. A withdrawal is a RULING — it carries grounds, it is recorded where git can blame it, and the ingest path honours it. A consumer that walks the directory instead of asking will offer a withdrawn document as a candidate forever, identically on every run, and nothing anywhere will say it was ruled out.

Only Path and Reason: the report this reads is keyed on the ruling, and who ruled and when are not on it. Ask the judgement store if you need them.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL