Tricu 2.0.0
Sorry for squashing all of this but 🤷
This commit is contained in:
596
docs/content-store-and-module-format.md
Normal file
596
docs/content-store-and-module-format.md
Normal file
@@ -0,0 +1,596 @@
|
||||
# Content Store and Module Format Design
|
||||
|
||||
Status: concrete design draft.
|
||||
|
||||
This document narrows the higher-level module-system direction into concrete
|
||||
format and storage decisions. It intentionally avoids source/provenance details:
|
||||
modules export usable portable artifacts, not edit history.
|
||||
|
||||
Related design overview: `docs/module-system-design.md`.
|
||||
|
||||
## 1. Scope
|
||||
|
||||
This document specifies the first target shape for:
|
||||
|
||||
- a neutral filesystem-backed content-addressed store;
|
||||
- Arboricx Merkle node persistence;
|
||||
- indexed Arboricx bundle import/export as transport;
|
||||
- module manifests as immutable export maps;
|
||||
- workspace aliases as mutable human-facing references;
|
||||
- View Contract artifact attachment to module exports.
|
||||
|
||||
It does not specify:
|
||||
|
||||
- package manager semantics;
|
||||
- dependency solving;
|
||||
- source-level rebuild/provenance metadata;
|
||||
- final import syntax;
|
||||
- garbage collection;
|
||||
- registry/sync protocol.
|
||||
|
||||
## 2. Non-Negotiable Boundaries
|
||||
|
||||
The content store is not `tricu`-specific and is not Haskell-specific.
|
||||
|
||||
The store may contain objects produced by `tricu`, Haskell, Tree Calculus tools,
|
||||
Arboricx tooling, or future frontends. The store core only knows object bytes,
|
||||
object kinds, hashes, aliases, and optionally structural references for known
|
||||
portable formats.
|
||||
|
||||
View Contracts may be first-class artifact references because they are portable
|
||||
Tree Calculus data checked by pure Tree Calculus code. They are not
|
||||
Haskell-private semantics.
|
||||
|
||||
Source and build provenance are intentionally excluded from the first module
|
||||
manifest format. A module manifest answers:
|
||||
|
||||
```text
|
||||
What portable artifacts does this module export, and what portable contracts are
|
||||
paired with them?
|
||||
```
|
||||
|
||||
It does not answer:
|
||||
|
||||
```text
|
||||
Which source file, parser, frontend, or build command produced these artifacts?
|
||||
```
|
||||
|
||||
## 3. Hashing Convention
|
||||
|
||||
Objects are content-addressed by SHA-256 over domain-separated canonical bytes.
|
||||
|
||||
General rule:
|
||||
|
||||
```text
|
||||
hash = SHA256(domainUtf8 || 0x00 || canonicalPayloadBytes)
|
||||
```
|
||||
|
||||
This matches the existing Merkle node convention in `Research.nodeHash`:
|
||||
|
||||
```text
|
||||
SHA256("arboricx.merkle.node.v1" || 0x00 || nodePayload)
|
||||
```
|
||||
|
||||
The domain string is part of the object format. It prevents identical payload
|
||||
bytes in different formats from accidentally sharing identity.
|
||||
|
||||
Hashes are represented externally as 64 lowercase hexadecimal characters.
|
||||
|
||||
## 4. Filesystem Store Layout
|
||||
|
||||
The canonical filesystem store layout is:
|
||||
|
||||
```text
|
||||
store/
|
||||
objects/
|
||||
abc/
|
||||
abc123... -- object bytes, sharded by first 3 hex chars
|
||||
aliases/
|
||||
names/
|
||||
modules/
|
||||
packages/
|
||||
manifests/
|
||||
tmp/
|
||||
```
|
||||
|
||||
The three-character shard follows the existing `lib/arboricx/server.tri`
|
||||
convention.
|
||||
|
||||
### 4.1 Object paths
|
||||
|
||||
For object hash:
|
||||
|
||||
```text
|
||||
abc123...
|
||||
```
|
||||
|
||||
object bytes live at:
|
||||
|
||||
```text
|
||||
store/objects/abc/abc123...
|
||||
```
|
||||
|
||||
The object filename is the full hash. The shard directory is the first three hex
|
||||
characters.
|
||||
|
||||
### 4.2 Atomic writes
|
||||
|
||||
Writers should use:
|
||||
|
||||
```text
|
||||
store/tmp/<hash>.<nonce>.tmp
|
||||
```
|
||||
|
||||
then atomically rename into:
|
||||
|
||||
```text
|
||||
store/objects/<shard>/<hash>
|
||||
```
|
||||
|
||||
Writing an existing object is idempotent if the existing bytes match the hash.
|
||||
|
||||
### 4.3 Store core metadata
|
||||
|
||||
The minimal filesystem store does not require sidecar metadata for every object.
|
||||
Object kind can be known by context or by manifest references.
|
||||
|
||||
A later index may cache:
|
||||
|
||||
```text
|
||||
hash -> kind
|
||||
hash -> size
|
||||
hash -> references
|
||||
hash -> createdAt
|
||||
```
|
||||
|
||||
but this index is not semantic identity.
|
||||
|
||||
## 5. Arboricx Merkle Node Object Format
|
||||
|
||||
The persistent Tree Calculus representation is a Merkle DAG of node objects.
|
||||
|
||||
Domain:
|
||||
|
||||
```text
|
||||
arboricx.merkle.node.v1
|
||||
```
|
||||
|
||||
Canonical payloads:
|
||||
|
||||
```text
|
||||
Leaf = 0x00
|
||||
Stem child = 0x01 || childHashRaw32
|
||||
Fork left right
|
||||
= 0x02 || leftHashRaw32 || rightHashRaw32
|
||||
```
|
||||
|
||||
Where `childHashRaw32`, `leftHashRaw32`, and `rightHashRaw32` are the raw 32-byte
|
||||
SHA-256 digests corresponding to child node hashes.
|
||||
|
||||
This is already implemented conceptually by:
|
||||
|
||||
```text
|
||||
Research.Node
|
||||
Research.serializeNode
|
||||
Research.deserializeNode
|
||||
Research.nodeHash
|
||||
```
|
||||
|
||||
The filesystem CAS should use this payload/hash convention directly.
|
||||
|
||||
## 6. Tree Roots
|
||||
|
||||
A Tree Calculus value stored in the CAS is identified by the hash of its root
|
||||
Merkle node.
|
||||
|
||||
```text
|
||||
treeRootHash = hash(rootNodePayload)
|
||||
```
|
||||
|
||||
The complete tree is reconstructed by recursively loading node objects reachable
|
||||
from the root.
|
||||
|
||||
Hydration is an interpretation step, not part of object identity. A client may
|
||||
hydrate a root as a plain tree, a graph with explicit sharing, or another runtime
|
||||
representation as long as the observable Tree Calculus value is the same. The
|
||||
filesystem CAS provides structural dedupe and portable identity; it does not by
|
||||
itself guarantee that a hydrated runtime value is the cheapest representation for
|
||||
all workloads.
|
||||
|
||||
Merkle nodes are useful for explicit DAG-oriented tooling, audit, and bundle
|
||||
packing. They are not the default representation for module executable exports:
|
||||
storing every subtree as a separate filesystem object is pathologically slow for
|
||||
large normal forms.
|
||||
|
||||
For module-backed evaluation and imports, a complete normalized named term is
|
||||
stored as one canonical object:
|
||||
|
||||
```text
|
||||
kind: arboricx.tree-term.v1
|
||||
hash: <whole-term object hash>
|
||||
abi: arboricx.abi.tree.v1
|
||||
```
|
||||
|
||||
The `arboricx.tree-term.v1` payload is a prefix encoding:
|
||||
|
||||
```text
|
||||
Leaf = 0x00
|
||||
Stem t = 0x01 Tree
|
||||
Fork l r = 0x02 Tree Tree
|
||||
```
|
||||
|
||||
## 7. Arboricx Indexed Bundles
|
||||
|
||||
Indexed `.arboricx` bundles remain the transport/execution format.
|
||||
|
||||
They are:
|
||||
|
||||
- compact;
|
||||
- self-contained;
|
||||
- deterministic;
|
||||
- suitable for restricted runtimes;
|
||||
- suitable for HTTP serving and deployment.
|
||||
|
||||
They are not the canonical long-lived deduplicated store representation.
|
||||
|
||||
### 7.1 Pack
|
||||
|
||||
Packing converts one or more CAS tree roots into an indexed bundle:
|
||||
|
||||
```text
|
||||
CAS tree roots -> indexed Arboricx bundle
|
||||
```
|
||||
|
||||
The packer traverses reachable Merkle nodes, emits a compact indexed node table,
|
||||
and writes a bundle manifest with export names and root indices.
|
||||
|
||||
### 7.3 Unpack
|
||||
|
||||
Unpacking converts a bundle into CAS nodes:
|
||||
|
||||
```text
|
||||
indexed Arboricx bundle -> CAS tree roots
|
||||
```
|
||||
|
||||
The unpacker verifies the bundle structure, reconstructs each exported tree, and
|
||||
stores the corresponding Merkle nodes. It returns the tree root hash for each
|
||||
bundle export.
|
||||
|
||||
## 8. Module Manifest v1
|
||||
|
||||
A module is an immutable manifest object. The module identity is the hash of its
|
||||
canonical manifest bytes.
|
||||
|
||||
A module name is not identity. It is a workspace alias to a module manifest hash.
|
||||
|
||||
### 8.1 Domain
|
||||
|
||||
Proposed domain:
|
||||
|
||||
```text
|
||||
arboricx.module-manifest.v1
|
||||
```
|
||||
|
||||
### 8.2 Purpose
|
||||
|
||||
A module manifest pairs human-facing export names with portable content objects
|
||||
and optional portable contracts.
|
||||
|
||||
It exists to support:
|
||||
|
||||
- reproducible import resolution;
|
||||
- executable export discovery;
|
||||
- View Contract lookup for imported symbols;
|
||||
- module-to-module reference tracking;
|
||||
- transport/store interop.
|
||||
|
||||
It does not describe source provenance.
|
||||
|
||||
### 8.3 Conceptual shape
|
||||
|
||||
```text
|
||||
moduleManifestV1:
|
||||
imports:
|
||||
- alias: <text>
|
||||
kind: <object kind>
|
||||
hash: <object hash>
|
||||
|
||||
exports:
|
||||
- name: <text>
|
||||
object:
|
||||
kind: <object kind>
|
||||
hash: <object hash>
|
||||
abi: <abi identifier>
|
||||
view: optional
|
||||
kind: <view artifact kind>
|
||||
hash: <view artifact hash>
|
||||
catalog: optional
|
||||
kind: <view catalog kind>
|
||||
hash: <view catalog hash>
|
||||
|
||||
metadata: optional human-facing fields
|
||||
```
|
||||
|
||||
### 8.4 Imports/references
|
||||
|
||||
The `imports` section is a manifest reference graph, not a store-level language
|
||||
dependency graph.
|
||||
|
||||
Each entry records direct content-addressed references used by the module:
|
||||
|
||||
```text
|
||||
alias: Prelude
|
||||
kind: arboricx.module-manifest.v1
|
||||
hash: <module hash>
|
||||
```
|
||||
|
||||
This supports reproducibility, partial fetch, and audit. The content store core
|
||||
stores this object but does not need to understand `Prelude` or import
|
||||
semantics.
|
||||
|
||||
### 8.5 Exports
|
||||
|
||||
Each export is a record, not a single hash. This is required so executable
|
||||
objects and advertised contracts cannot drift apart.
|
||||
|
||||
Minimal executable export:
|
||||
|
||||
```text
|
||||
name: "id"
|
||||
object:
|
||||
kind: arboricx.tree-term.v1
|
||||
hash: <whole-term hash>
|
||||
abi: arboricx.abi.tree.v1
|
||||
```
|
||||
|
||||
Export with View Contract:
|
||||
|
||||
```text
|
||||
name: "map"
|
||||
object:
|
||||
kind: arboricx.tree-term.v1
|
||||
hash: <whole-term hash>
|
||||
abi: arboricx.abi.tree.v1
|
||||
view:
|
||||
kind: arboricx.view-contract.type.v1
|
||||
hash: <view type hash>
|
||||
```
|
||||
|
||||
The manifest preserves the pairing between exported executable and exported
|
||||
contract. For workspace modules built from local source, annotated exports are
|
||||
checked before the manifest is published; only exports that pass producer-side
|
||||
View Contract checking receive direct `arboricx.view-contract.type.v1` refs.
|
||||
|
||||
### 8.6 Metadata
|
||||
|
||||
Metadata is optional and human-facing. Initial fields may include:
|
||||
|
||||
```text
|
||||
package
|
||||
version
|
||||
description
|
||||
license
|
||||
createdBy
|
||||
```
|
||||
|
||||
Metadata is not source provenance and is not required for execution or checking.
|
||||
|
||||
## 9. View Contract Artifacts
|
||||
|
||||
View Contract artifacts are portable Arboricx-layer data. They may be stored
|
||||
as content objects and referenced by module exports. `tricu` may emit these
|
||||
objects, but the object kind is not tricu-specific.
|
||||
|
||||
Current artifact kind:
|
||||
|
||||
```text
|
||||
arboricx.view-contract.type.v1
|
||||
```
|
||||
|
||||
`arboricx.view-contract.type.v1` is the direct export-view artifact. Its
|
||||
payload is a canonical prefix binary encoding of the syntactic ViewType:
|
||||
|
||||
```text
|
||||
Name = 0x00 u32be(byte-length) utf8-name
|
||||
Ref = 0x01 u32be(byte-length) utf8-ref
|
||||
List = 0x02 ViewType
|
||||
Maybe = 0x03 ViewType
|
||||
Pair = 0x04 ViewType ViewType
|
||||
Result = 0x05 ViewType ViewType
|
||||
Fn = 0x06 u32be(argument-count) ViewType* ViewType
|
||||
```
|
||||
|
||||
`utf8-ref` is tagged text:
|
||||
|
||||
```text
|
||||
i:<decimal-integer> numeric/legacy ref
|
||||
s:<text> symbolic user ref
|
||||
```
|
||||
|
||||
Symbolic refs are the preferred user-authored form; numeric refs remain useful
|
||||
for generated code, fixtures, and old low-level examples.
|
||||
|
||||
The object hash domain is the object kind:
|
||||
|
||||
```text
|
||||
arboricx.view-contract.type.v1 \0 <payload>
|
||||
```
|
||||
|
||||
### 9.1 Export-level pairing
|
||||
|
||||
The module manifest is the canonical pairing of an executable export and its
|
||||
advertised contract:
|
||||
|
||||
```text
|
||||
export name -> tree-term hash + optional view artifact hash
|
||||
```
|
||||
|
||||
This avoids drift such as:
|
||||
|
||||
```text
|
||||
map -> tree A
|
||||
map.view -> contract B
|
||||
```
|
||||
|
||||
where aliases might be retargeted independently.
|
||||
|
||||
### 9.2 Import checking
|
||||
|
||||
When a source file imports a module, a frontend can resolve an imported export,
|
||||
decode its direct `arboricx.view-contract.type.v1` ref, and emit typed program
|
||||
evidence locally:
|
||||
|
||||
```text
|
||||
imported List.map has view Fn [...]
|
||||
```
|
||||
|
||||
For locally built workspace modules this is backed by producer-side checking
|
||||
before the module manifest alias is published, including imported view facts from
|
||||
dependencies used by the producer source. External or prebuilt manifests are
|
||||
trusted boundary declarations for now; they are not accompanied by proof objects.
|
||||
The checker still consumes only local numeric symbols and typed-program evidence.
|
||||
Global content hashes do not become checker symbols.
|
||||
|
||||
Correct split:
|
||||
|
||||
```text
|
||||
local checker symbol: 3
|
||||
presentation label: "List.map"
|
||||
resolved object: sha256:...
|
||||
exported view: Fn [...]
|
||||
```
|
||||
|
||||
### 9.3 Execution hydration versus contract evidence
|
||||
|
||||
Execution imports should use a narrow, demand-driven path:
|
||||
|
||||
```text
|
||||
module import -> selected executable exports -> hydrate selected tree-term objects
|
||||
```
|
||||
|
||||
This path should not compute a dependency closure over other module exports.
|
||||
Each selected executable export is already a complete Tree Calculus value.
|
||||
|
||||
Contract-aware checking may use a broader path:
|
||||
|
||||
```text
|
||||
module import -> selected exports -> exported view type refs -> typed-program evidence
|
||||
```
|
||||
|
||||
That path emits portable evidence and leaves compatibility policy decisions to
|
||||
the Tree Calculus checker. typed programs and reusable catalogs do not need their
|
||||
own binary object kinds today: they are ordinary Tree Calculus data and can be
|
||||
stored as `arboricx.tree-term.v1` when persistence is useful.
|
||||
|
||||
## 10. Workspace Aliases
|
||||
|
||||
A workspace is mutable human-facing state over immutable content.
|
||||
|
||||
Examples:
|
||||
|
||||
```text
|
||||
List -> module manifest hash
|
||||
Prelude -> module manifest hash
|
||||
map -> tree-term hash
|
||||
httpServer -> bundle hash
|
||||
```
|
||||
|
||||
Aliases should live under:
|
||||
|
||||
```text
|
||||
store/aliases/
|
||||
```
|
||||
|
||||
Initial categories:
|
||||
|
||||
```text
|
||||
store/aliases/modules/<name>
|
||||
store/aliases/names/<name>
|
||||
store/aliases/packages/<name>
|
||||
```
|
||||
|
||||
Alias file contents should be simple and explicit, for example:
|
||||
|
||||
```text
|
||||
kind: arboricx.module-manifest.v1
|
||||
hash: abc123...
|
||||
```
|
||||
|
||||
Exact encoding can be decided with the first implementation. The important rule
|
||||
is that aliases are mutable pointers, not content identity.
|
||||
|
||||
## 11. Existing Convention Alignment
|
||||
|
||||
This design intentionally preserves existing conventions where they already fit:
|
||||
|
||||
- SHA-256 domain-separated Merkle node hashing;
|
||||
- `Leaf` / `Stem` / `Fork` node payload tags `0x00`, `0x01`, `0x02`;
|
||||
- three-character object sharding from `lib/arboricx/server.tri`;
|
||||
- indexed Arboricx bundles as compact transport objects;
|
||||
- optional human-facing export names in manifests;
|
||||
- View Contract checker evidence as portable Tree Calculus data.
|
||||
|
||||
It replaces or demotes conventions that do not fit:
|
||||
|
||||
- SQLite `terms.names` comma-separated aliases become workspace aliases/indexes;
|
||||
- SQLite `terms.tags` comma-separated tags become optional metadata/indexes;
|
||||
- file imports as AST flattening become transitional behavior;
|
||||
- names cease to be semantic identity.
|
||||
|
||||
## 12. Implementation Sketch
|
||||
|
||||
A staged implementation can proceed as follows:
|
||||
|
||||
1. Add filesystem CAS helpers alongside the existing SQLite store.
|
||||
2. Store/load Arboricx Merkle nodes using the filesystem layout.
|
||||
3. Implement tree-term storage and reconstruction from filesystem CAS.
|
||||
4. Implement pack from CAS tree terms/Merkle roots to indexed Arboricx bundle.
|
||||
5. Implement unpack from indexed Arboricx bundle to CAS tree terms/Merkle roots.
|
||||
6. Define a concrete module manifest encoding.
|
||||
7. Store/load module manifests as content-addressed objects.
|
||||
8. Add workspace alias read/write helpers.
|
||||
9. Teach import resolution to target module manifests/exports.
|
||||
10. Attach exported View Contract artifacts to module exports.
|
||||
11. Gradually migrate existing `!import` users.
|
||||
|
||||
## 13. Deferred Decisions
|
||||
|
||||
These are intentionally left out of the first concrete format:
|
||||
|
||||
- package version solving;
|
||||
- registry/remotes protocol;
|
||||
- garbage collection/reachability;
|
||||
- source/provenance/build-record objects;
|
||||
- editor/update workflows;
|
||||
- rich visibility/export rules;
|
||||
- final import syntax;
|
||||
- whether module manifests also need a tree-native encoding.
|
||||
|
||||
## 14. Summary
|
||||
|
||||
The concrete v1 direction is:
|
||||
|
||||
```text
|
||||
Store:
|
||||
filesystem-backed content-addressed objects
|
||||
|
||||
Hashing:
|
||||
SHA256(domain || 0x00 || canonical payload)
|
||||
|
||||
Tree persistence:
|
||||
Arboricx Merkle nodes
|
||||
|
||||
Transport:
|
||||
indexed .arboricx bundles, packable from and unpackable to CAS roots
|
||||
|
||||
Modules:
|
||||
immutable manifests pairing export names with object refs and optional View
|
||||
Contract refs
|
||||
|
||||
Workspace:
|
||||
mutable aliases from human names to immutable content hashes
|
||||
```
|
||||
|
||||
This keeps the store portable, preserves Arboricx's compact transport role,
|
||||
restores Merkle DAGs as the persistence model, and gives View Contracts a stable
|
||||
module/export attachment point without making the store `tricu`-specific.
|
||||
371
docs/guard-injection.md
Normal file
371
docs/guard-injection.md
Normal file
@@ -0,0 +1,371 @@
|
||||
# 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.
|
||||
505
docs/module-system-design.md
Normal file
505
docs/module-system-design.md
Normal file
@@ -0,0 +1,505 @@
|
||||
# Module System and Content Store Design
|
||||
|
||||
Status: design draft.
|
||||
|
||||
This document records the intended direction for reworking `tricu` modules,
|
||||
imports, Arboricx storage/transport, and the content store. It is not an
|
||||
implementation plan yet; it is a shared design target.
|
||||
|
||||
## 1. Problem Statement
|
||||
|
||||
The current module/import/content-store system is useful as a prototype, but it
|
||||
is not coherent enough to build on indefinitely.
|
||||
|
||||
Current behavior combines several partially-overlapping systems:
|
||||
|
||||
- `!import "path.tri" Namespace` and `!import "path.tri" !Local` perform
|
||||
filesystem-relative source preprocessing;
|
||||
- imported definitions are flattened into one program;
|
||||
- namespace qualification is implemented by string rewriting;
|
||||
- evaluation uses a flat `Map String T` environment;
|
||||
- the Haskell content store stores Tree Calculus Merkle nodes plus an ad hoc
|
||||
`terms` table with comma-separated names and tags;
|
||||
- the REPL can resolve names from the content store, including multiple versions;
|
||||
- Arboricx bundles provide compact indexed transport objects;
|
||||
- `lib/arboricx/server.tri` already sketches a filesystem-backed object store.
|
||||
|
||||
This works only when users and maintainers are mindful of sharp edges:
|
||||
|
||||
- names serve too many roles at once;
|
||||
- modules are not first-class semantic objects;
|
||||
- imports are closer to AST paste-and-prefix than resolution;
|
||||
- `!Local` imports can create global collisions;
|
||||
- content identity, human aliases, source files, and evaluated terms are not
|
||||
cleanly separated;
|
||||
- the SQLite schema is convenient but not a principled content-addressed store;
|
||||
- Arboricx transport and long-lived storage are not clearly distinguished.
|
||||
|
||||
## 2. Design Principles
|
||||
|
||||
### 2.1 Content addressability is foundational
|
||||
|
||||
Immutable content should be identified by hashes. Human names should be metadata
|
||||
or workspace aliases over content, not semantic identity.
|
||||
|
||||
This follows the core lesson from systems such as Unison: separate stable
|
||||
content identity from ergonomic naming and namespace organization.
|
||||
|
||||
### 2.2 The content store is language-neutral
|
||||
|
||||
The content store must not be married to `tricu` or Haskell.
|
||||
|
||||
It stores a small set of portable Arboricx artifacts: module manifests,
|
||||
complete tree terms, and direct View Contract types. Lower-level Merkle/bundle
|
||||
formats exist for transport and DAG tooling, but the store core should treat all
|
||||
objects as content-addressed bytes with formats/media types.
|
||||
|
||||
`tricu` and Haskell are clients/tooling. They are not the semantic owners of the
|
||||
store.
|
||||
|
||||
### 2.3 View Contracts are portable enough to integrate
|
||||
|
||||
The store may integrate with View Contracts because the checker and evidence
|
||||
format are pure Tree Calculus / portable tree data. View Contracts are not a
|
||||
Haskell-private or `tricu`-private semantic layer.
|
||||
|
||||
The module resolver may emit typed-program evidence, but checker semantics remain
|
||||
unchanged:
|
||||
|
||||
```text
|
||||
Haskell emits evidence.
|
||||
tricu judges evidence.
|
||||
```
|
||||
|
||||
### 2.4 Modules should reflect definitions as they actually exist
|
||||
|
||||
The module system should conform to the reality of content-addressed immutable
|
||||
artifacts and mutable human aliases. We should not contort definitions to fit a
|
||||
traditional text-file module system if that fights the storage model.
|
||||
|
||||
### 2.5 Transport and storage are different jobs
|
||||
|
||||
Indexed Arboricx bundles are excellent transport/execution objects. Merkle DAGs
|
||||
are better long-lived persistence objects. These should remain separate but
|
||||
interoperable representations.
|
||||
|
||||
## 3. Conceptual Architecture
|
||||
|
||||
```text
|
||||
Content Store
|
||||
neutral content-addressed object store
|
||||
|
||||
Arboricx CAS / Merkle Store
|
||||
Tree Calculus node/object formats suitable for persistence and dedupe
|
||||
|
||||
Arboricx Bundle
|
||||
compact indexed transport/execution format
|
||||
|
||||
View Contract Artifact
|
||||
portable evidence/checker data over tree artifacts
|
||||
|
||||
Module Manifest
|
||||
immutable export map from names to content objects and optional contracts
|
||||
|
||||
Workspace
|
||||
mutable aliases, selected versions, package pins, and user-facing names
|
||||
|
||||
tricu
|
||||
one frontend/toolchain that emits/consumes these portable artifacts
|
||||
```
|
||||
|
||||
The content store stores objects. Arboricx defines important object formats.
|
||||
View Contracts define portable checking artifacts. `tricu` produces and consumes
|
||||
those formats.
|
||||
|
||||
### 3.1 Execution imports versus contract checking
|
||||
|
||||
Import resolution has two intentionally different performance profiles.
|
||||
|
||||
For normal execution/evaluation, resolving a module import should hydrate only
|
||||
the executable exports directly demanded by the importing source. Exported Tree
|
||||
Calculus values are complete normal forms: importing `foo` does not require
|
||||
hydrating separate `bar` or `baz` exports that may have helped build it. This is
|
||||
the fast path for `!import`, including `!Local` imports.
|
||||
|
||||
View Contract checking is a separate evidence-gathering path. It may load
|
||||
exported direct view types for the symbols that participate in a check. That
|
||||
slower path must remain behind the typed program boundary:
|
||||
|
||||
```text
|
||||
Haskell emits evidence.
|
||||
tricu judges evidence.
|
||||
```
|
||||
|
||||
Reusable view catalogs are ordinary tricu libraries/tree terms, not a separate
|
||||
core CAS artifact kind.
|
||||
|
||||
For locally built workspace modules, advertised direct export views are
|
||||
producer-checked before the manifest alias is written. Producer checking includes
|
||||
advertised views from any imported modules used by that source, so a module
|
||||
cannot publish a local annotated export that contradicts a dependency's exported
|
||||
view. If producer checking fails, the module alias is not written.
|
||||
|
||||
Consumer checking then resolves selected module exports, decodes their
|
||||
`arboricx.view-contract.type.v1` refs, and emits trusted `KnownView` evidence
|
||||
for the local imported symbols. Those facts are module-boundary assumptions:
|
||||
local workspace builds create them after producer-side checking, while external
|
||||
or prebuilt manifests are trusted inputs for now. In all cases, compatibility
|
||||
with local requirements is still judged by the portable checker in `lib/view.tri`.
|
||||
|
||||
## 4. Content Store Direction
|
||||
|
||||
### 4.1 Store core
|
||||
|
||||
The store core should be a content-addressed object store:
|
||||
|
||||
```text
|
||||
hash -> object bytes
|
||||
hash -> object kind / media type
|
||||
hash -> optional metadata/index entries
|
||||
```
|
||||
|
||||
The hash should be over canonical bytes with domain separation. The object kind
|
||||
or media type determines how a client interprets those bytes.
|
||||
|
||||
Current module/check object kinds:
|
||||
|
||||
```text
|
||||
arboricx.module-manifest.v1
|
||||
arboricx.tree-term.v1
|
||||
arboricx.view-contract.type.v1
|
||||
```
|
||||
|
||||
Merkle nodes and indexed bundles remain lower-level Arboricx transport/DAG
|
||||
formats, but they are not the module/eval storage model. typed programs and view
|
||||
catalogs are ordinary tree terms unless a future external tooling use case proves
|
||||
that they need their own object kind.
|
||||
|
||||
The store core should not need to know what a `tricu` definition means.
|
||||
|
||||
### 4.2 Filesystem-backed layout
|
||||
|
||||
The long-term store should converge with the direction already sketched in
|
||||
`lib/arboricx/server.tri`:
|
||||
|
||||
```text
|
||||
store/
|
||||
objects/
|
||||
abc/
|
||||
abc123...object
|
||||
aliases/
|
||||
names/
|
||||
modules/
|
||||
packages/
|
||||
manifests/
|
||||
tmp/
|
||||
```
|
||||
|
||||
SQLite may remain useful as an optional index/cache, but it should not be the
|
||||
canonical store model.
|
||||
|
||||
### 4.3 Structural references, not language dependencies
|
||||
|
||||
The store may understand structural content references when they are part of an
|
||||
object format. For example, a Merkle node naturally references child hashes:
|
||||
|
||||
```text
|
||||
Leaf
|
||||
Stem childHash
|
||||
Fork leftHash rightHash
|
||||
```
|
||||
|
||||
This is not a `tricu` dependency graph. It is content structure.
|
||||
|
||||
Language/tool-level relationships such as "compiled from source", "exported by
|
||||
module", or "checked with contract" can live in manifests or indexes. They
|
||||
should not be required by the store core.
|
||||
|
||||
## 5. Arboricx Role
|
||||
|
||||
Arboricx should be understood as a family of portable Tree Calculus artifact
|
||||
formats, not as a single storage mechanism.
|
||||
|
||||
### 5.1 Arboricx Bundle
|
||||
|
||||
The existing indexed `.arboricx` format remains the preferred transport and
|
||||
execution object:
|
||||
|
||||
- compact;
|
||||
- self-contained;
|
||||
- deterministic;
|
||||
- easy to parse in constrained runtimes;
|
||||
- suitable for deployment and HTTP serving;
|
||||
- structurally verifiable without hash recomputation per node.
|
||||
|
||||
It says:
|
||||
|
||||
```text
|
||||
Here is everything you need, densely packed.
|
||||
```
|
||||
|
||||
### 5.2 Arboricx CAS / Merkle Store
|
||||
|
||||
The persistent store should use content-addressed structural objects:
|
||||
|
||||
```text
|
||||
Leaf
|
||||
Stem childHash
|
||||
Fork leftHash rightHash
|
||||
```
|
||||
|
||||
This enables dedupe across definitions, modules, packages, and versions. A large
|
||||
program that shares subtrees with other programs should not store those subtrees
|
||||
multiple times.
|
||||
|
||||
It says:
|
||||
|
||||
```text
|
||||
Here are immutable objects, addressable independently.
|
||||
```
|
||||
|
||||
### 5.3 Pack and unpack
|
||||
|
||||
Transport and storage should interoperate explicitly:
|
||||
|
||||
```text
|
||||
CAS root(s) -> pack -> indexed Arboricx bundle
|
||||
Arboricx bundle -> unpack -> CAS root(s)
|
||||
```
|
||||
|
||||
The bundle can be treated as an opaque content-addressed blob by the store, and
|
||||
it can also be unpacked into Merkle nodes for dedupe and partial reuse.
|
||||
|
||||
## 6. Modules
|
||||
|
||||
### 6.1 Module identity
|
||||
|
||||
A module should be an immutable manifest object. Its identity is the hash of its
|
||||
canonical manifest bytes.
|
||||
|
||||
A module name is not identity. It is a workspace alias or package-level alias to
|
||||
a module hash.
|
||||
|
||||
### 6.2 Module contents
|
||||
|
||||
A module manifest should primarily be an export map:
|
||||
|
||||
```text
|
||||
module hash
|
||||
exports:
|
||||
name -> content reference
|
||||
metadata:
|
||||
package
|
||||
version
|
||||
description
|
||||
license
|
||||
createdBy
|
||||
optional:
|
||||
view contract artifact refs
|
||||
ABI/media type info
|
||||
source/provenance refs
|
||||
```
|
||||
|
||||
The manifest should be portable and mostly format-oriented. It should not depend
|
||||
on Haskell data structures or `tricu`-specific internal semantics.
|
||||
|
||||
### 6.3 Export entries
|
||||
|
||||
An export entry may eventually look conceptually like:
|
||||
|
||||
```text
|
||||
name: "map"
|
||||
object: sha256:...
|
||||
kind: arboricx.tree-term.v1
|
||||
abi: arboricx.abi.tree.v1
|
||||
view: sha256:... -- optional View Contract artifact
|
||||
source: sha256:... -- optional source/provenance object
|
||||
```
|
||||
|
||||
Executable module exports are complete normalized tree terms stored as one
|
||||
`arboricx.tree-term.v1` object per named export. Merkle-node storage remains
|
||||
available for DAG-oriented tooling, but module/eval imports should not store or
|
||||
hydrate every subtree as a separate filesystem object.
|
||||
|
||||
### 6.4 Import behavior
|
||||
|
||||
Imports should resolve module aliases or content references to module manifests,
|
||||
then bind selected exports into the local source scope.
|
||||
|
||||
Export selection has one intentional aggregator special case:
|
||||
|
||||
```text
|
||||
module with local top-level definitions -> exports only those local definitions
|
||||
module with only imports -> reexports the evaluated import env
|
||||
```
|
||||
|
||||
This lets files such as `prelude.tri` act as explicit barrel modules without
|
||||
making every ordinary module reexport its imports. A module that defines even one
|
||||
local top-level name does not implicitly reexport imported names.
|
||||
|
||||
The future pipeline should be:
|
||||
|
||||
```text
|
||||
parse source
|
||||
resolve imports/names to module exports and content refs
|
||||
lower source using resolved refs
|
||||
emit a view-tree artifact
|
||||
check evidence when requested
|
||||
store/export artifacts
|
||||
```
|
||||
|
||||
It should not be:
|
||||
|
||||
```text
|
||||
paste imported ASTs into one file and rewrite strings
|
||||
```
|
||||
|
||||
## 7. Workspace Layer
|
||||
|
||||
Mutable human-facing state belongs in a workspace layer.
|
||||
|
||||
Examples:
|
||||
|
||||
```text
|
||||
List -> module hash
|
||||
Http -> module hash
|
||||
map -> definition/tree hash
|
||||
selected List version -> module hash
|
||||
package pin prelude -> package/module hash
|
||||
```
|
||||
|
||||
The workspace is where names, selections, pins, and aliases live. Renaming should
|
||||
usually mutate workspace aliases, not immutable content objects.
|
||||
|
||||
This gives humans stable ergonomic names without making names semantic identity.
|
||||
|
||||
## 8. Definition Identity
|
||||
|
||||
There are two useful identities and we should support both.
|
||||
|
||||
### 8.1 Tree identity
|
||||
|
||||
A Tree Calculus value has a Merkle root hash. This identifies the executable tree
|
||||
itself.
|
||||
|
||||
This is the right identity for:
|
||||
|
||||
- execution;
|
||||
- dedupe;
|
||||
- bundle roots;
|
||||
- low-level artifact sharing.
|
||||
|
||||
### 8.2 Module/export identity
|
||||
|
||||
The module manifest is the higher-level artifact boundary. It pairs each export
|
||||
name with its compiled tree term and optional direct View Contract type.
|
||||
|
||||
The content store should not require extra definition/source/provenance objects,
|
||||
and fully untyped Tree Calculus code must remain valid.
|
||||
|
||||
## 9. View Contract Integration
|
||||
|
||||
View Contracts should attach to modules/exports as portable artifacts.
|
||||
|
||||
An imported definition can be assigned a local numeric symbol while lowering a
|
||||
typed program. Its global identity remains a content hash or module export ref.
|
||||
|
||||
This is the intended split:
|
||||
|
||||
```text
|
||||
typed program local symbol: 3
|
||||
Debug label: "List.map"
|
||||
Resolved object: sha256:...
|
||||
Exported view: Fn [...]
|
||||
```
|
||||
|
||||
De Bruijn-style integer symbols are still appropriate inside a typed program. They
|
||||
are local evidence identifiers, not global content identity.
|
||||
|
||||
We should not make global objects depend on numeric checker symbols.
|
||||
|
||||
Untyped code remains valid with no contract artifact. If a boundary needs to
|
||||
participate in checking but has no information, it may use `Any` or rely on
|
||||
policy. We should not pretend all untyped functions have an infinite
|
||||
`Any -> Any -> ...` contract.
|
||||
|
||||
## 10. Import Syntax Direction
|
||||
|
||||
Exact syntax is future work, but the current `!import` form should be considered
|
||||
a transitional mechanism.
|
||||
|
||||
Future imports should distinguish:
|
||||
|
||||
- path-based source imports for local development;
|
||||
- workspace/module alias imports;
|
||||
- explicit content-addressed imports;
|
||||
- selected/exposed names;
|
||||
- qualified versus unqualified binding.
|
||||
|
||||
Possible directions:
|
||||
|
||||
```tri
|
||||
import "./list.tri" as List
|
||||
import List exposing (map foldl)
|
||||
import #abc123... as List
|
||||
```
|
||||
|
||||
The syntax should be designed after the object/module model is clearer.
|
||||
|
||||
## 11. Migration Strategy
|
||||
|
||||
A plausible migration path:
|
||||
|
||||
1. Define the neutral object store model and filesystem layout.
|
||||
2. Implement Merkle node persistence against that layout.
|
||||
3. Add pack/unpack between CAS roots and indexed Arboricx bundles.
|
||||
4. Replace ad hoc SQLite `terms` names/tags with workspace aliases or a clearer
|
||||
index layer.
|
||||
5. Define module manifest objects.
|
||||
6. Teach source imports to resolve manifests/exports instead of rewriting ASTs.
|
||||
7. Attach View Contract artifacts to module exports.
|
||||
8. Gradually migrate existing `lib/` and `demos/` imports.
|
||||
|
||||
Compatibility shims may keep existing `!import` working during migration.
|
||||
|
||||
## 12. Open Questions
|
||||
|
||||
- What exact canonical byte format should store objects use?
|
||||
- Should module manifests be binary, tree-encoded, or both?
|
||||
- What media type/kind registry do we need first?
|
||||
- How should object references be represented in source syntax?
|
||||
- How should workspaces be stored and shared?
|
||||
- What is the minimum useful module manifest?
|
||||
- Should source files compile directly to module manifests, or should manifests
|
||||
be produced by explicit package commands?
|
||||
- How much Arboricx bundle metadata should reference CAS roots?
|
||||
- What GC/reachability model should the store eventually use?
|
||||
|
||||
## 13. Summary
|
||||
|
||||
The desired design is:
|
||||
|
||||
```text
|
||||
Content store:
|
||||
portable CAS for immutable objects and structural references
|
||||
|
||||
Arboricx bundle:
|
||||
compact indexed transport/execution object
|
||||
|
||||
Arboricx CAS:
|
||||
persistent Merkle DAG/object representation for dedupe and partial reuse
|
||||
|
||||
Modules:
|
||||
immutable manifests mapping export names to content objects and optional
|
||||
contracts
|
||||
|
||||
Workspace:
|
||||
mutable human aliases, version selections, and package/module pins
|
||||
|
||||
View Contracts:
|
||||
portable evidence artifacts attached to exports and checked by pure Tree
|
||||
Calculus code
|
||||
```
|
||||
|
||||
The key architectural rule is that hashes provide stable identity, while names
|
||||
provide human usability. The module system should be built on that separation.
|
||||
582
docs/view-contract-syntax.md
Normal file
582
docs/view-contract-syntax.md
Normal file
@@ -0,0 +1,582 @@
|
||||
# View Contract Syntax Design
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
This document specifies source-level syntax sugar for emitting View Contract
|
||||
metadata from annotated `tricu` definitions.
|
||||
|
||||
The syntax is frontend sugar. It lowers to ordinary typed-program nodes consumed
|
||||
by the portable checker in `lib/view.tri` and catalog helpers in
|
||||
`lib/views/catalog.tri`.
|
||||
|
||||
The checker remains independent of source syntax.
|
||||
|
||||
## 2. Definition Annotations
|
||||
|
||||
A definition may carry argument and return view annotations directly in its head.
|
||||
|
||||
```tri
|
||||
name arg1@Type1 arg2@Type2 =@ReturnType body
|
||||
```
|
||||
|
||||
This declares:
|
||||
|
||||
```text
|
||||
name : Fn [Type1 Type2] ReturnType
|
||||
arg1 : Type1
|
||||
arg2 : Type2
|
||||
```
|
||||
|
||||
and lowers to View Contract metadata:
|
||||
|
||||
```tri
|
||||
typedDeclareFn nameSym [(Type1) (Type2)] ReturnType t
|
||||
typedValue arg1Sym Type1 t
|
||||
typedValue arg2Sym Type2 t
|
||||
```
|
||||
|
||||
If body flow metadata is emitted, the body result is required to satisfy the
|
||||
appropriate residual view.
|
||||
|
||||
## 3. Syntax Forms
|
||||
|
||||
### 3.1 Binder annotation
|
||||
|
||||
```tri
|
||||
x@Bool
|
||||
xs@(List Bool)
|
||||
f@(Fn [Bool] String)
|
||||
```
|
||||
|
||||
A binder annotation introduces a normal term binder and contributes an argument
|
||||
view to the function contract.
|
||||
|
||||
### 3.2 Phantom argument annotation
|
||||
|
||||
```tri
|
||||
name @A @B =@C body
|
||||
```
|
||||
|
||||
A phantom argument annotation contributes an argument view to the function
|
||||
contract but introduces no term binder.
|
||||
|
||||
This is useful for point-free and combinator-heavy definitions.
|
||||
|
||||
```tri
|
||||
name @A @B =@C body
|
||||
```
|
||||
|
||||
declares:
|
||||
|
||||
```text
|
||||
name : Fn [A B] C
|
||||
```
|
||||
|
||||
The body itself must satisfy the residual function view:
|
||||
|
||||
```text
|
||||
Fn [A B] C
|
||||
```
|
||||
|
||||
### 3.3 Binder prefix with phantom tail
|
||||
|
||||
Phantom annotations may appear after binder annotations:
|
||||
|
||||
```tri
|
||||
name x@A @B =@C body
|
||||
```
|
||||
|
||||
This declares:
|
||||
|
||||
```text
|
||||
name : Fn [A B] C
|
||||
x : A
|
||||
```
|
||||
|
||||
The body must satisfy:
|
||||
|
||||
```text
|
||||
Fn [B] C
|
||||
```
|
||||
|
||||
This allows a named binder prefix with a point-free tail.
|
||||
|
||||
### 3.4 Return annotation
|
||||
|
||||
```tri
|
||||
name x@A =@B body
|
||||
name =@B body
|
||||
```
|
||||
|
||||
`=@B` contributes the result view.
|
||||
|
||||
A definition with no arguments and a return annotation is a value contract, not a
|
||||
zero-arity function contract:
|
||||
|
||||
```tri
|
||||
name =@Bool body
|
||||
```
|
||||
|
||||
lowers to:
|
||||
|
||||
```tri
|
||||
typedValue nameSym viewBool t
|
||||
```
|
||||
|
||||
not:
|
||||
|
||||
```tri
|
||||
typedDeclareFn nameSym [] viewBool t
|
||||
```
|
||||
|
||||
## 4. Ordering Rule
|
||||
|
||||
Phantom argument annotations may only appear at the end of the argument list.
|
||||
|
||||
Valid:
|
||||
|
||||
```tri
|
||||
foo x@A y@B =@C body
|
||||
foo @A @B =@C body
|
||||
foo x@A @B =@C body
|
||||
foo x y@B @C =@D body
|
||||
```
|
||||
|
||||
Invalid:
|
||||
|
||||
```tri
|
||||
foo x@A @B z@C =@D body
|
||||
foo @A x@B =@C body
|
||||
```
|
||||
|
||||
Once a phantom `@Type` item appears, no later named binder may appear.
|
||||
|
||||
## 5. Contract-Bearing Definitions
|
||||
|
||||
A definition is contract-bearing if its head contains any of:
|
||||
|
||||
```text
|
||||
binder@Type
|
||||
@Type
|
||||
=@Type
|
||||
```
|
||||
|
||||
Ordinary unannotated definitions do not emit View Contract metadata.
|
||||
|
||||
```tri
|
||||
foo x y = body
|
||||
```
|
||||
|
||||
emits no contract metadata.
|
||||
|
||||
## 6. Unannotated Binders in Contract-Bearing Heads
|
||||
|
||||
In a contract-bearing definition, an unannotated binder contributes `Any`.
|
||||
|
||||
```tri
|
||||
foo x y@Bool =@String body
|
||||
```
|
||||
|
||||
means:
|
||||
|
||||
```text
|
||||
foo : Fn [Any Bool] String
|
||||
x : Any
|
||||
y : Bool
|
||||
```
|
||||
|
||||
This keeps mixed annotation lightweight without emitting contracts for fully
|
||||
unannotated definitions.
|
||||
|
||||
## 7. Missing Return Annotation
|
||||
|
||||
If a contract-bearing definition has argument annotations but no return
|
||||
annotation, the return view defaults to `Any`.
|
||||
|
||||
```tri
|
||||
foo x@Bool = body
|
||||
```
|
||||
|
||||
means:
|
||||
|
||||
```text
|
||||
foo : Fn [Bool] Any
|
||||
x : Bool
|
||||
```
|
||||
|
||||
## 8. Type Annotation Grammar
|
||||
|
||||
Annotations are intentionally small at the attachment site.
|
||||
|
||||
After `@` or `=@`, the parser accepts either a single atomic view expression or
|
||||
a parenthesized compound view expression.
|
||||
|
||||
Valid:
|
||||
|
||||
```tri
|
||||
x@Bool
|
||||
x@(List Bool)
|
||||
f@(Fn [Bool] String)
|
||||
r@(Result String Bool)
|
||||
name =@Bool body
|
||||
name =@(List Bool) body
|
||||
```
|
||||
|
||||
These are not structural annotations:
|
||||
|
||||
```tri
|
||||
x@List Bool
|
||||
f@Fn [Bool] String
|
||||
name =@List Bool body
|
||||
```
|
||||
|
||||
They are parsed according to normal definition-head rules. For example,
|
||||
`x@List Bool` means binder `x` has the atomic view expression `List`, followed by
|
||||
an unannotated binder named `Bool`. Use parentheses when the annotation itself is
|
||||
an application.
|
||||
|
||||
## 9. Type Grammar
|
||||
|
||||
View expressions are ordinary value-level expressions in a restricted annotation
|
||||
grammar:
|
||||
|
||||
```text
|
||||
ViewExpr
|
||||
= name
|
||||
| integer
|
||||
| [ViewExpr...]
|
||||
| ViewExpr ViewExpr
|
||||
| (ViewExpr)
|
||||
```
|
||||
|
||||
Built-in names lower to standard view values:
|
||||
|
||||
```text
|
||||
Any -> viewAny
|
||||
Bool -> viewBool
|
||||
String -> viewString
|
||||
Byte -> viewByte
|
||||
Unit -> viewUnit
|
||||
```
|
||||
|
||||
Atomic refs lower explicitly. String refs are the preferred user-facing form;
|
||||
numeric refs remain available for low-level/generated code:
|
||||
|
||||
```text
|
||||
Ref "Nat" -> viewRef "Nat"
|
||||
Ref 10 -> viewRef 10
|
||||
```
|
||||
|
||||
Additional named views and view constructors are ordinary `tricu` values:
|
||||
|
||||
```tri
|
||||
Nat = viewRef "Nat"
|
||||
Box a = viewPair (viewRef "Box") a
|
||||
|
||||
idNat x@Nat =@Nat x
|
||||
idBox x@(Box String) =@(Box String) x
|
||||
```
|
||||
|
||||
The frontend resolves names and evaluates view expressions, but well-formedness
|
||||
is judged by the self-hosted checker (`wellFormedView?` in `lib/view.tri`).
|
||||
Malformed view values are rejected when checked or published.
|
||||
|
||||
## 10. List Syntax in Types
|
||||
|
||||
Function argument lists use the source type grammar:
|
||||
|
||||
```tri
|
||||
Fn [Bool String] Unit
|
||||
Fn [(List Bool) (Maybe String)] Unit
|
||||
```
|
||||
|
||||
The lowered typed program must still respect ordinary `tricu` list syntax, where
|
||||
each list element is parenthesized when needed:
|
||||
|
||||
```tri
|
||||
viewFn [(viewBool) (viewString)] viewUnit
|
||||
```
|
||||
|
||||
## 11. Residual Body View
|
||||
|
||||
For a contract-bearing definition, the full definition view is always:
|
||||
|
||||
```text
|
||||
Fn [allArgumentViews...] returnView
|
||||
```
|
||||
|
||||
except for nullary value annotations, which use the return view directly.
|
||||
|
||||
The body obligation depends on how many argument views are represented by named
|
||||
binders in the definition head.
|
||||
|
||||
Let:
|
||||
|
||||
```text
|
||||
argViews = [A B C]
|
||||
returnView = R
|
||||
binderCount = number of named binders before the phantom tail
|
||||
remaining = drop binderCount argViews
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```text
|
||||
bodyRequiredView = residual(remaining, returnView)
|
||||
```
|
||||
|
||||
where:
|
||||
|
||||
```text
|
||||
residual([], R) = R
|
||||
residual([A ...], R) = Fn [A ...] R
|
||||
```
|
||||
|
||||
Examples:
|
||||
|
||||
```tri
|
||||
foo x@A y@B =@C body
|
||||
```
|
||||
|
||||
Body required view:
|
||||
|
||||
```text
|
||||
C
|
||||
```
|
||||
|
||||
```tri
|
||||
foo @A @B =@C body
|
||||
```
|
||||
|
||||
Body required view:
|
||||
|
||||
```text
|
||||
Fn [A B] C
|
||||
```
|
||||
|
||||
```tri
|
||||
foo x@A @B =@C body
|
||||
```
|
||||
|
||||
Body required view:
|
||||
|
||||
```text
|
||||
Fn [B] C
|
||||
```
|
||||
|
||||
## 12. Lowering Examples
|
||||
|
||||
### 12.1 Fully annotated binders
|
||||
|
||||
Source:
|
||||
|
||||
```tri
|
||||
foo x@Bool xs@(List Bool) =@String body
|
||||
```
|
||||
|
||||
Definition contract:
|
||||
|
||||
```tri
|
||||
typedDeclareFn fooSym [(viewBool) (viewList viewBool)] viewString t
|
||||
typedValue xSym viewBool t
|
||||
typedValue xsSym (viewList viewBool) t
|
||||
```
|
||||
|
||||
Body obligation:
|
||||
|
||||
```tri
|
||||
typedRequire bodySym viewString t
|
||||
```
|
||||
|
||||
### 12.2 Pure phantom signature
|
||||
|
||||
Source:
|
||||
|
||||
```tri
|
||||
foo @Bool @(List Bool) =@String body
|
||||
```
|
||||
|
||||
Definition contract:
|
||||
|
||||
```tri
|
||||
typedDeclareFn fooSym [(viewBool) (viewList viewBool)] viewString t
|
||||
```
|
||||
|
||||
Body obligation:
|
||||
|
||||
```tri
|
||||
typedRequire bodySym (viewFn [(viewBool) (viewList viewBool)] viewString) t
|
||||
```
|
||||
|
||||
### 12.3 Binder prefix with phantom tail
|
||||
|
||||
Source:
|
||||
|
||||
```tri
|
||||
foo x@Bool @(List Bool) =@String body
|
||||
```
|
||||
|
||||
Definition contract:
|
||||
|
||||
```tri
|
||||
typedDeclareFn fooSym [(viewBool) (viewList viewBool)] viewString t
|
||||
typedValue xSym viewBool t
|
||||
```
|
||||
|
||||
Body obligation:
|
||||
|
||||
```tri
|
||||
typedRequire bodySym (viewFn [(viewList viewBool)] viewString) t
|
||||
```
|
||||
|
||||
### 12.4 Value annotation
|
||||
|
||||
Source:
|
||||
|
||||
```tri
|
||||
message =@String "hello"
|
||||
```
|
||||
|
||||
Definition contract:
|
||||
|
||||
```tri
|
||||
typedValue messageSym viewString t
|
||||
```
|
||||
|
||||
Body obligation:
|
||||
|
||||
```tri
|
||||
typedRequire bodySym viewString t
|
||||
```
|
||||
|
||||
## 13. `tricu check`
|
||||
|
||||
`tricu check` consumes an annotated program, lowers annotations to typed program
|
||||
metadata, runs the checker, and reports either `ok` or rendered diagnostics.
|
||||
|
||||
Initial behavior:
|
||||
|
||||
```bash
|
||||
tricu check path/to/program.tri
|
||||
```
|
||||
|
||||
outputs checker success or errors. Diagnostics are rendered by the portable
|
||||
checker, then annotated by the frontend with source/debug labels when available:
|
||||
|
||||
```tri
|
||||
id x@String =@Bool x
|
||||
```
|
||||
|
||||
reports:
|
||||
|
||||
```text
|
||||
symbol 1 (x) expected Bool but got String
|
||||
```
|
||||
|
||||
Application result labels include the application head when known:
|
||||
|
||||
```tri
|
||||
xs =@(List String) [(g "hi")]
|
||||
g y@String =@Bool y
|
||||
```
|
||||
|
||||
reports:
|
||||
|
||||
```text
|
||||
symbol 3 (g application result) expected String but got Bool
|
||||
```
|
||||
|
||||
These labels are presentation-only metadata. The checker still judges only the
|
||||
emitted typed-program evidence.
|
||||
|
||||
Future behavior may include:
|
||||
|
||||
```bash
|
||||
tricu check --out path/to/executable.arboricx path/to/program.tri
|
||||
```
|
||||
|
||||
which checks an annotated source program and emits an executable Arboricx bundle.
|
||||
|
||||
The checker library remains available independently of the CLI workflow.
|
||||
|
||||
## 14. Frontend Lowering Boundaries
|
||||
|
||||
The annotation syntax is frontend sugar. The canonical checker input remains a
|
||||
plain typed program: ordinary `typedValue`, `typedDeclareFn`,
|
||||
`typedRequire`, and `typedApply` nodes represented as portable `tricu`
|
||||
data.
|
||||
|
||||
The frontend may emit richer evidence from source forms, but it does not decide
|
||||
semantic compatibility. In short:
|
||||
|
||||
```text
|
||||
Haskell emits evidence.
|
||||
tricu judges evidence.
|
||||
```
|
||||
|
||||
Current source-driven evidence includes:
|
||||
|
||||
- literal views for strings, bytes, unit, and homogeneous list literals;
|
||||
- expected element requirements for `List T` bodies;
|
||||
- expected `Fn` requirements for lambda literals and curried application spines;
|
||||
- application argument requirements when the callee has a known `Fn` view;
|
||||
- expected constructor flow for unshadowed stdlib constructors:
|
||||
- `pair` with expected `Pair A B`;
|
||||
- `just` and `nothing` with expected `Maybe A`;
|
||||
- `ok` and `err` with expected `Result E A`.
|
||||
|
||||
Constructor lowering only applies when the constructor name is not shadowed by a
|
||||
local binder or top-level definition in the checked source. If a program defines
|
||||
its own `pair`, `just`, `nothing`, `ok`, or `err`, checking falls back to normal
|
||||
application evidence.
|
||||
|
||||
For tooling and regression tests, the frontend exposes a lowering-only API that
|
||||
returns emitted typed program text without invoking the checker:
|
||||
|
||||
```hs
|
||||
lowerSource :: String -> Either String String
|
||||
```
|
||||
|
||||
It also exposes debug labels for symbols:
|
||||
|
||||
```hs
|
||||
lowerSourceWithDebug :: String -> Either String (String, Map Integer String)
|
||||
```
|
||||
|
||||
Debug labels are presentation metadata only. They are not part of checker
|
||||
semantics and are not consumed by `lib/view.tri`.
|
||||
|
||||
`do` blocks have no separate View Contract semantics. The parser lowers them
|
||||
through their explicit bind operator:
|
||||
|
||||
```tri
|
||||
do bind
|
||||
x <- action
|
||||
next x
|
||||
```
|
||||
|
||||
becomes ordinary application/lambda structure. Checking then follows the known
|
||||
`Fn` view of the bind operator, including the callback argument view when it is
|
||||
available.
|
||||
|
||||
## 15. Summary
|
||||
|
||||
The annotation syntax is:
|
||||
|
||||
```tri
|
||||
name arg@A arg2@B =@C body
|
||||
name @A @B =@C body
|
||||
name arg@A @B =@C body
|
||||
name =@C body
|
||||
```
|
||||
|
||||
Core rules:
|
||||
|
||||
1. Binder annotations introduce binders and argument views.
|
||||
2. Phantom annotations introduce argument views only.
|
||||
3. Phantom annotations may only appear after all binders.
|
||||
4. Unannotated binders in contract-bearing heads contribute `Any`.
|
||||
5. Missing return annotations in contract-bearing heads default to `Any`.
|
||||
6. Nullary `=@T` definitions are value contracts, not zero-arity functions.
|
||||
7. Compound annotation types must be parenthesized.
|
||||
8. Lowering emits ordinary typed-program nodes for the existing checker.
|
||||
337
docs/view-contracts.md
Normal file
337
docs/view-contracts.md
Normal file
@@ -0,0 +1,337 @@
|
||||
# View Contracts and View Trees
|
||||
|
||||
## 1. Purpose
|
||||
|
||||
View Contracts are the portable checking layer for Tree Calculus programs.
|
||||
|
||||
The checker does not consume detached metadata about a separate executable. Its
|
||||
canonical input is a typed, checkable tree artifact: ordinary tree data that
|
||||
contains both the executable program payloads and the view/contract structure
|
||||
needed to validate and transform them.
|
||||
|
||||
The checker consumes this artifact and returns either:
|
||||
|
||||
```text
|
||||
checked-execution artifact
|
||||
```
|
||||
|
||||
or:
|
||||
|
||||
```text
|
||||
structured diagnostic
|
||||
```
|
||||
|
||||
A checked-execution artifact is interpreted by `runChecked`. Unguarded programs
|
||||
are represented as `checkedPure rootPayload`; guarded programs contain explicit
|
||||
checked guard/bind nodes.
|
||||
|
||||
This keeps checking independent of any particular host implementation. A typed
|
||||
artifact may be produced by any frontend, compiler, hand-written generator, or
|
||||
future self-hosted `tricu` toolchain.
|
||||
|
||||
## 2. Design Principle
|
||||
|
||||
The model follows the same discipline as interaction trees.
|
||||
|
||||
Interaction trees use tagged structural envelopes with explicit executable
|
||||
payloads:
|
||||
|
||||
```tri
|
||||
io action = pair "tricuIO" (pair version action)
|
||||
pure x = pair 0 x
|
||||
bind action k = pair 1 (pair action k)
|
||||
```
|
||||
|
||||
The interpreter understands the outer structure, but it does not recursively
|
||||
mistake every subtree for interpreter metadata. A continuation `k` is an opaque
|
||||
executable tree until the interpreter reaches the `bind` step that applies it.
|
||||
|
||||
View trees use the same rule:
|
||||
|
||||
```text
|
||||
structure says how to check;
|
||||
opaque executable fields are only executed/applied by the checker at the
|
||||
appropriate step.
|
||||
```
|
||||
|
||||
This is the key distinction that allows Views to carry guards without confusing
|
||||
ordinary program trees with View metadata.
|
||||
|
||||
## 3. Views
|
||||
|
||||
A View is an extrinsic contract over an ordinary Tree Calculus value. Tree
|
||||
Calculus values do not carry native runtime types; a View describes how a value
|
||||
may be treated by the checker or by a checked boundary.
|
||||
|
||||
Core View forms:
|
||||
|
||||
```text
|
||||
Any
|
||||
Ref ref
|
||||
Fn [argView...] resultView
|
||||
List elemView
|
||||
Maybe elemView
|
||||
Pair leftView rightView
|
||||
Result errView okView
|
||||
Guarded baseView guard
|
||||
```
|
||||
|
||||
`Ref` supports both generated/numeric and symbolic references. Symbolic refs are
|
||||
preferred for user-authored views:
|
||||
|
||||
```tri
|
||||
UserId = viewRef "UserId"
|
||||
```
|
||||
|
||||
A guarded view refines a base view with an executable guard:
|
||||
|
||||
```tri
|
||||
UserId = viewGuarded (viewRef "UserId") userIdGuard
|
||||
```
|
||||
|
||||
The guard is ordinary program code. The View validator checks that the guarded
|
||||
view envelope is well-formed, and recursively validates the `baseView`, but it
|
||||
must treat the guard payload/reference as opaque executable data, not as another
|
||||
View.
|
||||
|
||||
## 4. Guards
|
||||
|
||||
Guards are ordinary `tricu` values/functions grouped with the Views they refine.
|
||||
|
||||
Example:
|
||||
|
||||
```tri
|
||||
userIdGuard = value :
|
||||
-- ordinary program that validates value
|
||||
|
||||
UserId = viewGuarded (viewRef "UserId") userIdGuard
|
||||
|
||||
loadUser id@UserId = ...
|
||||
```
|
||||
|
||||
Guards return the standard checked-runtime protocol:
|
||||
|
||||
```tri
|
||||
guardOk value
|
||||
guardFail
|
||||
```
|
||||
|
||||
Guards do not author diagnostics. The checked-exec runner owns guard failure and
|
||||
malformed-guard diagnostics using boundary context from the checked artifact.
|
||||
|
||||
Guards are injected by the checker. They are not discovered by the runtime as a
|
||||
separate metadata layer. The checking process transforms a view tree into an
|
||||
executable tree with the necessary guard applications inserted.
|
||||
|
||||
## 5. View Tree Artifact
|
||||
|
||||
The primary checker-facing artifact is a view executable term graph.
|
||||
|
||||
Conceptually:
|
||||
|
||||
```text
|
||||
ViewTree
|
||||
version
|
||||
root node id
|
||||
nodes
|
||||
```
|
||||
|
||||
Each node is tagged tree data. Nodes combine executable payloads, view claims,
|
||||
and structural relationships in one graph.
|
||||
|
||||
Representative node forms:
|
||||
|
||||
```text
|
||||
Value node view executableTree
|
||||
Apply node calleeNode argNode expectedOrInferredView
|
||||
Require node requiredView sourceNode
|
||||
External node name view
|
||||
```
|
||||
|
||||
This is not a mandatory final encoding; it is the semantic target. The important
|
||||
property is that executable trees and checking structure are carried together in
|
||||
a single portable artifact.
|
||||
|
||||
A node may contain opaque executable fields. Those fields are tree terms, but
|
||||
they are not recursively decoded as view-tree nodes or Views unless the node's
|
||||
semantics explicitly says so.
|
||||
|
||||
## 6. Checker Semantics
|
||||
|
||||
The checker is an interpreter over the view tree.
|
||||
|
||||
For each node it may:
|
||||
|
||||
1. validate the node envelope;
|
||||
2. validate Views referenced by the node;
|
||||
3. check compatibility between expected and actual Views;
|
||||
4. recursively check child nodes;
|
||||
5. inject guards required by guarded Views;
|
||||
6. produce the executable tree for that node;
|
||||
7. memoize node results by node id.
|
||||
|
||||
The root node result is a checked-execution program.
|
||||
|
||||
In abstract form:
|
||||
|
||||
```text
|
||||
checkViewTree : ViewTree -> Result CheckedExec Diagnostic
|
||||
```
|
||||
|
||||
or, in self-hosted terms:
|
||||
|
||||
```tri
|
||||
checkViewTree viewTree = ... -- ok checkedExec / err diagnostic
|
||||
```
|
||||
|
||||
## 7. Compatibility and Guard Injection
|
||||
|
||||
Structural compatibility is about Views. Guard injection is about producing the
|
||||
checked-execution tree.
|
||||
|
||||
For example, if a node is required to satisfy:
|
||||
|
||||
```tri
|
||||
viewGuarded (viewRef "UserId") userIdGuard
|
||||
```
|
||||
|
||||
then the checker verifies the underlying View relationship and emits executable
|
||||
code that applies `userIdGuard` at the appropriate checked boundary.
|
||||
|
||||
The checker, not the runtime metadata system, owns this transformation.
|
||||
|
||||
## 8. Source Annotations
|
||||
|
||||
Source annotations are one frontend syntax for producing view-tree nodes.
|
||||
|
||||
Examples:
|
||||
|
||||
```tri
|
||||
Nat = viewRef "Nat"
|
||||
Box a = viewPair (viewRef "Box") a
|
||||
|
||||
idNat x@Nat =@Nat x
|
||||
idBox x@(Box String) =@(Box String) x
|
||||
```
|
||||
|
||||
Annotations are value-level View expressions. Names such as `Nat` and `Box` are
|
||||
ordinary program values/functions that evaluate to Views.
|
||||
|
||||
A frontend that supports this syntax should lower the source into a view tree
|
||||
that contains the relevant executable terms, views, and checking structure. The
|
||||
artifact must not depend on source names or on the frontend implementation that
|
||||
produced it.
|
||||
|
||||
## 9. Contract Expressions
|
||||
|
||||
Contract-expression helpers remain useful as authoring/building tools, but they
|
||||
are not the fundamental artifact model.
|
||||
|
||||
Preferred style for expression-oriented authoring is pipeline-first:
|
||||
|
||||
```tri
|
||||
mapBoolStringUse = cFn <|
|
||||
[(viewFn [(viewBool)] viewString) (viewList viewBool)] (viewList viewString)
|
||||
|> cApply (cFn [(viewBool)] viewString)
|
||||
|> cApply (cValue (viewList viewBool))
|
||||
|> cRequire (viewList viewString)
|
||||
```
|
||||
|
||||
These helpers should be understood as convenient ways to build typed/checkable
|
||||
structure, not as a permanent replacement for view-tree artifacts.
|
||||
|
||||
## 10. Artifact Direction
|
||||
|
||||
The target direction is to make the view tree the canonical checked-program
|
||||
artifact.
|
||||
|
||||
Older split concepts remain useful internally or during development:
|
||||
|
||||
```text
|
||||
tree term
|
||||
view value
|
||||
typed-program node
|
||||
module/export manifest
|
||||
```
|
||||
|
||||
But the durable design should avoid treating contracts as detached facts about a
|
||||
separate program. The portable checker input is the checkable program itself.
|
||||
|
||||
In short:
|
||||
|
||||
```text
|
||||
Do not store code over here and contracts over there.
|
||||
Store a view tree: executable code plus the structure needed to check and guard it.
|
||||
```
|
||||
|
||||
## 11. IO Interaction Trees
|
||||
|
||||
`tricu` IO is represented as ordinary interaction-tree data:
|
||||
|
||||
```tri
|
||||
io action = pair "tricuIO" (pair version action)
|
||||
pure value = pair 0 value
|
||||
bind action k = pair 1 (pair action k)
|
||||
```
|
||||
|
||||
View Contracts do not change that representation. A checked program may produce
|
||||
an ordinary IO interaction tree, and the existing IO driver can execute it
|
||||
unchanged.
|
||||
|
||||
For source evaluation with contracts enabled, `tricu eval --io` performs an
|
||||
additional frontend instrumentation pass over visible IO continuations. When a
|
||||
continuation returns a `pure (...)` value that mentions source-annotated
|
||||
functions, the frontend lowers that pure expression into the existing portable
|
||||
checked-exec protocol before returning the next IO action.
|
||||
|
||||
This means source sugar works for practical checked IO paths such as:
|
||||
|
||||
```tri
|
||||
acceptNames xs@(NonEmptyList String) =@String "accepted"
|
||||
|
||||
main = io (bind (pure []) (xs : pure (acceptNames xs)))
|
||||
```
|
||||
|
||||
and for explicit higher-order boundaries:
|
||||
|
||||
```tri
|
||||
useHandler handler@(Fn [(NonEmptyList String)] String) xs@(List String) =@String
|
||||
handler xs
|
||||
|
||||
main = io (bind (pure []) (xs : pure (useHandler acceptNames xs)))
|
||||
```
|
||||
|
||||
The IO runtime does not perform View inference or guard injection at every step.
|
||||
The source/frontend pass constructs checked-exec boundaries once; the runtime
|
||||
only evaluates the resulting interaction tree.
|
||||
|
||||
Current limitations:
|
||||
|
||||
- This is source-visible instrumentation, not whole-program function-flow
|
||||
tracking.
|
||||
- Higher-order guarantees require explicit annotated boundaries.
|
||||
- Raw prebuilt interaction trees, imported executable artifacts, and content-store
|
||||
terms are not automatically re-instrumented unless they pass through this
|
||||
source-lowering path.
|
||||
- The IO action shape itself is only shallowly checkable unless users provide
|
||||
guarded Views for the relevant boundaries.
|
||||
- Continuation result Views are not inferred from external effects; dynamic IO
|
||||
values should cross annotated/guarded boundaries when runtime enforcement is
|
||||
required.
|
||||
|
||||
Making IO checking more complete is future work. In particular, a future design
|
||||
may validate every continuation-produced action structurally, carry checked
|
||||
wrappers with higher-order function values, or define a portable checked-IO
|
||||
artifact instead of relying on Haskell/frontend source instrumentation.
|
||||
|
||||
## 12. Host Independence
|
||||
|
||||
No part of the core View Tree design is specific to Haskell or to the current implementation.
|
||||
|
||||
Any producer may emit a view-tree artifact if it follows the portable tree-data
|
||||
encoding. Any checker implementation may consume it if it implements the typed
|
||||
node semantics.
|
||||
|
||||
The current implementation can produce and consume these artifacts, but it is
|
||||
not the semantic authority. The artifact format and the self-hosted checker
|
||||
semantics are the authority.
|
||||
Reference in New Issue
Block a user