Help us shape the future of Go! Take the 2026 Go Developer Survey

contentsource

package
v0.21.0 Latest Latest
Warning

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

Go to latest
Published: Oct 29, 2026 License: AGPL-3.0 Imports: 23 Imported by: 0

Documentation

Overview

Package contentsource reads a service's content — its description, its keywords, its picture — from a system outside Atlas, and decides whether any of it may reach the shop (ADR-0491).

Nothing here is engine state. A source is design-time configuration, a fetch is a catalogue-service request made before any command and outside the processor, and what it yields is ordinary catalogue data written through the run loop. No record is replayed, so nothing here is fetched during replay, and I2, I4 and I6 are untouched (§ 7 of the record).

The package does not know the catalogue. It answers three questions for it — what a page says, whether a page may be shown, and whether the account Atlas reads with is what it must be — and the catalogue decides what to do with the answers. That keeps the security rules a pure function of the facts an adapter returns (rules.go), which is what lets them be tested row by row.

Index

Constants

View Source
const KindWikiWikiBangBang = "wikiwikibangbang"

KindWikiWikiBangBang is the first source (§ 3): the typed item model whose pages a product owner keeps one per language.

View Source
const MaxWait = 30 * time.Second

MaxWait is the longest the source holds a long poll (its ChangesWait budget).

View Source
const Placeholder = '▒'

Placeholder is the character a source veils a passage with. wikiwikibangbang writes eight of them for every passage a reader may not read (its SecretPlaceholder); one is enough to know that something was veiled.

View Source
const RecheckInterval = 15 * time.Minute

RecheckInterval is the sweep's period, and the age a passed recheck may reach before publishing refuses (§ 9).

View Source
const RoleViewer = "viewer"

RoleViewer is the one role the account may hold in a space (§ 4). Any other — editor, space administrator — reads protected passages or bypasses restrictions, which is exactly what the shop must not be built from.

View Source
const ScopeRead = "read"

ScopeRead is the one token scope that passes (its ADR-0027): a token that can change no content.

Variables

View Source
var ErrNoSource = errors.New("no such content source")

ErrNoSource is a binding naming a source that is not configured.

View Source
var ErrUnreadable = errors.New("the page cannot be read")

ErrUnreadable is a page the account cannot read, or that is gone. The source does not say which, and must not have to: an unreadable item is non-existent to whoever cannot read it (its D10), and either way the page is refused.

View Source
var ImageID = regexp.MustCompile(`^[0-9a-f]{32}$`)

ImageID is the shape of an image's id in Atlas's store.

Kinds are the source kinds this build has an adapter for. A kind is admitted only once its adapter proves the facts of § 10, so the list is closed and a configuration naming anything else is refused rather than stored for a later build to interpret.

Functions

func CheckPage

func CheckPage(p Page, ancestors []Page, st Standing) []string

CheckPage refuses a page for any protection of § 8: a read restriction on it or on any ancestor, an open hold, a page not live, a space not released for the shop. ancestors is the whole parent chain; an ancestor that could not be read never reaches here, because reading the chain fails first.

An edit restriction alone is no reason: reading stays open, and the facts do not carry it at all.

func CheckStanding

func CheckStanding(st Standing) []string

CheckStanding refuses an account that is anything but a read-only viewer (§ 4): a token not of scope read, any system role, any space role other than viewer. A wrong standing stops every fetch and quarantines every bound item of the source.

func Keywords

func Keywords(src Source, labels ...[]string) []string

Keywords are the labels of every language page, minus the excluded labels (§ 7), lower-cased, sorted and once each.

func LooksLikeCredential

func LooksLikeCredential(ref string) bool

LooksLikeCredential reports whether a credentialsRef is a credential rather than the name of one. The field names a vault secret; a token typed into it is stored in clear beside the configuration, shown on the console, written to the audit trail and quoted in error messages product managers read. So such a value is refused when a source is saved, and never shown or logged when one that predates the refusal is read.

func PictureNames

func PictureNames(p Page, field string) []string

PictureNames are the attachment names a page's picture field names, in order. A string is a list separated by commas or new lines; a list of strings is one name each. Anything else names nothing.

