discovery

package
v0.1.0 Latest Latest
Warning

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

Go to latest
Published: Sep 17, 2026 License: MIT Imports: 19 Imported by: 0

Documentation

Overview

Package discovery builds PayCLI's adaptive capability manifest (§7).

The package exists because one binary has to drive *any* Payload 3.x project: slugs, GraphQL type names, id types, upload/versions/drafts/trash support and the queryable field set are all project-specific, and several of them are not obtainable at all from some deployments. Three rules therefore govern every line of it:

  • **Shape, never status.** Every REST probe validates the shape of the response body. A custom collection endpoint can shadow any built-in route, an unauthenticated /api/access answers 200 with a truncated view, and a wrong base URL answers 200 text/html.

  • **Tri-state, never defaulted** (§7.6). Every fact only GraphQL can establish is value | unknown, with a *_source sibling. An unknown value must never cause a local rejection: a defaulted id_type would make PayCLI reject valid Mongo ObjectIds with an error message that is a lie.

  • **Read-only by default** (§7.7). Nothing here creates a document unless the caller passed AllowWriteProbes, and §8.4's Level-3 reactive invalidation is never armed from this package — discovery deliberately generates every Level-3 trigger, so an ambient rule would recurse.

The package never imports the CLI layer or cobra, never reads a clock of its own (Options.Now is injected), and never writes to disk: it returns a typed Manifest plus field Shards and lets internal/cache persist them.

Index

Constants

View Source
const (
	KindScalar      = "SCALAR"
	KindObject      = "OBJECT"
	KindInterface   = "INTERFACE"
	KindUnion       = "UNION"
	KindEnum        = "ENUM"
	KindInputObject = "INPUT_OBJECT"
	KindList        = "LIST"
	KindNonNull     = "NON_NULL"
)

GraphQL introspection kinds.

View Source
const (
	DBPostgres = "postgres"
	DBMongoDB  = "mongodb"
	DBSQLite   = "sqlite"
	DBUnknown  = "unknown"
)

DB adapter values (§7.11).

View Source
const (
	// ReachabilityOK means both /api/access and the GraphQL Access type
	// listed the entity — or, when GraphQL is unavailable, that /api/access
	// listed it and nothing contradicted that.
	ReachabilityOK = "ok"
	// ReachabilityGraphQLDisabled means /api/access saw it and GraphQL did
	// not.
	ReachabilityGraphQLDisabled = "graphql-disabled"
	// ReachabilityAccessDenied means GraphQL saw it and /api/access did not,
	// i.e. this identity has zero permissions on it.
	ReachabilityAccessDenied = "access-denied"
)

Reachability records which of the two inventory sources saw an entity (§7.3). Neither source alone is complete: /api/access omits payload-kv (zero permission for this identity) and GraphQL omits payload-migrations (endpoints: false), both verified live.

View Source
const (
	ReasonAccessDenied      = "access-denied"
	ReasonEndpointsDisabled = "endpoints-disabled"
	ReasonGraphQLDisabled   = "graphql-disabled"
)

Reasons recorded in manifest.unreachable[].

View Source
const (
	GraphQLModeOK                    = "ok"
	GraphQLModeIntrospectionDisabled = "introspection_disabled"
	GraphQLModeErrors                = "errors"
	GraphQLModeDisabled              = "disabled"
	GraphQLModeRouteMissing          = "route_missing"
	GraphQLModeUnreachable           = "unreachable"
)

GraphQL degradation modes (§7.6).

View Source
const (
	SourceConfigured   = "configured"
	SourceProbed       = "probed"
	SourceGraphQL      = "graphql"
	SourceGraphQLInput = "graphql-input"
	SourceGraphQLEnum  = "graphql-enum"
	SourceGraphQLArg   = "graphql-arg"
	// SourceGraphQLArgAbsent is §7.8.1's localization.locales_source for "the
	// plural Query field has no locale argument", i.e. the project is provably
	// not localised.
	SourceGraphQLArgAbsent    = "graphql-arg-absent"
	SourceGraphQLEnumMangled  = "graphql-enum-mangled"
	SourceProbe               = "probe"
	SourceAccess              = "access"
	SourceObserved            = "observed"
	SourceLocaleAll           = "locale-all"
	SourceLocaleAllUnverified = "locale-all-unverified"
	SourceProjectSource       = "project-source"
	SourceProjectPackageJSON  = "project-package-json"
	SourceBulkDeleteMessage   = "bulk-delete-message"
	SourceDerived             = "derived"
	SourceAccessZip           = "access-zip"
	SourceTypeProbe           = "type-probe"
	SourceFuzzy               = "fuzzy"
	SourceBootstrap           = "bootstrap"
	SourceCached              = "cached"
	SourceInferred            = "inferred"
	SourceWriteProbe          = "write-probe"
	SourceUnknown             = "unknown"
	// SourceNA is the documented sentinel for "this key does not apply to
	// this field", as opposed to "PayCLI could not find out".
	SourceNA = "n/a"
	// SourceDerivedGraphQLPath mirrors config.SourceDerivedGraphQL so the two
	// layers report the same string for the same fact.
	SourceDerivedGraphQLPath = "derived:api_path+graphql_route"
)

Provenance values. Every derived fact is written together with the story of where it came from, or neither is written (§7.8.3).

View Source
const (
	ConfidenceGraphQL   = "graphql"
	ConfidenceInferred  = "inferred"
	ConfidenceObserved  = "observed"
	ConfidenceHeuristic = "heuristic"
	ConfidenceMeasured  = "measured"
	ConfidenceUnknown   = "unknown"
)

Confidence values for payload_type and sortable.

View Source
const (
	IDTypeNumber  = "number"
	IDTypeString  = "string"
	IDTypeUnknown = "unknown"
)

IDType values (§7.6). Unknown is a first-class answer, not an error state.

View Source
const (
	TypeText         = "text"
	TypeTextarea     = "textarea"
	TypeNumber       = "number"
	TypeCheckbox     = "checkbox"
	TypeDate         = "date"
	TypeEmail        = "email"
	TypeSelect       = "select"
	TypeRadio        = "radio"
	TypeRelationship = "relationship"
	TypeUpload       = "upload"
	TypeJoin         = "join"
	TypeBlocks       = "blocks"
	TypeRichText     = "richText"
	TypeJSON         = "json"
	TypeGroup        = "group"
	TypeArray        = "array"
	TypePoint        = "point"
	TypeID           = "id"
	TypeUnknown      = "unknown"
)

Payload field kinds inferred by §7.4's table.

View Source
const (
	WriteShapeID       = "id"
	WriteShapeIDArray  = "id_array"
	WriteShapeRelValue = "{relationTo,value}"
	WriteShapeRelList  = "[{relationTo,value}]"
	WriteShapeJSON     = "json"
)

WriteShape is §7.8.2's closed enum. It is mandatory and non-null on every relationship, upload and polymorphic field and null on every other kind, because §9.10's --set coercion reads exactly this key.

