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

Events and alerts

The closed event vocabulary, the heartbeat, and the alert catalogue that is the platform's only control surface.


Source: verifi-engine-contract/src/event.ts.

zEvent

export const zEvent = z.object({
  id: z.string().min(1),
  runId: z.string().min(1),
  /** Threaded from the first byte. Without it an event cannot be joined to its run. */
  correlationId: z.string().min(1),
  type: zEventType,
  agentId: z.string().optional(),
  severity: z.enum(['debug', 'info', 'warn', 'error']),
  ts: zISO,
  payload: z.record(z.string(), z.unknown()).default({}),
}).strict();

The SDK fills runId, correlationId and ts for you; you supply type, severity and payload.

zEventType

export const zEventType = z.enum([
  'phase_start',
  'phase_end',
  'agent_spawn',
  'agent_reap',
  'tool_call',
  'tool_result',
  'finding',
  'heartbeat',
  'approval_request',
  'approval_decision',
  /** Replaced budget_warn/budget_exceeded when cost stopped being terminal. */
  'threshold_breach',
  /** A discovered host; outside the authorized apex it needs HITL confirm. */
  'scope_expansion',
  /** Declared coverage not achieved — a first-class run output. */
  'coverage_gap',
  'error',
  'log',

  // ── emitted by the harness SDK; added in contract 1.4.0 ──────────────────
  /** A tool call that FAILED. Distinct from tool_result: it cost a round trip and returned nothing. */
  'tool_error',
  /** A slot switched to its fallback. Raised ONCE per slot per run. Also an alert key. */
  'mcp.fallback_used',
  /** A slot has nothing healthy behind it — a lost capability, not a slow one. */
  'mcp.no_healthy_tool',
  'model_call',
  'model_error',
  /** A model whose price is unknown, so its spend records as $0. Emitted once. */
  'model_price_unknown',
]);

Twenty-one members. Verified against contract 1.5.1.

mcp.fallback_used and mcp.no_healthy_tool are members of both zEventType and zAlertKey. That is deliberate rather than accidental: the run timeline needs the event to explain a gap, and an operator needs the alert. The same name in both places is what stops the two describing the same thing differently.

The harness SDK does not enforce the enum — emitEvent's type is EventType | string, so the members autocomplete and a novel string still compiles. Stay inside it for events you emit yourself; that is what makes them legible to the console.

Two members are worth calling out for harness authors:

scope_expansion — you discovered a host outside the authorized apex. Emit it rather than scanning it. security.scope_expansion_outside_apex is an urgent analyst alert.

coverage_gap — you did not achieve coverage you declared in declarePlan(). This is a first-class run output, not an absence. A passed check is not a finding, and a check that never ran is not a pass.

zHeartbeat

export const zHeartbeat = z.object({
  runId: z.string().min(1),
  agentId: z.string().min(1),
  ts: zISO,
  phase: z.string().min(1),
  spend: zSpend,
  /** Monotonic per run. The engine persists the last acked seq, so a re-dialed
   *  stream replays from that cursor and delivery is idempotent by (runId, seq). */
  seq: z.number().int().nonnegative(),
}).strict();

GrpcEngineChannel sends a keepalive heartbeat every keepaliveMs (default 25s) automatically. You do not send these.

The alert catalogue

export const zAlertUrgency = z.enum(['info', 'digest', 'warn', 'urgent', 'critical']);
/** Analyst and ops are different people with different urgencies. */
export const zAlertAudience = z.enum(['analyst', 'ops', 'both']);

zAlertKey is a closed enum rather than free-form strings, because alerting is the platform's primary control surface — nothing is hard-capped except time, so an alert is the only thing that stops a runaway. A typo'd alert key is a control that silently does not exist.

ALERT_CATALOGUE maps every key to its default audience and urgency. The ones a harness or MCP author will actually meet:

KeyAudienceUrgencyMeaning
run.findings_readyanalystwarnA run produced findings to review.
run.completed_zero_findingsbothwarnA clean run is either a healthy target or a broken scanner.
run.coverage_gapanalystwarnDeclared coverage was not achieved.
run.failedbothwarn
run.timed_outbothwarnThe one terminal bound.
run.provision_failedopswarn
run.queued_too_longopswarn
hitl.pendinganalysturgentA destructive action is waiting on approval.
hitl.escalatedanalysturgent
hitl.timeout_proceeded / hitl.timeout_skippedanalystwarn
threshold.run_cost · run_tokens · run_tool_calls · run_evidence_bytes · run_duration_warningopswarnThe cap replacement. Alerts; stops nothing.
threshold.assessment_costopswarn
threshold.platform_daily_burnopsurgent
quota.tenant_approachingopswarn
quota.tenant_exceededopsurgentA commercial event, not a technical one — nothing is blocked.
mcp.fallback_usedopswarnSilent failover is how a degraded primary hides for months.
mcp.no_healthy_toolbothurgentA slot has nothing behind it.
mcp.probe_failedopswarn
model.probe_failedopswarn
kb.zero_chunksopswarnAnswers HTTP, retrieves nothing.
kb.proposals_pendinganalystdigest
security.egress_denied_internalopscriticalA container reached for the instance metadata service or our VPC. Always an incident.
security.scope_expansion_outside_apexanalysturgent
security.credential_probe_failedopswarn
schedule.auto_pausedbothurgentA paused schedule is silently not testing.
schedule.occurrence_skipped / occurrence_missed / service_driftanalyst / opsdigest
residency.probe_failedopswarn
residency.unreachablebothurgentThe client's dashboard is showing an error right now.
residency.publish_refusedanalysturgent
residency.migration_verify_failedopsurgent
residency.purge_awaiting_confirmopsinfo
engine.component_unavailableopscritical
engine.lease_adoption_stormopsurgent
engine.boot_orphans_foundopswarn
engine.teardown_leakopsurgentA teardown report's leaked list must be empty.
export const zAlert = z.object({
  key: zAlertKey,
  urgency: zAlertUrgency,
  audience: zAlertAudience,
  raisedAt: zISO,
  /** What the alert is about: runId, tenantId, scheduleId, mcpId… */
  subject: z.record(z.string(), z.string()).default({}),
  message: z.string().min(1),
  acknowledgedAt: zISO.optional(),
  acknowledgedBy: z.string().optional(),
}).strict();

Harnesses do not raise alerts directly. You emit events; the engine derives alerts. The catalogue is here because knowing which of your events becomes an urgent page for a human should change how carefully you emit it.

Threshold breaches

export const zThresholdBreach = z.object({
  metric: z.enum(['cost_usd', 'tokens', 'tool_calls', 'evidence_bytes']),
  threshold: z.number(),
  actual: z.number(),
  alertedAt: zISO,
});

Each level fires at most once per run. See The budget ledger.

security.egress_denied_internal

The only critical alert a harness can trigger by accident. A harness container that reaches for 169.254.169.254 (cloud instance metadata) or an internal VPC address raises it, and it is always treated as an incident.

You will not trigger it by accident if you only ever reach the network through ctx.mcp() — which is another reason there is no raw HTTP client on the context.