func Recheck

func Recheck(ctx context.Context, ad Adapter, checks []Check) ([]string, []string, error)

Recheck asks whether the content copied from these pages may still be shown (§ 9). It reads the account's standing and every page's protection — never its content — and returns every reason it may not, and the ids it read, for the change feed to watch: the pages and every ancestor, because a restriction set on an ancestor protects the page without changing it.

A passage protected after the copy was taken refuses as well. Its text was plain when it was copied, so Atlas holds it; § 8 removes a passage *before* converting, and this is the same rule applied to a copy that already exists.

func ReleasedFor

func ReleasedFor(st Standing, space string) bool

ReleasedFor reports whether a space is released for the shop: the account holds viewer there, and nothing else (§ 4). A space it holds no grant in is not released, whatever the account happens to read through an inherited default.

func RichText

func RichText(blocks []Rich) string

RichText is a rich description as plain text, for a reader that keeps only text: headings and paragraphs stand apart, list items are lines, a table is its rows with cells separated by " | ", a child page or an embedded item is its title followed by its own text. An image is its caption, where it has one.

func SafeRef

func SafeRef(ref string) string

SafeRef is a credentialsRef as a log line or a message may name it.

Types

type Adapter

type Adapter interface {
	// Standing reads the account's own standing.
	Standing(ctx context.Context) (Standing, error)
	// Search lists pages matching a query.
	Search(ctx context.Context, q string) ([]Hit, error)
	// Resolve turns what a product manager pasted — an address, a key or an id
	// — into the page's stable id. An address of another host than the source's
	// is refused.
	Resolve(ctx context.Context, input string) (string, error)
	// Page reads one page by id, with its security facts.
	Page(ctx context.Context, id string) (Page, error)
	// Ancestors reads the whole parent chain of a page, nearest first. An
	// ancestor the account cannot read is [ErrUnreadable].
	Ancestors(ctx context.Context, p Page) ([]Page, error)
	// Children lists a page's readable child pages, by id, in sibling order.
	Children(ctx context.Context, p Page) ([]string, error)
	// Attachment reads the file of a page named name — or whose id is name, as
	// an image block names it — at most max bytes, with the media type the
	// source gives it.
	Attachment(ctx context.Context, pageID, name string, max int64) ([]byte, string, error)
	// Watch asks the source to report changes to a page in its feed.
	Watch(ctx context.Context, id string) error
	// Unwatch ends the watch of a page Atlas no longer holds anything of, by
	// id — which ends it even when the page can no longer be read.
	Unwatch(ctx context.Context, id string) error
	// Head is the feed's current position, from which no change is yet known.
	Head(ctx context.Context) (int64, error)
	// Changes waits up to wait for changes after the cursor.
	Changes(ctx context.Context, after int64, wait time.Duration) (Changes, error)
}

Adapter is one source kind (§ 10). Every method talks to the source and none may be called on the run loop.

A new adapter is admitted only when it proves the facts the rules read, and where its source has no equivalent of a protection it reports the fact as Unknown — which refuses — rather than as absent.

func DialKind

func DialKind(src Source, token string, transport http.RoundTripper) (Adapter, error)

DialKind builds the adapter for a source's kind.

type Block

type Block struct {
	Kind BlockKind
	// Source is the source's own name for the block's kind, for the preview's
	// account of what was dropped.
	Source string
	Text   string
	Marks  []Mark
	// Ref names what an image (an attachment of the page) or an embed (another
	// item) shows.
	Ref string
	// Level is a heading's level, 1 to 3; Tone a panel's tone; Checked whether a
	// task is done; Align and Width how an image or a paragraph is laid out.
	Level   int
	Tone    string
	Checked bool
	Align   string
	Width   int
}

Block is one block of a body.

type BlockKind

type BlockKind string

BlockKind is what a block of a page's body is, in the vocabulary the conversion understands. An adapter maps its source's kinds onto these, and everything it cannot map is BlockOther, which is never taken.