View Source
const (
	HookMutatedUnknown = "unknown"
	HookMutatedTrue    = "true"
	HookMutatedFalse   = "false"
)

HookMutated is tri-state as a string because "unknown" is the normal answer: beforeChange / beforeValidate hooks are not introspectable (§7.6, HOOK_MUTATION_UNKNOWN).

View Source
const (
	LimFieldsUnavailable          = "FIELDS_UNAVAILABLE"
	LimSelectOptionsUnavailable   = "SELECT_OPTIONS_UNAVAILABLE"
	LimBlockSlugsUnknown          = "BLOCK_SLUGS_UNKNOWN"
	LimCustomEndpointsNotEnum     = "CUSTOM_ENDPOINTS_NOT_ENUMERABLE"
	LimSortabilityHeuristic       = "SORTABILITY_HEURISTIC"
	LimLocalizationUnknown        = "LOCALIZATION_UNKNOWN"
	LimRichTextShapeUnknown       = "RICHTEXT_SHAPE_UNKNOWN"
	LimIDTypeUnknown              = "ID_TYPE_UNKNOWN"
	LimCapabilityUnknown          = "CAPABILITY_UNKNOWN"
	LimLabelsUnavailable          = "LABELS_UNAVAILABLE"
	LimLocalesUnknown             = "LOCALES_UNKNOWN"
	LimLocalizationPerFieldUnknwn = "LOCALIZATION_PER_FIELD_UNKNOWN"
	LimBlockSlugsUnknownNoSource  = "BLOCK_SLUGS_UNKNOWN_NO_SOURCE"
	LimAuthCandidatesTruncated    = "AUTH_CANDIDATES_TRUNCATED"
	LimHookMutationUnknown        = "HOOK_MUTATION_UNKNOWN"
	LimPayloadVersionUnknown      = "PAYLOAD_VERSION_UNKNOWN"
	LimDBAdapterUnknown           = "DB_ADAPTER_UNKNOWN"
)

Limitation codes (§7.6). They are stable identifiers: an agent branches on the code, never on the prose.

View Source
const (
	StageModeProbe   = "mode_probe"
	Stage1           = "stage1"
	Stage2           = "stage2"
	Stage3           = "stage3"
	StageFingerprint = "fingerprint"
)

Stage names recorded in diagnostics.degraded[].

View Source
const DefaultTTL = 10 * time.Minute

DefaultTTL is the discovery manifest's fresh window (§8.5).

View Source
const FallbackNone = "none"

FallbackNone is the value PayCLI sends as fallback-locale on every read when localisation is enabled (§7.9a). sanitizeFallbackLocale maps 'false' | 'none' | 'null' to false, so an untranslated field comes back null/absent instead of masquerading as a translation.

This is not a display preference. config.localization.fallback defaults to true, so without it `pay get pages 1 --locale de` returns English strings for every untranslated field, and an agent that read-modify-writes copies English into de and believes it translated the page.

View Source
const LocaleUnverifiedMessage = "" /* 203-byte string literal not displayed */

LocaleUnverifiedMessage is §7.9c's message, verbatim. It is verbatim because the behaviour it describes is invisible: an unknown locale code is silently coerced to the default locale with HTTP 200 and no warning.

View Source
const LocaleWarnUnverified = "locale_unverified"

LocaleWarnUnverified is the warning code attached when --locale could not be validated client-side.

View Source
const ManifestVersion = 2

ManifestVersion is §7.8.1's layout version. It is 2 because the single consolidated manifest of the first draft was split into an index plus field shards; a manifest_version: 1 file on disk is a cache miss, not a parse error.

View Source
const MaxLearnedOperatorFailures = 64

MaxLearnedOperatorFailures bounds the memo. It is a cache file an agent may never garbage-collect by hand, and a project with more than this many distinct failing operator/collection pairs has a bigger problem than the memo can express; the oldest record is dropped first.

Variables

View Source
var FieldKeys = []string{
	"name", "path", "graphql_path", "parent",
	"payload_type", "payload_type_confidence",
	"graphql_type", "json_type",
	"required", "required_source",
	"has_many", "localized", "localized_source",
	"read_only",
	"options", "options_source",
	"relation_to", "relation_to_source", "polymorphic",
	"write_shape",
	"queryable", "operators",
	"sortable", "sortable_confidence",
	"hook_mutated",
	"label",
}

FieldKeys is the exact, ordered key set every field entry carries. It is exported so a consumer can assert the contract instead of re-deriving it.

Functions

func AttributeOperatorFailure

func AttributeOperatorFailure(serverFailed bool, operators []string) (string, bool)

AttributeOperatorFailure implements the test described above. It returns the operator to blame and true only when the failure is genuinely attributable.

serverFailed is the caller's verdict that the server, not PayCLI, refused the request: an HTTP 500 or a GraphQL QueryError. operators is every distinct operator the request carried.

func CrossValidateLocales

func CrossValidateLocales(candidates, enumNames []string) bool

CrossValidateLocales is §7.9b step 2's GraphQL check: accept the locale=all candidates only when their count matches LocaleInputType's and every candidate formatName()s to an enum name.

func DescribeBlocksHelp

func DescribeBlocksHelp(interfaces []string, profile, fieldPath string) string

DescribeBlocksHelp is the text `pay describe <c> --field <f>` prints for an unresolved blocks field, verbatim from §7.10. It names only sources that can actually answer: no hint in PayCLI may name a `pay` command that cannot.

func FlagsFromObject

func FlagsFromObject(obj *IntroType) (drafts, trash, folders *bool)

FlagsFromObject reads the three capability flags that Payload encodes as fields of the object type. This is strictly better evidence than §7.5's REST probes — it is the schema rather than an inference from a 400 — and it is what keeps §7.1's four-request cold budget intact on a project whose GraphQL is reachable.

_status => drafts are enabled (verified: Page has it, Category does not) deletedAt => trash is enabled (verified: CrmContact has it, Page does not) folder => folders are enabled (verified: Media has it, Page does not)

func FormatName

func FormatName(slug string) string

FormatName reproduces Payload's formatName(): every character in -./+,()'[] and space becomes an underscore. It is what turns the slug `crm-contacts` into the Access field `crm_contacts`, and it is the only direction that is safe to compute — the inverse is ambiguous.

func HashShard

func HashShard(s *Shard) string

HashShard is the content hash written to fields_sha256. The hash covers the fields and their provenance but not the generation or the hash slot itself, so an unchanged schema keeps a stable hash across runs and §8.4's staleness check cannot be defeated by a new generation id.

func HumanizeField

func HumanizeField(name string) string

HumanizeField turns a camelCase or snake_case field name into an admin-ish label. It is a display aid only: Payload's real labels are not introspectable per field, and nothing behavioural keys off this string.

func IDString

func IDString(v any) string

IDString renders an observed id for the adapter inference without going through fmt, so a json.Number keeps its exact digits.

func IDTypeFromArg

