VERIFISDK docsengine v2 · contract 1.5.1 · harness-sdk 1.2.0
Contract reference

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  ·  authRef

Every 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: '…' }       REFUSED

The 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 key

The 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

ConditionRefusal, 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                      REFUSED

Note 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.