const (
	// BlockParagraph, BlockHeading and the list items are the text § 7 takes.
	BlockParagraph BlockKind = "paragraph"
	BlockHeading   BlockKind = "heading"
	BlockBullet    BlockKind = "bullet"
	BlockOrdered   BlockKind = "ordered"
	// The blocks the rich description takes besides the text (amendment of
	// 2026-10-28): a quote, code, a panel with its tone, a table whose text holds
	// its rows (one per line) and cells (separated by tabs), a divider, an image
	// attachment of the page (Ref names it) and another item embedded (Ref names
	// it). The plain description still takes only the text kinds above.
	BlockQuote   BlockKind = "quote"
	BlockCode    BlockKind = "code"
	BlockPanel   BlockKind = "panel"
	BlockTable   BlockKind = "table"
	BlockDivider BlockKind = "divider"
	BlockImage   BlockKind = "image"
	BlockEmbed   BlockKind = "embed"
	// BlockOther is everything else — videos, models, computed lists, a kind
	// added to the source later. Dropped and reported, never converted.
	BlockOther BlockKind = "other"
)

type Bound

type Bound interface {
	// Referenced names every source a binding names, configured or not: a
	// binding to a source that was removed is rechecked too, and fails.
	Referenced() ([]string, error)
	// Targets lists the products bound to a source.
	Targets(source string) ([]Target, error)
	// Record writes what a recheck found.
	Record(source string, results []Result) error
}

Bound is the catalogue, as the recheck sees it. Every method is called off the run loop and makes its own visit to it.

type Change

type Change struct {
	Seq int64
	// Item is the changed page's id, or empty for "a watched page you can no
	// longer read changed" — which names nothing, so everything is rechecked.
	Item string
}

Change is one entry of the source's change feed (§ 11).

type Changes

type Changes struct {
	Changes []Change
	Last    int64
}

Changes is one delivery of the change feed: what changed after the cursor, and the cursor to ask from next.

type Check

type Check struct {
	ID       string   `json:"id"`
	Passages []string `json:"passages,omitempty"`
}

Check is one page a recheck asks about: its id, and the passages the copy of it was taken without.

type Content

type Content struct {
	Languages map[string]Language
	Keywords  []string
	Picture   *Picture
	// Images are the images the rich descriptions show, by the id their blocks
	// name: copied out of the source, so the shop never asks it for anything.
	Images map[string]*Picture
}

Content is everything a binding supplies: a description per language, the keywords, and the picture.

func Gather

func Gather(ctx context.Context, ad Adapter, src Source, slots map[string]string, primary string, maxPicture int64) (Content, error)

Gather reads every language page of a binding, and the picture of the primary one, and refuses the whole of it if any part fails (§ 7: per item, all or nothing). slots maps a language tag to what to read for it.

func GatherExcept

func GatherExcept(ctx context.Context, ad Adapter, src Source, slots map[string]string, primary string, maxPicture int64,
	others map[string]string) (Content, error)

GatherExcept is Gather for a source some of whose pages already describe other products: others maps such a page's id to its product, and a child page among them is left out of every language page's description with everything below it, as PreviewExcept leaves it out.

type Converted

type Converted struct {
	// Text is the page's body as plain text. Never markup: it is rendered as
	// text wherever it is shown.
	Text string
	// Passages are the protected passages removed, by the id the source gives
	// them. They are kept so a recheck can tell a passage protected after the
	// copy was taken — whose text Atlas then holds — from one it removed.
	Passages []string
	// Dropped names the blocks not taken, by the source's kind, in order.
	Dropped []string
	// Problems refuse the page: something the conversion could not make safe.
	Problems []string
}

Converted is one page's body as text, and what the conversion left out.

func Convert

func Convert(p Page) Converted

Convert turns a page's body into text (§ 7, § 8).

The order is the rule: every protected passage is removed by its mark *before* anything is converted, whether or not the account could read it, so the text of a passage never enters Atlas. Mentions go the same way. A link keeps its label and loses its target, because only the text is taken. Paragraphs, headings and list items are text; every other block is dropped and named.

Then the guard: a placeholder character left anywhere in the result means the source veiled something Atlas did not recognise as a passage, and the page is refused rather than shown with a hole whose edges might be wrong.