func IDTypeFromArg(f *IntroField) (string, bool)

IDTypeFromArg reads §7.3's authoritative signal: the `id` argument of the singular Query field. Int means a relational adapter's integer primary key, String means Mongo ObjectIds or UUIDs.

This matters beyond validation: passing a non-castable id causes 500s, and on /duplicate it caused unintended writes.

func IDTypeOfJSON

func IDTypeOfJSON(v any) (string, bool)

IDTypeOfJSON types a decoded JSON id value. json.Number and float64 are both accepted because a decoder configured with UseNumber and one without must agree.

func InferDBAdapter

func InferDBAdapter(idTypes []string, sampleIDs []string) (adapter, source string)

InferDBAdapter is §7.11 source 3: the only inference the API supports, and it is deliberately weak. It never unlocks §9.3's client-side unsupported_operator block, which requires db_adapter_source == "configured" — because on MongoDB `all` maps to $all and works, so a guessed adapter would make PayCLI refuse a query the server would have answered.

idTypes are the resolved id_types of every collection; sampleIDs are any observed string ids.

func IsComplexityError

func IsComplexityError(msgs []string) bool

IsComplexityError reports §7.4's halve-the-batch trigger.

func IsInternal

func IsInternal(slug string) bool

IsInternal reports Payload's own collections, which §2's ground truth says to de-emphasise in default listings but keep reachable.

func IsJoinType

func IsJoinType(t *IntroType) bool

IsJoinType reports §7.4's "OBJECT with exactly {docs, hasNextPage, totalDocs}". The check is on the key set, not on the type name, because the name is {T}_{field} and a group field can share that shape of name.

func IsRelationshipType

func IsRelationshipType(t *IntroType) bool

IsRelationshipType reports §7.4's polymorphic wrapper: an OBJECT whose only fields are relationTo and value.

func IsUploadType

func IsUploadType(t *IntroType) bool

IsUploadType reports whether an OBJECT type carries all four upload markers.

func LocaleEnumNames

func LocaleEnumNames(schema *Schema) []string

LocaleEnumNames reads LocaleInputType's values. They are formatName()-mangled (en-US is exposed as en_US) and introspection never exposes the underlying value, so they are used for cross-validation and for locale_count only — never as codes (§7.9).

func LocaleUnverifiedHint

func LocaleUnverifiedHint(profile string) string

LocaleUnverifiedHint is §7.9c's hint, verbatim modulo the profile name.

func LocalesValidatable

func LocalesValidatable(source string) bool

LocalesValidatable reports §7.9c: --locale is checked client-side only when the codes are configured or were cross-validated from a locale=all sample. Anything else is passed through with the locale_unverified warning.

func LooksNumeric

func LooksNumeric(s string) bool

LooksNumeric reports whether a raw command-line id is all digits, used by callers that must decide whether a string id is safe to send to a number-typed collection.

func Mitigation

func Mitigation(code string) string

Mitigation returns §7.6's recorded mitigation for a limitation code.

func OperatorFailureCandidate

func OperatorFailureCandidate(operator string) bool

OperatorFailureCandidate reports whether an operator is one §7.11 will ever learn about. Callers use it to skip the memo lookup for the ninety per cent of queries that only use equals/greater_than/contains.

func OperatorsFor

func OperatorsFor(payloadType, operatorTypeName string) []string

OperatorsFor returns the operators Payload accepts for a field kind. A polymorphic relationship's where entry is a {T}_{f}_Relation input object taking relationTo and value rather than an operator set, which is why the operator type name is inspected as well as the kind.

func ParseDeleteMessage

func ParseDeleteMessage(message string) (string, bool)

ParseDeleteMessage extracts the plural label from a bulk-delete message. The second result is false for a translated, reworded or custom message.

func ResolveIDType

func ResolveIDType(in IDLadderInput) (idType, source string)

ResolveIDType walks §7.6's ladder, stopping at the first hit. The last rung is "unknown" — reachable on purpose, because a defaulted "number" would make PayCLI reject every valid 24-hex ObjectId with an error message that is a lie, and a defaulted "string" would silently lose the Postgres protection the check exists for.

func ResolveLocales

func ResolveLocales(in LocaleInput) (locales []string, source string, count *int)

ResolveLocales walks §7.9b, stopping at the first hit. The mangled GraphQL enum names are deliberately not returned as codes: en-US is exposed as en_US and es.419 as es_419, and introspection never exposes the underlying value, so codes harvested that way would be rejected by PayCLI's own validation if fed back.

func RouteMissingHint

func RouteMissingHint(graphQLPath, apiPath, graphQLRoute, graphQLPathSource string) string

RouteMissingHint is §7.6's path-specific hint. It fires only when the mode is route_missing, graphql_path was derived rather than set, and api_path is not the default — the exact situation in which a working GraphQL endpoint is being missed.

func SchemaSHA256

func SchemaSHA256(queryFieldNames []string) string

SchemaSHA256 is §8.4's Level-2 fingerprint: sha256 over the sorted field names of {__type(name:"Query"){fields{name}}}. It is byte-identical authenticated and unauthenticated, which makes it the only permission-free schema-version proxy the API offers.

func SortableKind

func SortableKind(payloadType string) bool

SortableKind reports §7.5's sortability heuristic. It is a heuristic, not a measurement, which is exactly why every field records sortable_confidence: "heuristic" and the manifest carries SORTABILITY_HEURISTIC.

func TitleField

func TitleField(shard *Shard) *string

TitleField picks the field `pay find` shows as a document's human label. It is a display aid with no behavioural consequence, so a heuristic is honest here in a way it would not be for a capability.

func TitleFromSlug

func TitleFromSlug(slug string) string

TitleFromSlug turns a slug into a title-cased label.

func TopologySHA256

func TopologySHA256(p TopologyProjection) string

TopologySHA256 is §8.4's Level-1 fingerprint. It detects a collection or global being added or removed, versions being enabled (readVersions appears) and access-control changes for this identity — for ~14 ms and 30 KB.

It cannot detect a field being added to an existing collection, because `fields` collapses to the boolean true for a privileged key. That blind spot is stated honestly here and is what Level 3 exists for.

func UnresolvedBlocksReason

func UnresolvedBlocksReason(fieldPath string) string

UnresolvedBlocksReason is §7.10's publishable_reason, verbatim.

func UploadFromObject

func UploadFromObject(obj *IntroType) *bool

UploadFromObject reports §7.4's upload signal on the entity's own type.

func UseAPIKey

func UseAPIKey(input *IntroType) *bool

UseAPIKey reports §7.3's use_api_key signal: the mutation input type carries both apiKey and enableAPIKey (verified live on mutationUserInput). It drives §5.4's `pay doctor` reporting and nothing else.

func WriteShapeFor

func WriteShapeFor(payloadType string, polymorphic, hasMany bool) *string

WriteShapeFor implements §7.8.2's closed enum. It is the only producer of the key, so a relationship can never be written without one.

