apicontract_local

package
v0.20.0 Latest Latest
Warning

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

Go to latest
Published: Sep 9, 2026 License: Apache-2.0 Imports: 11 Imported by: 0

Documentation

Overview

Package apicontract_local is datatug-cli's OWN provisional copy of the shared JSON types core-investigation-loop's normative transport appendix (datatug/datatug spec/features/core-investigation-loop/api-contract.md) defines: Scope, SourceRef, TypedValue, Fact, Limitation, Binding, Result, Candidate, ExecutionRequest, the agent-info envelope and the structured error envelope.

Plan task 12 (S64) assigns the real schema authority to a new github.com/datatug/datatug-core/pkg/apicontract package (lane S64a, concurrent with this one) that will also freeze the shared fixtures (pkg/apicontract/fixtures/*.json) both server and client consume. This package exists ONLY because that module tag did not exist yet when this stream needed to start writing server code against the contract — see the stream brief (s64-server-task12.md): "start by writing the server against the contract markdown with your own provisional types under pkg/apicontract_local ... and when the lead tells you the core tag exists (or v0.25.0 shows up on `git ls-remote --tags`), replace the local types with the module's and consume its fixtures."

Every type below is named, field-tagged and shaped to be byte-identical to the appendix's own examples, so that swap is a mechanical find-and-replace of the import path (github.com/datatug/datatug-cli/pkg/ apicontract_local -> github.com/datatug/datatug-core/pkg/apicontract) plus deleting this package, not a redesign. Fixtures under ./fixtures mirror the appendix's "Acceptance and migration" section: empty arrays, null/ false/zero/large-integer values, ambiguous/missing inputs, omitted unauthorized metadata, HTTP failure and every error code.

Index

Constants

This section is empty.

Variables

This section is empty.

Functions

func NewRequestID

func NewRequestID() string

NewRequestID returns a fresh opaque correlation ID: 16 random bytes, hex encoded (api-contract.md requires every error to carry one; "Server logs record a correlation ID and operation, not raw facts").

func StatusFor

func StatusFor(code ErrorCode) int

StatusFor maps code to its exact appendix HTTP status. An unknown code (should never happen — every Error this package constructs uses one of the constants above) maps to 500, matching handleError's existing unknown-error fallback.

Types

type AgentInfoCapabilities

type AgentInfoCapabilities struct {
	ProtectedQueries bool `json:"protectedQueries"`
	OpaqueReadOnly   bool `json:"opaqueReadOnly"`
}

AgentInfoCapabilities is agent-info's capabilities object.

type AgentInfoPrincipal

type AgentInfoPrincipal struct {
	ID     string   `json:"id"`
	Roles  []string `json:"roles"`
	Groups []string `json:"groups"`
}

AgentInfoPrincipal is agent-info's principal object.

type AgentInfoProject

type AgentInfoProject struct {
	ID string `json:"id"`
}

AgentInfoProject is one entry of agent-info's projects array.

type AgentInfoResponse

type AgentInfoResponse struct {
	Version           string                `json:"version"`
	Principal         AgentInfoPrincipal    `json:"principal"`
	SecurityContextID string                `json:"securityContextId"`
	Projects          []AgentInfoProject    `json:"projects"`
	Capabilities      AgentInfoCapabilities `json:"capabilities"`
}

AgentInfoResponse is GET agent-info's exact success envelope (api-contract.md "Endpoint table").

type ApplicableRequest

type ApplicableRequest struct {
	Scope
	Values []Fact `json:"values"`
}

ApplicableRequest is POST queries/applicable's body.

type ApplicableResponse

type ApplicableResponse struct {
	Applicable []Candidate `json:"applicable"`
	NotYet     []Candidate `json:"notYet"`
}

ApplicableResponse is POST queries/applicable's exact success envelope.

type Binding

type Binding struct {
	ParameterID    string         `json:"parameterId"`
	Value          TypedValue     `json:"value"`
	Origin         BindingOrigin  `json:"origin"`
	OriginEvidence OriginEvidence `json:"originEvidence"`
	FactID         string         `json:"factId,omitempty"`
}

Binding is one query parameter's resolved value and its provenance.

type BindingOrigin

type BindingOrigin string

BindingOrigin is Binding.Origin's closed set — one more member than FactOrigin ("default", a declared query default).

const (
	BindingOriginSelection BindingOrigin = "selection"
	BindingOriginContext   BindingOrigin = "context"
	BindingOriginManual    BindingOrigin = "manual"
	BindingOriginDefault   BindingOrigin = "default"
)

type BindingOriginInput

type BindingOriginInput struct {
	ParameterID string        `json:"parameterId"`
	Origin      BindingOrigin `json:"origin"`
	FactID      string        `json:"factId,omitempty"`
}

BindingOriginInput is one ExecutionRequest.BindingOrigins entry.

type Candidate

type Candidate struct {
	QueryID        string                `json:"queryId"`
	Targets        []CandidateTarget     `json:"targets"`
	SelectedSource string                `json:"selectedSource,omitempty"`
	Bindings       []Binding             `json:"bindings"`
	Chain          []CandidateChainEntry `json:"chain"`
	Missing        []string              `json:"missing"`
	Ambiguous      []CandidateAmbiguous  `json:"ambiguous"`
	State          CandidateState        `json:"state"`
}

Candidate is one library query's resolution state against the caller's available facts (api-contract.md "Endpoint table" / the Candidate type).

type CandidateAmbiguous

type CandidateAmbiguous struct {
	ParameterID string   `json:"parameterId"`
	FactIDs     []string `json:"factIds"`
}

CandidateAmbiguous names one parameter more than one distinct automatic value could bind, with the conflicting fact IDs.

type CandidateChainEntry

type CandidateChainEntry struct {
	ParameterID string `json:"parameterId"`
	FactID      string `json:"factId,omitempty"`
	Explanation string `json:"explanation"`
}

CandidateChainEntry explains how one parameter resolved (or why it is still missing).

type CandidateState

type CandidateState string

CandidateState is Candidate.State's closed set.

const (
	StateRunnable          CandidateState = "runnable"
	StateNeedsInput        CandidateState = "needs-input"
	StateNeedsTarget       CandidateState = "needs-target"
	StateSourceUnavailable CandidateState = "source-unavailable"
)

type CandidateTarget

type CandidateTarget struct {
	Source string `json:"source"`
	Label  string `json:"label"`
}

CandidateTarget is one authorized eligible target option on a Candidate.

type Column

type Column struct {
	Name string `json:"name"`
	Type string `json:"type"`
}

Column is one Result.Recordset column.

type ColumnEntry

type ColumnEntry struct {
	Column     string           `json:"column"`
	Entity     string           `json:"entity"`
	Field      string           `json:"field"`
	Provenance ColumnProvenance `json:"provenance"`
}

ColumnEntry is one GET semantic/columns response entry. Unmapped columns are omitted entirely (never present with empty entity/field).

type ColumnProvenance

type ColumnProvenance string

ColumnProvenance is ColumnEntry.Provenance's closed set.

const (
	ColumnDeclared ColumnProvenance = "declared"
	ColumnInferred ColumnProvenance = "inferred"
)

type ColumnsResponse

type ColumnsResponse struct {
	Columns []ColumnEntry `json:"columns"`
}

ColumnsResponse is GET semantic/columns's exact success envelope.

type Error

type Error struct {
	Code      ErrorCode
	Message   string
	Field     string
	RequestID string
	Targets   []CandidateTarget
}

Error is the Go error type every contract-facing handler in this repo returns instead of a bare error, carrying everything ErrorEnvelope needs. It implements the error interface so it composes with errors.Is/As and fmt.Errorf(%w) like any other error.

func NewAccessDenied

func NewAccessDenied(message string) *Error

NewAccessDenied builds an ACCESS_DENIED error. message MUST name the policy and MUST NOT echo the hidden field/value it protects.

func NewAmbiguousBinding

func NewAmbiguousBinding(field, message string) *Error

NewAmbiguousBinding builds an AMBIGUOUS_BINDING error naming the conflicting parameter.

func NewError

func NewError(code ErrorCode, message, field string) *Error

NewError builds an *Error, stamping a fresh RequestID. Use the code- specific constructors below where one exists; this is the general escape hatch (e.g. NOT_FOUND, UNAUTHENTICATED).

func NewInvalidRequest

func NewInvalidRequest(field, message string) *Error

NewInvalidRequest builds an INVALID_REQUEST error naming field.

func NewMissingParameter

func NewMissingParameter(field string) *Error

NewMissingParameter builds a MISSING_PARAMETER error naming the parameter field.

func NewNotFound

func NewNotFound(message string) *Error

NewNotFound builds a 404 NOT_FOUND error.

func NewResponseTooLarge

func NewResponseTooLarge(message string) *Error

NewResponseTooLarge builds a 413 RESPONSE_TOO_LARGE error.

func NewSourceUnavailable

func NewSourceUnavailable(message string) *Error

NewSourceUnavailable builds a 503 SOURCE_UNAVAILABLE error.

func NewStaleContext

func NewStaleContext(message string) *Error

NewStaleContext builds a 409 STALE_CONTEXT error.

func NewTargetRequired

func NewTargetRequired(message string, targets []CandidateTarget) *Error

NewTargetRequired builds a 400 TARGET_REQUIRED error carrying the authorized eligible target options (api-contract.md: "TARGET_REQUIRED errors return the same authorized target options in error.targets, never hidden source IDs").

func NewTimeout

func NewTimeout(message string) *Error

NewTimeout builds a 504 TIMEOUT error.

func NewTypeMismatch

func NewTypeMismatch(field, message string) *Error

NewTypeMismatch builds a TYPE_MISMATCH error naming field.

func NewUnsupportedProtectedExecution

func NewUnsupportedProtectedExecution(message string) *Error

NewUnsupportedProtectedExecution builds a 403 UNSUPPORTED_PROTECTED_EXECUTION error (REQ:opaque-sql-limitation).

func (*Error) Envelope

func (e *Error) Envelope() ErrorEnvelope

Envelope converts e into the wire ErrorEnvelope, stamping a fresh RequestID if none was set (defensive: every constructor above already sets one).

func (*Error) Error

func (e *Error) Error() string

type ErrorBody

type ErrorBody struct {
	Code      ErrorCode         `json:"code"`
	Message   string            `json:"message"`
	Field     string            `json:"field,omitempty"`
	RequestID string            `json:"requestId"`
	Targets   []CandidateTarget `json:"targets,omitempty"`
}

ErrorBody is the inner "error" object of the appendix's error envelope. Targets is populated ONLY for TARGET_REQUIRED (api-contract.md: "Only TARGET_REQUIRED may include authorized target options").

type ErrorCode

type ErrorCode string

ErrorCode is the closed set api-contract.md "Security and errors" names.

const (
	CodeInvalidRequest           ErrorCode = "INVALID_REQUEST"
	CodeTypeMismatch             ErrorCode = "TYPE_MISMATCH"
	CodeMissingParameter         ErrorCode = "MISSING_PARAMETER"
	CodeAmbiguousBinding         ErrorCode = "AMBIGUOUS_BINDING"
	CodeTargetRequired           ErrorCode = "TARGET_REQUIRED"
	CodeUnauthenticated          ErrorCode = "UNAUTHENTICATED"
	CodeAccessDenied             ErrorCode = "ACCESS_DENIED"
	CodeUnsupportedProtectedExec ErrorCode = "UNSUPPORTED_PROTECTED_EXECUTION"
	CodeNotFound                 ErrorCode = "NOT_FOUND"
	CodeStaleContext             ErrorCode = "STALE_CONTEXT"
	CodeResponseTooLarge         ErrorCode = "RESPONSE_TOO_LARGE"
	CodeSourceUnavailable        ErrorCode = "SOURCE_UNAVAILABLE"
	CodeTimeout                  ErrorCode = "TIMEOUT"
)

The exact error codes and their HTTP statuses (api-contract.md's own table): "HTTP 400 covers INVALID_REQUEST, TYPE_MISMATCH, MISSING_PARAMETER, AMBIGUOUS_BINDING and TARGET_REQUIRED; 401 UNAUTHENTICATED; 403 ACCESS_DENIED or UNSUPPORTED_PROTECTED_EXECUTION; 404 NOT_FOUND (including invisible resources); 409 STALE_CONTEXT; 413 RESPONSE_TOO_LARGE; 503 SOURCE_UNAVAILABLE; 504 TIMEOUT."

type ErrorEnvelope

type ErrorEnvelope struct {
	Error ErrorBody `json:"error"`
}

ErrorEnvelope is the appendix's exact error response shape: {"error": {...}}.

type ExecutionMode

type ExecutionMode string

ExecutionMode is Result.Provenance.Mode / ExecutionRequest.Mode's closed set.

const (
	ModeLive     ExecutionMode = "live"
	ModeSnapshot ExecutionMode = "snapshot"
)

type ExecutionProfile

type ExecutionProfile string

ExecutionProfile is Result.Provenance.ExecutionProfile's closed set (REQ:opaque-sql-limitation).

const (
	ProfileProtected        ExecutionProfile = "protected"
	ProfileOpaquePrivileged ExecutionProfile = "opaque-privileged"
)

type ExecutionRequest

type ExecutionRequest struct {
	Project           string                `json:"project"`
	Environment       string                `json:"environment"`
	SecurityContextID string                `json:"securityContextId"`
	Source            string                `json:"source,omitempty"`
	QueryID           string                `json:"queryId,omitempty"`
	DTQL              string                `json:"dtql,omitempty"`
	Parameters        map[string]TypedValue `json:"parameters"`
	BindingOrigins    []BindingOriginInput  `json:"bindingOrigins"`
	Mode              ExecutionMode         `json:"mode"`
	SnapshotID        string                `json:"snapshotId,omitempty"`
	Limit             int                   `json:"limit,omitempty"`
}

ExecutionRequest is POST exec/run_query's body (api-contract.md "Endpoint table" / the ExecutionRequest type). Exactly one of QueryID/DTQL is required; Source is required for ad-hoc DTQL and optional for a saved query obeying target resolution.

type Fact

type Fact struct {
	ID       string       `json:"id"`
	Entity   string       `json:"entity"`
	Field    string       `json:"field"`
	Value    TypedValue   `json:"value"`
	Origin   FactOrigin   `json:"origin"`
	Physical *PhysicalRef `json:"physical,omitempty"`
	Mapping  string       `json:"mapping,omitempty"` // "declared" | "inferred"
	Enabled  bool         `json:"enabled"`
}

Fact is one typed semantic value the browser holds (a grid selection, an Investigation Context item): api-contract.md "Shared JSON types".

type FactOrigin

type FactOrigin string

FactOrigin is Fact.Origin's closed set.

const (
	OriginSelection FactOrigin = "selection"
	OriginContext   FactOrigin = "context"
	OriginManual    FactOrigin = "manual"
)

type Limitation

type Limitation struct {
	Policy        string   `json:"policy"`
	RowsFiltered  bool     `json:"rowsFiltered"`
	HiddenColumns []string `json:"hiddenColumns"`
}

Limitation reports one applied restriction — never the number or values of the rows/columns it restricted (api-contract.md "Shared JSON types").

type OriginEvidence

type OriginEvidence string

OriginEvidence distinguishes a client-reported binding origin from an actual server-attested default (api-contract.md "Endpoint table" / "Binding and context behavior": "The UI must not present client origins as server-attested provenance").

const (
	EvidenceServerDefault  OriginEvidence = "server-default"
	EvidenceClientReported OriginEvidence = "client-reported"
)

type PhysicalRef

type PhysicalRef struct {
	Source     string `json:"source"`
	Collection string `json:"collection"`
	Column     string `json:"column"`
}

PhysicalRef is one physical column an EntityField maps to.

type Provenance

type Provenance struct {
	Source           string           `json:"source"`
	Collection       string           `json:"collection,omitempty"`
	QueryID          string           `json:"queryId,omitempty"`
	Mode             ExecutionMode    `json:"mode"`
	SnapshotID       string           `json:"snapshotId,omitempty"`
	ObservedAt       string           `json:"observedAt"`
	ExecutionProfile ExecutionProfile `json:"executionProfile"`
}

Provenance is Result.Provenance.

type Recordset

type Recordset struct {
	Columns []Column       `json:"columns"`
	Rows    [][]TypedValue `json:"rows"`
}

Recordset is Result.Recordset: ordered columns, and rows with exactly one TypedValue per returned column, in column order.

type RelatedEntry

type RelatedEntry struct {
	LookupID   string `json:"lookupId"`
	Label      string `json:"label"`
	Source     string `json:"source"`
	Collection string `json:"collection"`
	Count      *int   `json:"count"`
}

RelatedEntry is one POST semantic/related response entry. Count is omitted (null) when an exact authorized count could not be obtained.

type RelatedRequest

type RelatedRequest struct {
	Scope
	Fact  Fact `json:"fact"`
	Limit int  `json:"limit,omitempty"`
}

RelatedRequest is POST semantic/related's body (beyond the embedded Scope, sent as query params per api-contract.md's GET/POST placement rule... except semantic/related is itself POST — see api-contract.md "Endpoint table": "POST semantic/related | Scope + {fact:Fact,limit?}"). Scope travels in the JSON body here (unlike semantic/columns' GET, which carries it in the URL query) because the whole request is a POST body.

type RelatedResponse

type RelatedResponse struct {
	Related   []RelatedEntry `json:"related"`
	Truncated bool           `json:"truncated"`
}

RelatedResponse is POST semantic/related's exact success envelope.

type RelatedRowsRequest

type RelatedRowsRequest struct {
	Scope
	LookupID string     `json:"lookupId"`
	Value    TypedValue `json:"value"`
	Limit    int        `json:"limit,omitempty"`
}

RelatedRowsRequest is POST semantic/related/rows's body.

type Result

type Result struct {
	Recordset       Recordset    `json:"recordset"`
	Limitations     []Limitation `json:"limitations"`
	BindingsApplied []Binding    `json:"bindingsApplied"`
	Provenance      Provenance   `json:"provenance"`
	Truncated       bool         `json:"truncated"`
}

Result is the appendix's one execution/read response shape, returned by exec/run_query and semantic/related/rows.

type Scope

type Scope struct {
	Project           string `json:"project"`
	Environment       string `json:"environment"`
	SecurityContextID string `json:"securityContextId"`
}

Scope identifies one authorized request context: api-contract.md "Scope and identity". Project and Environment are required, nonempty project- local IDs; SecurityContextID is the opaque staleness token agent-info issues (never authentication/authority on its own).

type SourceRef

type SourceRef struct {
	Source     string `json:"source"`
	Collection string `json:"collection"`
}

SourceRef identifies one collection within one registered project source: api-contract.md "Scope and identity". Source is a stable project-local ID resolved through the project's source registry for Scope.Environment — see pkg/api's resolver — never a filesystem path, URL or credential.

type TypedValue

type TypedValue struct {
	Type ValueType
	// Text carries the wire "value" for string, integer, decimal, date and
	// datetime — every variant whose JSON value is itself a string.
	Text string
	// Number carries the wire "value" for TypeNumber: a finite float64,
	// exactly representable as a JSON number (never NaN/Inf).
	Number float64
	// Bool carries the wire "value" for TypeBoolean.
	Bool bool
}

TypedValue is the appendix's {type, value} discriminated union. Exactly one of the typed fields below is meaningful, selected by Type; the zero TypedValue{} is invalid (no valid Type) and must never be marshalled or returned to a caller — build one with the New* constructors or Null().

func FromGoValue

func FromGoValue(v any, declaredType string) (TypedValue, error)

FromGoValue converts a Go-native value (as returned by secureread's row data: typically int64/float64/string/bool/[]byte/time.Time/nil from a database/sql driver) into a TypedValue. declaredType is an optional hint (a QueryDef/Recordset column's own declared "type" string: "integer", "number", "string", "boolean", "date", "datetime", "decimal") consulted first; when it is empty or does not match v's runtime shape, the type is inferred from v itself. This is Phase 1's own engineering rule for shaping a database row into the appendix's typed Result.recordset — the appendix defines the wire TypedValue shape but not how a server derives one from a driver value, and no richer per-column type registry exists yet in this codebase to consult instead.

func NewBooleanValue

func NewBooleanValue(b bool) TypedValue

NewBooleanValue builds a TypeBoolean TypedValue.

func NewDateTimeValue

func NewDateTimeValue(t time.Time) TypedValue

NewDateTimeValue builds a TypeDateTime TypedValue, normalizing t to UTC and formatting it RFC3339 with a literal "Z" offset.

func NewDateValue

func NewDateValue(text string) (TypedValue, error)

NewDateValue builds a TypeDate TypedValue from a YYYY-MM-DD string, validating it is a real calendar date.

func NewDecimalValue

func NewDecimalValue(text string) (TypedValue, error)

NewDecimalValue builds a TypeDecimal TypedValue from already-formatted canonical decimal text, validating it.

func NewIntegerText

func NewIntegerText(text string) (TypedValue, error)

NewIntegerText builds a TypeInteger TypedValue from already-decimal text (e.g. one too large for int64), validating it is canonical.

func NewIntegerValue

func NewIntegerValue(n int64) TypedValue

NewIntegerValue builds a TypeInteger TypedValue from a Go int64, always producing the appendix's canonical decimal text (no leading zeros, "-0" normalized to "0").

func NewNumberValue

func NewNumberValue(n float64) TypedValue

NewNumberValue builds a TypeNumber TypedValue. n must be finite.

func NewStringValue

func NewStringValue(s string) TypedValue

NewStringValue builds a TypeString TypedValue.

func NullValue

func NullValue() TypedValue

NullValue is the TypeNull TypedValue.

func (TypedValue) Equal

func (v TypedValue) Equal(other TypedValue) bool

Equal reports whether v and other carry the same type AND the same value — AC:typed-context-isolation's "distinct typed values 5 and \"5\"... types stay distinct": TypedValue{integer,"5"} and TypedValue{string,"5"} are never Equal, regardless of how their Go-native forms might compare.

func (TypedValue) IsZero

func (v TypedValue) IsZero() bool

IsZero reports whether v is the zero TypedValue (no Type set) — never a valid wire value, only a "not built yet" sentinel for Go callers.

func (TypedValue) MarshalJSON

func (v TypedValue) MarshalJSON() ([]byte, error)

MarshalJSON writes v as {"type":..., "value":...}, exactly the appendix's shape. An invalid Type or a non-finite Number is refused rather than silently emitting a malformed envelope.

func (TypedValue) Native

func (v TypedValue) Native() (any, error)

Native converts v to a plain Go value suitable for driver-bound query parameter binding (dal.Param substitution -> database/sql args) and for JSON-free comparisons: string/date/datetime stay string, integer parses to int64 (returning an error for a canonical value too large for int64 — Phase 1's demo parameters never need bigint), decimal stays string (preserve precision; no lossy float64 conversion), number is float64, boolean is bool, null is nil.

func (*TypedValue) UnmarshalJSON

func (v *TypedValue) UnmarshalJSON(data []byte) error

UnmarshalJSON parses {"type":..., "value":...}, validating value against type's exact wire rules (canonical integer/decimal text, calendar date, RFC3339-UTC datetime, finite number). Unknown fields alongside type/value are rejected: the appendix requires "exact field names", and this is also where a client-supplied principal/role hidden in a values payload would first be caught (defense in depth; the real guard is server-side scope validation elsewhere).

type ValueType

type ValueType string

ValueType is TypedValue's discriminant, exactly the appendix's eight variants (api-contract.md "Shared JSON types").

const (
	TypeString   ValueType = "string"
	TypeNumber   ValueType = "number"
	TypeInteger  ValueType = "integer"
	TypeDecimal  ValueType = "decimal"
	TypeBoolean  ValueType = "boolean"
	TypeDate     ValueType = "date"
	TypeDateTime ValueType = "datetime"
	TypeNull     ValueType = "null"
)

The eight TypedValue variants. Every other string is invalid.

Jump to

Keyboard shortcuts

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