type Dropped

type Dropped struct {
	// Page names the page it was on, by key where it has one.
	Page string `json:"page"`
	// What is "page" for a child page dropped with everything below it,
	// "passage" for a protected passage removed, or the source's kind of a block
	// not taken.
	What    string   `json:"what"`
	Reasons []string `json:"reasons,omitempty"`
}

Dropped is something a description does not carry, and why.

type Fact

type Fact int

Fact is one security fact about a page, as far as its adapter could establish it.

It is three-valued on purpose, and the zero value is the third. § 10 says "a fact an adapter cannot establish counts as present", and a boolean cannot say that: false would read as "established absent" for every field an adapter forgot to fill. With Unknown as the zero value, a forgotten field refuses the page — the failure is a refusal somebody reads, not content in the shop.

const (
	// Unknown is a fact the adapter could not establish. The rules treat it as
	// present.
	Unknown Fact = iota
	// Absent is a fact the adapter established does not hold.
	Absent
	// Present is a fact the adapter established holds.
	Present
)

func FactOf

func FactOf(holds bool) Fact

FactOf is a fact the adapter established either way.

func (Fact) Held

func (f Fact) Held() bool

Held reports whether the rules must treat this fact as holding: present, or not established.

type Hit

type Hit struct {
	ID    string `json:"id"`
	Key   string `json:"key"`
	Title string `json:"title"`
	Space string `json:"space"`
	State string `json:"state,omitempty"`
	URL   string `json:"url,omitempty"`
}

Hit is one search result (§ 5).

type Language

type Language struct {
	// Input is what was asked for — an address, a key or an id.
	Input string `json:"input"`
	// Page is the language page itself.
	Page  Taken  `json:"page"`
	Space string `json:"space,omitempty"`
	State string `json:"state,omitempty"`
	// Labels are the page's own labels, as the source answered them.
	Labels []string `json:"labels,omitempty"`
	// Children are the child pages taken, depth first in sibling order.
	Children []Taken `json:"children,omitempty"`
	// Text is the description: the page's body followed by its children's.
	Text string `json:"text"`
	// Rich is the description as the source lays it out: the page's blocks,
	// then each child page with its own, depth first, every embedded item in
	// place (amendment of 2026-10-28).
	Rich []Rich `json:"rich,omitempty"`
	// Embeds are the items embedded anywhere in the description and taken.
	Embeds []Taken `json:"embeds,omitempty"`
	// Dropped is everything the rules or the conversion left out.
	Dropped []Dropped `json:"dropped,omitempty"`
	// Refusals refuse the page. Empty is the only pass.
	Refusals []string `json:"refusals,omitempty"`
	// contains filtered or unexported fields
}

Language is one language page as a fetch read it (§ 5, § 7).

func Preview

func Preview(ctx context.Context, ad Adapter, st Standing, input string) (Language, error)

Preview reads one language page as a fetch would take it, with everything the rules would drop and every reason they refuse (§ 5). It never fails on a rule: a refusal is part of the answer. It fails on a source it cannot talk to.

func PreviewExcept

func PreviewExcept(ctx context.Context, ad Adapter, st Standing, input string, others map[string]string) (Language, error)

PreviewExcept is Preview for a source some of whose pages already describe other products. others maps such a page's id to the product it describes: a child page found there is that product's description and not this one's, so it is dropped with everything below it, naming the product. The page asked for is never skipped — whether it may be bound is the caller's question.

type Mark

type Mark struct {
	Kind  MarkKind
	Start int
	End   int
	// Ref names the passage a MarkPassage protects.
	Ref string
}

Mark is one range over a block's text, in Unicode code points (runes) — the unit wikiwikibangbang counts its marks in.

type MarkKind

type MarkKind string

MarkKind is what a mark over a block's text is. Only the two that remove text matter to the conversion; every other mark (bold, a link) keeps its text and loses its formatting or target.

const (
	// MarkPassage is a protected passage (its ADR-0088). Its text is removed
	// whatever the account could read.
	MarkPassage MarkKind = "passage"
	// MarkMention is a mention of a person or an item. Its text is removed: a
	// mention of somebody is not a description of a service.
	MarkMention MarkKind = "mention"
	// MarkOther keeps its text.
	MarkOther MarkKind = "other"
)

