Skip to content

Commit 9e951e4

Browse files
authored
docs(sdk): add compiled examples and close the integrator contract (NVIDIA#2253)
Signed-off-by: Mark Chmarny <mark@chmarny.com>
1 parent 7227511 commit 9e951e4

4 files changed

Lines changed: 767 additions & 4 deletions

File tree

docs/integrator/go-library.md

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,44 @@ You _may_ also import `pkg/*` subpackages directly, but their APIs are
2222
not covered by the same stability guarantees — see the [public API
2323
surface](./public-api.md) for the details.
2424

25+
## Runnable examples
26+
27+
Each facade entry point below has a compiled counterpart in
28+
[`pkg/client/v1`](https://pkg.go.dev/github.com/NVIDIA/aicr/pkg/client/v1#pkg-examples).
29+
They are ordinary Go example functions, so `go test` builds them on every
30+
change — a facade change that breaks one of these fails in AICR's tree rather
31+
than in yours.
32+
33+
| Example | Covers | Runs |
34+
|---|---|---|
35+
| `Example` | Quick start: client, resolve from criteria | yes |
36+
| `Example_errorCodes` | Matching structured error codes | yes |
37+
| `Example_bundleAndVerify` | Resolve → bundle → verify, hermetically | yes |
38+
| `Example_trustLevels` | The accepted trust levels, and their ordering trap | yes |
39+
| `Example_criteriaDimensions` | The coverage dimensions | yes |
40+
| `Example_committedConfig` | `AICRConfig` → source → catalog → criteria, in the required order | no |
41+
| `Example_resolveFromSnapshot` | `LoadSnapshot` plus snapshot criteria relaxation | no |
42+
| `ExampleClient_LoadRecipe` | Reading a previously emitted recipe | no |
43+
| `ExampleClient_CollectSnapshot` | Capturing cluster state via the snapshotter Job | no |
44+
| `ExampleClient_ValidateState` | Selecting validation phases, and `--no-cluster` mode | no |
45+
| `ExampleClient_RecipeDigest` | The digest a CI staleness gate compares | no |
46+
| `ExampleClient_VerifyEvidence` | Evidence verification and exit classes | no |
47+
| `ExampleClient_VerifyCatalog` / `ExampleClient_SignCatalog` | Checking and producing the catalog signature | no |
48+
| `ExampleClient_PublishEvidence` | Signing and pushing an evidence bundle | no |
49+
| `ExampleVerifyBinaryAttestation` | Proving a binary came from NVIDIA CI | no |
50+
51+
**What "runs" means, and what it does not.** Examples marked *yes* print an
52+
`Output:` block, so `go test` executes them and asserts the output. The rest
53+
are **compiled but not executed** — they need a cluster, a registry, a signing
54+
identity, or files that belong to your environment. Compilation still pins
55+
every signature, field name, and option they touch, so a renamed method or a
56+
dropped field breaks the build; it does not prove those flows behave
57+
correctly at runtime.
58+
59+
The guarantee covers the examples, not this page. Prose here can still drift,
60+
and short illustrative snippets outside the table are not compiled — prefer
61+
copying from the examples, which are complete and known to build.
62+
2563
## Installing
2664

2765
```bash
@@ -168,9 +206,15 @@ capture instead of re-reading afterwards.
168206
snapCtx, cancelSnap := context.WithTimeout(context.Background(), 10*time.Minute)
169207
defer cancelSnap()
170208
snap, err := client.CollectSnapshot(snapCtx, &aicr.AgentConfig{
171-
Kubeconfig: "/path/to/target-kubeconfig",
209+
Kubeconfig: "/path/to/target-kubeconfig",
210+
// Namespace, Image, JobName, and ServiceAccountName are all required on
211+
// the SDK path. Only Namespace is validated; the rest are copied straight
212+
// into the Job and RBAC objects, so an empty value becomes an empty
213+
// metadata.name or container image that the API server rejects. The CLI
214+
// defaults them from its own flags, which the facade does not share.
172215
Namespace: "aicr-snapshot",
173216
Image: "ghcr.io/nvidia/aicr:v0.11.1",
217+
JobName: "aicr-snapshot",
174218
ServiceAccountName: "aicr-agent",
175219
Timeout: 5 * time.Minute,
176220
Cleanup: true,
@@ -906,7 +950,9 @@ concern the caller owns, so both can run unattended from a server.
906950
## Errors
907951

908952
All errors returned by the facade are `*pkg/errors.StructuredError`
909-
values carrying an `ErrorCode`. Use `errors.As` to inspect:
953+
values carrying an `ErrorCode`. Match on the code with `errors.Is`
954+
`StructuredError.Is` reports a match when the target is a `StructuredError`
955+
with the same code, so this works through wrap chains:
910956

911957
```go
912958
import (
@@ -916,9 +962,25 @@ import (
916962
)
917963

918964
_, err := client.ResolveRecipe(ctx, req)
919-
var se *aicrerrors.StructuredError
920-
if stderrors.As(err, &se) && se.Code == aicrerrors.ErrCodeInvalidRequest {
965+
switch {
966+
case stderrors.Is(err, aicrerrors.New(aicrerrors.ErrCodeInvalidRequest, "")):
921967
// handle invalid input
968+
case stderrors.Is(err, aicrerrors.New(aicrerrors.ErrCodeNotFound, "")):
969+
// handle missing recipe
970+
}
971+
```
972+
973+
Runnable version: [`Example_errorCodes`](https://pkg.go.dev/github.com/NVIDIA/aicr/pkg/client/v1#example-package-ErrorCodes).
974+
975+
Reach for `errors.As` only when you need the error's *payload* rather than
976+
its class — `se.Context`, which carries structured detail such as a coverage
977+
failure's `uncovered` dimensions:
978+
979+
```go
980+
var se *aicrerrors.StructuredError
981+
if stderrors.As(err, &se) {
982+
uncovered := se.Context["uncovered"]
983+
_ = uncovered
922984
}
923985
```
924986

@@ -966,6 +1028,38 @@ Per-operation caps:
9661028
Passing a `nil` `context.Context` returns `ErrCodeInvalidRequest`. Use
9671029
`context.Background()` (or a deadline-bounded child) for unbounded callers.
9681030

1031+
## The integrator contract
1032+
1033+
Four commitments, stated plainly, so you know what you are depending on.
1034+
1035+
**Import `pkg/client/v1`. That is the contract.** Everything else under
1036+
`pkg/*` stays importable, but only this package is compatibility-reviewed, and
1037+
only its exported surface is checked by the API-diff gate on every PR. The
1038+
[stability matrix](./public-api.md#stability-tiers) tiers each package;
1039+
`Internal` packages will break you on upgrade.
1040+
1041+
**When the facade is missing something, tell us instead of routing around
1042+
it.** [Open an issue](https://github.com/NVIDIA/aicr/issues/new/choose)
1043+
describing the capability. Reaching into an evolving subpackage works today
1044+
and is the thing most likely to break you later, and we would rather extend
1045+
the facade — that is how `LoadSnapshot`, `LoadConfig`, and the verification
1046+
surface all arrived. Where this guide shows a deliberate escape hatch (the
1047+
fingerprint step under [Criteria relaxation](#criteria-relaxation-on-the-snapshot-path)),
1048+
it says so and explains the coupling you are accepting.
1049+
1050+
**Breaking changes are detected, not merely intended.** `tools/api-diff`
1051+
compares the facade and its transparent-alias targets against the last release
1052+
on every PR; an incompatible change fails CI and requires a recorded, reviewed
1053+
exception. That is a mechanical guarantee, not a policy promise — but note
1054+
what it does *not* cover: behavior. A function keeping its signature while
1055+
changing what it does passes the gate.
1056+
1057+
**The examples are compiled.** Every entry in the [examples
1058+
table](#runnable-examples) builds in AICR's own test suite, so a facade change
1059+
that invalidates one fails here first. Scope that honestly: it covers those
1060+
examples, not this page's prose or its shorter inline snippets, and for the
1061+
majority it proves compilation rather than runtime behavior.
1062+
9691063
## Compatibility
9701064

9711065
Today AICR is pre-1.0. Under Go module versioning, a v0 minor release may

docs/integrator/public-api.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,10 @@ unrelated exports in their evolving packages remain free to change.
7878
| `aicr.BundleArtifact` | `*pkg/bundler/result.Output` | Deliberate transparent alias. Callers receive the complete bundler result, including `HasErrors`, without a lossy projection. |
7979
| `aicr.OIDCResolveOptions` | `pkg/bundler/attestation.ResolveOptions` | Deliberate transparent alias. CLI and server callers can pass the same late-bound signing inputs used by the attestation resolver. |
8080
| `aicr.CriteriaRegistry` | `pkg/recipe.CriteriaRegistry` | Documented transparent alias. Kept as an alias intentionally because the registry is behavior-rich (`ParseService`, `SetStrict`, `Values`, ...) and carries mutable per-`DataProvider` state — wrapping would either break the per-Client identity coupling (copy) or add no isolation win over the alias (pointer). |
81+
| `aicr.BundleVerifyReport` | `pkg/bundler/verifier.VerifyResult` | Deliberate transparent alias. Callers receive the verifier's complete report (`TrustLevel`, `TrustReason`, `Errors`, per-stage booleans) rather than a projection that would have to grow with every new check. |
82+
| `aicr.EvidenceVerification` | `pkg/evidence/verifier.VerifyResult` | Deliberate transparent alias, for the same reason, and so `aicr.RenderEvidenceJSON` / `RenderEvidenceMarkdown` render the identical document `aicr evidence verify` emits. |
83+
| `aicr.Config` | `pkg/config.AICRConfig` | **Facade-owned wrapper**, not an alias: Go cannot attach methods to another package's type through an alias, and the config document's ~30 nested types would otherwise freeze under the API-diff gate. Obtain one from `aicr.LoadConfig` (file or HTTP(S) URL) or `aicr.WrapConfig`. Its methods DERIVE options (`BundleVerifyOptions`, `RecipeSource`, `RecipeCriteria`, `RecipeResolveOptions`, ...) rather than applying them, so caller overrides stay explicit; `Unwrap()` reaches the raw document for fields the facade does not project. |
84+
| `aicr.CriteriaDimension`, `aicr.DimensionService` / `DimensionAccelerator` / `DimensionIntent` / `DimensionOS` / `DimensionPlatform` | string consts | **Facade-owned.** The criteria dimensions subject to the coverage post-condition, and the values `WithSnapshotCriteriaRelaxation` accepts. Values match `pkg/recipe.CoverageDimensionNames` exactly, which a test asserts. `nodes` is absent: no overlay gates on it. |
8185

8286
## Recommended consumption pattern
8387

pkg/client/v1/aicr.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,18 @@
3434
// already exists and no cluster is needed.
3535
// - ValidateState — evaluate a resolved recipe against a snapshot,
3636
// running deployment / conformance / performance phases.
37+
// - LoadConfig — read and validate the AICRConfig a team commits, from a
38+
// file or an HTTP(S) URL. WrapConfig lifts one already parsed elsewhere;
39+
// it does no parsing itself. Either way the resulting Config DERIVES
40+
// options (Config.BundleVerifyOptions, Config.RecipeSource,
41+
// Config.RecipeCriteria, ...) rather than applying them: a Config never
42+
// attaches to a Client and is never consulted implicitly, so caller
43+
// precedence stays one readable line at the call site.
44+
//
45+
// Resolution behavior is tuned per call with RecipeResolveOption —
46+
// WithProfile, WithAccountingMode, and WithSnapshotCriteriaRelaxation (the
47+
// relax-and-retry policy behind `aicr recipe --snapshot`, which takes the
48+
// criteria dimensions the caller stated explicitly and may clear the rest).
3749
//
3850
// The supply-chain half covers both producing and checking artifacts:
3951
//

0 commit comments

Comments
 (0)