aci

package
v0.14.1 Latest Latest
Warning

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

Go to latest
Published: Aug 25, 2026 License: Apache-2.0 Imports: 36 Imported by: 0

Documentation

Overview

Package aci implements a client for the Dstack private-ai-gateway "aci/1" confidential-inference protocol. This file defines the attestation failure reasons and the typed errors the package returns.

Every attestation-chain failure is reported as the provider-neutral, fail-closed *llm.AttestationError (defined in pkg/llm); for those this package only supplies the reason strings and thin constructors. The one exported error type it does introduce is UnpinnedPolicyError, which is deliberately NOT an *llm.AttestationError: it is a pre-attestation configuration refusal (the supplied Policy pins nothing) raised before any chain runs, not an attestation result.

Index

Constants

View Source
const SupportedAPIVersion = "aci/1"

SupportedAPIVersion is the only Dstack ACI wire api_version this client speaks. A report declaring any other version is rejected (reasonUnsupportedAPIVersion) — the version-drift tripwire.

Variables

This section is empty.

Functions

func Canonicalize

func Canonicalize(v Value) ([]byte, error)

Canonicalize emits the constrained-JCS canonical UTF-8 encoding of v. Object keys are sorted by UTF-16 code units at every level; numbers must be integers. It returns a *FloatNotAllowedError if any number violates the integer rule.

func CompactJSON

func CompactJSON(v Value) ([]byte, error)

CompactJSON emits the compact, order-preserving JSON encoding of v, matching serde_json::to_vec (preserve_order) byte-for-byte for the body-hash profile. Object keys are emitted in insertion order; non-integer floats are emitted via the Float variant. It returns a *NonFiniteFloatError for a NaN/Inf Float, a *FloatNotAllowedError for a non-integer Number (only Float carries fractionals), an *InvalidUTF8Error for a malformed string/key, or a *nilValueError for a nil Value.

func New

func New(baseURL, apiKey string, policy Policy, opts ...Option) (inference.Client, error)

