# Testing and Publishing
A graph test supplies a state — `facts`, `outcomes` and `authorities` — and asserts what the Evaluation and Route must be. It never runs a command or calls a model, so it is fast, deterministic and safe in CI.
```yaml
schema: agent-graph.test.v1
name: claimed publication without a URL is checked
graph: report
entry: default
state:
facts:
report: { digest: "sha256:abc", approved: true }
outcomes:
report/publish: completed
expect:
statusCode: actionable
primaryNode: check-publication
```
Outcome keys use the same `/` form as `afterAction.recordNode`.
| Assertion group | `expect` fields |
| --- | --- |
| Status and selection | `statusCode`, `primaryNode`, `primaryReasonCode`, `alternativeNodes`, `availability`, `outcome` |
| What the Route runs | `command`, `handler`, `recordNode` |
| What the Route loads | `requiredResources`, `recommendedResources` |
| Gates | `gateResolution`, `inspectionCommand`, `inspectionHandler`, `inspectionSkill`, `inspectionInputSchema`, `resolutionCommand`, `resolutionHandler`, `resolutionSkill`, `resolutionInputSchema` |
| Diagnostics | `diagnosticsInclude` |
Assert on schema fields and codes, never on human descriptions.
## What to cover
For each production Graph, write a case for:
- the initial Route, and its stable reason code;
- every branch Outcome;
- the user Gate and the explicitly delegated Gate;
- missing required Facts;
- fact-backed completion, and claimed completion without proof (`unverified`);
- partial and failed batch recovery;
- `join: all` and `join: any` where used;
- repeat continuation and terminal stopping;
- Subgraph child Routes and parent continuation;
- primary and alternative ranking where several Routes are legal;
- every diagnostic expected to block execution.
The weekly-report Graph from [Authoring Graphs]() needs seven cases to reach that bar:
| Case | State | Expected |
| --- | --- | --- |
| Fresh week | none | `actionable`, `draft`, `route.report.draft` |
| Loop | `report/draft: partial` | `actionable`, `draft` again |
| Draft observed | `report.digest` | `waiting-user`, `approve`, `gateResolution: user` |
| Delegated | `report.digest`, authority `report.publish` | `actionable`, `approve`, `gateResolution: session-authority` |
| Approved | `report.digest`, `report.approved: true` | `actionable`, `publish` |
| Unproven publish | approved, `report/publish: completed`, no URL | `actionable`, `check-publication` |
| Done | approved, `publication.url` | `complete`, `outcome: completed` |
If a Provider uses dynamic or host-produced Resources, also cover coexistence of the three resource protocols, rejection of bad host input before dispatch, inline and managed-resource results, handler or schema mismatches, stale revisions and recovery of persisted results.
## Run it in CI
```bash
npx @c4a/agent-graph@0.3.0 --manifest provider.yaml validate
npx @c4a/agent-graph@0.3.0 --manifest provider.yaml test tests
npx @c4a/agent-graph@0.3.0 --manifest provider.yaml build dist/provider
npx @c4a/agent-graph@0.3.0 --manifest dist/provider/manifest.json validate
```
Pin the version for reproducible automation. Failures exit non-zero and, with `--format json`, print an `agent-graph.error.v1` envelope; branch on `error.code`. Typical codes while authoring:
| Code | Cause |
| --- | --- |
| `graph-flow-cycle` | An ordinary edge closes a loop; use a `repeat` edge |
| `route-reason-code-missing` | A node's `reasonCode` is not declared in the Code Catalog |
| `test-path-missing` | The test path does not exist — an empty or missing path is an error, not a vacuous pass |
Validation separately catches missing files, duplicate IDs, paths that escape the Provider, missing Subgraph entries, invalid repeat targets and recursion.
## The bundle
`build` loads and validates everything reachable, then copies only what the runtime needs: Graphs, referenced Actions and their entries, schemas and declared `files`, the directories of referenced Skills, static and dynamic Resource definitions with their content, materializer Actions, and the Code Catalog with its documents. Tests and unrelated files stay out.
`manifest.json` records every file digest, catalogs, the same-Provider Graph dependency index and an overall digest. It has no timestamp, so unchanged inputs give the same digest — building the weekly-report Provider twice yields an identical `sha256:` digest. The builder refuses to overwrite referenced sources and rejects any file that contains the source Provider's absolute path. Loading the bundle verifies the overall and per-file digests, the Provider identity, and that no reachable file is missing or extra.
## Ship it
A Provider project normally depends on `@c4a/agent-graph` and publishes its built bundle inside its own package — a plugin, CLI or product. It can also copy the CLI's single-file `dist/agent-graph.mjs` next to the bundle for a no-install entry; that file validates and routes on Node.js 20+ and is the same implementation, not a second one.
Whichever way you install it — from source, a link, a packed tarball or a registry — the Provider should return the same Graph, Action and Resource identities. Only absolute `filePath` values differ, and Route IDs change whenever relevant definitions or inputs change.
Before releasing, check that:
1. no test, fixture or bundle contains credentials, business data or machine-specific paths;
2. built digests match the installed files;
3. `path:` Skill bindings still resolve after relocation, and `provider:` bindings resolve through a clean registry;
4. dynamic view materialization writes only to the explicit cache;
5. host-produced Resources never execute inside Agent Graph, and their results survive serialization and recovery validation.
## Security boundary
Validation proves structure, not safety. Provider authors and the hosts that install them must review Action commands, effects, scripts and resources as executable supply-chain content. `effect: read` is a declaration, not a sandbox, and `external` effects should normally sit behind an explicit Gate or product policy. For the exact commands and SDK functions, see [CLI and SDK]().
---
## Sources
- [agent-graph/docs/en/testing-and-publishing.md L1–L21 · 9e8b98a]()
- [agent-graph/schemas/test-case.schema.json L1–L60 · 9e8b98a]()
- [agent-graph/docs/en/specification.md L384–L386 · 9e8b98a]()
- [agent-graph/docs/en/testing-and-publishing.md L23–L43 · 9e8b98a]()
- [agent-graph/examples/review-gate/tests/delegated.yaml L1–L14 · 9e8b98a]()
- [agent-graph/examples/facts-recovery/tests/unverified.yaml L1–L10 · 9e8b98a]()
- [agent-graph/docs/en/testing-and-publishing.md L45–L54 · 9e8b98a]()
- [agent-graph/docs/en/testing-and-publishing.md L85–L89 · 9e8b98a]()
- [agent-graph/docs/en/specification.md L408–L410 · 9e8b98a]()
- [agent-graph/docs/en/testing-and-publishing.md L56–L71 · 9e8b98a]()
- [agent-graph/docs/en/specification.md L388–L400 · 9e8b98a]()
- [agent-graph/docs/en/testing-and-publishing.md L83–L83 · 9e8b98a]()
- [agent-graph/docs/en/testing-and-publishing.md L91–L110 · 9e8b98a]()
- [agent-graph/docs/en/testing-and-publishing.md L112–L114 · 9e8b98a]()
- [agent-graph/src/materialize.ts L40–L54 · 9e8b98a]()