Types

type AuthBootstrap

type AuthBootstrap struct {
	// Collection is the resolved auth-collection slug.
	Collection string
	// Source is "bootstrap" when Stage -1 ran, "configured" when it was
	// skipped because the slug was set, "cached" when it came from
	// auth-resolution.json.
	Source string
	// Candidates are every slug that survived the /init filter, in the order
	// they were tried. They are recorded in diagnostics.auth_candidates[].
	Candidates []string
	// Truncated reports that the candidate set was the anonymous slug list
	// alone.
	Truncated bool
	// Requests is the number of HTTP requests Stage -1 spent.
	Requests int
}

AuthBootstrap is Stage -1's result (§7.0).

type BlockResolution

type BlockResolution struct {
	Slugs  []string
	Source string
	// Reason is the plain-words explanation written into
	// publishable_reason when nothing resolved.
	Reason string
}

BlockResolution is the answer for one blocks field.

func ResolveBlocks

func ResolveBlocks(fieldPath string, interfaces []string, src BlockSources) BlockResolution

ResolveBlocks walks §7.10's order: profile map -> project source -> observed document values -> unresolved.

interfaces are the UNION possibleTypes (interfaceNames). They are never returned as slugs: feeding an interfaceName back to the API is exactly the silent-wrong-answer this function exists to prevent.

type BlockSources

type BlockSources struct {
	// Configured is the profile's blocks map, keyed by field path.
	Configured map[string][]string
	// ProjectSource is §7.10's local filesystem scan result — every slug:
	// literal found in an object that also has fields:. It is not keyed by
	// field because the scan cannot know which collection a block belongs to.
	ProjectSource []string
	// ProjectSourceFiles are the absolute paths each slug came from, recorded
	// in diagnostics.block_slug_files[].
	ProjectSourceFiles []string
	// Observed are blockType values seen in sampled documents, keyed by field
	// path. On a fresh project this yields nothing — verified that all 12 live
	// pages have layout: [].
	Observed map[string][]string
}

BlockSources are the four candidate origins of a blocks field's real blockType slugs, in §7.10's resolution order.

The API does not have the answer when interfaceName is set: verified that Page_Layout.possibleTypes is CallToActionBlock, ContentBlock, MediaBlock, ArchiveBlock, FormBlock while the real slugs are cta, content, mediaBlock, archive, formBlock. The answer exists on disk, in the project's own source.

type Capabilities

type Capabilities struct {
	GraphQL         GraphQLCapability `json:"graphql"`
	Localization    Localization      `json:"localization"`
	Reorder         *bool             `json:"reorder"`
	OG              *bool             `json:"og"`
	MethodOverride  *bool             `json:"method_override"`
	Jobs            Jobs              `json:"jobs"`
	Preferences     Preferences       `json:"preferences"`
	CustomEndpoints []string          `json:"custom_endpoints"`
}

Capabilities are the project-level facts.

type Collection

type Collection struct {
	Slug         string        `json:"slug"`
	Labels       Labels        `json:"labels"`
	GraphQL      *GraphQLNames `json:"graphql"`
	IDType       string        `json:"id_type"`
	IDTypeSource string        `json:"id_type_source"`
	Reachability string        `json:"reachability"`
	Internal     bool          `json:"internal"`

	Publishable       bool    `json:"publishable"`
	PublishableReason *string `json:"publishable_reason"`

	Flags       Flags       `json:"flags"`
	FlagsSource FlagsSource `json:"flags_source"`
	Permissions Permissions `json:"permissions"`

	Stats Stats `json:"stats"`

	FieldsCount  int    `json:"fields_count"`
	FieldsSHA256 string `json:"fields_sha256"`
	FieldsShard  string `json:"fields_shard"`
	FieldsSource string `json:"fields_source"`

	TitleField *string  `json:"title_field"`
	DateFields []string `json:"date_fields"`
	Warnings   []string `json:"warnings"`
}

Collection is one index entry.

type Diagnostics

type Diagnostics struct {
	Requests          int      `json:"requests"`
	ElapsedMS         int64    `json:"elapsed_ms"`
	Bytes             int64    `json:"bytes"`
	GraphQLBatches    int      `json:"graphql_batches"`
	Degraded          []string `json:"degraded"`
	Warnings          []string `json:"warnings"`
	CreatedAndDeleted []string `json:"created_and_deleted"`
	AuthCandidates    []string `json:"auth_candidates"`
	BlockSlugFiles    []string `json:"block_slug_files"`
}

Diagnostics is the cost and degradation record for one discovery run.

type Discoverer

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

Discoverer runs §7's pipeline.

func New

func New(opt Options) (*Discoverer, error)

New validates the options and returns a Discoverer.

func (*Discoverer) Run

func (d *Discoverer) Run(ctx context.Context) (*Result, error)

Run executes §7's pipeline and returns the manifest plus one field shard per entity.

The one hard failure is §7.6's: discovery_failed (exit 10) is reserved for the case where REST-only discovery also fails, i.e. GET {api_path}/access itself did not return parseable JSON. A working project must never be made unusable by a GraphQL-layer refusal.

type Field

type Field struct {
	Name        string  `json:"name"`
	Path        string  `json:"path"`
	GraphQLPath string  `json:"graphql_path"`
	Parent      *string `json:"parent"`

	PayloadType           string `json:"payload_type"`
	PayloadTypeConfidence string `json:"payload_type_confidence"`

	GraphQLType *string `json:"graphql_type"`
	JSONType    string  `json:"json_type"`

	Required       *bool  `json:"required"`
	RequiredSource string `json:"required_source"`

	HasMany         bool   `json:"has_many"`
	Localized       *bool  `json:"localized"`
	LocalizedSource string `json:"localized_source"`

	ReadOnly bool `json:"read_only"`

	Options       []string `json:"options"`
	OptionsSource string   `json:"options_source"`

	RelationTo       []string `json:"relation_to"`
	RelationToSource string   `json:"relation_to_source"`
	Polymorphic      bool     `json:"polymorphic"`

	// WriteShape is mandatory and non-null on every relationship, upload and
	// polymorphic field and null on every other kind. §9.10's --set coercion
	// reads exactly this key.
	WriteShape *string `json:"write_shape"`

	Queryable bool     `json:"queryable"`
	Operators []string `json:"operators"`

	Sortable           bool   `json:"sortable"`
	SortableConfidence string `json:"sortable_confidence"`

	HookMutated string `json:"hook_mutated"`

	Label string `json:"label"`
}

Field is one entry of a field shard.

**Every key below is mandatory on every entry**, using null (or the documented sentinels "n/a" / "unknown") where it does not apply. An agent parses a field entry without presence checks, so `jq '.fields[] | select(.required)'` is total. A unit test asserts the exact key set.

func NewField

func NewField(name, path string) Field

NewField returns a field with every tri-state key already set to its honest unknown value, so a producer can never emit a half-populated entry.

type Fingerprint

