372 lines
11 KiB
Markdown
372 lines
11 KiB
Markdown
# Guard Injection Semantics
|
|
|
|
This document describes the runtime guard model for View Contracts.
|
|
|
|
Views describe portable structural contracts. Guarded views refine those
|
|
contracts with executable predicates while keeping ordinary value-level code free
|
|
of `Maybe`, `Result`, sentinel, or host-language abort handling.
|
|
|
|
```tri
|
|
viewGuarded baseView guard
|
|
```
|
|
|
|
A guarded view means: when this guarded view is observed along the reachable
|
|
checked-execution path, run `guard` against the runtime value.
|
|
|
|
## Goals
|
|
|
|
- Preserve ordinary value-level program shapes.
|
|
- Keep guard failure out of user code.
|
|
- Avoid Haskell-specific checker/runtime semantics.
|
|
- Represent guard boundaries explicitly in portable tree data.
|
|
- Make successful guarded execution transparent: guarded values are unwrapped
|
|
before ordinary code receives them.
|
|
- Prefer correctness-by-default over avoiding repeated predicate cost.
|
|
|
|
## Non-goals
|
|
|
|
- Preventing user-written guards from diverging.
|
|
- Letting guards author their own diagnostics.
|
|
- Solving IO interaction-tree composition.
|
|
- Finalizing long-term artifact identity policy.
|
|
- Deduplicating or hoisting repeated guard checks.
|
|
|
|
## Plain Views vs Guards
|
|
|
|
Plain Views still provide concrete benefits without guards:
|
|
|
|
- structural flow checking;
|
|
- portable API metadata;
|
|
- module/export contract metadata;
|
|
- content-store view-tree metadata;
|
|
- cross-frontend agreement on contract structure;
|
|
- diagnostics for wrong-view flows.
|
|
|
|
Guards are for invariants that require runtime value inspection, such as:
|
|
|
|
- non-empty list;
|
|
- sorted list;
|
|
- byte string of exactly 32 bytes;
|
|
- protocol payload with a valid checksum;
|
|
- domain-specific runtime predicate.
|
|
|
|
Guards are deliberately more expensive than ordinary Views. Use them when the
|
|
runtime contract must be enforced.
|
|
|
|
## Guard Result Protocol
|
|
|
|
Guards return one of two standardized shapes:
|
|
|
|
```tri
|
|
guardOk value
|
|
guardFail
|
|
```
|
|
|
|
Guards do not provide diagnostics. The checked-exec runner owns diagnostics.
|
|
Malformed guard output is treated as a checked-runtime failure.
|
|
|
|
## Checked Execution Protocol
|
|
|
|
A successful typed-program check returns a checked-execution artifact, not a raw
|
|
payload.
|
|
|
|
Current constructors:
|
|
|
|
```tri
|
|
checkedPure value
|
|
checkedFail diagnostic
|
|
checkedGuard view guard value continuation
|
|
checkedGuardWithContext context view guard value continuation
|
|
checkedBind exec continuation
|
|
```
|
|
|
|
`checkedGuard` is the compatibility/default constructor. It lowers to
|
|
`checkedGuardWithContext` with an unknown context. Checker-injected guard
|
|
boundaries use `checkedGuardWithContext` so failures can identify where the
|
|
boundary came from.
|
|
|
|
Runner:
|
|
|
|
```tri
|
|
runChecked checkedExec
|
|
```
|
|
|
|
Semantics:
|
|
|
|
```text
|
|
runChecked (checkedPure value)
|
|
= checkedRuntimeOk value
|
|
|
|
runChecked (checkedFail diagnostic)
|
|
= checkedRuntimeFail diagnostic
|
|
|
|
runChecked (checkedGuardWithContext context view guard value continuation)
|
|
= case guard value of
|
|
guardOk checkedValue -> runChecked (continuation checkedValue)
|
|
guardFail -> checkedRuntimeFail (guardFailed context view)
|
|
malformed -> checkedRuntimeFail (malformedGuardResult context view malformed)
|
|
|
|
runChecked (checkedGuard view guard value continuation)
|
|
= runChecked (checkedGuardWithContext unknownContext view guard value continuation)
|
|
|
|
runChecked (checkedBind exec continuation)
|
|
= case runChecked exec of
|
|
checkedRuntimeOk value -> runChecked (continuation value)
|
|
checkedRuntimeFail diag -> checkedRuntimeFail diag
|
|
```
|
|
|
|
Important invariant:
|
|
|
|
> Guard failure is consumed by `runChecked`. It is never passed into ordinary
|
|
> user code.
|
|
|
|
## Checker Result Shape
|
|
|
|
`checkTypedProgramWith` returns checked-exec on success:
|
|
|
|
```tri
|
|
ok checkedExec env
|
|
```
|
|
|
|
Even unguarded programs return:
|
|
|
|
```tri
|
|
checkedPure rootPayload
|
|
```
|
|
|
|
Compatibility helper:
|
|
|
|
```tri
|
|
checkedProgramTree result
|
|
```
|
|
|
|
`checkedProgramTree` runs/unwraps checked-exec to preserve older raw-tree helper
|
|
behavior.
|
|
|
|
The Haskell `tricu check` path now evaluates successful checker output through
|
|
`runChecked`, so source-level guarded annotations fail through the same portable
|
|
checked-exec protocol.
|
|
|
|
## Boundary Semantics
|
|
|
|
Guard insertion follows correctness-first semantics:
|
|
|
|
> Every guarded View observation on the reachable checked-execution path runs
|
|
> its guard.
|
|
|
|
Important boundary kinds:
|
|
|
|
### Guarded typed value
|
|
|
|
```tri
|
|
typedValue sym (viewGuarded base guard) payload
|
|
```
|
|
|
|
This observes `sym` as a guarded value. It also supplies base-view evidence for
|
|
flow checking.
|
|
|
|
### Guarded requirement
|
|
|
|
```tri
|
|
typedRequire sym (viewGuarded base guard) payload
|
|
```
|
|
|
|
The symbol must satisfy `base`; the guarded observation is attached to `sym` and
|
|
is enforced when `sym` is used or exposed along the reachable root path.
|
|
|
|
### Guarded function argument
|
|
|
|
For:
|
|
|
|
```tri
|
|
viewFn [(viewGuarded base guard)] result
|
|
```
|
|
|
|
application checking guards the argument before the callee receives it.
|
|
|
|
### Guarded function result
|
|
|
|
For:
|
|
|
|
```tri
|
|
viewFn [arg] (viewGuarded base guard)
|
|
```
|
|
|
|
application checking guards the application result before exposing it as the
|
|
result value.
|
|
|
|
### Guarded callee symbol
|
|
|
|
If a function symbol itself has a guarded observation, that guard runs before the
|
|
function value is applied. A successful guard may transform the function value;
|
|
the application uses the guarded value.
|
|
|
|
## Global Symbol Observations
|
|
|
|
Guarded `typedValue` and `typedRequire` nodes are **global per-symbol
|
|
observations**, not position-sensitive flow events.
|
|
|
|
All guarded observations for a symbol compose in typed-node order whenever that
|
|
symbol is used or exposed on the reachable checked-execution path.
|
|
|
|
This means a later requirement still applies to an earlier syntactic use:
|
|
|
|
```tri
|
|
typedValue 1 viewString "x"
|
|
typedApply 2 f 1 "x"
|
|
typedRequire 1 (viewGuarded viewString guard) "x"
|
|
```
|
|
|
|
The guarded requirement is attached to symbol `1`; compiling the reachable root
|
|
path that uses symbol `1` runs that guard.
|
|
|
|
Rationale:
|
|
|
|
- typed programs are declarative symbol graphs, not imperative event traces;
|
|
- global observations are simpler and more correct-by-default;
|
|
- producers cannot accidentally bypass a guard by ordering a requirement too
|
|
late;
|
|
- staged raw/checked phases should use distinct symbols.
|
|
|
|
## Reachability and Repetition
|
|
|
|
Guards are not run eagerly for every guarded node in a program.
|
|
|
|
Execution is root-reachable:
|
|
|
|
```tri
|
|
compileSymbol (typedProgramRoot program)
|
|
```
|
|
|
|
Only guarded observations reachable from the root checked-execution path run.
|
|
Unreachable guarded symbols do not pay guard cost and do not fail execution.
|
|
|
|
Repeated reachable uses rerun guards. There is currently no deduplication or
|
|
hoisting. This is intentional: each guarded observation/use is a runtime contract
|
|
boundary.
|
|
|
|
Future optimization policies may add explicit deduplication or hoisting, but the
|
|
baseline semantics are repeated, deterministic guard execution.
|
|
|
|
## Function and Application Compilation
|
|
|
|
Checked execution is built compositionally from typed-node dependencies:
|
|
|
|
1. compile the callee symbol;
|
|
2. compile the argument symbol;
|
|
3. run any guarded observations attached to the argument symbol;
|
|
4. run the guarded function-argument boundary, if present;
|
|
5. apply the callee to the checked argument;
|
|
6. run the guarded function-result boundary, if present;
|
|
7. run guarded observations attached to the application result symbol.
|
|
|
|
This handles nested and curried application chains because each `typedApply`
|
|
consumes one function argument and produces a symbol whose inferred view is the
|
|
function residual/result view.
|
|
|
|
## Diagnostics
|
|
|
|
Guards do not author diagnostics. The checked-exec runner renders diagnostics
|
|
from checker-owned boundary context plus the guarded View.
|
|
|
|
Checker-injected guard nodes carry portable structural context. Current context
|
|
kinds are:
|
|
|
|
- root `typedValue` exposure;
|
|
- root `typedRequire` exposure;
|
|
- non-root `typedValue` symbol observation;
|
|
- non-root `typedRequire` symbol observation;
|
|
- function argument boundary;
|
|
- function result boundary;
|
|
- unknown/default context for manually constructed `checkedGuard` values.
|
|
|
|
Examples:
|
|
|
|
```text
|
|
guard failed at root typedValue symbol 0 for Guarded String
|
|
guard failed at root typedRequire symbol 3 for Guarded String
|
|
guard failed at typedRequire symbol 6 for Guarded String
|
|
guard failed at argument 0 of application symbol 2 (callee symbol 0, arg symbol 1) for Guarded String
|
|
guard failed at result of application symbol 2 (callee symbol 0, arg symbol 1) for Guarded String
|
|
malformed guard result at argument 0 of application symbol 2 (callee symbol 0, arg symbol 1) for Guarded String
|
|
```
|
|
|
|
Manually constructed `checkedGuard` values use unknown context and therefore
|
|
render without a boundary suffix:
|
|
|
|
```text
|
|
guard failed for String
|
|
malformed guard result for String
|
|
```
|
|
|
|
The context is diagnostic-only. It does not affect guard execution, View
|
|
compatibility, success/failure semantics, or continuation values.
|
|
|
|
The context deliberately contains raw portable data such as symbols and
|
|
application edges. It does not preserve source aliases such as `NonEmptyString`,
|
|
and it does not rely on Haskell-side post-processing or source-name annotation.
|
|
Named View rendering is a separate future design topic.
|
|
|
|
## Why Not Abort in Haskell?
|
|
|
|
A host-level abort primitive would move guard semantics into Haskell. The design
|
|
instead encodes guard failure in portable checked-exec artifacts and interprets
|
|
it with portable `tricu` code.
|
|
|
|
Haskell may evaluate the runner, but Haskell is not the semantic source of guard
|
|
validity or failure behavior.
|
|
|
|
## Why Not Maybe / Result Everywhere?
|
|
|
|
Returning `Maybe` or `Result` from every guarded boundary would infect ordinary
|
|
APIs. A function expecting a `List Byte` would have to accept
|
|
`Maybe (List Byte)` or `Result Error (List Byte)`, and every downstream caller
|
|
would need defensive handling.
|
|
|
|
The checked-exec runner avoids this. It unwraps successful guard results before
|
|
continuing and stops checked execution on failure.
|
|
|
|
## Known Sharp Edges
|
|
|
|
### Guard divergence
|
|
|
|
A user-written guard may diverge. This design handles intentional failure via
|
|
`guardFail`; it does not solve arbitrary nontermination. Fuel or timeouts are
|
|
separate runtime concerns.
|
|
|
|
### Payload trust
|
|
|
|
Typed nodes carry executable payloads. Guard injection must not expose an
|
|
unchecked precomputed payload at a guarded boundary. Boundaries are mediated by
|
|
checked-exec nodes.
|
|
|
|
This does not make malicious producer forgery impossible; it gives honest
|
|
frontends a portable, checkable protocol that avoids accidental bypasses.
|
|
|
|
### Cyclic typed-apply graphs
|
|
|
|
The current symbol compiler assumes typed programs are well-founded dependency
|
|
graphs as emitted by the frontend/lowering path. Cyclic typed-apply graphs are a
|
|
malformed-program validation concern, not a guard-specific semantic feature.
|
|
|
|
## Current Implementation Status
|
|
|
|
Implemented in `lib/view.tri` and exercised by tests:
|
|
|
|
- `guardOk` / `guardFail`;
|
|
- `checkedPure`, `checkedFail`, `checkedGuard`, `checkedGuardWithContext`, `checkedBind`;
|
|
- `runChecked`;
|
|
- success from `checkTypedProgramWith` returns checked-exec;
|
|
- `checkedProgramTree` compatibility helper;
|
|
- guarded root exposure;
|
|
- guarded `typedValue` and `typedRequire`;
|
|
- guarded function arguments and results;
|
|
- guarded callee observations;
|
|
- nested/curried application guard composition;
|
|
- global per-symbol observations;
|
|
- root-reachability behavior;
|
|
- repeated reachable uses rerun guards;
|
|
- source/Haskell `tricu check` integration;
|
|
- imported/module `VTGuarded` lowering to portable `viewGuarded`;
|
|
- portable guard boundary diagnostics with symbol/application context.
|