type Page

type Page struct {
	ID      string
	Key     string
	Title   string
	Space   string
	Parent  string
	URL     string
	Version int
	State   string
	Labels  []string
	// Properties are the page's typed properties, as the source answered them.
	Properties map[string]any
	Body       []Block

	// ReadRestricted is a read restriction on the page itself, whoever it
	// names — including the account (§ 8).
	ReadRestricted Fact
	// Held is an open data protection hold.
	Held Fact
	// Withdrawn is a page not live: trashed, purged or redacted.
	Withdrawn Fact
	// Presence is the source's own word for where the page stands, for the
	// refusal's sentence.
	Presence string
}

Page is one page as its adapter read it, with the security facts of § 8.

func WalkAncestors

func WalkAncestors(ctx context.Context, read func(context.Context, string) (Page, error), p Page) ([]Page, error)

WalkAncestors reads a page's parent chain, nearest first, one page at a time. It is what an adapter's Ancestors is made of, and what a recheck uses through its cache. A cycle or a chain past [maxAncestors] cannot be established, and is an error rather than a shorter chain.

type Picture

type Picture struct {
	Name string
	Type string
	Data []byte
}

Picture is the first picture the primary page names, copied out of the source so the shop never asks the source for anything (ADR-0391).

type Refused

type Refused struct {
	// Problems name the slot (a language tag, or "" for the whole binding) and
	// why.
	Problems []SlotProblem
}

Refused is a fetch that may not be written, with every reason at once: a product manager who fixes one refusal per attempt eventually works around the gate.

func (*Refused) Error

func (r *Refused) Error() string

type Registry

type Registry struct {

	// Transport is the HTTP transport adapters use; nil is the process-wide one.
	// Tests set a test server's.
	Transport http.RoundTripper
	// Dial builds the adapter for a source. Nil dials by kind; tests replace it.
	Dial func(src Source, token string, transport http.RoundTripper) (Adapter, error)
	// contains filtered or unexported fields
}

Registry is how the rest of Atlas reaches a configured source: the record from the store, the token from the secret store, and the adapter for its kind.

The first two are the run loop's (I3 — the vault and the stores are written there); dialling and every request after it are not, and must not be. So every method here makes its own visit to the loop and none may be called from inside one: Do is a rendezvous, and a nested one deadlocks.

func NewRegistry

func NewRegistry(loop *runloop.Loop, store *Store, secret func(ref string) string) *Registry

NewRegistry builds the registry over a store. secret runs on the loop.

func (*Registry) Configured

func (r *Registry) Configured() (bool, error)

Configured reports whether any source is configured — the switch the whole feature hangs on (§ 3: off until an administrator configures one). Off the loop only.

func (*Registry) OnChange

func (r *Registry) OnChange(fn func())

OnChange registers what to tell when a source is written or removed.

func (*Registry) Open

func (r *Registry) Open(id string) (Source, Adapter, error)

Open reads a source and its token and dials its adapter. Off the loop only.

A token that resolves to nothing is an error naming the reference and never a value — there is none to name, and the sentence must not change when there is.

func (*Registry) Sources

func (r *Registry) Sources() ([]Source, error)

Sources lists the configured sources. Off the loop only.

type Result

type Result struct {
	Item   string
	At     int64
	Passed bool
	Reason string
}

Result is one product's recheck.

type Rich