type Fingerprint struct {
	TopologySHA256 string    `json:"topology_sha256"`
	SchemaSHA256   string    `json:"schema_sha256"`
	CheckedAt      time.Time `json:"checked_at"`
	ServerIdentity string    `json:"server_identity"`
}

Fingerprint carries §8.4's two invalidation levels. Level 1 is identity-scoped and cheap; Level 2 is identity-independent and is the only permission-free schema-version proxy the API offers.

type Flags

type Flags struct {
	Upload            *bool `json:"upload"`
	Auth              *bool `json:"auth"`
	UseAPIKey         *bool `json:"use_api_key"`
	Versions          *bool `json:"versions"`
	Drafts            *bool `json:"drafts"`
	Trash             *bool `json:"trash"`
	Folders           *bool `json:"folders"`
	Duplicate         *bool `json:"duplicate"`
	Orderable         *bool `json:"orderable"`
	EndpointsDisabled *bool `json:"endpoints_disabled"`
}

Flags are §7.6's tri-state capability flags. nil means "never learned" and must never block an operation locally — the operation is attempted and the server's answer is classified after the fact.

type FlagsSource

type FlagsSource struct {
	Upload            string `json:"upload"`
	Auth              string `json:"auth"`
	UseAPIKey         string `json:"use_api_key"`
	Versions          string `json:"versions"`
	Drafts            string `json:"drafts"`
	Trash             string `json:"trash"`
	Folders           string `json:"folders"`
	Duplicate         string `json:"duplicate"`
	Orderable         string `json:"orderable"`
	EndpointsDisabled string `json:"endpoints_disabled"`
}

FlagsSource carries one provenance string per flag.

type Global

type Global struct {
	Slug         string        `json:"slug"`
	Labels       Labels        `json:"labels"`
	GraphQL      *GraphQLNames `json:"graphql"`
	Reachability string        `json:"reachability"`
	Internal     bool          `json:"internal"`

	Flags       GlobalFlags       `json:"flags"`
	FlagsSource GlobalFlagsSource `json:"flags_source"`
	Permissions GlobalPermissions `json:"permissions"`

	FieldsCount  int    `json:"fields_count"`
	FieldsSHA256 string `json:"fields_sha256"`
	FieldsShard  string `json:"fields_shard"`
	FieldsSource string `json:"fields_source"`

	Warnings []string `json:"warnings"`
}

Global is one global index entry. A global has no id, no bulk verbs and no trash, so the collection keys that cannot apply are absent rather than written as a misleading false.

type GlobalFlags

type GlobalFlags struct {
	Versions *bool `json:"versions"`
	Drafts   *bool `json:"drafts"`
}

GlobalFlags is the reduced flag set a global can have.

type GlobalFlagsSource

type GlobalFlagsSource struct {
	Versions string `json:"versions"`
	Drafts   string `json:"drafts"`
}

GlobalFlagsSource carries provenance for GlobalFlags.

type GlobalPermissions

type GlobalPermissions struct {
	Read         bool  `json:"read"`
	Update       bool  `json:"update"`
	ReadVersions bool  `json:"read_versions"`
	FieldLevel   *bool `json:"field_level"`
}

GlobalPermissions is the /api/access globals entry.

type GraphQLCapability

type GraphQLCapability struct {
	Mode          string `json:"mode"`
	Introspection *bool  `json:"introspection"`
	Path          string `json:"path"`
	Detail        string `json:"detail,omitempty"`
	Hint          string `json:"hint,omitempty"`
}

GraphQLCapability is §7.6's ladder result. Introspection is tri-state: on a route_missing or unreachable endpoint PayCLI learned nothing about it.

type GraphQLNames

type GraphQLNames struct {
	Singular string `json:"singular"`
	Plural   string `json:"plural,omitempty"`
	Count    string `json:"count,omitempty"`
	Source   string `json:"source"`
}

GraphQLNames is the slug <-> GraphQL type mapping recovered by §7.3's index-for-index zip of the Access type against Query.docAccess*. It is nil when no GraphQL name could be established, and the entity then runs REST-only.

type IDLadderInput

type IDLadderInput struct {
	// GraphQL is the Stage-1 answer, "" when GraphQL was unavailable.
	GraphQL string
	// SampleID is docs[0].id from GET /{slug}?limit=1&depth=0&select[id]=true.
	SampleID any
	// SampleIDPresent distinguishes "the collection is empty" from "id was
	// null".
	SampleIDPresent bool
	// VersionParentID is docs[0].parent from /{slug}/versions, which is the
	// *document* id while the version's own id is a different row.
	VersionParentID      any
	VersionParentPresent bool
	// Configured is the profile's id_type pin.
	Configured string
}

IDLadderInput is §7.6's REST id_type ladder, expressed as already-fetched samples so the ladder itself is pure and table-testable.

type Identity

type Identity struct {
	AuthMode             string  `json:"auth_mode"`
	AuthCollection       *string `json:"auth_collection"`
	AuthCollectionSource string  `json:"auth_collection_source"`
	UserID               any     `json:"user_id"`
	Verified             bool    `json:"verified"`
	CanAccessAdmin       bool    `json:"can_access_admin"`
	Strategy             string  `json:"strategy"`
	KeyFingerprint       string  `json:"key_fingerprint"`
}

Identity is Stage 0's verified answer to "who am I". The /me response body itself is never written to disk (§7.8.3) — it carries the API key in plaintext on a project with useAPIKey.

type IntroArg

type IntroArg struct {
	Name string   `json:"name"`
	Type *TypeRef `json:"type"`
}

IntroArg is one field argument.

type IntroEnumValue

type IntroEnumValue struct {
	Name string `json:"name"`
}

IntroEnumValue is one ENUM value.

type IntroField

type IntroField struct {
	Name string     `json:"name"`
	Args []IntroArg `json:"args"`
	Type *TypeRef   `json:"type"`
}

IntroField is one field of an OBJECT type.

func (*IntroField) Arg

func (f *IntroField) Arg(name string) (IntroArg, bool)

Arg returns a named argument.

func (*IntroField) HasArg

func (f *IntroField) HasArg(name string) bool

HasArg reports whether a named argument exists.

type IntroInputField

type IntroInputField struct {
	Name string   `json:"name"`
	Type *TypeRef `json:"type"`
}

IntroInputField is one field of an INPUT_OBJECT type.

type IntroType

type IntroType struct {
	Kind          string            `json:"kind"`
	Name          string            `json:"name"`
	Fields        []IntroField      `json:"fields"`
	InputFields   []IntroInputField `json:"inputFields"`
	EnumValues    []IntroEnumValue  `json:"enumValues"`
	PossibleTypes []IntroTypeName   `json:"possibleTypes"`
}

IntroType is one __type(name:) result.

func (*IntroType) EnumNames

func (t *IntroType) EnumNames() []string

EnumNames returns the ENUM value names.

func (*IntroType) FieldNames

func (t *IntroType) FieldNames() []string

FieldNames returns the OBJECT field names.

