Files
markitect-tool/docs/runtime-context-forms-assessments.md

221 lines
5.4 KiB
Markdown

# Runtime Context, Forms, Rules, And Assessments
Date: 2026-05-04
## Purpose
The runtime layer turns contract extension points into executable behavior while
keeping the deterministic contract framework intact. Static checks still handle
document type, sections, assertions, and metric bands. Runtime checks add
external context, field prefill, UI-neutral form state, dynamic rules, and a
provider-neutral assessment protocol.
The layer is deliberately local-first. Core Markitect reads YAML or JSON context
files and runs deterministic rules. Network calls, application lookups, and LLM
providers belong behind adapters.
## Context Files
Runtime context can be a raw YAML/JSON mapping:
```yaml
recipient:
name: Ada Lovelace
sender:
name: Markitect Team
```
or an envelope with metadata and schema:
```yaml
metadata:
case_id: case-42
schema:
type: object
required: [recipient, sender]
context:
recipient:
name: Ada Lovelace
sender:
name: Markitect Team
```
The value under `context` is bound as `context` in field sources and dynamic
rules. `schema` validates the full context object. `schemas` can validate named
objects individually.
Malformed context and schema failures produce normal diagnostics:
- `runtime.context.malformed`
- `runtime.context.schema_invalid`
- `runtime.context.schema_target_missing`
- `runtime.context.schema`
## Field Runtime
Field specs continue to live in the contract:
```yaml
fields:
recipient_name:
type: string
required: true
source: context.recipient.name
delivery_channel:
type: string
default: email
enum: [email, print]
```
Runtime resolution order is:
1. Manual document value from `path`, usually frontmatter.
2. Context value from `source` or `sources`.
3. Contract `default`.
4. Missing.
Manual document values win over context. If both exist and differ, Markitect
emits `runtime.field.conflict` as a warning by default. A field can set
`conflict: error` to make that stricter. Multiple context sources with distinct
values produce `runtime.field.ambiguous`.
`mkt contract check` uses runtime evaluation only when `--context` is supplied:
```text
mkt contract check document.md --contract contract.md --context context.yaml
```
`mkt contract form-state` always emits the UI-neutral runtime view:
```text
mkt contract form-state document.md --contract contract.md --context context.yaml
```
## Form State
Form state is not a UI framework. It is a stable contract that future UIs,
agents, generators, and workflow steps can render:
- field id
- value
- origin: `manual`, `prefilled`, `defaulted`, `calculated`, or `missing`
- required/optional
- visible/hidden
- enabled/disabled
- allowed values
- diagnostics
- metadata
Hidden fields are not required unless a future adapter explicitly asks for
hidden validation. This matches practical form behavior and avoids punishing
authors for data that the current context made irrelevant.
## Dynamic Rules
Rules are deterministic YAML. They use a deliberately small condition language:
```yaml
rules:
- id: postal-address-for-print
if:
path: fields.delivery_channel.value
equals: print
then:
required: [postal_address]
visible:
postal_address: true
else:
hidden: [postal_address]
```
Supported condition operators:
- `exists`
- `equals` / `eq`
- `not_equals`
- `in`
- `contains`
- `matches`
- `gt`, `gte`, `lt`, `lte`
- `all`, `any`, `not`
Supported actions:
- `required` / `optional`
- `visible` / `hidden`
- `enabled` / `disabled`
- `allowed_values`
- `set`
- `assert`
- `sections`
Calculated values can reference runtime paths:
```yaml
then:
set:
contact_label: "${fields.sender_name.value} <${context.sender.email}>"
```
Context assertions use the same condition vocabulary:
```yaml
assert:
path: context.sender.email
matches: "@example\\.com$"
message: Sender email must come from example.com.
severity: warning
```
Dynamic section rules are intentionally narrow. They can require, recommend,
discourage, or forbid section specs already declared in the contract.
## Assessment Protocol
Rubrics remain provider-neutral contract declarations:
```yaml
rubrics:
- id: tone-fit
scope: section.body
criteria: The body should match the recipient relationship.
threshold: 0.75
```
Core Markitect turns rubrics into `AssessmentRequest` objects and normalizes
adapter results into `AssessmentResult` and diagnostics. It does not call an LLM
provider directly. The cache key includes contract id, rule id, scope, text,
criteria, context, structured inputs, threshold, provider, model, and metadata.
Adapters can be injected from workflows, applications, or tests. A transparent
in-memory cache exists for tests and short runs; persistent storage remains a
backend concern.
## Workflow Integration
Workflow `contract_check` steps accept `context`:
```yaml
steps:
- id: check-letter
kind: contract_check
document: letter.md
contract: letter.contract.md
context: letter.context.yaml
```
Workflow `form_state` steps expose the runtime state as a step result:
```yaml
steps:
- id: form
kind: form_state
document: letter.md
contract: letter.contract.md
context: letter.context.yaml
```
This keeps workflow orchestration separate from the runtime engine. The runtime
engine answers "what does this contract imply in this context"; the workflow
engine decides when to run it and where to send the output.