New builds a Client and returns it as the inference.Client it implements. baseURL is the gateway origin (e.g. https://gateway.example); apiKey is the bearer token; policy is the attestation acceptance allow-list. Defaults — a timed *http.Client (TLS >= 1.2), time.Now, the live DCAP quote verifier, a crypto/rand nonce source, and a session cache wrapping the production attest — are applied first, then options override. Order matters: the cache binds the clock, so WithNow (when supplied) must take effect before the default cache is built; we therefore apply options that may need the clock after seeding the defaults, and (re)build the default cache only if no WithAttestFunc replaced it.

New FAILS CLOSED on an unpinned policy: if policy pins no acceptance set and did not explicitly opt out via UnpinnedPolicy(), New returns (nil, *UnpinnedPolicyError) BEFORE any network object is constructed, so attestation can never be silently accepted against an empty allow-list.

func Sha256Hex

func Sha256Hex(v Value) (string, error)

Sha256Hex returns the canonical digest as "sha256:" + lowercase hex of the SHA-256 of the canonical encoding of v.

func Sha256HexBytes

func Sha256HexBytes(b []byte) (string, error)

Sha256HexBytes returns "sha256:" + lowercase hex of the SHA-256 of the given bytes. It is the receipt body-hash function over an already-serialized compact body (sha256_hex in the Rust reference), distinct from Sha256Hex which hashes the CANONICAL (JCS) encoding of a Value. Callers pass CompactJSON(body) here.

func Sha256Raw

func Sha256Raw(v Value) ([32]byte, error)

Sha256Raw returns the raw SHA-256 digest of the canonical encoding of v.

func VerifyReceipt

func VerifyReceipt(receiptJSON []byte, verified *VerifiedReport, expect ReceiptExpect) error

VerifyReceipt verifies a signed §9 receipt against an attested VerifiedReport and the caller's expectations, enforcing every mandatory binding fail-closed.

It accepts the receipt JSON either bare ({...}) or wrapped ({"receipt": {...}}) — when a top-level "receipt" object key is present it is unwrapped first. It then verifies the signature (Task 4.1) and, only on a valid signature, the identity / request-body / response-hash / upstream bindings. The first failing check wins: identity, body, and response misses (and the signature) return the fail-closed *llm.AttestationError with reason receipt_invalid; an absent or non-matching upstream.verified event returns reason upstream_unverified. The returned error wraps a typed cause and never carries secret material. It returns nil only when ALL mandatory bindings hold.

Types

type Array

type Array []Value

Array is an ordered list of values; order is preserved on emit.

type Attestation

type Attestation struct {
	Vendor            string            `json:"vendor"`
	TEEType           string            `json:"tee_type"`
	Keyset            Keyset            `json:"workload_keyset"`
	ReportDataHex     string            `json:"report_data"`
	KeysetEndorsement KeysetEndorsement `json:"keyset_endorsement"`
	SourceProvenance  SourceProvenance  `json:"source_provenance"`
	Freshness         Freshness         `json:"freshness"`
	Evidence          Evidence          `json:"evidence"`
}

Attestation is the report's attestation envelope: the workload keyset, the report_data the quote binds to, the keyset endorsement, build provenance, freshness window, and the TEE evidence (quote + event log + custody).

type Bool

type Bool bool

Bool is a JSON boolean.

type Client

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

Client is the ACI confidential-inference client. It attests each model (cached), seals requests E2EE to the attested model key, POSTs them, opens the sealed response, and verifies the signed receipt before decoding — returning a provider-neutral *inference.Response only when every check passes.

It implements inference.Client. It is safe for concurrent use: the only mutable shared state is the session cache, which is internally synchronized.

Connection-binding note (fail-safe asymmetry): unlike the generic transport.Client — which binds one Endpoint and rejects a request whose Model.Provider/BaseURL differs with a pre-I/O *failure.ModelMismatchError — this client binds its gateway endpoint at construction (New's baseURL) and enforces model identity per request via TEE attestation. A provider/endpoint mismatch therefore surfaces as an *AttestationError (attestation cannot succeed against the wrong model/gateway), not a *failure.ModelMismatchError. This is fail-safe: the request is never sent when the check fails; only the error type differs.

func (*Client) Invoke

func (c *Client) Invoke(ctx context.Context, req inference.Request) (*inference.Response, error)

Invoke runs the buffer-until-verified flow for a single non-streaming chat request and returns the decoded *inference.Response, or a typed error and a NIL response on ANY failure. See the file header for the ordered stages; the decode runs ONLY after VerifyReceipt passes, so a verification failure never yields a partial response.

func (*Client) Stream

Stream runs the buffer-until-verified flow for a streaming chat request. It buffers the FULL sealed SSE response, opens + verifies it (receipt signature + request body_hash + wire_hash + upstream + the E2EE-authenticated open of every delta), and ONLY THEN returns a *stream.StreamReader replaying the already-verified opened deltas. On ANY failure it returns a typed error and a NIL reader, so the caller observes ZERO chunks — no unverified delta is ever observable.

Streaming SKIPS the receipt cleartext_hash check (RespBodyCleartext is nil): the client only sees the sealed WIRE bytes, not the raw upstream SSE framing the gateway hashed for cleartext_hash, so it cannot reconstruct that preimage. wire_hash (over the exact wire bytes) plus the per-delta AEAD open — each delta AAD-bound to the attested gateway — carry content authenticity instead.

type EventLogEntry

type EventLogEntry struct {
	IMR          uint32 `json:"imr"`
	EventType    uint32 `json:"event_type"`
	Digest       string `json:"digest"`
	Event        string `json:"event"`
	EventPayload string `json:"event_payload"`
}

EventLogEntry is one parsed event-log record. evidence.event_log is a raw JSON-in-string at THIS layer; this type is modeled now so Task 2.5 can decode the log into it. The serde rename type -> EventType applies here (Go has no "type" field name and the wire key stays "event_type").

type Evidence

type Evidence struct {
	Quote                string     `json:"quote"`
	QuoteReportData      string     `json:"quote_report_data"`
	EventLog             string     `json:"event_log"`
	VMConfig             string     `json:"vm_config"`
	KeyCustody           KeyCustody `json:"key_custody"`
	DownstreamTLSBinding TLSBinding `json:"downstream_tls_binding"`
}

Evidence is the TEE evidence: the TDX quote and its report_data, the raw (double-encoded) event log and VM config strings, the KMS key-custody chain, and the downstream TLS binding. quote/event_log/vm_config stay raw strings at this layer; later tasks parse them.

type Float

type Float float64

Float is a NON-integer JSON number used ONLY by the compact body serializer (CompactJSON), not by the JCS canonical path. Real chat-request bodies carry fractional sampling params (temperature/top_p, e.g. 0.7, 0.9), so the body hash must serialize floats; the constrained JCS profile forbids them. Float therefore lives in the shared Value union but is REJECTED by Canonicalize (it surfaces *FloatNotAllowedError there) and accepted only by CompactJSON. See body.go for the emission rules and their validated serde_json domain.

type FloatNotAllowedError

type FloatNotAllowedError struct {
	// Literal is the offending JSON number literal (e.g. "1.5", "1e5", "+1",
	// "01"). It is a numeric token only and carries no secret material.
	Literal string
}

FloatNotAllowedError reports a JSON number that is not an acceptable integer under the constrained JCS profile: a fraction, an exponent, a value outside the i64 ∪ u64 range, OR a literal that is not in canonical JSON-integer form (a leading '+' or a leading zero such as "01"). It carries the offending decimal literal so callers can inspect the cause; it is the Go analogue of the Rust CanonicalError variant FloatNotAllowed. It is an internal canonicalization failure, distinct from the protocol-level *llm.AttestationError reasons.

The grammar gate matters off the JSON-parse path: serde_json (and Go's json.Decoder) only ever produce grammar-valid number literals, so on the parse path the looser cases never occur. But a programmatically-constructed Number("+1") / Number("01") must fail closed rather than be silently normalized — a digest validator must reject anything it would not have emitted.

func (*FloatNotAllowedError) Error

func (e *FloatNotAllowedError) Error() string

type FloatOutOfDomainError

type FloatOutOfDomainError struct {
	// Value is the shortest decimal form of the offending magnitude (e.g.
	// "1e+16"). It is a numeric label and carries no payload bytes.
	Value string
}

FloatOutOfDomainError reports a finite Float whose magnitude falls in the range where serde_json (ryu) emits EXPONENT form rather than plain decimal — |x| >= 1e16 or 0 < |x| < 1e-5. In that range serde's spelling (e.g. "1e+16", "1e-6") cannot be reproduced by Go's shortest-float emitter without re-deriving ryu's exact exponent rules, so this serializer REFUSES to emit such a value rather than risk a wrong body hash. The realistic sampling-parameter domain (temperature/top_p ∈ [0,2]) lies entirely inside the decimal-form window, so this never fires on a real body; it is the runtime form of Task 1.3's "blocker #2: STOP and report" rule — a value here must be re-pinned against a fresh Rust vector before its hash can be trusted.

It carries the offending value's shortest decimal form (a numeric label, no secret material) so the caller can identify it.

func (*FloatOutOfDomainError) Error

func (e *FloatOutOfDomainError) Error() string

type Freshness

type Freshness struct {
	FetchedAt  int64 `json:"fetched_at"`
	StaleAfter int64 `json:"stale_after"`
}

Freshness is the report's validity window in Unix seconds. Both bounds fit int64 comfortably (they are wall-clock seconds, not the epoch sentinel).

type Int

type Int int64

Int is a signed-integer JSON number (the i64 arm of the number rule).

type InvalidUTF8Error

type InvalidUTF8Error struct {
	// Where names the location of the fault: "string" for a String value or
	// "object key" for a member key. It is a fixed label, never external data.
	Where string
}

InvalidUTF8Error reports a String value or object key that is not valid UTF-8. The emitter writes string bytes verbatim while the key sort decodes them via []rune (which folds invalid sequences to U+FFFD); those two views diverge on malformed input, so a digest validator must reject it rather than risk an order/encoding mismatch. On the JSON-parse path this never occurs (json guarantees valid UTF-8), but Task 1.3 builds String values programmatically.

It deliberately does NOT echo the offending bytes (they may be arbitrary, possibly secret-adjacent payload); it reports only where the fault was found.

func (*InvalidUTF8Error) Error

func (e *InvalidUTF8Error) Error() string

type KeyCustody

type KeyCustody struct {
	Provider string            `json:"provider"`
	Keys     []KeyCustodyEntry `json:"keys"`
}

KeyCustody is the KMS custody record: which provider holds the workload keys and, per key, the signature chain proving custody back to the KMS root.

type KeyCustodyEntry

type KeyCustodyEntry struct {
	Role           string   `json:"role"`
	Path           string   `json:"path"`
	Purpose        string   `json:"purpose"`
	Algo           string   `json:"algo"`
	PublicKeyHex   string   `json:"public_key"`
	SignatureChain []string `json:"signature_chain"`
}

KeyCustodyEntry is one custody record: the key's role/path/purpose, its algorithm and hex public key, and the signature chain (hex links) binding it to the KMS root. public_key -> PublicKeyHex applies the serde rename.

type KeyEntry

type KeyEntry struct {
	KeyID        string `json:"key_id"`
	Algo         string `json:"algo"`
	PublicKeyHex string `json:"public_key"`
}

KeyEntry is one entry in the receipt-signing and E2EE key lists: a key id, its algorithm, and the hex-encoded public key. The two lists share this shape, so they share the type. public_key -> PublicKeyHex applies the serde rename.

type Keyset

type Keyset struct {
	Identity           WorkloadIdentity `json:"workload_identity"`
	Epoch              KeysetEpoch      `json:"keyset_epoch"`
	ReceiptSigningKeys []KeyEntry       `json:"receipt_signing_keys"`
	E2EEPublicKeys     []KeyEntry       `json:"e2ee_public_keys"`
	TLSPublicKeys      []TLSBinding     `json:"tls_public_keys"`
}

Keyset is the workload's published key material: the workload identity key, the epoch (version + expiry), and the receipt/E2EE/TLS key lists the gateway uses to sign receipts, accept E2EE envelopes, and bind TLS endpoints.

type KeysetEndorsement

type KeysetEndorsement struct {
	Algo     string `json:"algo"`
	ValueHex string `json:"value"`
}

KeysetEndorsement is the signature over the workload keyset by the KMS root. value -> ValueHex applies the serde rename.

type KeysetEpoch

type KeysetEpoch struct {
	Version  uint64 `json:"version"`
	NotAfter uint64 `json:"not_after"`
}

KeysetEpoch is the keyset's monotonic epoch and its expiry. NotAfter is uint64 because the fixture carries 2^64-1 (a "never expires" sentinel) which does not fit int64. Version is uint64 for symmetry with the unsigned epoch.

type NonFiniteFloatError

type NonFiniteFloatError struct {
	// Repr is the Go string form of the non-finite value ("NaN", "+Inf",
	// "-Inf"). It is a fixed numeric label and carries no payload bytes.
	Repr string
}

NonFiniteFloatError reports a Float value that is NaN or ±Inf. JSON has no representation for non-finite numbers (serde_json's Number cannot even hold them — Number::from_f64 returns None), so a non-finite Float can never appear in a real body and must fail closed rather than emit a non-JSON token. It is typed (per CLAUDE.md's no-bare-error rule) and carries the Go textual form of the offending value ("NaN", "+Inf", "-Inf") — a numeric label, never secret material.

func (*NonFiniteFloatError) Error

func (e *NonFiniteFloatError) Error() string

type Null

type Null struct{}

Null is the JSON null literal.

type Number

type Number json.Number

Number is an integer JSON number carried verbatim as the source decimal literal (e.g. produced by the parser, which defers the i64-then-u64 check to emit time). Canonicalize validates it: a non-integer or out-of-range literal yields a *FloatNotAllowedError, matching the Rust reference's as_i64/as_u64 fallthrough.

type Object

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

Object is a JSON object stored as ordered key/value pairs in insertion order. JCS key sorting is applied by the emitter, not by this type, so the same Object can be emitted in insertion order by a non-sorting serializer.

func NewObject

func NewObject() *Object

NewObject returns an empty Object ready for ordered Set calls.

func (*Object) KeyAt

func (o *Object) KeyAt(i int) string

KeyAt returns the key of the i-th member in insertion order. It panics on an out-of-range index, matching slice-indexing semantics.

func (*Object) Len

func (o *Object) Len() int

Len reports the number of members.

func (*Object) Set

func (o *Object) Set(key string, val Value) *Object

Set inserts or updates a (key, value) pair and returns the Object so calls can chain. It follows IndexMap / serde_json(preserve_order) semantics: a new key is appended (preserving insertion order); an existing key keeps its original position and has its value overwritten (last value wins). This guarantees an Object never holds duplicate keys, so the emitter can never produce invalid JSON — the same parity serde_json yields on the deserialize path. The canonical emitter is still responsible for the UTF-16 key SORT; Set only dedups.

func (*Object) ValueAt

func (o *Object) ValueAt(i int) Value

ValueAt returns the value of the i-th member in insertion order.

type Option

type Option func(*Client)

Option configures a Client at construction. Only the knobs production or tests actually use are exposed; nothing speculative.

func WithAttestFunc

func WithAttestFunc(attest attestFunc) Option

WithAttestFunc overrides the per-model attestation the session cache wraps. Tests supply a fake that returns a synthetic *VerifiedReport, bypassing the real attestation chain (already covered by Phase-2 tests).

func WithHTTPDoer

func WithHTTPDoer(d httpDoer) Option

WithHTTPDoer sets the HTTP transport (the gateway seam). Tests inject a fake gateway; production uses the default timed *http.Client.

func WithNonceFunc

func WithNonceFunc(newNonce func() string) Option

WithNonceFunc overrides the attestation nonce source (the report_data binding nonce). Production draws 32 crypto/rand bytes; tests can pin it.

func WithNow

func WithNow(now func() time.Time) Option

WithNow sets the wall clock used for seal/open timestamps and the session-cache TTL. Tests inject a fixed clock for determinism.

func WithQuoteVerifier

func WithQuoteVerifier(v quoteVerifier) Option

WithQuoteVerifier overrides the DCAP quote verifier seam used by the live attest path (offline tests of the production attest wiring).

type Policy

type Policy struct {
	// AcceptedWorkloadIDs is the set of accepted workload_id strings
	// ("sha256:<hex>"). Commonly left EMPTY: the workload_id is a digest of the
	// keyset, which ROTATES with each keyset epoch, so pinning it would reject a
	// legitimately rotated keyset. Trust is instead anchored by app-id +
	// provenance + KMS-root custody (the fields below), which are stable across
	// rotations. Populate this only to pin one exact keyset.
	AcceptedWorkloadIDs map[string]struct{}

	// AcceptedSourceProvenance is the set of accepted {repo_url, repo_commit}
	// build-provenance pairs (rtmr.go's ProvenanceKey). Checked in step 5.
	AcceptedSourceProvenance map[ProvenanceKey]struct{}

	// AcceptedAppIDs is the set of accepted workload app-ids as lowercase hex of
	// the app-id bytes (encoding/hex form). Checked in step 5.
	AcceptedAppIDs map[string]struct{}

	// AcceptedKMSRootPubKeys is the set of accepted KMS-root public keys as
	// compressed-SEC1 hex. Checked in step 7 (key custody recovers the root and
	// requires membership).
	AcceptedKMSRootPubKeys map[string]struct{}
	// contains filtered or unexported fields
}

Policy is the attestation acceptance allow-list set: which app-ids, source provenances, KMS roots, and workload_ids VerifyReport will accept. A nil/empty field skips its check ("when configured"); a non-empty field requires membership. At the LOW-LEVEL verifyReport (verify.go) a zero value Policy{} still accepts any genuine, quote-backed report (no allow-listing) — that is the mechanism. The PUBLIC entry points (New and VerifyReport both call requireAcceptable) instead FAIL CLOSED: an unpinned Policy is rejected with *UnpinnedPolicyError unless the caller opts in with UnpinnedPolicy(). Callers narrow trust by populating fields.

func UnpinnedPolicy

func UnpinnedPolicy() Policy

UnpinnedPolicy returns a Policy that explicitly accepts any cryptographically genuine report WITHOUT allow-listing a workload. This is a deliberate, greppable opt-out of the fail-closed default; prefer a pinned Policy in production.

func (Policy) IsPinned

func (p Policy) IsPinned() bool

IsPinned reports whether the policy pins at least one acceptance set. Any non-empty set means VerifyReport runs at least one allow-list check (steps 5/7/9), so the policy is not fail-open. AcceptedWorkloadIDs counts: a workload-ID-only policy is the strictest pin (one exact keyset digest).

type ProvenanceKey

type ProvenanceKey struct {
	RepoURL    string
	RepoCommit string
}

ProvenanceKey is the comparable allow-list key for source provenance: the {repo_url, repo_commit} pair. A Policy's AcceptedSourceProvenance set uses this exact key form so checkProvenancePolicy can do a single map lookup. It is a struct (not a joined string) so neither field can be confused with a delimiter inside the other. It is exported so callers outside this package (a provider's pinned-policy constructor) can populate AcceptedSourceProvenance directly.

type PublicKey

type PublicKey struct {
	Algo         string `json:"algo"`
	PublicKeyHex string `json:"public_key"`
}

PublicKey is an algorithm-tagged public key (algo + hex-encoded key bytes). public_key -> PublicKeyHex applies the serde rename; the wire key stays "public_key".

type Receipt

type Receipt struct {
	APIVersion           string           `json:"api_version"`
	ReceiptID            string           `json:"receipt_id"`
	ChatID               *string          `json:"chat_id"`
	WorkloadID           string           `json:"workload_id"`
	WorkloadKeysetDigest string           `json:"workload_keyset_digest"`
	Endpoint             string           `json:"endpoint"`
	Method               string           `json:"method"`
	ServedAt             uint64           `json:"served_at"`
	EventLog             []ReceiptEvent   `json:"event_log"`
	Signature            ReceiptSignature `json:"signature"`
}

Receipt is a parsed ACI §9 receipt: the per-request signed event log plus its metadata and the signature over the canonical projection. ChatID is a pointer because the wire field is nullable (Rust Option<String>): nil when absent/null, a string when present — the two project to different canonical bytes. ServedAt and ReceiptEvent.Seq are uint64 (the Rust u64 arm).

func ParseReceipt

func ParseReceipt(data []byte) (*Receipt, error)

ParseReceipt decodes a §9 receipt from its wire JSON. It first decodes the fixed receipt fields, then re-decodes each event-log entry to split the fixed seq/type members from the free-form (type-specific) fields, which are retained as raw JSON for the canonical projection. Any stdlib decode failure is wrapped in a typed *receiptParseError.

type ReceiptEvent

type ReceiptEvent struct {
	Seq       uint64          `json:"seq"`
	EventType string          `json:"type"`
	Fields    json.RawMessage `json:"-"`
}

ReceiptEvent is one event in the receipt's event log. The wire shape flattens seq and type to the top of the object alongside any type-specific fields; this struct keeps Fields as RAW JSON (an object), parsed into a jcs.Value only when building the canonical projection. EventType reads the wire key "type".

type ReceiptExpect

type ReceiptExpect struct {
	Endpoint          string
	Method            string
	Vendor            string
	ModelID           string
	ReqBody           []byte
	RespBodyCleartext []byte
	RespWireBytes     []byte
}

ReceiptExpect carries the caller-supplied expectations VerifyReceipt binds the receipt against. The request body is supplied as the ALREADY-COMPACT serde_json bytes (the caller — Task 5.2 — computes CompactJSON over the decrypted cleartext request body); VerifyReceipt hashes those bytes directly with Sha256HexBytes. The response cleartext and wire fields are likewise already-serialized bytes.

RespBodyCleartext is nil to SKIP the cleartext_hash check. The non-streaming caller (Invoke) supplies it, so cleartext_hash is enforced; the STREAMING caller (Stream — Task 5.3) passes nil, because the client only sees the sealed WIRE bytes and cannot reconstruct the raw upstream framing the gateway hashed for cleartext_hash. For streaming, wire_hash (over the exact wire bytes) plus the E2EE-authenticated open of each delta cover content authenticity instead.

RespWireBytes is nil to SKIP the wire_hash check (the wire-bytes hash is optional in the design doc). The two response-hash checks are independent: a nil preimage skips only its own hash. Vendor is the upstream provider TYPE (the design doc's "vendor" maps to the event's `provider` field); an empty Vendor skips the provider check, binding the upstream event on result + model_id alone.

type ReceiptSignature

type ReceiptSignature struct {
	Algo     string `json:"algo"`
	KeyID    string `json:"key_id"`
	ValueHex string `json:"value"`
}

ReceiptSignature is the receipt's signature block: the algorithm, the key id naming the attested receipt-signing key, and the hex signature value. ValueHex reads the wire key "value"; it is OMITTED from the canonical projection (it is the signed thing).

type Report

type Report struct {
	APIVersion           string              `json:"api_version"`
	WorkloadID           string              `json:"workload_id"`
	WorkloadKeysetDigest string              `json:"workload_keyset_digest"`
	Attestation          Attestation         `json:"attestation"`
	ServiceCapabilities  ServiceCapabilities `json:"service_capabilities"`
}

Report is a decoded Dstack ACI attestation report. It is the root of the model every Phase 2 verification step consumes.

func ParseReport

func ParseReport(data []byte) (*Report, error)

ParseReport decodes a Dstack ACI report document into *Report and enforces the api_version tripwire.

It unmarshals the whole document first, THEN guards api_version: a malformed body therefore yields a typed *reportParseError (the decode failure) rather than being masked by a version check, and a well-formed body with the wrong version yields the fail-closed *llm.AttestationError carrying reasonUnsupportedAPIVersion (via errUnsupportedAPIVersion). On any error the returned *Report is nil. The bytes are untrusted external input; decoding is the validation boundary and is performed before the value is handed back.

type ServiceCapabilities

type ServiceCapabilities struct {
	SupportedE2EEVersions []string `json:"supported_e2ee_versions"`
}

ServiceCapabilities advertises which E2EE protocol versions the gateway supports. Versions are wire strings ("2").

type SourceProvenance

type SourceProvenance struct {
	RepoURL         string  `json:"repo_url"`
	RepoCommit      string  `json:"repo_commit"`
	ImageDigest     *string `json:"image_digest"`
	ImageProvenance *string `json:"image_provenance"`
}

SourceProvenance is the build provenance of the workload image. ImageDigest and ImageProvenance are null in the fixture, so both are optional pointers.

type String

type String string

String is a JSON string value.

type TLSBinding

type TLSBinding struct {
	Domain        string `json:"domain"`
	SPKISHA256Hex string `json:"spki_sha256"`
}

TLSBinding binds a domain to the SHA-256 of its TLS SubjectPublicKeyInfo. It is reused by both tls_public_keys and evidence.downstream_tls_binding. spki_sha256 -> SPKISHA256Hex applies the serde rename.

type Uint

type Uint uint64

Uint is an unsigned-integer JSON number (the u64 tail above the i64 range).

type UnpinnedPolicyError

type UnpinnedPolicyError struct{}

UnpinnedPolicyError is returned by the public aci entry points when a Policy pins no acceptance set and did not explicitly opt into unpinned mode via UnpinnedPolicy(). Fail-secure: attestation refuses to run allow-list-free unless the caller asks for it. Like apiVersionMismatchError it uses a pointer receiver and is returned as *UnpinnedPolicyError so callers can errors.As it.

func (*UnpinnedPolicyError) Error

func (e *UnpinnedPolicyError) Error() string

type Value

type Value interface {
	// contains filtered or unexported methods
}

Value is the sealed union of canonicalizable JSON values. The unexported marker method keeps the set of concrete types closed to this package.

func ParseBodyValue

func ParseBodyValue(data []byte) (Value, error)

ParseBodyValue decodes a request-body JSON document into the ordered Value union, ACCEPTING non-integer floats (mapped to the Float variant). It is the body-hash counterpart to the strict, float-REJECTING ParseValue: bodies carry fractional sampling params that the JCS profile forbids, so the body path needs its own float-tolerant parse. Objects keep insertion order; integers go to Int/Uint (or stay as the verbatim Number where they exceed neither range); non-integer numbers become Float (the wire literal parsed to f64, exactly as serde_json does before re-emitting via ryu). All decoder failures are wrapped in the same typed *parseError; surplus tokens yield *trailingDataError.

Sharing the parse spine with ParseValue (rather than forking it) would couple the strict and tolerant policies; instead this is a small, separate parser that reuses only the leaf helpers, keeping each policy single-responsibility.

func ParseValue

func ParseValue(data []byte) (Value, error)

ParseValue decodes JSON bytes into the ordered Value union. Objects keep insertion order (Go maps would lose it), numbers are validated against the integer rule on the way in, and the only any in the package lives here at the json.Token boundary, immediately narrowed to a concrete Value. All stdlib decoder failures are wrapped in *parseError (Unwrap-able); the integer rule yields *FloatNotAllowedError; surplus tokens yield *trailingDataError.

type VerifiedReport

type VerifiedReport struct {
	// WorkloadID is the validated workload_id ("sha256:<hex>"), recomputed and
	// matched in step 2.
	WorkloadID string
	// WorkloadKeysetDigest is the validated workload_keyset_digest
	// ("sha256:<hex>"), recomputed and matched in step 2.
	WorkloadKeysetDigest string
	// Keyset is the full validated keyset: identity, epoch, and the
	// receipt-signing / E2EE / TLS key lists Phase 3/4/5 consume. It is the value
	// whose digest step 2 verified, so its keys are attestation-backed.
	Keyset Keyset
}

VerifiedReport is the validated output of a passing attestation chain: the workload identity (workload_id + keyset digest) and the FULL validated keyset. Phase 3 (E2EE sealing) reads Keyset.E2EEPublicKeys; Phase 4 (receipt verification) reads Keyset.ReceiptSigningKeys; both get their key ids, algos, and public keys from the embedded Keyset, which is the exact value whose digest was recomputed and matched in step 2 (so the keys are the attested ones, not merely claimed). Returned only on a fully successful chain; nil on any failure.

func VerifyReport

func VerifyReport(reportJSON []byte, nonce *string, now time.Time, policy Policy) (*VerifiedReport, error)

VerifyReport runs the full Dstack ACI attestation chain (steps 1–9) against reportJSON with the LIVE DCAP quote verifier and returns the validated *VerifiedReport, or the first failing step's typed *llm.AttestationError. nonce is the report_data binding nonce (nil if none was sent), now is the wall clock for the freshness check, and policy is the acceptance allow-list.

FAIL CLOSED. This is a public entry point, so the fail-closed policy gate runs FIRST: a policy that pins no acceptance set is rejected with *UnpinnedPolicyError BEFORE the chain runs and BEFORE any parse or network collateral fetch — unless the caller explicitly opts into genuineness-only verification by passing UnpinnedPolicy(). Supply a pinned Policy (a provider package ships a preset) or UnpinnedPolicy() to opt out; a bare Policy{} is refused.

On an acceptable policy it delegates to the unexported verifyReport with defaultQuoteVerifier, so step 4 fetches Intel collateral over the bounded HTTPS-only getter — meaning this entry point requires network access and cannot run fully offline. verifyReport remains the UNGUARDED low-level runner shared with the client's per-request attest path (already gated at construction by aci.New) and offline tests, which inject a fake quote seam.

type WorkloadIdentity

type WorkloadIdentity struct {
	PublicKey PublicKey `json:"public_key"`
	Subject   *string   `json:"subject"`
}

WorkloadIdentity is the workload's identity key plus an optional subject. The fixture's subject is null, so Subject is a pointer that stays nil when absent.

Jump to

Keyboard shortcuts

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