func (*IntroType) InputFieldNames

func (t *IntroType) InputFieldNames() []string

InputFieldNames returns the INPUT_OBJECT field names.

func (*IntroType) PossibleTypeNames

func (t *IntroType) PossibleTypeNames() []string

PossibleTypeNames returns a UNION's member names, preserving schema order — the *_RelationTo enum and its matching union list their members in the same order, and the polymorphic target mapping depends on that.

type IntroTypeName

type IntroTypeName struct {
	Name string `json:"name"`
	Kind string `json:"kind"`
}

IntroTypeName is a bare type reference in possibleTypes.

type Jobs

type Jobs struct {
	Collection  *string `json:"collection"`
	StatsGlobal *string `json:"stats_global"`
}

Jobs and Preferences name Payload's internal collections when this project has them, so a command never has to hardcode the slug.

type Kind

type Kind struct {
	PayloadType string
	Confidence  string
	JSONType    string
	HasMany     bool
	ReadOnly    bool
	Polymorphic bool

	Options       []string
	OptionsSource string

	RelationTo       []string
	RelationToSource string

	// Blocks is the UNION member list for a blocks field. These are
	// interfaceNames, NOT blockType slugs — §7.10 resolves the real slugs from
	// project source.
	BlockInterfaces []string

	// Children is the named type whose fields should be walked as nested
	// group/array entries, or "" when there are none.
	Children string
	// Leaves are type names this inference needs resolved to be complete.
	Leaves []string
}

Kind is the result of §7.4's field-kind inference table.

func InferKind

func InferKind(ref *TypeRef, ownerSingular, fieldName string, s *Schema) Kind

InferKind implements §7.4's table. It is pure: everything it needs is either in the TypeRef or already in the Schema, and anything missing is reported through Leaves so the caller can batch one more __type round.

ownerSingular is the entity's GraphQL type name, used only to recognise the {Owner}_{field}* naming convention.

type Labels

type Labels struct {
	Singular string `json:"singular"`
	Plural   string `json:"plural"`
	Source   string `json:"source"`
}

Labels are §7.5 probe 8's harvest. They are never blank, never null and never half-parsed: a failed parse falls back to the title-cased slug with source "derived".

func DeriveLabels

func DeriveLabels(slug, singularType string) Labels

DeriveLabels is the fallback: the title-cased slug with - and _ turned into spaces (crm-contacts -> "Crm Contacts"). Labels are never blank, never null and never half-parsed.

singularType is the entity's GraphQL type name when one is known (CrmContact); it yields a genuinely singular label without any pluralisation guessing, which §7.3 forbids. Without it the plural form is reused, because inventing a singular by stripping an "s" mangles real slugs (`cms` -> `Cm`).

func LabelsFromMessage

func LabelsFromMessage(slug, singularType, plural string) Labels

LabelsFromMessage builds the labels for a successful probe-8 parse. The singular still comes from the GraphQL type name or the slug, because the message only ever carries the plural.

type LearnedFailure

type LearnedFailure struct {
	Operator   string    `json:"operator"`
	Collection string    `json:"collection"`
	Evidence   string    `json:"evidence"`
	LearnedAt  time.Time `json:"learned_at"`
}

LearnedFailure is §7.11's reactive record: an operator that provably failed on a collection, so the next use warns before sending.

type Limitation

type Limitation struct {
	Code       string `json:"code"`
	Entity     string `json:"entity,omitempty"`
	Field      string `json:"field,omitempty"`
	Detail     string `json:"detail,omitempty"`
	Mitigation string `json:"mitigation"`
}

Limitation is a declared hole. Holes are declared, never guessed.

type LocaleAnalysis

type LocaleAnalysis struct {
	// Candidates are the true locale codes, or nil when the sample proved
	// nothing. These are real codes, unlike the formatName()-mangled names the
	// GraphQL LocaleInputType enum exposes.
	Candidates []string
	// Localized are the field names that came back as a locale map.
	Localized []string
	// NotLocalized are the field names that did not, on a document where at
	// least one sibling did — which is the only evidence REST offers for a
	// per-field false.
	NotLocalized []string
}

LocaleAnalysis is the result of reading a `?locale=all` sample.

func AnalyzeLocaleAll

func AnalyzeLocaleAll(docs []map[string]any) LocaleAnalysis

AnalyzeLocaleAll implements §7.9b step 2. A localized field comes back as {"en": …, "de": …} and the keys are true codes. The candidate set is the intersection of the key sets that appeared in at least two fields across the sampled documents — one field alone could be an ordinary group whose keys happen to look like codes.

type LocaleInput

type LocaleInput struct {
	// Configured is the profile's locales pin.
	Configured []string
	// Analysis is the locale=all sample result.
	Analysis LocaleAnalysis
	// EnumNames are LocaleInputType's enumValues, which are
	// formatName()-mangled and are therefore NEVER usable as codes.
	EnumNames []string
	// GraphQLReachable reports whether the cross-validation could run at all.
	GraphQLReachable bool
}

LocaleInput is §7.9b's ladder input, already fetched.

type Localization

type Localization struct {
	Enabled *bool `json:"enabled"`
	// EnabledSource is the story behind `enabled`, which §7.8.3 requires of
	// every derived fact: "graphql-arg" / "graphql-arg-absent" when the plural
	// Query field settled it, "locale-all" when a ?locale=all sample proved it
	// positively, "configured" when the profile pinned locale codes, and
	// "unknown" while it is still nil.
	EnabledSource string   `json:"enabled_source"`
	Locales       []string `json:"locales"`
	// Default is Payload's localization.defaultLocale. No API exposes it, so
	// it is null unless something actually established it — it is NEVER the
	// first entry of a sorted locale list (§21.3 rule 1: a defaulted value is
	// not a discovered fact).
	Default         *string `json:"default"`
	DefaultSource   string  `json:"default_source"`
	LocalesSource   string  `json:"locales_source"`
	FallbackDefault *string `json:"fallback_default"`
	LocaleCount     *int    `json:"locale_count"`
}

Localization models §7.9. `enabled` is tri-state because REST cannot tell a non-localised project from a localised one — `locale` is accepted and ignored when localisation is off.

type Manifest

type Manifest struct {
	ManifestVersion int       `json:"manifest_version"`
	Generation      string    `json:"generation"`
	CLIVersion      string    `json:"cli_version"`
	GeneratedAt     time.Time `json:"generated_at"`
	ExpiresAt       time.Time `json:"expires_at"`

	Meta        ManifestMeta `json:"meta"`
	Fingerprint Fingerprint  `json:"fingerprint"`
	Source      Source       `json:"source"`
	Identity    Identity     `json:"identity"`

	Capabilities Capabilities `json:"capabilities"`

	Collections []*Collection `json:"collections"`
	Globals     []*Global     `json:"globals"`

	Unreachable             []Unreachable         `json:"unreachable"`
	Limitations             []Limitation          `json:"limitations"`
	UnsupportedOperators    []UnsupportedOperator `json:"unsupported_operators"`
	LearnedOperatorFailures []LearnedFailure      `json:"learned_operator_failures"`

	Diagnostics Diagnostics `json:"diagnostics"`
}

