Skip to content
3 min read · 515 words

Declarative Rule Types Reference

Every atomic profile in the validation battery is composed of one or more declarative rule objects. The battery supports seven typed rule contracts exported from @nhtio/adk/batteries/validation.

ts
export type OrderingRule =
  | OrderRule
  | RequiredMetadataRule
  | AlternationRule
  | AdjacencyRule
  | PreservationRule
  | RoleRemapRule
  | StaleContentAdvisoryRule

1. OrderRule (type: 'order')

Enforces relative ordering between two primitive categories (before and after).

ts
export interface OrderRule {
  type: 'order'
  id: string
  before: 'message' | 'thought' | 'toolCall'
  after: 'message' | 'thought' | 'toolCall'
  scope: 'adjacent-same-role-group' | 'entire-turn'
  onlyLatestGroup?: boolean
}
  • before / after: Defines the required relative order.
  • scope: When 'adjacent-same-role-group', ordering is checked within contiguous role segments. When 'entire-turn', ordering spans the full dispatch timeline.
  • onlyLatestGroup: When true, ignores older historical groups and checks only the active turn (e.g. Anthropic's manual thinking rule).

2. RequiredMetadataRule (type: 'requiredMetadata')

Enforces that a primitive carries specific provider metadata in its payload object.

ts
export interface RequiredMetadataRule {
  type: 'requiredMetadata'
  id: string
  kind: 'message' | 'thought' | 'toolCall'
  applyTo: 'first-in-group' | 'every'
  requiredPayloadKey: string
  severity?: 'blocking' | 'advisory'
  gatedByReplayCompatibility?: string[]
  fallbackPayloadValue?: unknown
  fallbackReplayCompatibility?: string
}
  • applyTo: 'first-in-group' targets only the leading primitive in an assistant group; 'every' checks all matching primitives.
  • requiredPayloadKey: Dot-path in value.payload (e.g. 'thoughtSignature').
  • severity: 'blocking' (default) halts dispatch on violation; 'advisory' records an informational finding without halting.
  • fallbackPayloadValue / fallbackReplayCompatibility: Documented sentinel values used by mutate mode when metadata fallback repair is enabled.

3. AlternationRule (type: 'alternation')

Enforces strict role cycling across conversation turns.

ts
export interface AlternationRule {
  type: 'alternation'
  id: string
  roles: ReadonlyArray<'user' | 'assistant'>
  mode: 'strict'
  maxPerGroup?: number
}
  • roles: Permitted role alternation sequence (normally ['user', 'assistant']).
  • mode: 'strict' requires every consecutive turn to alternate roles.
  • maxPerGroup: Optional upper bound on ToolCall primitives within a single assistant role group (e.g. 1 for Llama 3).

4. AdjacencyRule (type: 'adjacency')

Constrains the immediate successor of a primitive. In this ADK, tool execution results are stored directly on ToolCall rather than on separate message payloads, so adjacency rules directly forbid disallowed primitive kinds from appearing immediately after a specified primitive.

ts
export interface AdjacencyRule {
  type: 'adjacency'
  id: string
  first: 'message' | 'thought' | 'toolCall'
  disallowBetween: Array<'message' | 'thought' | 'toolCall'>
}
  • first: The primitive kind whose immediate successor is restricted.
  • disallowBetween: Array of primitive kinds that are forbidden from appearing immediately after first.

5. PreservationRule (type: 'preservation')

A stateful check that diffs the current dispatch timeline against the previous iteration's snapshot on ctx.stash.

ts
export interface PreservationRule {
  type: 'preservation'
  id: string
  kind: 'message' | 'thought' | 'toolCall'
  invariant:
    | 'count-non-decreasing'
    | 'payload-field-stable'
    | 'pruned-after-latest-turn'
  payloadField?: string
  resetOnModelSwitch?: boolean
}
  • count-non-decreasing: Enforces that the total count of historical primitives of kind never decreases.
  • payload-field-stable: Enforces that the value at payloadField remains unchanged (compared as JSON) across iterations.
  • pruned-after-latest-turn: Allows primitives older than the latest non-tool-call user message to be pruned, while requiring all primitives at or after that boundary to remain present and stable.

6. RoleRemapRule (type: 'roleRemap')

Describes required provider-specific wire role tags for model families with custom role schemas (such as IBM Granite).

ts
export interface RoleRemapRule {
  type: 'roleRemap'
  id: string
  kind: 'message' | 'thought' | 'toolCall'
  variant: string
  expectedRoleTag: string
}
  • variant: Profile-defined mapping identifier the producer is expected to have set (e.g. 'granite-3.x').
  • expectedRoleTag: Dot-path into value.payload expected to equal variant.

7. StaleContentAdvisoryRule (type: 'staleContentAdvisory')

Non-blocking hygiene recommendation rule that checks for obsolete content without blocking dispatch.

ts
export interface StaleContentAdvisoryRule {
  type: 'staleContentAdvisory'
  id: string
  kind: 'message' | 'thought' | 'toolCall'
  scope: 'before-latest-user-turn'
  optOutOptionKey: string
}

See Also