Validation and errors
What the open numi validate checks, what it deliberately leaves to the engine, the exit codes, and the real error messages you will see.
The schemas, the loader and the open numi validate build are in the public numinance-spec. Profile resolution, floor evaluation and the commands that run a change are designed and are not in the open build.
The open validator proves that your YAML has the shape of a Numinance kind. It does not prove that your configuration is allowed, complete or safe. A CI that cannot tell the difference is a CI that lies, so the open build is built to make the difference impossible to miss.
Two validation modes
| Mode | Command | What it does | Exit on a clean pass |
|---|---|---|---|
| Example validator | numi validate --files PATH | Checks each YAML document against the strict schema of its kind, then the loader checks. No resolver, no floors. | 0 |
| Repository validator | numi validate [path] | Schema-checks every kind document under .numinance/ in the org, repository and component trees (working tree, uncommitted edits included), then stops. | 7 |
A repository run that exits 7 has not failed. Exit 7 in the open build has exactly one meaning: the schema pass succeeded and profile resolution is not available. The final line says so:
numi validate: profile resolution needs the Numinance engine buildTreat exit 7 in CI as a distinct state, not as success. If you wire the open build into a pipeline, assert the exit code you expect, and do not map 7 to green.
What is open schema-only, and what is engine-side
Open numi build (checks the shape) | Engine-side resolution (designed, not in the open build) |
|---|---|
| Valid YAML, one document, size and depth limits | Loading from a pinned commit, not the working tree |
Strict kind schema: no unknown fields except under extensions | Merging org, repository and component scope into one resolved profile |
apiVersion, kind, metadata, spec present; scope not unspecified | Checking floors: autonomy, residency, approvals, packs |
Enums by full proto name; durations in 7200s form; digests as sha256: plus 64 hex | Resolving names (playbooks, toolchains, verifiers, providers) to real objects and lockfile entries |
| Org-only kinds and floor fields refused below the org scope | Deciding whether a document has authority (protected path or signed policy bundle) |
Per-kind rules such as read_only true, ttl above zero, sorted EvalSet cases | Printing the profile digest and per-field provenance |
When an authoritative engine build resolves a repository it reads a pinned commit and prints a digest with the provenance of each field. The open build cannot, and does not pretend to.
Exit codes
The stable table in the spec CLI is shared by all numi commands:
| Code | Meaning | Seen in the open validator |
|---|---|---|
| 0 | OK | Clean --files pass |
| 1 | Internal, and any unknown kind | A panic or an error of no kind exits 1, never 0 |
| 2 | Usage: bad flags or arguments, or a non-empty --rev in the open build | No path, more than one path, --files combined with --rev, --org or --component, a single file not named *.yaml or *.yml |
| 3 | Invalid: schema errors, loader errors | Any document that fails |
| 4 | Not found, including no kind documents found | A missing path, or a directory with no YAML |
| 5 | Conflict | Not produced by validate in my runs. numi init returns 5 when .numinance already has files and no --force. |
| 6 | PolicyDenied | Not produced by the open validator |
| 7 | Unavailable | Repository mode after the schema pass |
| 8 | Timeout | Not exercised |
A cancel (an interrupt) exits 1, never 7. For the CLI as a whole, see commands and exit codes and numi validate.
The loader's four stages
Each document runs through four stages in order and stops at the first failure. Errors name a path or a field and never quote a value from your file, so a rejected document cannot leak its contents into logs.
| Stage | Refuses |
|---|---|
| Size | A document over 128 KiB |
| YAML | More than one document, a root that is not a mapping, anchors, aliases, merge keys, explicit tags, non-string keys, duplicate keys, nesting beyond 64 levels, more than 16384 values |
| Schema | A missing or unknown kind; a produced kind (ContextBundle) loaded as configuration; an unknown field; a wrong type or enum; a duration that is not in proto JSON form |
| Loader checks | apiVersion or kind not as expected; missing metadata or spec; an empty metadata.name; a zero enum anywhere; required lists left empty; non-reverse-DNS extensions keys; then the per-kind rules (org-only, FORBID fields, unique keys, value shapes) |
Anchors and aliases are refused on purpose. They are the YAML feature that makes a small file expand into a large one, and a config format that hides expansion from reviewers is a poor place for policy.
Working examples
Success on the shipped examples. This is the first command to run after installing from a clone; see install and first configuration.
go run ./cli/numi validate --files schemas/examples/SDLCProfileThe machine-readable form is a single JSON object per run, with a per-document status. For a failing document it carries the same message in an error field.
$ numi validate --files schemas/examples/SDLCProfile
revision: "files"
ok file "org-baseline.yaml" SDLCProfile "org-baseline"
ok file "payments-api.yaml" SDLCProfile "payments-api"
(exit 0)
$ numi validate --files --json ok.yaml
{"status":"ok","revision":"files","documents":[{"scope":"file","path":"ok.yaml","kind":"BudgetPolicy","name":"x","status":"ok"}]}
(exit 0)
$ numi validate --files --json bad/doc.yaml # KnowledgeConnection with read_only: false
{"status":"invalid","revision":"files","documents":[{"scope":"file","path":"doc.yaml","status":"invalid","error":"kind: invalid: a value of numinance.kinds.v1.KnowledgeConnectionSpec.read_only fails its check"}]}
(exit 3)
$ numi validate . # a repository with one LanguageToolchain under .numinance/policy/
revision: "working tree (uncommitted changes included)"
ok repo ".numinance/policy/go-1.27.yaml" LanguageToolchain "go-1.27"
numi validate: profile resolution needs the Numinance engine build
(exit 7)
$ numi validate --files missing-dir
numi validate: path not found
(exit 4)
$ numi validate --files empty-dir
revision: "files"
numi validate: no kind documents found
(exit 4)
$ numi validate --files
numi validate: --files needs exactly one path
usage: numi validate [--rev REV] [--org DIR] [--component ROOT] [--json] [path]
numi validate --files [--json] path
...
(exit 2)(The usage text above is shortened at the dots; the tool prints the full flag list.)
Common mistakes
Every message below was produced by editing a copy of a real example or a minimal document and running the open build. All exit 3 unless noted.
| Mistake | Message |
|---|---|
| A misspelt or unknown field | kind: does not match the kind schema: schema: value does not match (violations: 1): \"/spec/verifier\": additionalProperties |
An enum written short (HIGH) or with an invalid member | ... \"/spec/rules/0/severity\": enum |
| A missing required field the schema can see | ... \"/spec/counsel_status\": required |
| A required list left empty | kind: invalid: numinance.kinds.v1.LanguageToolchainSpec.verifiers is empty |
A duration as 4h or 2h | kind: does not match the kind schema: a value is not in protobuf JSON form |
| An org-only kind at another scope | kind: invalid: ControlPack is org-scoped only |
| A floor set below the org | kind: invalid: numinance.kinds.v1.ResidencyPolicySpec.floors is set only at the org scope |
metadata.scope left unspecified | kind: invalid: numinance.kinds.v1.Metadata.scope is SCOPE_UNSPECIFIED |
Wrong apiVersion (here numinance.dev/v2) | kind: invalid: apiVersion is not numinance.dev/v1 |
No spec | kind: invalid: spec is missing |
A kind name that does not exist (Widget) | kind: unknown kind |
| A YAML anchor | kind: unsupported YAML: line 4: anchor |
| A duplicated key | kind: duplicate key: line 6 |
| A produced kind in the configuration tree | kind: produced kind: ContextBundle is not configuration |
| An alpha kind in repository mode | kind: alpha kind is not enabled: EvalSet |
A non-reverse-DNS extensions key | kind: invalid: a key of numinance.kinds.v1.IndexerPlugin.extensions is not reverse-DNS |
A single file not named *.yaml or *.yml (exit 2) | numi validate: a file must be named *.yaml or *.yml |
A backup such as ok.yaml.bak inside a directory | numi validate: ok.yaml.bak: a file name mentioning .yaml or .yml must end in lowercase .yaml or .yml |
--files together with --rev (exit 2) | numi validate: --files excludes --rev, --org and --component |
The file-name rule is strict by design: in --files mode, a stray config.yaml.bak or config.YAML is an error, not an ignored file, because an ignored policy file is how a reviewed rule quietly stops being checked. Other names, such as README.md~, are ignored.
What a pass does not prove
- That the file is where policy will look for it. Authority comes from a protected path or a signed policy bundle. A document that decodes has no authority by decoding.
- That a name you wrote exists.
provider,verifier,playbookandknowledgevalues are references, resolved later against declared providers and the lockfile. - That a floor holds. Cross-document checks such as a pack that tightens an org floor are resolution, not validation.
- That an extension is installed, signed or allowed. See registry and trust.
- That your change is safe. Verification, policy and evidence do that at run time, in the designed runtime.
Troubleshooting
Next steps
EvalSet and ContextBundle
The alpha EvalSet kind for reference-only replay cases, and the produced ContextBundle record, which you read and validate but never author.
Integrations
How Numinance reaches external systems through plugins that implement typed ports, and which providers, kits and trust mechanisms exist today.