Manifest is the discovery index — everything help text, `pay collections`, `pay explain --slim`, shell completion and target resolution need. It stays roughly 1 KB per collection regardless of field count, because the fields themselves live in per-entity Shards.

func DecodeManifest

func DecodeManifest(b []byte) (*Manifest, bool)

DecodeManifest parses a manifest index. A manifest_version other than the current one is reported as a miss by returning ok == false rather than an error, because §8.3 requires every cache read failure to be a miss.

func NewManifest

func NewManifest() *Manifest

NewManifest returns a manifest with every slice non-nil, so the JSON form never has a null where an agent expects an array.

func (*Manifest) AddLimitation

func (m *Manifest) AddLimitation(code, entity, field, detail string)

AddLimitation appends a limitation with its §7.6 mitigation, de-duplicating on (code, entity, field) so a per-collection hole is recorded once.

func (*Manifest) AddUnreachable

func (m *Manifest) AddUnreachable(slug, kind, reason, detail string)

AddUnreachable appends an unreachable entry, de-duplicating on slug+kind.

func (*Manifest) Collection

func (m *Manifest) Collection(slug string) (*Collection, bool)

Collection returns the index entry for a slug.

func (*Manifest) CollectionSlugs

func (m *Manifest) CollectionSlugs() []string

CollectionSlugs returns every collection slug in index order (key-sorted).

func (*Manifest) Degrade

func (m *Manifest) Degrade(stage string)

Degrade records that a stage fell back to REST-only discovery.

func (*Manifest) Global

func (m *Manifest) Global(slug string) (*Global, bool)

Global returns the index entry for a global slug.

func (*Manifest) GlobalSlugs

func (m *Manifest) GlobalSlugs() []string

GlobalSlugs returns every global slug in index order.

func (*Manifest) LearnedOperatorFailure

func (m *Manifest) LearnedOperatorFailure(operator, collection string) (LearnedFailure, bool)

LearnedOperatorFailure looks the memo up for one operator on one collection. It is deliberately NOT project-wide: `all` failing on crm-contacts says nothing about `all` on pages, and warning about the second because of the first would be the same "assume, do not learn" mistake §1 conflict 36 exists to correct.

func (*Manifest) RecordOperatorFailure

func (m *Manifest) RecordOperatorFailure(operator, collection, evidence string, at time.Time) bool

RecordOperatorFailure appends §7.11's reactive memo, or refreshes the record that is already there. It reports whether the manifest changed, so a caller can skip an expensive cache rewrite when it did not.

Records are keyed by (operator, collection): re-running the same failing query updates the evidence and the timestamp instead of growing the file without bound.

func (*Manifest) ResolveCollection

func (m *Manifest) ResolveCollection(slug string) (*Collection, error)

ResolveCollection looks a slug up and produces §11's collection_unknown with did-you-mean candidates when it is absent.

func (*Manifest) ResolveGlobal

func (m *Manifest) ResolveGlobal(slug string) (*Global, error)

ResolveGlobal is ResolveCollection for globals.

func (*Manifest) Revision

func (m *Manifest) Revision() string

Revision is a short, stable identifier for the manifest's content, used as the discovery revision stamped into generated skill docs (§14) and into meta.discovery_revision.

func (*Manifest) Sort

func (m *Manifest) Sort()

Sort orders the index deterministically: collections and globals by slug, and every declared hole by (code, entity, field). Two discovery runs against an unchanged server must produce byte-identical manifests apart from the timestamps, or the §8.4 fingerprints would flap.

type ManifestMeta

type ManifestMeta struct {
	Scope          string    `json:"scope"`
	Profiles       []string  `json:"profiles"`
	BaseURL        string    `json:"base_url"`
	APIPath        string    `json:"api_path"`
	GraphQLPath    string    `json:"graphql_path"`
	HeaderNames    []string  `json:"header_names"`
	KeyFingerprint string    `json:"key_fingerprint"`
	CreatedAt      time.Time `json:"created_at"`
	ConfirmedAt    time.Time `json:"confirmed_at"`
}

ManifestMeta binds the manifest to the cache scope that produced it (§8.1). It carries the sorted header *names* only — a [profiles.X.headers] value is never written to any artefact (§7.8.3).

type Options

type Options struct {
	Client *payload.Client

	BaseURL string

	APIPath       string
	APIPathSource string
	// GraphQLRoute is routes.graphQL, defaulting to /graphql. graphql_path is
	// DERIVED from api_path + this (§1 conflict 34), never an independent
	// literal.
	GraphQLRoute      string
	GraphQLPath       string
	GraphQLPathSource string

	AuthMode       string
	AuthCollection string
	// AuthCollectionSource is "configured" when the slug was pinned.
	AuthCollectionSource string
	// CachedAuthCollection is a slug already resolved for this
	// (normURL, keyFP) pair by a previous run (§7.0e). When set, Stage -1 is
	// skipped entirely.
	CachedAuthCollection string
	// CredentialAbsent reports anonymous operation. Stage 0's /me request is
	// then not made at all and discovery continues.
	CredentialAbsent bool
	KeyFingerprint   string

	Concurrency int

	Profile     string
	Profiles    []string
	ScopeKey    string
	HeaderNames []string

	CLIVersion string
	Generation string
	Now        func() time.Time
	TTL        time.Duration

	// Deep widens Stage 3 to every collection and adds the document census.
	Deep bool
	// AllowWriteProbes enables §7.7's empty-POST harvest. It creates a real
	// document in any collection with no required fields, so it is off by
	// default and the flag name is deliberately unpleasant.
	AllowWriteProbes bool
	// NoLabels skips §7.5 probe 8, which is the only DELETE verb discovery
	// ever issues.
	NoLabels bool
	// NoProbes disables Stage 3 entirely.
	NoProbes bool

	ConfiguredIDType  string
	ConfiguredLocales []string
	ConfiguredBlocks  map[string][]string
	CustomEndpoints   []string

	PayloadVersion       string
	PayloadVersionSource string
	DBAdapter            string
	DBAdapterSource      string

	// ProjectBlockSlugs are §7.10's `slug:` literals found on the local
	// filesystem, and ProjectBlockSlugFiles the absolute files they came from.
	ProjectBlockSlugs     []string
	ProjectBlockSlugFiles []string
	// ProjectAuthSlugs are auth-collection slug literals from the project's
	// own payload.config.ts, used only to widen Stage -1's candidate set.
	ProjectAuthSlugs []string
	// ProjectRoutes are `routes:` literals, named in the endpoint_not_payload
	// hint when nothing accepted.
	ProjectRoutes []string

	Logger *slog.Logger
}

Options configures one discovery run.

