generated from coulomb/repo-seed
context loading, path resolution, form state, dynamic rules, and provider-neutral assessment runner/cache boundary
This commit is contained in:
220
docs/runtime-context-forms-assessments.md
Normal file
220
docs/runtime-context-forms-assessments.md
Normal file
@@ -0,0 +1,220 @@
|
||||
# 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.
|
||||
Reference in New Issue
Block a user