type Rich struct {
	// Kind is heading, paragraph, bullet, ordered, quote, code, panel, table,
	// divider, image, page (a child page: Title and Blocks) or embed (an
	// embedded item: Title and Blocks).
	Kind string `json:"kind"`
	// Level is a heading's level, 1 to 3.
	Level int `json:"level,omitempty"`
	// Text is the block's text with every protected passage and mention
	// removed; an image's caption.
	Text string `json:"text,omitempty"`
	// Checked is a task's state, for a bullet that came from one.
	Checked bool `json:"checked,omitempty"`
	// Tone is a panel's tone, as the source names it.
	Tone string `json:"tone,omitempty"`
	// Rows are a table's cells, the first row its header.
	Rows [][]string `json:"rows,omitempty"`
	// Image is an image's id in Atlas's own store: the shop never asks the
	// source for anything. Empty in a preview, which copies nothing.
	Image string `json:"image,omitempty"`
	// Width is an image's width in percent of the column, 0 for its own size;
	// Align its alignment, as the source names it.
	Width int    `json:"width,omitempty"`
	Align string `json:"align,omitempty"`
	// Title and Blocks are a child page's or an embedded item's.
	Title  string `json:"title,omitempty"`
	Blocks []Rich `json:"blocks,omitempty"`
	// contains filtered or unexported fields
}

Rich is one block of a rich description.

func CopyRich

func CopyRich(blocks []Rich) []Rich

CopyRich is a list of blocks sharing no memory with this one.

type Runner

type Runner struct {

	// Interval is the sweep's period; Wait is how long one long poll of the feed
	// is held; Backoff is the pause after the feed failed. Settable for tests.
	Interval time.Duration
	Wait     time.Duration
	Backoff  time.Duration
	// contains filtered or unexported fields
}

Runner owns the recheck goroutines.

func NewRunner

func NewRunner(reg *Registry, bound Bound, now func() int64) *Runner

NewRunner builds the runner. now is the server's clock, in Unix nanoseconds.

func (*Runner) Kick

func (r *Runner) Kick(source string)

Kick rechecks a source now, in the background — after an unlink, so the watches nothing needs any more end without waiting for the next sweep.

func (*Runner) RecheckItems

func (r *Runner) RecheckItems(ctx context.Context, source string, items []string) error

RecheckItems rechecks the named products of a source.

func (*Runner) Reload

func (r *Runner) Reload()

Reload asks the runner to look at the configured sources again now.

func (*Runner) Run

func (r *Runner) Run(quit <-chan struct{})

Run keeps one goroutine per source until quit closes, then stops them all and waits for them.

func (*Runner) Sweep

func (r *Runner) Sweep(ctx context.Context, source string) error

Sweep rechecks every product bound to a source.

func (*Runner) Watched

func (r *Runner) Watched(source string) []string

Watched reports the ids the runner watches at a source, sorted — for tests and for the status of a source.

type Service

type Service struct {

	// InUse names the products bound to a source, so removing one that still
	// supplies something is refused rather than leaving bindings nothing can
	// recheck. Off the loop. Nil answers none.
	InUse func(id string) ([]string, error)
	// contains filtered or unexported fields
}

Service is the content-source configuration API.

func NewService

func NewService(reg *Registry, now func() int64) *Service

NewService builds the configuration API over a registry.

func (*Service) HandleCheck

func (s *Service) HandleCheck(w http.ResponseWriter, r *http.Request)

HandleCheck asks the source for the account's standing, so an administrator sees before any product is bound whether the token and the roles are right. It answers 200 either way: the result is the answer, not the request's failure.

func (*Service) HandleCreate

func (s *Service) HandleCreate(w http.ResponseWriter, r *http.Request)

HandleCreate configures a new source. An id already taken is a conflict: a create that silently replaced a source would rebind every product bound to it.

func (*Service) HandleDelete

func (s *Service) HandleDelete(w http.ResponseWriter, r *http.Request)

HandleDelete removes a source no product is bound to.

func (*Service) HandleGet

func (s *Service) HandleGet(w http.ResponseWriter, r *http.Request)

HandleGet reads one.

func (*Service) HandleList

func (s *Service) HandleList(w http.ResponseWriter, r *http.Request)

HandleList lists the configured sources.

func (*Service) HandleUpdate

func (s *Service) HandleUpdate(w http.ResponseWriter, r *http.Request)

HandleUpdate replaces a source's configuration. The id is the path's.

type SlotProblem

type SlotProblem struct {
	Lang    string `json:"lang,omitempty"`
	Page    string `json:"page,omitempty"`
	Message string `json:"message"`
}

SlotProblem is one reason a binding cannot be written.

type Source