Everything here is a plain value rather than a config.* type on purpose: internal/discovery must not depend on the configuration layer's shape, and every project fact it needs (block slugs, the payload version, the db adapter) is produced by a local filesystem scan the caller already ran.

type Permissions

type Permissions struct {
	Create       bool  `json:"create"`
	Read         bool  `json:"read"`
	Update       bool  `json:"update"`
	Delete       bool  `json:"delete"`
	ReadVersions bool  `json:"read_versions"`
	Unlock       bool  `json:"unlock"`
	FieldLevel   *bool `json:"field_level"`
}

Permissions is the /api/access entry for this identity. field_level is tri-state: `fields` is the boolean true for a privileged key and an object for a restricted one, and it is NEVER used as a field-name source (§1 conflict 12).

type Preferences

type Preferences struct {
	Collection *string `json:"collection"`
}

Preferences names the preferences collection when present.

type Result

type Result struct {
	Manifest *Manifest
	// Shards are keyed by the cache-relative shard path, e.g.
	// "fields/pages.json", so a caller can hand them straight to cache.Set.
	Shards map[string]*Shard

	AuthCollection       string
	AuthCollectionSource string
	// AuthResolved reports that Stage -1 ran and produced a slug worth
	// persisting to auth-resolution.json.
	AuthResolved bool

	APIPath           string
	APIPathSource     string
	GraphQLPath       string
	GraphQLPathSource string

	Warnings []output.Warning
}

Result is one discovery run's output. Nothing here has been written to disk: persistence is internal/cache's job, and keeping it there means a failed write is a warning rather than a corrupt manifest.

type Schema

type Schema struct {
	// Types are the resolved __type results, keyed by type name.
	Types map[string]*IntroType
	// SlugBySingular maps a GraphQL singular type name back to its slug, so a
	// monomorphic relationship target is reported as a slug, never as a type
	// name an agent cannot pass on the command line.
	SlugBySingular map[string]string
}

Schema is everything the field-kind inference needs to resolve a named type without another round trip.

func (*Schema) Type

func (s *Schema) Type(name string) *IntroType

Type looks up a resolved type.

type Shard

type Shard struct {
	Generation string  `json:"generation"`
	Slug       string  `json:"slug"`
	SHA256     string  `json:"sha256"`
	Fields     []Field `json:"fields"`
	// JoinFields lists read-only join fields, which must be requested with
	// joins[field][limit] rather than selected like a normal field.
	JoinFields []string `json:"join_fields"`
	// Blocks maps a blocks field's path to its resolved blockType slugs. It is
	// null — not an empty map — when no source could resolve them, which is
	// the difference between "this project has no blocks" and "PayCLI does not
	// know" (§7.10).
	Blocks       map[string][]string `json:"blocks"`
	BlocksSource string              `json:"blocks_source"`
	// RequiredPaths is the flattened list of paths whose required is true.
	RequiredPaths []string `json:"required_paths"`
}

Shard is one entity's field schema — the artefact decoded only for the collection named on the command line (§8.2).

func NewShard

func NewShard(generation, slug string) *Shard

NewShard returns an empty shard for a slug.

func (*Shard) DateFields

func (s *Shard) DateFields() []string

DateFields returns every field whose payload_type is date, in shard order.

func (*Shard) Field

func (s *Shard) Field(path string) (Field, bool)

Field returns the entry for a dotted path.

func (*Shard) Finalize

func (s *Shard) Finalize()

Finalize sorts the derived lists, recomputes required_paths and stamps the content hash. It is the single place a shard becomes publishable, so a caller cannot forget the hash the index refers to.

func (*Shard) Paths

func (s *Shard) Paths() []string

Paths returns every field path in shard order.

func (*Shard) QueryablePaths

func (s *Shard) QueryablePaths() []string

QueryablePaths returns the subset that Payload's where parser accepts. It feeds query.Schema, whose nil-means-unknown contract this preserves: an empty shard yields nil, never an empty non-nil slice that would reject everything.

func (*Shard) SortablePaths

func (s *Shard) SortablePaths() []string

SortablePaths returns the sortable subset, with the same nil contract.

type Source

type Source struct {
	BaseURL              string  `json:"base_url"`
	APIPath              string  `json:"api_path"`
	APIPathSource        string  `json:"api_path_source"`
	GraphQLPath          string  `json:"graphql_path"`
	GraphQLPathSource    string  `json:"graphql_path_source"`
	PoweredBy            string  `json:"powered_by"`
	PayloadVersion       *string `json:"payload_version"`
	PayloadVersionSource string  `json:"payload_version_source"`
	DBAdapter            string  `json:"db_adapter"`
	DBAdapterSource      string  `json:"db_adapter_source"`
}

Source records how PayCLI reached the server and what it could learn about the project itself. payload_version and db_adapter are not obtainable from the API at all (§7.11), so both are nullable with provenance.

type Stats

type Stats struct {
	TotalDocs *int `json:"total_docs"`
	Sampled   bool `json:"sampled"`
}

Stats is the optional document census. total_docs is nil unless the caller asked for it (--deep): one count request per collection is 49 requests on the live project and §7.1's cold budget is four.

type TopologyProjection

type TopologyProjection struct {
	Collections map[string][]string `json:"c"`
	Globals     map[string][]string `json:"g"`
	Admin       bool                `json:"admin"`
}

TopologyProjection is §8.4's Level-1 normalised projection of GET /api/access. Only key *sets* are hashed, never values, so a permission flip still changes the fingerprint (a collapsed entry has a different key set) while transient data does not.

func ProjectTopology

func ProjectTopology(collections, globals map[string]any, canAccessAdmin bool) TopologyProjection

ProjectTopology builds the projection from a decoded /api/access body.

type TypeRef

type TypeRef struct {
	Kind   string   `json:"kind"`
	Name   string   `json:"name"`
	OfType *TypeRef `json:"ofType"`
}

TypeRef is a GraphQL type reference as introspection returns it: a chain of NON_NULL / LIST wrappers around one named type.

func (*TypeRef) Named

func (t *TypeRef) Named() (name, kind string, nonNull, list bool)

Named walks the wrapper chain and returns the innermost named type together with the two facts the wrappers carry: whether the outermost position is NON_NULL (required) and whether a LIST appeared anywhere (has_many).

type Unreachable

type Unreachable struct {
	Slug   string `json:"slug"`
	Kind   string `json:"kind"`
	Reason string `json:"reason"`
	Detail string `json:"detail"`
}

Unreachable records an entity PayCLI knows about but cannot fully use, with the evidence for why.

type UnsupportedOperator

type UnsupportedOperator struct {
	Operator   string `json:"operator"`
	Collection string `json:"collection,omitempty"`
	Reason     string `json:"reason"`
}

UnsupportedOperator is a pre-emptive client-side block. It is only ever populated when db_adapter_source == "configured" (§7.11): on MongoDB `all` maps to $all and works, so a guessed list would make PayCLI refuse a query the server would have answered.

Jump to

Keyboard shortcuts

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