Registering a component
The four registration schemas — what a caller may send, what is deliberately unrepresentable, and why every one of them is .strict().
registration.ts is the caller-facing half of the registries. These schemas are
deliberately narrower than the records in reference.ts, and the rule they make
structural is one sentence:
A field a caller can set is a field a caller can lie about.
Source: verifi-engine-contract/src/registration.ts. Every claim on this page was verified
by parsing real objects against contract 1.5.1.
What no registration carries
status · probe · probeInvoked · tools · lastProbedAt · authRefEvery one of those is an outcome — something the engine learned by going and looking.
None appears on any registration schema, and every schema is .strict(), so sending one is
a parse error rather than a silently ignored field.
That is P4 expressed in the type system instead of in a comment asking people to be careful, and it is why the console's register form has no status control.
zModelRegistration + { status: 'available' } REFUSED
zModelRegistration + { probe: … } REFUSED
zModelRegistration + { probeInvoked: true } REFUSED
zModelRegistration + { tools: [ … ] } REFUSED
zModelRegistration + { authRef: 'secret:…' } REFUSED
zModelRegistration + { lastProbedAt: '…' } REFUSEDThe one place a raw secret is accepted
apiKey — and it is accepted only in flight:
POST /registries/models { …, apiKey: "…" }
│
├─ parse against zModelRegistration
├─ split apiKey OFF the record ← before anything is stored
├─ write it to Secrets Manager as verifi/engine/<stage>/<registry>/<id>
├─ store the record with authRef: "secret:verifi/engine/<stage>/<registry>/<id>"
└─ 201 { entry } ← carries authRef, NEVER the keyThe key is separated before anything is persisted, so there is no window in which the raw value could be logged or stored with the entry. There is no route that reads it back — the console shows the reference so an operator can see where it went, which is also why the form says the key cannot be retrieved.
zCredRef already makes a raw key unrepresentable on the stored record. If the engine has
no secret store configured it returns 503 rather than registering a component that could
never be probed.
The key itself must be at least 8 characters, at most 4096, and contain no whitespace — that last check is there to catch a stray paste.
zModelRegistration
export const zModelRegistration = z.object({
id: z.string().min(1),
displayName: z.string().min(1),
provider: zModelProvider, // bedrock | anthropic | openai_compat | nim | vertex | local
/** The provider's own identifier. */
modelId: z.string().min(1),
tier: zModelTier, // frontier | balanced | fast | local
capabilities: z.object({
maxContextTokens: z.number().int().positive(),
maxOutputTokens: z.number().int().positive(),
supportsTools: z.boolean(),
}).optional(),
costModel: z.object({
inputPer1kUsd: z.number().nonnegative(),
outputPer1kUsd: z.number().nonnegative(),
}).optional(),
/** What to actually INVOKE with — a cross-region inference profile id where one is needed. */
invocationId: z.string().min(1).optional(),
/** Required for every provider except bedrock. Consumed in flight, never stored. */
apiKey: zApiKey.optional(),
/** Required for openai_compat / nim, where there is no safe default host. */
baseUrl: z.string().url().optional(),
/** Required for bedrock. */
region: z.string().min(1).optional(),
}).strict()
.refine(/* keyed provider ⇒ apiKey */)
.refine(/* openai_compat | nim ⇒ baseUrl */)
.refine(/* bedrock ⇒ region */);The three conditional requirements
| Condition | Refusal, and why |
|---|---|
a keyed provider with no apiKey | "this provider authenticates with an API key, and without one the model can never pass an invocation probe — it would sit in probing permanently" |
openai_compat or nim with no baseUrl | "defaulting to OpenAI would send this deployment's traffic and key to a third party" |
bedrock with no region | "the wrong region is a silent 403" |
isKeyedProvider(p) is false for bedrock alone: it authenticates with the AWS role the
container already has, which is why P6 prefers it — there is no long-lived key to leak,
rotate, or lose.
Each of those refusals prevents the same shape of outcome: a component that registers
cleanly and is permanently unusable. Verified — bedrock without a region is refused; a
keyed provider without a key is refused; openai_compat becomes valid the moment a
baseUrl is supplied.
capabilities and costModel are optional
New in contract 1.4.3, and it is a correction rather than a loosening. They were required, so the register form asked an operator to type a context window and a per-1k price. AWS publishes the price and does not publish the limits — half of that was a lookup a human should never have been doing and half was a guess. Neither is read by anything in the engine or the SDK today.
The catalogue supplies both where it can (price from the AWS Price List API, limits from a curated table), and an absent value is recorded as absent rather than defaulted.
Verified: a model registration with neither field parses.
invocationId
modelId is the model. invocationId is what you actually invoke with, when the two
differ — which for most current Bedrock models means a cross-region inference profile such
as us.anthropic.…, because the bare id is INFERENCE_PROFILE only and returns a
validation error on an on-demand call.
The probe calls buildModelRequest(provider, invocationId ?? modelId, …). See
the inference-profile trap.
zMcpRegistration
export const zMcpRegistration = z.object({
id: z.string().min(1),
name: z.string().min(1),
/** Slot kind this server can fill: 'recon', 'web_proxy', 'cloud', 'exec', 'kb'… */
kind: z.string().min(1),
/** An already-running server to probe. */
endpoint: z.string().url().optional(),
/** Or an image to trial: start it, probe it, record what it serves, tear it down. */
image: z.string().min(1).optional(),
transport: z.enum(['sse', 'http', 'stdio']).default('http'),
/** ⛔ `shared` is absent from this enum on purpose. */
concurrencyModel: zConcurrencyModel.exclude(['shared']).default('sidecar'),
}).strict()
.refine((m) => Boolean(m.endpoint) || Boolean(m.image), …);One of endpoint or image is required. With neither there is nothing to probe, and
the entry could never leave probing.
shared is not representable. It requires a passed statelessness conformance probe,
so declaring it at registration would be asserting an outcome. Within a tenant a state leak
is a correctness bug; at shareScope: 'global' the identical bug is a cross-tenant data
leak. Register as sidecar and earn the widening — see
Statelessness and sharing.
{ id, name, kind, endpoint } accepted
{ id, name, kind, image } accepted
{ id, name, kind } REFUSED (nothing to probe)
{ …, concurrencyModel: 'shared' } REFUSED
{ …, tools: [{ name: 'run_command' }] } REFUSED (a tool list is what a probe FOUND)
defaults { transport: 'http', concurrencyModel: 'sidecar' }zHarnessRegistration
export const zHarnessRegistration = z.object({
id: z.string().min(1),
name: z.string().min(1),
/**
* The ECS task-definition family or image. A harness SERVES NOTHING — it dials out,
* works and exits — so there is deliberately no endpoint here.
*/
image: z.string().min(1),
/** REQUIRED, and a DIGEST, not a tag. */
imageDigest: z.string().regex(/^sha256:[0-9a-f]{64}$/, 'must be a sha256 digest, not a tag'),
semver: z.string().min(1).optional(),
}).strict();A digest and not a tag, because a tag can be repointed after the probe passes, so a pass against one is a statement about nothing in particular.
imageDigest: 'sha256:' + 64 hex chars accepted
imageDigest: 'v1.2.0' REFUSED "must be a sha256 digest, not a tag"
imageDigest omitted REFUSEDNote there is no endpoint field. What is verifiable about a harness is the artefact — see how a harness is probed.
zKbRegistration
export const zKbRegistration = z.object({
id: z.string().min(1),
name: z.string().min(1),
/** The KB's MCP endpoint. */
endpoint: z.string().url(),
/** Consumed in flight; stored as `secret:<id>`. */
apiKey: zApiKey,
domain: z.string().min(1).optional(),
}).strict();endpoint and apiKey are both required — a KB with no key could not be
authenticated by the probe, and the endpoint must parse as a URL (a bare hostname is
refused). A KB is not a special case: it is an MCP, probed through the same three gates,
with zKnowledgeBase additionally refusing available when indexStats.chunks === 0,
because a corpus that answers HTTP and retrieves nothing is not available.
The route
POST /registries/:registry parse → split apiKey → store as `probing` → 201 { entry }
GET /registries/:registry { entries } ?status= to filter
POST /registries/:registry/:id/probe the ONLY path to `available`
POST /registries/:registry/:id/status operator change; CANNOT set `available`, needs a reason:registry is one of models, mcps, kbs, harnesses, and each maps to its schema
above. A parse failure is a 400 naming the first offending path and message, plus the
full issue list — "invalid registration" alone would send an operator back to guessing
which of a dozen inputs was wrong.
Probe is a POST because it has an effect: an operator hitting "probe" is making a claim about the world.