type Source struct {
	// ID names the source in a binding. It is chosen, not minted, because a
	// binding names it for as long as the item exists.
	ID string `json:"id"`
	// Name is what a product manager reads when choosing a source.
	Name string `json:"name"`
	// Kind is the adapter, one of [Kinds].
	Kind string `json:"kind"`
	// BaseURL is where the source answers, https only. A page address of any
	// other host is refused when it is pasted (§ 5), and the adapter follows no
	// redirect away from it.
	BaseURL string `json:"baseURL"`
	// CredentialsRef names the vault secret (or ATLAS_CONNECTOR_<REF>_TOKEN)
	// holding the technical account's read token.
	CredentialsRef string `json:"credentialsRef"`
	// CredentialsRefRedacted is set only on a source as an API answer shows it
	// (see [Source.Shown]): the stored credentialsRef looked like a credential
	// itself, so it is not shown, and the administrator must name a secret.
	CredentialsRefRedacted bool `json:"credentialsRefRedacted,omitempty"`
	// PictureField is the property of the primary page that names the
	// attachments to use as pictures. Empty means the source supplies none.
	PictureField string `json:"pictureField,omitempty"`
	// ExcludedLabels are labels that organise the wiki and are not keywords.
	ExcludedLabels []string `json:"excludedLabels,omitempty"`
	// LinkInShop says whether the shop may show "more in the wiki" under a
	// description. Off by default: the address of a page is itself something
	// the product owner may not have meant every orderer to see.
	LinkInShop bool `json:"linkInShop,omitempty"`
	// CreatedAt and UpdatedAt are the server's Unix nanoseconds.
	CreatedAt int64 `json:"createdAt,omitempty"`
	UpdatedAt int64 `json:"updatedAt,omitempty"`
}

Source is one configured content source (§ 3).

It is configuration an administrator writes and nothing else does. The credential is a reference into the secret store (ADR-0041), never the token itself: a source record is what a backup carries and what an API answer shows, and a token in either is a token leaving the building.

func (Source) Excluded

func (s Source) Excluded(label string) bool

Excluded reports whether a label is one of the source's excluded labels.

func (Source) Normalized

func (s Source) Normalized() Source

Normalized is the source as it is stored: trimmed, the base URL without a trailing slash, the excluded labels lower-cased and sorted once each.

func (Source) Problem

func (s Source) Problem() string

Problem returns why a source cannot be stored, or "" when it can.

It is a statement about the record alone. Whether the credential resolves and whether the account's standing is right are questions for the source, asked by the check route and before every fetch — a configuration refused because the wiki was down at the moment somebody saved it would be a configuration nobody can write during an outage.

func (Source) Shown

func (s Source) Shown() Source

Shown is the source as an API answer carries it: a credentialsRef that looks like a credential is left out and flagged, so the administrator sees that it must be replaced without the value reaching the screen.

type SpaceGrant

type SpaceGrant struct {
	Space string
	Role  string
	Via   string
}

SpaceGrant is one role the account holds in one space, directly or through a group.

type Standing

type Standing struct {
	// Scope is the token's scope; only "read" passes.
	Scope            string
	SystemRoles      []string
	SystemRolesKnown bool
	Spaces           []SpaceGrant
	SpacesKnown      bool
}

Standing is the account's own standing at the source (§ 4).

The two Known flags are the three-valued rule for a list: an empty list the source answered and a list it never answered are different statements, and only the first one is "none".

type Store

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

Store holds the configured sources, one JSON file each. Like every design-time store it is the run loop's to write (I3).

func NewStore

func NewStore(dir string) (*Store, error)

NewStore opens (creating if needed) the directory backing the sources.

func (*Store) Delete

func (s *Store) Delete(id string) error

Delete removes a source. Removing one that is not there is not an error.

func (*Store) Save

func (s *Store) Save(src Source) error

Save writes a source, replacing any with the same id.

func (*Store) Source

func (s *Store) Source(id string) (Source, bool, error)

Source reads one source. A missing one is not an error.

func (*Store) Sources

func (s *Store) Sources() ([]Source, error)

Sources lists every source, by id.

type Taken

