The VERIFI engine, for developers
Build a harness that drives a security assessment, or an MCP that exposes tools to one — and prove it works before you register it.
VERIFI runs security assessments against a client's own authorized attack surface. The engine does not itself know how to test anything. It knows how to compose things that do, run them under a deadline, meter what they spend, and keep every finding they produce.
There are two things a developer outside this codebase can build:
- A harnessTypeScript. The agent that drives an assessment: declares what it needs, receives resolved endpoints, streams findings. Runs as a container, one per run.
- An MCPPython + FastMCP. A tool server a harness calls through a slot. Must pass a three-gate probe before the engine will bind it.
Both have to prove they work before the engine will use them, and the two proofs are different in kind:
- an MCP passes the three-gate probe — liveness, then readiness against
its real backend, then a real MCP
tools/listreturning at least one tool; - a Model passes a real invocation that returns non-zero token counts, because a provider will list models it cannot invoke;
- a Harness passes an image check against
a
sha256:digest — it serves nothing, so there is no endpoint to probe.
Everything on this site is written against source that was read, not remembered. Where a claim could not be verified against code, it says so. See Source provenance for the file list, and Known gaps for the places the SDKs are currently confusing, contradictory, or incomplete.
The object model
Six objects carry the whole system. They are defined as zod schemas in
@pragyacyber/engine-contract, and that package is the normative definition — this
page is a reading of it.
| Object | What it is |
|---|---|
Harness | A registered, versioned container image plus the declaration of what it needs: slots, model roles, phases, inputs. |
McpServer | A registered tool server with an endpoint, an image digest, a concurrency model, and a probe result. |
Model | A registered model with a provider, a cost model, and a flag recording whether a real invocation probe returned tokens. |
KnowledgeBase | A registered corpus with a pinned contentVersion and index statistics. |
Service | The admin composition: one harness + one model per declared role + a primary (and optional fallback) per slot + KB bindings. |
Run | One execution of a Service against a target, bounded by a hard deadline. |
A harness author writes against the Harness shape. An MCP author writes against the
McpServer shape. Neither composes a Service — that is an admin action, and it is
what supplies a run with the endpoints and model ids the harness declared it needed.
How a Service becomes a Run
A harness declares what it needs by name. A Service says what fills those names. A Run freezes what was actually used, as content digests.
How a run actually happens
The stream carries handshake, event, finding, heartbeat and done up, and ack,
stop, approval_decision and budget_update down. The ack is a monotonic sequence
cursor: a dropped stream replays from it, which is what lets a run survive the engine
being replaced mid-scan. A stop can come from the deadline, an operator, or a failure —
never from cost.
The manifest carries credential indirections, never credential values. Model credentials arrive in the container's environment, minted per run.
Findings are two-layer
A harness produces raw findings. A raw finding is immutable, belongs to exactly one
run, and has no review status at all — zRawFinding refuses reviewStatus,
publishedAt, reviewedBy and clientVisible outright, and the stored zFinding is
.strict() so one cannot be smuggled onto the immutable layer either.
Finding (raw, immutable, one per detection per run)
│ keyed by dedupeKey = sha256(assetId + location + category.toLowerCase())
▼
PublishedFinding (curated, client-visible, analyst-editable)
│ keyed by dedupeKey across every run of an Assessment
│ carries BOTH client-facing versions and audit run ids
▼
the client's reportNothing reaches a client before an analyst reviews it. There is deliberately no
review status meaning "auto-approved" and no assessment status that skips in_review.
As a harness author you never touch the curated layer; you emit raw observations and
the engine normalises them at the boundary.
What is fixed, and what is not
| Package | Version | Status |
|---|---|---|
@pragyacyber/engine-contract | 1.5.1 | Stable. Additive-only within a major, enforced by a diff gate in CI. Depend on it at ^1.0.0. |
@pragyacyber/harness-sdk | 1.2.0 | Stable. createModelResolver now reads per-role provider/baseUrl/apiKey from the injected env, so a keyed (NIM/OpenAI) role works from a harness and one service can mix a Bedrock role with a keyed one. ^1.0.0 is the right range — see below. |
@pragyacyber/mcp-sdk | 0.1.0 | Not the path for a production MCP. Its authoring rules are right and are taught here, but its HTTP server does not speak MCP JSON-RPC, so a server built on it cannot pass gate 3. Details. Build MCPs in Python. |
What changed recently
| Version | Change |
|---|---|
| harness-sdk 1.2.0 | createModelResolver reads a per-role VERIFI_MODEL_<ROLE>_PROVIDER / _BASEURL / _APIKEY from the env the engine injects (from its registry + Secrets Manager), so a keyed provider role (NIM / OpenAI-compatible / Anthropic / Vertex) is now usable from inside a harness and one service can mix a Bedrock lead with a keyed sub-agent. Absent env still falls back to the Bedrock defaults. Read from source; a live end-to-end NIM run is still pending. See Models. |
| harness-sdk 1.1.0 | ctx.model() now builds every request through the contract (buildModelRequest / parseModelResponse / modelEndpoint), so a model that passed its probe cannot 400 on a shape only the SDK sends — non-Anthropic Bedrock families and non-bedrock providers work now. It also resolves Fargate task-role credentials and double-encodes the SigV4 canonical URI. See Models. |
| contract 1.5.1 | buildModelRequest resolves the Bedrock model family through a cross-region inference-profile prefix. See Models. |
| contract 1.5.0 | zHarnessRegistration requires an imageDigest matching /^sha256:[0-9a-f]{64}$/. A tag is refused. |
| contract 1.4.3 | capabilities and costModel became optional on a model registration; invocationId was added. |
| contract 1.4.2 | Bedrock is a host, not a model family — each family gets its own request body, and an unknown one is refused rather than guessed at. |
| contract 1.4.0 | zRawFinding published, so ctx.emitFinding() has a real type; registration.ts added; zEventType widened to cover the six types the SDK actually emits. |
| harness-sdk 1.0.0 | ctx.target and ctx.hardDeadline exposed; redaction actually switched on; emitFinding typed RawFinding; phase order validated as a positive integer at authoring time. |
Start reading
- Getting the packagesStart here if you are outside the organisation. The SDKs are private: access, tokens, .npmrc and Docker builds.
- QuickstartThe smallest harness that compiles, runs against a fake channel, and streams a finding.
- Operating principlesFour rules that explain why these APIs refuse the things they refuse.
- ConformanceThe three-gate probe: what it checks, what each failure means, and why you run it before registering.
- Contract referenceThe zod schemas as the normative types, and exactly what the additive-only guarantee lets you rely on.
- Building with a coding agent?Pull
AGENTS.mdand theSKILL.mdfiles straight into your repo — one curl, or one prompt pasted into Claude.