type Taken struct {
	ID       string   `json:"ref"`
	Key      string   `json:"key,omitempty"`
	Title    string   `json:"title,omitempty"`
	URL      string   `json:"url,omitempty"`
	Version  int      `json:"version"`
	Passages []string `json:"passages,omitempty"`
}

Taken is one page whose text a description holds.

type Target

type Target struct {
	Item   string
	Checks []Check
}

Target is one bound product and every page it holds content of.

type WikiWikiBangBang

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

WikiWikiBangBang is one configured wikiwikibangbang source.

func NewWikiWikiBangBang

func NewWikiWikiBangBang(baseURL, token string, transport http.RoundTripper) (*WikiWikiBangBang, error)

NewWikiWikiBangBang builds the adapter for a source's base URL and token. transport is nil in production — the process-wide transport, which carries the outbound proxy (internal/sysproxy) — and a test server's in tests.

func (*WikiWikiBangBang) Ancestors

func (w *WikiWikiBangBang) Ancestors(ctx context.Context, p Page) ([]Page, error)

Ancestors walks the parent chain with one GET per ancestor. The source answers a page's own restriction and not its ancestors', so the chain is the only way to learn what the page inherits.

func (*WikiWikiBangBang) Attachment

func (w *WikiWikiBangBang) Attachment(ctx context.Context, pageID, file string, max int64) ([]byte, string, error)

Attachment reads the newest file of a page by its name: the listing, then the download (GET /api/items/{id}/attachments/{attachment}).

func (*WikiWikiBangBang) Changes

func (w *WikiWikiBangBang) Changes(ctx context.Context, after int64, wait time.Duration) (Changes, error)

Changes long-polls the feed for changes after the cursor.

func (*WikiWikiBangBang) Children

func (w *WikiWikiBangBang) Children(ctx context.Context, p Page) ([]string, error)

Children lists a page's readable children in sibling order, following the tree's cursor (GET /api/spaces/{space}/tree?parent=).

func (*WikiWikiBangBang) Head

func (w *WikiWikiBangBang) Head(ctx context.Context) (int64, error)

Head reads the feed's position: GET /api/changes without a cursor answers no changes and the head.

func (*WikiWikiBangBang) Page

func (w *WikiWikiBangBang) Page(ctx context.Context, id string) (Page, error)

Page reads one page (GET /api/items/{id}).

func (*WikiWikiBangBang) Resolve

func (w *WikiWikiBangBang) Resolve(ctx context.Context, input string) (string, error)

Resolve turns an address (https://host/#/item/SVC-12), a key or an id into the page's id. The id is what a binding stores: a key changes when an item moves between spaces, an id never does.

func (*WikiWikiBangBang) Search

func (w *WikiWikiBangBang) Search(ctx context.Context, q string) ([]Hit, error)

Search runs the source's full-text search (GET /api/search).

func (*WikiWikiBangBang) Standing

func (w *WikiWikiBangBang) Standing(ctx context.Context) (Standing, error)

Standing reads the account's standing from two routes: GET /api/me for the token's scope and the two system roles it reports as flags, and GET /api/me/roles for every grant, direct and through groups (§ 11). A source that does not answer the second has not proved the account is a viewer, and the fields stay unknown.

func (*WikiWikiBangBang) Unwatch

func (w *WikiWikiBangBang) Unwatch(ctx context.Context, id string) error

Unwatch ends the account's watch of a page by its id. By id it ends even the watch of a page the account can no longer read (its ADR-0120); by key it would not, which is why a binding keeps ids.

func (*WikiWikiBangBang) Watch

func (w *WikiWikiBangBang) Watch(ctx context.Context, id string) error

Watch asks the source to report a page in the account's change feed. A read-scope token may watch (§ 11): watching changes no content.

Directories

Path Synopsis
Package wikifake is a wikiwikibangbang that lives in a test: the routes Atlas's adapter reads, answering from pages a test writes, under an https test server.
Package wikifake is a wikiwikibangbang that lives in a test: the routes Atlas's adapter reads, answering from pages a test writes, under an https test server.

Jump to

Keyboard shortcuts

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